俺の特性に過集中がある。
基本俺はこれに助けられていることが多いけど、重要な予定をすっぽかすリスクが常に付きまとう。
そんな自分をうまくコントロールするためにタスク管理をNotionでしているんだけど、それをLINEに飛ばすことによって、過集中による予定忘れの防止とプログラミングスキルの向上を狙っていきたい。
システムの概要
使用技術一覧
- Google Apps Script (GAS):スケジュール管理や自動化処理の実行。
- Notion API:Notionデータベースからスケジュール情報を取得。
- LINE Messaging API:LINE公式アカウントを使用してメッセージを送信。
やっていること
元々Notionでスケジュール管理をしていて、そのdateプロパティに登録されている時間を元に、開始10分前にLINE公式アカウントに通知を送る。


簡単な流れはこんな感じ。
- 未着手かつ日付が設定された予定をNotion APIで取得。
- 現在時刻と予定開始時刻を比較し、通知対象を判定。
- LINEで通知メッセージを送信し、通知済みの予定IDを記録。
- 古い通知記録を定期的に削除してデータを管理。
実際にコードを見てみる
今回のシステム概要。
関数を一個一個見て行こうと思う
LINEのブロードキャストメッセージを送信する関数
- アクセストークン取得: スクリプトプロパティからLINE APIのアクセストークンを取得。
- リクエスト作成: メッセージペイロードをJSON形式で作成。
- API呼び出し: LINE APIのブロードキャストエンドポイントにPOSTリクエストを送信。
- エラーハンドリング: API呼び出しの結果をログに記録。
function sendBroadcastMessage(message) {
const lineToken = PropertiesService.getScriptProperties().getProperty("YOUR_LINE_ACCESS_TOKEN");
const url = 'https://api.line.me/v2/bot/message/broadcast';
const payload = {
messages: [{
type: 'text',
text: message
}]
};
const options = {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + lineToken
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(url, options);
Logger.log("LINE送信結果: " + response.getContentText());
} catch (error) {
Logger.log("LINE送信エラー: " + error.message);
}
}
Notionデータベースからスケジュール情報を取得する関数
- アクセストークンとデータベースID取得: スクリプトプロパティから取得。
- リクエスト作成: フィルター条件(例: 未着手かつ日付が設定されている)を指定。
- API呼び出し: Notion APIのqueryエンドポイントにPOSTリクエストを送信。
- レスポンス処理: レスポンスをパースし、データベースの結果を返す。エラー時はログを出力。
function fetchNotionSchedules() {
const notionToken = PropertiesService.getScriptProperties().getProperty("YOUR_NOTION_API_TOKEN");
const databaseId = PropertiesService.getScriptProperties().getProperty("YOUR_DATABASE_ID");
const url = `https://api.notion.com/v1/databases/${databaseId}/query`;
const filter = {
filter: {
and: [
{
property: "日付",
date: {
is_not_empty: true
}
},
{
property: "ステータス",
status: {
equals: "未着手" // 実際に設定されているステータスの値を指定
}
}
]
}
};
const options = {
method: 'post',
headers: {
'Authorization': `Bearer ${notionToken}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
payload: JSON.stringify(filter),
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(url, options);
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
if (responseCode !== 200) {
Logger.log(`Notion APIエラー: ステータスコード ${responseCode}`);
Logger.log(`レスポンス内容: ${responseText}`);
return [];
}
const data = JSON.parse(responseText);
Logger.log("取得データ: " + JSON.stringify(data.results, null, 2));
return data.results;
} catch (error) {
Logger.log("Notion APIリクエストエラー: " + error.message);
return [];
}
}
LINE通知を送る関数
最後に上記の2関数を使いながらスケジュールを確認し、LINE通知を送る関数。(checkAndNotifySchedules関数)
- スケジュール取得: fetchNotionSchedulesを呼び出してデータを取得。
- 現在時刻と比較: スケジュール開始まで10分以内かどうかを判定。
- 通知済み確認: getNotifiedIdsで過去の通知済みIDをチェック。
- 通知送信: 条件を満たすスケジュールについてLINEに通知を送信。
- 通知ID管理: 通知済みIDを記録し、古いIDを削除。
function checkAndNotifySchedules() {
const schedules = fetchNotionSchedules(); // 取得済みデータ
const now = new Date();
// 通知済みの予定IDを取得
const notifiedIds = getNotifiedIds();
schedules.forEach(schedule => {
const properties = schedule.properties;
const scheduleId = schedule.id; // ページID
// 日付プロパティを取得
const dateProperty = properties["日付"];
if (dateProperty && dateProperty.date) {
const scheduleStart = new Date(dateProperty.date.start); // 開始時間
// 現在時刻との差分を計算(分単位)
const timeDifference = (scheduleStart - now) / 60000; // ミリ秒から分へ変換
if (timeDifference > 0 && timeDifference <= 10 && !notifiedIds[scheduleId]) {
// 名前プロパティからタイトルを取得
const nameProperty = properties["名前"];
const scheduleName = nameProperty.title[0]?.plain_text || "不明な予定";
// メッセージ作成
const message = `「${scheduleName}」という予定の10分前です。`;
// LINEメッセージを送信
sendBroadcastMessage(message);
// 通知済みIDとして記録
addNotifiedId(scheduleId);
}
}
});
// 古い通知IDをクリア
clearOldNotifiedIds();
}
通知済みの予定を再通知しないようにする
追加として、通知済みの予定を再通知しないようにするシステムも作成。というのも、初期装備だと10分間毎分同じ予定が通知されるようになってしまってノイローゼになりかけたため。
// 通知済みの予定IDを追加
function addNotifiedId(scheduleId) {
const scriptProperties = PropertiesService.getScriptProperties();
const notifiedIds = getNotifiedIds();
// 現在時刻を記録
notifiedIds[scheduleId] = new Date().toISOString();
scriptProperties.setProperty("NOTIFIED_IDS", JSON.stringify(notifiedIds)); // JSON形式で保存
}
// 古い通知IDをクリア
function clearOldNotifiedIds() {
const scriptProperties = PropertiesService.getScriptProperties();
const notifiedIds = getNotifiedIds();
const now = new Date();
// 1週間前の日時を計算
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
// 古いエントリを削除
Object.keys(notifiedIds).forEach(scheduleId => {
const notifiedTime = new Date(notifiedIds[scheduleId]);
if (notifiedTime < oneWeekAgo) {
delete notifiedIds[scheduleId];
}
});
// 更新された通知済みIDリストを保存
scriptProperties.setProperty("NOTIFIED_IDS", JSON.stringify(notifiedIds));
}
つまずいたところ
主に以下のポイントでつまづいた。
- 無限通知地獄(前述)
- LINE ACCESS TOKENはシークレットトークンとは別
- LINE Notifyはサービスが終了する
順に見ていく。
無限通知地獄(前述)
純粋に現在時刻とNotion データベースに登録している時刻を比較するというロジックであるために、再通知を防止するツールを作らないと10分間無限に通知される。ノイローゼ不可避。
LINE ACCESS TOKENはシークレットトークンとは別
Messaging APIのページに行くとこんな感じでめちゃくちゃ意味深な2行がある。
ずっとChannel secretの値がアクセストークンだと思ってた。

正解はLINE Developersにアクセスをして作成した公式ラインを選択してMessaging API設定を選択。
この一番下にある。
LINE Notifyはサービスが終了する
「LINE 通知」で検索するとLINE Notifyなるサービスがめっちゃヒットする。LINEと外部ツールを連携するための無料ツールらしい。
ただ、このツールは2025年3月で終了するらしい。
https://notify-bot.line.me/closing-announce
まとめ
今回はプログラミングの練習としてLINEとNotionの連携をした。
これでド忘れをなくしつつ、これを改善していくことで自分のプログラミング知識も拡張して行こうと思う。
今後やりたいこととして、Notion でタイムボクシング方式のタスク管理をしているんだけど、これを通知に落とし込んでいきたい。