はじめに
LINE Notifyは2025年3月31日にサービス終了しました。
この記事では、代替手段としてLINE Messaging APIを使った実装方法を紹介します。
この記事でやること
- Google Apps Script (GAS) を使って、Googleカレンダーからランチタイム(11:00〜13:00)の空き時間を取得
- LINE Messaging API を使って、空き時間をLINE通知
- トリガーを設定して毎日自動実行
この記事により解決すること
- カレンダーアプリを開かなくても、LINEに昼ご飯を食べられる時間が通知される
- 今日は何時に昼ごはん食べられるか、妻に報告しなくてよくなる
前提
- Googleアカウントを持っている
- Googleカレンダーでスケジュールを管理している
- LINEアカウントを持っている
料金について
LINE Messaging APIは月200通まで無料で利用できます。
平日のみ1通/日なら月20通程度なので、個人利用なら無料枠で十分です。
1. LINE公式アカウントの作成
LINE Messaging APIを利用するには、LINE公式アカウントとMessaging APIチャネルの作成が必要です。
詳細な手順は公式が一番わかりやすかったです。
作成の流れ(概要)
- 今回は個人利用のため、自前のLINEアカウントにてQRコードでログイン
- 同じく自分の電話番号にてSMS認証
- 下記のページに遷移するため、アカウント情報を記載
- アカウント情報を登録し、利用規約に同意するとLINE Official Account Managerのページに遷移します。
- LINE Official Account Managerは当分使いませんが、開いておいてください。(8-1にて通知先を追加する際に使用します)
2. チャネルアクセストークンを取得
- LINE Developers Console にアクセス
- プロバイダーを作成(作成方法は割愛※)
- チャネルを作成(作成方法は割愛※)
- 作成したチャネルを選択
- 「Messaging API設定」タブをクリック
- 「チャネルアクセストークン」の「発行」ボタンをクリック
- 表示されたトークンをコピーして保存
※大筋から逸れるため割愛します。公式のドキュメントを参照してください。
3. 自分のユーザーIDを取得
プッシュメッセージを送るには、送信先のユーザーIDが必要です。
開発者自身のユーザーIDは、LINE Developersコンソールで簡単に確認できます。
- LINE Developers Console にアクセス
- 作成したチャネルを選択
- 「チャネル基本設定」タブをクリック
- ページ下部の「あなたのユーザーID」に表示されているIDをコピー
4. Google Apps Scriptでスクリプトを作成
4-1. スクリプトエディタを開く
- Google Apps Script にアクセス
- 「新しいプロジェクト」をクリック
4-2. スクリプトを記述
/**
* ランチタイム設定
*/
const LUNCH_CONFIG = {
START_HOUR: 11,
END_HOUR: 13,
MIN_FREE_MINUTES: 30
};
const MINUTES_IN_MS = 60 * 1000;
/**
* 設定情報を取得
* @returns {{CHANNEL_ACCESS_TOKEN: string, USER_ID: string, CALENDAR_ID: string}}
*/
function getConfig() {
const props = PropertiesService.getScriptProperties();
return {
CHANNEL_ACCESS_TOKEN: props.getProperty('CHANNEL_ACCESS_TOKEN'),
USER_ID: props.getProperty('USER_ID'),
CALENDAR_ID: props.getProperty('CALENDAR_ID')
};
}
/**
* ランチタイムの空き時間を取得してLINEに通知
*/
function sendLunchAvailability() {
const config = getConfig();
const today = new Date();
const lunchStart = new Date(today.getFullYear(), today.getMonth(), today.getDate(), LUNCH_CONFIG.START_HOUR, 0, 0);
const lunchEnd = new Date(today.getFullYear(), today.getMonth(), today.getDate(), LUNCH_CONFIG.END_HOUR, 0, 0);
const calendar = CalendarApp.getCalendarById(config.CALENDAR_ID);
if (!calendar) {
Logger.log('エラー: カレンダーが見つかりません');
return;
}
const events = calendar.getEvents(lunchStart, lunchEnd);
const freeSlots = findFreeSlots(events, lunchStart, lunchEnd);
const message = createLunchMessage(freeSlots);
sendLinePushMessage(config, message);
}
/**
* 有効なイベントかどうか判定(終日・0分イベントを除外)
* @param {GoogleAppsScript.Calendar.CalendarEvent} event
* @returns {boolean}
*/
function isValidEvent(event) {
if (event.isAllDayEvent()) return false;
return event.getStartTime().getTime() !== event.getEndTime().getTime();
}
/**
* 空き時間を計算
* @param {GoogleAppsScript.Calendar.CalendarEvent[]} events
* @param {Date} lunchStart
* @param {Date} lunchEnd
* @returns {{start: Date, end: Date, isFullSlot?: boolean}[]}
*/
function findFreeSlots(events, lunchStart, lunchEnd) {
const validEvents = events
.filter(isValidEvent)
.sort((a, b) => a.getStartTime() - b.getStartTime());
if (validEvents.length === 0) {
return [{ start: lunchStart, end: lunchEnd, isFullSlot: true }];
}
const freeSlots = [];
let currentTime = lunchStart;
for (const event of validEvents) {
const eventStart = event.getStartTime();
const eventEnd = event.getEndTime();
if (eventStart > currentTime) {
const gapMinutes = (eventStart - currentTime) / MINUTES_IN_MS;
if (gapMinutes >= LUNCH_CONFIG.MIN_FREE_MINUTES) {
freeSlots.push({ start: new Date(currentTime), end: eventStart });
}
}
if (eventEnd > currentTime) {
currentTime = eventEnd;
}
}
if (currentTime < lunchEnd) {
const gapMinutes = (lunchEnd - currentTime) / MINUTES_IN_MS;
if (gapMinutes >= LUNCH_CONFIG.MIN_FREE_MINUTES) {
freeSlots.push({ start: new Date(currentTime), end: lunchEnd });
}
}
return freeSlots;
}
/**
* ランチ空き時間のメッセージを作成
* @param {{start: Date, end: Date, isFullSlot?: boolean}[]} freeSlots
* @returns {string}
*/
function createLunchMessage(freeSlots) {
if (freeSlots.length === 0) {
return '🍚 今日のランチタイムは空きがありません';
}
if (freeSlots.length === 1 && freeSlots[0].isFullSlot) {
return '🍚 いつでも食べられます!';
}
const slotTexts = freeSlots.map(slot =>
`${formatTime(slot.start)}~${formatTime(slot.end)}`
);
return `🍚 ${slotTexts.join(', ')} の間でご飯を食べられます`;
}
/**
* LINE Messaging APIでプッシュメッセージを送信
* @param {{CHANNEL_ACCESS_TOKEN: string, USER_ID: string}} config
* @param {string} message
*/
function sendLinePushMessage(config, message) {
const url = 'https://api.line.me/v2/bot/message/push';
const options = {
method: 'post',
contentType: 'application/json',
headers: {
Authorization: `Bearer ${config.CHANNEL_ACCESS_TOKEN}`
},
payload: JSON.stringify({
to: config.USER_ID,
messages: [{ type: 'text', text: message }]
})
};
try {
UrlFetchApp.fetch(url, options);
Logger.log('LINE通知を送信しました');
} catch (e) {
Logger.log(`エラー: ${e}`);
}
}
/**
* 時刻をフォーマット(例: 09:00)
* @param {Date} date
* @returns {string}
*/
function formatTime(date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
4-3. スクリプトプロパティを設定
コード内にトークンを直接書かず、GASのスクリプトプロパティで管理します。
- 左メニューの「⚙️ プロジェクトの設定」をクリック
- 「スクリプト プロパティ」セクションで「スクリプト プロパティを追加」をクリック
- 以下の3つのプロパティを追加:
| プロパティ | 値 |
|---|---|
CHANNEL_ACCESS_TOKEN |
取得したチャネルアクセストークン |
USER_ID |
取得した自分のユーザーID |
CALENDAR_ID |
通知したいカレンダーのID(※) |
- 「スクリプト プロパティを保存」をクリック
- 「プロジェクト名」をクリックして名前を変更(例: 「カレンダー通知」)
- 💾 保存ボタンをクリック
※ カレンダーIDは、Googleカレンダーの「設定」→ 対象カレンダーの「カレンダーの統合」から確認できます。デフォルトカレンダーの場合は自分のGmailアドレスです。
5. 動作確認
5-1. テスト実行
- 上部の関数選択で
sendLunchAvailabilityを選択 - 「実行」ボタン(▶)をクリック
- 初回は権限の許可を求められるので、以下の手順で許可:
- 「権限を確認」をクリック
- Googleアカウントを選択
- 「詳細」→「(プロジェクト名)に移動」をクリック
- 「許可」をクリック
- LINEに通知が届いたか確認
6. 毎日自動実行するトリガーを設定
6-1. トリガーを追加
- 左メニューの「トリガー」(時計アイコン)をクリック
- 右下の「トリガーを追加」をクリック
- 以下のように設定:
-
実行する関数を選択:
sendLunchAvailability -
実行するデプロイを選択:
Head -
イベントのソースを選択:
時間主導型 -
時間ベースのトリガーのタイプを選択:
日付ベースのタイマー -
時刻を選択:
午前8時~9時(好みの時間帯を選択)
-
実行する関数を選択:
- 「保存」をクリック
これで毎朝8時頃に自動でランチの空き時間がLINEに通知されるようになります!
7. まとめ
GASとLINE Messaging APIを組み合わせることで、ランチ空き時間通知システムを構築できました。
- 月200通まで無料(個人利用なら十分)
- サーバー不要で自動実行
- 11:00〜13:00の空き時間を自動計算(30分以上のみ)
8. 応用:家族にも通知を送る
自分だけでなく、家族(妻など)にも通知を送りたい場合の手順です。
Push APIの課題
ここまでの実装ではPush APIを使用しています。Push APIは特定のユーザーにメッセージを送るため、送信先のユーザーIDが必要です。
- 開発者自身のユーザーIDはLINE Developersコンソールで確認可能
- しかし、家族のユーザーIDを取得するにはWebhookの設定が必要で煩雑
そこで、ブロードキャストAPIを使用します。
ブロードキャストAPIとは
友だち追加した全員に一斉送信できるAPIです。ユーザーIDは不要なので、家族に友だち追加してもらうだけでOKです。
8-1. 友だち追加URLを発行
- LINE Official Account Manager にアクセス(公式アカウント作成時に遷移したページです。ようやく使います!)
- 左メニューの「友だち追加ガイド」をクリック
- 「URLを作成」でURLを発行
- このURLを家族に共有して友だち追加してもらう
- もちろんURL発行以外の方法でも差し支えないです。
8-2. ブロードキャストAPIに切り替え
sendLinePushMessage 関数を以下の sendBroadcastMessage に置き換えます。
/**
* LINE Messaging APIでブロードキャストメッセージを送信
* @param {{CHANNEL_ACCESS_TOKEN: string}} config
* @param {string} message
*/
function sendBroadcastMessage(config, message) {
const url = 'https://api.line.me/v2/bot/message/broadcast';
const options = {
method: 'post',
contentType: 'application/json',
headers: {
Authorization: `Bearer ${config.CHANNEL_ACCESS_TOKEN}`
},
payload: JSON.stringify({
messages: [{ type: 'text', text: message }]
})
};
try {
UrlFetchApp.fetch(url, options);
Logger.log('LINE通知を送信しました');
} catch (e) {
Logger.log(`エラー: ${e}`);
}
}
また、sendLunchAvailability 関数内の呼び出しも変更します。
// Before
sendLinePushMessage(config, message);
// After
sendBroadcastMessage(config, message);
ブロードキャストAPIではユーザーIDが不要なため、スクリプトプロパティの USER_ID は削除してOKです。
8-3. 自分への通知を止める(任意)
ブロードキャストは友だち全員に届きます。テスト完了後、自分への通知が不要であれば、自分のLINEからこの公式アカウントをブロックまたは友だち解除すれば、家族だけに通知が届くようになります。