LINE Notifyサービスが終了したので、忘れてはいけないシフト業務をLINEでリマインドするシステムを、Chat GPT先生に相談しながら新たにつくってみた

## はじめに
私は、大阪在住の小児科医です:grinning:
普段は複数の医師が在籍し、地域の救急医療を担う病院で勤務をしています。その中で私は、複数の医師の勤務を調整し、派遣先へ連絡する役割を担っています。

この勤務は、日頃から働いている場所とは異なることが多く、また必ずしも決まった場所や時間で行われるものではないため、**“イレギュラーな仕事”**　といえます。日常業務も決して暇ではないため、このようなイレギュラーな勤務を忘れてしまう――そんな「起こってはならない事態」が、時に起きてしまうのです。

そうなると、コーディネートしている私へ連絡が来るのですが、その「指摘をすること」自体にもストレスがかかります。自分が若手の頃は、**「指摘されること」に対してのストレスしか感じていませんでしたが、実は「指摘する側」にもストレスがかかる** ということに、数年前に気づきました。

このストレスは、医療職でなくとも複数のメンバーで仕事を行なっている場合、複数人で業務を行う場面ではよく起こり得るものだと思います。

そこで私は[2年前にLINE Notifyを使用したリマインドシステム](https://qiita.com/ytktkmr/items/7b39f7d27a286fba5437)を作成しました:raised_hands:
当日の朝にその日の仕事がLINEで届くだけ、というシンプルな仕組みですが、複数の同僚から　**「あれ助かってるよ！」**　や　**「あれのおかげで危うく忘れそうな状況を回避できたよ！」**　というお声がけをいただきましたし、実際にそのシステムが稼働して以降、**少なくとも私の耳に入ってきた明らかな出務忘れや遅刻などの報告は1件もありませんでした**:clap:

 ところが、**2025年3月31日をもってこのシステムの根幹であった[LINE Notifyのサービスは終了](https://notify-bot.line.me/closing-announce)**　してしまいました。
LINE公式の案内によると「LINEを用いてユーザーに通知を送るプロダクトとしては、より多彩な機能を備えたMessaging APIを提供している」とのことでした。なるほど。たしかにそれはその通り。LINEさん、ありがとう！と言いたいところですが、明らかに手続きは煩雑ですぐには対応できませんでした:sweat:

 でも…せっかく[LINEのAPIを使い他のプロダクトを作成した経験](https://qiita.com/ytktkmr/items/df824bf94e41d0f17336)もあるし、私が少しの手間を惜しんで、何より私が少しの手間を惜しんで、同僚やその先にいる患者さんへ迷惑をかけることは許されません！
 
しかも、今回は **Chat GPT先生** もついています！ということで、重い腰を上げて新たなリマインドシステムを作成したので、同じような悩みを持つ方々の参考になればと思い、この記事を書くことにしました。

あくまで私はこの分野の素人ですし、プロの方から見れば「紹介するほどのレベルではない」と思われるかもしれません。でも、 **LINE Notifyの終了によって困っている人がいるかもしれない**、そう考えてこの記録を残すことにしました。

素人の趣味レベルで作ったものですが、あたたかい目でお読みいただければ幸いです:hugging:
 ## 実際はこんな感じ
 ![リマインドLINEスマホ画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919962/6fae305f-dfa2-4860-a59e-4ac1480d0c08.png)
当日の朝に、その日の仕事があれば「本日の外のお仕事」として箇条書きで予定が届けられ、何も仕事がない日は「本日の外のお仕事はありません」と通知が届きます。
## システムとつくり方
**1.　全体の流れ**
![システムはこんな感じ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919962/8dcf3655-e5d7-4e7c-aaf8-0ccbd8915528.png)
GoogleカレンダーからIDを取得し、Google App Scriptに入力し、LINE Messaging APIを介して、LINEのグループへメッセージを送ります。　**作成したLINEのBotが、グループ内へ定期的にカレンダーの予定をリマインドする**イメージです:alarm_clock:

**2.　GoogleカレンダーからカレンダーIDを取得する**
まず、グループの予定を入力しているGoogleカレンダーから、予定を取得します。
ここでは「テストイシ1〜4」という4人の医師が、それぞれ「勤務A〜D」という4か所の勤務地に予定が入っている例を用いて、手順を説明します。
![34343.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919962/1b079094-d644-4e0a-b329-fe2b98f97dc4.png)

次に、Googleカレンダーの予定をGASで取得するために、**カレンダーIDを取得します。**
![44444.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919962/7115a243-380d-416f-bb9b-1c7b2e16f386.png)
カレンダーIDは、勤務ごとに異なって設定されているため、それぞれ同様の手順でコピーし、メモ帳などに保存しておきましょう:writing_hand_tone1:

**3. LINEのグループIDを取得する**
　GASからLINEへメッセージを送るためには、LINEグループIDを取得する必要があります。ただし、これには事前の準備が必要です。
 
:airplane_departure: **準備手順（概要）**
:black_small_square:[LINE Developpers](https://developers.line.biz/ja/?status=success)からプロバイダーを作成し、Messaging APIチャネルを立ち上げ、LINE公式アカウントを作成します。
:black_small_square:Messaging API設定タブをクリックし、下へスクロールし、チャネルアクセストークンを作成します。
:black_small_square:発行されたチャネルアクセストークンは、先ほどのカレンダーIDといっしょに保存します。
:black_small_square:詳細な手順については、[Line Messaging APIで簡単Line Bot作成（超初心者向け）](https://qiita.com/YSFT_KOBE/items/8dc62ac40c5112df2ed3)をみるとわかりやすいです:boy_tone1:
　
ここまで準備ができたら、いよいよ **LINEグループIDを取得** する段階へ進みます。

Gooleドライブの「新規」→「その他」→「Google Apps Script」を開き、以下のコードを貼り付けます。

これは、[GASを使ったLINE Messaging APIでのユーザーID取得方法](https://note.com/m316jp2/n/nc466911fa9ab)を参考に、一部を調整しています。
```ruby:Google Apps Script
function doPost(e) {
  const token = "ここにチャネルアクセストークンをいれてください";
  
try {
  const eventData = JSON.parse(e.postData.contents).events[0];
  const source = eventData.source;

  let replyMessage = '';

  if (source.type === 'group') {
    replyMessage = `このグループのIDは\n${source.groupId}\nです。`;
  } else {
    replyMessage = 'グループからのメッセージではありません。';
  }

  const payload = {
    'replyToken': eventData.replyToken,
    'messages': [{
      'type': 'text',
      'text': replyMessage
    }]
  };

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    'payload': JSON.stringify(payload),
    'method': 'POST',
    'headers': {'Authorization': 'Bearer ' + token},
    'contentType': 'application/json'
  });

} catch (error) {
  Logger.log('エラー: ' + error);
}
}
```
　これを貼り付けた後にデプロイして、表示されたウェブアプリIDをコピーして、これまでと同様にメモします:writing_hand_tone1:再びLINE Developersをひらき、さきほどのチャネルアクセストークンの上にあるWebhook設定のWebhook URLに、先ほどメモしたウェブアプリIDをペーストします。「検証」ボタンをクリックし、「成功」と表示されれば連携完了です。
　Google App Scriptの実行を押すと、LINEグループにメッセージが届き、そこにグループIDが記載されています。
![555555.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919962/f5788299-a7b3-4ef1-897e-93b715831ece.png)

他にもLINEのグループIDを取得する方法はありますが、私はこの方法が最も直感的に理解しやすいと感じました:relieved:ただし、**事前にグループのメンバーへこの通知が届くことを断っておく方が良い**と思います。急に来たらびっくりする方もいるかと思うので。笑

**4.　別のGoogle Apps Scriptを立ち上げてLINE Messaging APIと連携する**
いよいよ最終段階です！
先ほどグループIDを取得したものと別のGoogle Apps Scriptを立ち上げます。そこに以下のコードを貼り付けます。これは、[Googleカレンダーの予定をLINEbotで毎日通知する！](https://qiita.com/hyuga20011212/items/d32e8e2e4b8dcf987e37)を参考に、私の使いやすい様に修正したものです。
```ruby:Google Apps Script
//Google Calendarと連携する設定
const LINE_ACCESS_TOKEN = 'ここにチャネルアクセストークンをいれてください';
const LINE_USER_ID = 'ここにLINEのグループIDをいれてください';
const URL = 'https://api.line.me/v2/bot/message/push';

// マイカレンダーの複数ID（Googleカレンダーの設定画面で確認可能）
const CALENDAR_IDS = [
  'GoogleカレンダーID01をいれてください',
  'GoogleカレンダーID02をいれてください',
  'GoogleカレンダーID03をいれてください',
  'GoogleカレンダーID04をいれてください',
];

function getGoogleCalendarSimple() {
  const today = new Date();
  today.setHours(0, 0, 0, 0); // 0時0分にリセット

  // 日付表示用フォーマット
  const monthNum = today.getMonth() + 1;
  const dateNum = today.getDate();
  const day = today.getDay();
  const dayArray = ['日', '月', '火', '水', '木', '金', '土'];
  const dateLabel = `${monthNum}/${dateNum}（${dayArray[day]}）`;

  // 全カレンダーから予定を取得
  let allEvents = [];

  CALENDAR_IDS.forEach(id => {
    const cal = CalendarApp.getCalendarById(id);
    if (cal) {
      const events = cal.getEventsForDay(today);
      allEvents = allEvents.concat(events);
    }
  });

  // メッセージ組み立て
  let messageBody = '';

  if (allEvents.length === 0) {
    messageBody = '本日の外のお仕事はありません。';
  } else {
    messageBody = '本日の外のお仕事\n';
    allEvents.forEach(event => {
      const title = event.getTitle();
      messageBody += `・${title}\n`;
    });
  }

  const sendMessage = `${dateLabel}\n${messageBody}`;

  // LINE通知
  const postData = {
    to: LINE_USER_ID,
    messages: [
      {
        type: 'text',
        text: sendMessage,
      },
    ],
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(postData),
    headers: {
      Authorization: 'Bearer ' + LINE_ACCESS_TOKEN,
    },
  };

  UrlFetchApp.fetch(URL, options);
}
```
:black_small_square:チャネルアクセストークンは、LINE公式アカウントとの連携なので、先ほどと同じものです。
:black_small_square:LINEのグループIDは先のステップで得られたものです。
:black_small_square:Googleカレンダー01〜04は、2.で得られたカレンダーIDです。数はご自身のカレンダー（ここでは勤務地）の数に合わせてコピペしてください。

それぞれ入力し終わったらデプロイし、ウェブURLを発行します。このウェブURLを、先ほどと同様にLINE Developersをひらき、Webhook設定のWebhook URLに、グループIDを取得するために入力したウェブURLを削除して新たにペーストします。「検証」ボタンをクリックし、「成功」と表示されれば連携完了です。

最後に、Google Apps Scriptをひらき、以下の通りに設定します。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919962/5d01fbf1-83ec-4a59-8d5f-97a8fea88880.png)
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919962/1e72e91f-6cea-4de6-a637-02ac4bd24e9c.png)
届く時刻を詳細に設定することはできず、7時から8時の間に届きます。我々のグループには、**なぜか毎日7時43分にリマインドが届いています。** これで、設定は完了です！お疲れ様でした:boy_tone1:

## 気になる点 Q&A
**Q1. グループのメンバーをいれかえても動作に問題ない？**
A1. はい。グループ内にBotのアカウントが入っていれば、それ以外のメンバーを入れ替えても問題ありません。

**Q2. 適切にシステムが稼働するかを確認するために翌朝まで待たないといけない？**
A2. いいえ。グループID取得の時と同様に、Google Apps Scriptの「実行」を押すとメッセージが届きます。


## 終わりに
以前の記事を投稿したのは、約2年前になりますが……正直なところ、私の技術力はあまり進歩していません:joy:

ただ、今回の開発にあたっては前回（2023年2月）と大きく違う点がありました。それは**Chat GPTの存在**です。

前回は、Qiitaをはじめとするネット記事を複数集めて少しずつ修正して、実際に稼働するかドキドキしながらデプロイする、という作業をおこなっていました。しかし今回は、途中で紹介した記事を元にして、そのスクリプトを直接Chat GPTへいれて、変更したい内容を自然言語で指示をだし、提供されたスクリプトをそのままペーストして完成までこぎつけました。
Chat GPT先生は**何を質問しても嫌な顔ひとつせず、時に私を労い、時に私を褒め、最後に必ず新たなシステムの提案もする**とてつもない**優秀な先生**でした。笑
 
今回の取り組みが、私自身のスキルアップにつながったかというと微妙ですが……
とはいえこれは本業ではなく、**日々のストレスを減らすため、趣味を兼ねて作ったもの** です。その意味では、ChatGPTという最高の相棒と一緒に、とてもいい開発体験ができたと感じています。

この記事が、**LINE Notifyのサービス終了** に困っているどこかの誰かに届くことを祈っています。
最後までお読みいただき、ありがとうございました:open_hands_tone1: