(1/9 追記) シングルクオートが入っている時にエラーになってしまっていたので、記事を修正しました。また、作成済みのバイナリを Github で配布しています。こちらもバグ修正しています。
はじめに
これまで Notion のタスク登録に、自作の Alfred workflow を使っていました。 ポモドーロの設定やタスクの終了は比較的楽なのですが、タスクの登録は文字列の入力やコピーがあり、少し手間がかかりました。 このため、ついタスク登録をサボってしまうことが何度か発生し、その度に締切などに追われたことがありました。 そこで、タスク登録に関してはさらに簡単な仕組みが必要だと感じました。
ほとんどの時間で macOS を使っているので、せっかくならサービスを作ってしまうといいのではと思いつきました。
名前は NotionQuickAction
とします。
作業手順は以下のようになります。
- アプリケーションは問わず、タスクにしたい文字列を選択します。
NotionQuickAction
サービスに割り振ったショートカットキーを押します。- ダイアログが出るので、日付・開始時間・終了時間を設定します。
- 全てを設定しなかった場合には、日付なしになります。
- 開始時間、終了時間を省略した場合には終日タスクになります。
- 終了時間を省略した場合には、開始時間から1時間のタスクになります。
- Notion にタスクが作成され、自動的に Notion アプリまたはブラウザで開きます。
- 日付ありのタスクであり、かつカレンダーへの登録機能をオンにした場合には、カレンダーにも登録されます。
- カレンダーの詳細には Notion のタスク ID が登録されています。Google カレンダーとの同期のために使うためです。
動作している様子の動画をここに示します。
午前中に Notion タスク登録する macOS サービスを作ってみました。作成方法も記事にしているので、後で公開します。#notion #まいにちnotion pic.twitter.com/gNZ8DKl33V
— hkob (@hkob) 2022年1月8日
作成方法
macOS のサービスを作るために、Automator
を用います。
クイックアクションを選択します。
Automator 画面は以下のようになります。右上の項目は以下のように設定しました。
- ワークフローが受け取る現在の項目: テキスト
- 検索対象: 全てのアプリケーション
- イメージ: 共有 (特になんでもよい)
- カラー: ブラック (特になんでもよい)
アクションは以下の二つを連結しています。
- Javascript を実行
- Web ページを表示
あとは JavaScript を実行の部分にスクリプトを以下のように記述しました。ユーザごとに異なる部分は上に定数として設定しています。この部分を自分のデータに書き換えてください。
// ########## 個人設定用定数 ########## // Notion API Token を設定 (secret で始まる文字列) const NOTION_API_TOKEN = "secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" // データベース ID を設定 (32桁16進数) const DATABASE_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX" // タスクデータベースのタイトルのプロパティ名 const TASK_NAME = "タスク名" // タスクデータベースの日付のプロパティ名 const TASK_DATE = "日付" // タイムゾーン (see https://www.iana.org/time-zones) const TIME_ZONE = "Asia/Tokyo" // Notion タスクだけでなくカレンダーにも登録したい場合には true const ADD_CALENDAR = true // Notion のカレンダー名を設定 const NOTION_CALENDAR = "Notion" // Notion で開く場合には true、ブラウザで開く時には false const OPEN_BY_APP = true function dialogText(app, input) { const message = "Input date and time: (format) [[YYYY/]MM/DD [HH:MM [HH:MM]]]\n" + "Example: (no input) -> unsettled\n" + "\t 4/25 -> 25th, April\n" + "\t 4/25 10:00 -> 25th, April 10:00\n" + "\t 4/25 9:00 10:00 -> 25th, April 9:00 - 10:00\n" + "for Message : \n" + input return app.displayDialog(message, { defaultAnswer: "", withIcon: "note", buttons: ["Cancel", "Continue"], defaultButton: "Continue" }) } function parseDateTime(dateTimeStr) { const app = Application.currentApplication() app.includeStandardAdditions = true var endTimeStr = null var startTimeStr = null // query を分割 let words = dateTimeStr.split(" ") // 最後のパラメータが時間かどうか var timeStr = words.slice(-1)[0] if (timeStr && timeStr.match("[0-9]+:[0-9]+")) { endTimeStr = words.pop() } // 時間が設定されていたら開始時間もあるかどうか調べる。なければそれが開始時間 if (endTimeStr) { timeStr = words.slice(-1)[0] if (timeStr && timeStr.match("[0-9]+:[0-9]+")) { startTimeStr = words.pop() } else { startTimeStr = endTimeStr endTimeStr = null } } var dateStr = words.slice(-1)[0] if (dateStr) { if (dateStr.match("^[0-9]+/[0-9]+$")) { dateStr = new Date().getFullYear() + "/" + words.pop() } else if (dateStr.match("^[0-9]+/[0-9]+/[0-9]+$")) { dateStr = words.pop() } else { dateStr = null } } if (dateStr == null && startTimeStr != null) { const timeNow = new Date() dateStr = timeNow.getFullYear() + "/" + (timeNow.getMonth() + 1) + "/" + timeNow.getDate() } if (startTimeStr) { // 開始時間が設定されていたら時間を含めた日付を設定 const startTime = new Date(dateStr + " " + startTimeStr) var endTime if (endTimeStr) { // 終了時間が設定されていたら時間を含めた日付を設定 endTime = new Date(dateStr + " " + endTimeStr) } else { // 終了時間が設定されていなかったら開始時間の1時間後を設定 endTime = new Date(dateStr + " " + startTimeStr) endTime.setHours(endTime.getHours() + 1) } return {startTime: startTime, endTime: endTime} } else if (dateStr) { return {date: new Date(dateStr) } } else { return {noDate: true} } } // Notion に payload を send する function sendNotion(app, url, payload, method) { const header = " --header 'Authorization: Bearer '" + NOTION_API_TOKEN + "'' " + "--header 'Content-Type: application/json' " + "--header 'Notion-Version: 2021-08-16' " + "--data '" const script = "curl -X " + method + " " + url + header + JSON.stringify(payload).replaceAll("'", '\'"\'"\'') + "'" return JSON.parse(app.doShellScript(script)) } function createPage(app, payload) { return sendNotion(app, "https://api.notion.com/v1/pages", payload, "POST") } function dateTime2str(dt) { return dt.toLocaleTimeString([], {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"}).replaceAll("/", "-") } function date2str(dt) { return dt.toLocaleDateString([], {year: "numeric", month: "2-digit", day: "2-digit"}).replaceAll("/", "-") } // カレンダーイベントから日付の hash を作成 (削除時にも呼ばれるので、イベントに日付が設定されていなければ null を返却) function notionDateHash(hash) { if (hash.startTime) { return { start: dateTime2str(hash["startTime"]), end: dateTime2str(hash["endTime"]), time_zone: TIME_ZONE } } else if (hash["date"]) { return { start: date2str(hash["date"]) } } else { return { start: "", end: "" } } } // title と date_hash から create payload を作成 function createPayload(title, date_hash) { return { parent: { database_id: DATABASE_ID, }, properties: { [TASK_NAME]: { title: [ { text: { content: title } } ] }, [TASK_DATE]: { type: "date", date: date_hash }, } } } ObjC.import("stdlib"); function createCalendar(task_id, title, dateTimeHash) { let Calendar = Application("Calendar") let notionCal = Calendar.calendars[NOTION_CALENDAR] var event = { summary: title } // Notion のタスク ID をカレンダーの description に入れます。 // Google カレンダーの変更で Notion タスクを連携するためです。 // 私の場合には、description の最終行に「id:」で始まる行があるかどうかで判断しています。 // automate.io などで タスクID だけを入れている場合にはここを修正してください。 if (task_id != "") { event.description = "id:" + task_id // automate.io などで task_id だけを入れている場合にはこちら // event["description"] = task_id } if (dateTimeHash.startTime) { // 開始時間が設定されていたら時間を含めた日付を設定 event.startDate = dateTimeHash.startTime event.endDate = dateTimeHash.endTime let newEvent = Calendar.Event(event) notionCal.events.push(newEvent) } else { event.startDate = dateTimeHash.date event.endDate = dateTimeHash.date event.alldayEvent = true let newEvent = Calendar.Event(event) notionCal.events.push(newEvent) } } function run(input, parameters) { const app = Application.currentApplication() app.includeStandardAdditions = true if (input.length > 0) { const response = dialogText(app, input) if (response.buttonReturned == "Continue") { const parsedTime = parseDateTime(response.textReturned) // データベースにタスクを作成 const payload = createPayload(String(input), notionDateHash(parsedTime)) const taskCreateResult = createPage(app, payload) // 日付が設定されていた時にはカレンダーも登録 const task_id = taskCreateResult.id if (ADD_CALENDAR && !parsedTime.noDate) { createCalendar(task_id, input, parsedTime) } const tmp_url = taskCreateResult.url var url if (OPEN_BY_APP == true) { url = tmp_url.replace("https", "notion") let notion = Application("Notion") notion.activate() } else { url = tmp_url } } return url } else { return "" } }
記述が終わったら保存します。私は NotionQuickAction としています。保存すると、自分のライブラリフォルダの下の Services に保存されるはずです。
ショートカットの設定
設定された NotionQuickAction は環境設定のキーボード - ショートカットに表示されます。チェックマークをつけると有効になります。またここで、ショートカットを適当な空いているものに設定します。ここでは「option - command - N」に設定してみました。これは自分の使いやすいものに設定してもらっていいです。
おわりに
Alfred と違って、macOS 使っている人なら誰でも使えるので是非試してみてください。