6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Googleカレンダーの予定をLINEで自動通知して妻との会話を減らす方法

Last updated at Posted at 2025-12-18

はじめに

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チャネルの作成が必要です。
詳細な手順は公式が一番わかりやすかったです。

作成の流れ(概要)

  1. 今回は個人利用のため、自前のLINEアカウントにてQRコードでログイン
  2. 同じく自分の電話番号にてSMS認証
  3. 下記のページに遷移するため、アカウント情報を記載
    スクリーンショット 2025-12-16 22.30.13.png
  4. アカウント情報を登録し、利用規約に同意するとLINE Official Account Managerのページに遷移します。
  5. LINE Official Account Managerは当分使いませんが、開いておいてください。(8-1にて通知先を追加する際に使用します)

2. チャネルアクセストークンを取得

  1. LINE Developers Console にアクセス
  2. プロバイダーを作成(作成方法は割愛※)
  3. チャネルを作成(作成方法は割愛※)
  4. 作成したチャネルを選択
  5. 「Messaging API設定」タブをクリック
  6. 「チャネルアクセストークン」の「発行」ボタンをクリック
  7. 表示されたトークンをコピーして保存

※大筋から逸れるため割愛します。公式のドキュメントを参照してください。

3. 自分のユーザーIDを取得

プッシュメッセージを送るには、送信先のユーザーIDが必要です。

開発者自身のユーザーIDは、LINE Developersコンソールで簡単に確認できます。

  1. LINE Developers Console にアクセス
  2. 作成したチャネルを選択
  3. チャネル基本設定」タブをクリック
  4. ページ下部の「あなたのユーザーID」に表示されているIDをコピー

4. Google Apps Scriptでスクリプトを作成

4-1. スクリプトエディタを開く

  1. Google Apps Script にアクセス
  2. 「新しいプロジェクト」をクリック

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のスクリプトプロパティで管理します。

  1. 左メニューの「⚙️ プロジェクトの設定」をクリック
  2. 「スクリプト プロパティ」セクションで「スクリプト プロパティを追加」をクリック
  3. 以下の3つのプロパティを追加:
プロパティ
CHANNEL_ACCESS_TOKEN 取得したチャネルアクセストークン
USER_ID 取得した自分のユーザーID
CALENDAR_ID 通知したいカレンダーのID(※)
  1. 「スクリプト プロパティを保存」をクリック
  2. 「プロジェクト名」をクリックして名前を変更(例: 「カレンダー通知」)
  3. 💾 保存ボタンをクリック

※ カレンダーIDは、Googleカレンダーの「設定」→ 対象カレンダーの「カレンダーの統合」から確認できます。デフォルトカレンダーの場合は自分のGmailアドレスです。

5. 動作確認

5-1. テスト実行

  1. 上部の関数選択で sendLunchAvailability を選択
  2. 「実行」ボタン(▶)をクリック
  3. 初回は権限の許可を求められるので、以下の手順で許可:
    • 「権限を確認」をクリック
    • Googleアカウントを選択
    • 「詳細」→「(プロジェクト名)に移動」をクリック
    • 「許可」をクリック
  4. LINEに通知が届いたか確認

6. 毎日自動実行するトリガーを設定

6-1. トリガーを追加

  1. 左メニューの「トリガー」(時計アイコン)をクリック
  2. 右下の「トリガーを追加」をクリック
  3. 以下のように設定:
    • 実行する関数を選択: sendLunchAvailability
    • 実行するデプロイを選択: Head
    • イベントのソースを選択: 時間主導型
    • 時間ベースのトリガーのタイプを選択: 日付ベースのタイマー
    • 時刻を選択: 午前8時~9時(好みの時間帯を選択)
  4. 「保存」をクリック

これで毎朝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を発行

  1. LINE Official Account Manager にアクセス(公式アカウント作成時に遷移したページです。ようやく使います!)
  2. 左メニューの「友だち追加ガイド」をクリック
  3. 「URLを作成」でURLを発行
  4. このURLを家族に共有して友だち追加してもらう
  5. もちろん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からこの公式アカウントをブロックまたは友だち解除すれば、家族だけに通知が届くようになります。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?