Googlespreadsheetでオリジナルカレンダー（勤務表）作ってみた②

# シフト表を自動生成してLINEで見られるようにしたい②

[Googlespreadsheetでオリジナルカレンダー（勤務表）作ってみた①](https://qiita.com/chiapis2/items/738929c15c5f5b3d794c)に引き続き、作ったカレンダーをLINEで見られるようにしていきます。

## 1.毎月のカレンダーを完成させたら新しいシートを作成する
前回作ったオリジナルカレンダーは自動で更新ができて楽ちんです。先生の予定が変わって書き換えることもできます。しかし月を変えて更新をするとせっかく編集したものが消えてしまうデメリットも持っています。
そこで、完成したカレンダーは新しいシートに保存することにしました。これがLINEと繋がる大事なシートになってきます。

[![Image from Gyazo](https://i.gyazo.com/8493a577063ef21ea93eb90c57db688c.png)](https://gyazo.com/8493a577063ef21ea93eb90c57db688c)

Appscriptで新しくスクリプトを作成します。
[![Image from Gyazo](https://i.gyazo.com/34bc5fb80865f6a0e923c54e1ab98f5b.png)](https://gyazo.com/34bc5fb80865f6a0e923c54e1ab98f5b)
（.gs.gsになっているのがバレる…。）

シート名は必要に応じて変えます。
私は医師の勤務リストを`Doctorlist`、医師の予定が入ったカレンダーを`DoctorCalendar`としました。

新しいシートは`2022年6月`のようにシート名を付けて、シートが既にあったら更新だけされる仕組みになっています。
新しいシートを作るのと同時にリスト作成ができないので、コードの書き方は悪そうです。
シートがないと1度はシート作成の実行、2度目にリスト作成の実行になります。
下の方は同じことを繰り返していますが、for文を考えるのに疲れたのでゴリ押しコードになっています。


```js
function sample24() {

  var ss = SpreadsheetApp.getActiveSpreadsheet()
// spreadsheetのIDはURLのd/～/editの間の部分です。下のXXXの部分。
// https://docs.google.com/spreadsheets/d/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/edit
  var sp = SpreadsheetApp.openById("自分のspreadsheetのID")

  var sheet02 = sp.getSheetByName("DoctorCalendar")

  var year = sheet02.getRange("B3").getDisplayValue()
  var day = sheet02.getRange("C3").getDisplayValue()

  var SheetName = year+"年"+day+"月";

  var sheet = SpreadsheetApp.getActive().getSheetByName(SheetName);


  if(sheet==null){
    var sheet = ss.insertSheet(SheetName);

  }else{

    var sheet01 = sp.getSheetByName(SheetName)
    var sheet02 = sp.getSheetByName("DoctorCalendar")
    sheet01.getRange("A1").setValue("date")
    sheet01.getRange("B1").setValue("am")
    sheet01.getRange("C1").setValue("pm")

  // 日付を入れる
    var date = new Date(sheet02.getRange("C6").getValues())
    sheet01.getRange("A2").setValue(date);
    
    for(var i=0; i<30; i++){
      date.setDate(date.getDate()+1);
      sheet01.getRange(3+i,1).setValue(date);
    }


    for (h = 0; h<7; h++){
    // 午前の先生
      var am = sheet02.getRange(8,3+h,7,1).getValues();
      var value = am.join("\n");
      sheet01.getRange(2+h,2).setValue(value)

    // 午後の先生
      var pm = sheet02.getRange(16,3+h,7,1).getValues();
      var value01 = pm.join("\n");
      sheet01.getRange(2+h,3).setValue(value01)
    }

    for (h = 0; h<7; h++){
    // 午前の先生
      var am = sheet02.getRange(25,3+h,7,1).getValues();
      var value = am.join("\n");
      sheet01.getRange(9+h,2).setValue(value)

    // 午後の先生
      var pm = sheet02.getRange(33,3+h,7,1).getValues();
      var value01 = pm.join("\n");
      sheet01.getRange(9+h,3).setValue(value01)

    }

    for (h = 0; h<7; h++){
    // 午前の先生
      var am = sheet02.getRange(42,3+h,7,1).getValues();
      var value = am.join("\n");
      sheet01.getRange(16+h,2).setValue(value)

    // 午後の先生
      var pm = sheet02.getRange(50,3+h,7,1).getValues();
      var value01 = pm.join("\n");
      sheet01.getRange(16+h,3).setValue(value01)
    }

    for (h = 0; h<7; h++){
    // 午前の先生
      var am = sheet02.getRange(59,3+h,7,1).getValues();
      var value = am.join("\n");
      sheet01.getRange(23+h,2).setValue(value)

    // 午後の先生
      var pm = sheet02.getRange(67,3+h,7,1).getValues();
      var value01 = pm.join("\n");
      sheet01.getRange(23+h,3).setValue(value01)

    }
    
    for (h = 0; h<7; h++){
    // 午前の先生
      var am = sheet02.getRange(76,3+h,7,1).getValues();
      var value = am.join("\n");
      sheet01.getRange(30+h,2).setValue(value)

    // 午後の先生
      var pm = sheet02.getRange(84,3+h,7,1).getValues();
      var value01 = pm.join("\n");
      sheet01.getRange(30+h,3).setValue(value01)

    }
  }

}
```

挿入→図形描画から完成ボタンを作成し、スクリプトを割り当てます。
[![Image from Gyazo](https://i.gyazo.com/7a0be8e578901747b7533097547812af.png)](https://gyazo.com/7a0be8e578901747b7533097547812af)

これで先生の予定変更をした後のカレンダーリストを保持することができます。

2.steinを使ってspreadsheetをAPI化する
[Stein](https://steinhq.com/)を利用します。
普通に検索するとファッションサイトが表示されるので上記からどうぞ。

spreadsheetのURLを入れるだけでAPIにしてくれるので超便利です。
ライブラリもあるので、追加、削除、閲覧などの方法は調べます。
[![Image from Gyazo](https://i.gyazo.com/0bda329862745192378f1c89a11827e5.png)](https://gyazo.com/0bda329862745192378f1c89a11827e5)

作成出来たら`API URL`をコピーして控えておきます。

3.LINEと連携する
実物は今日の担当医以外にも、今月の予定表や専門外来の案内など機能を付けているのですが、コードが長すぎるので今回は今日の担当医と曜日で呼び出す部分だけにします。

LINEはVScode上で、JavaScript、node.jsを使って書いています。

リッチメニューに「今日の担当医は？」を入れ込んでいて、その後はクイックリプライを仕込んでいます。
今回はリッチメニューがないので、自分で「今日の担当医は？」とメッセージを送って返答があれば成功です！

LINEのSDK入れたり…の部分は、こちらを読んでセッティングしてください。
めちゃ分かりやすいので、何度も読んで助けてもらっている記事です。
参考：[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)

moment.jsというパッケージを入れているので、ターミナルで以下をインストールします。
```
$ npm i moment
```
参考：[momentで時間データの操作を楽にする](https://qiita.com/taizo/items/3a5505308ca2e303c099)

```js

'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
var mom = require('moment')
const PORT = process.env.PORT || 3000;

const channel = {
    channelSecret: '自分のチャンネルシークレット',
    channelAccessToken: '自分のアクセストークン'
  };
  
  const app = express();
  
  app.post('/webhook', line.middleware(channel), (req, res) => {
    console.log(req.body.events);
    Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
  });
  
  const client = new line.Client(channel);
  
  // ------------------------------------------------
// スプレットシートAPI
const SteinStore = require("stein-js-client");
const store = new SteinStore("steinで作成したAPIのURL");


async function handleEvent(event) {

  var replyText = "";
  var msg = event.message.text;
  
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  
// 〇年〇月を取得する
  var date = new Date();
  const formatDate = (date)=>{
  let formatted_date =  date.getFullYear() + "年" + (date.getMonth() + 1) + "月"
   return formatted_date;
  }
  console.log(formatDate(date));

// spreadsheetのシートをどの範囲で取得するか指示する
  var data = await store.read("Doctorlist", { limit: 45, offset: 0 })
  var data01 = await store.read(formatDate(date), { limit: 31, offset: 0 })
  // console.log(data); 
  // console.log(msg);


  // 取得する期間を今月の初めから月末までに指定
  var today  = mom().format('YYYY/MM/DD');

// 〇〇先生と送られてきたら〇〇（名前の部分）をspreadsheetから探すようにする
  var checkname = data.filter(value => value.name.match(msg.replace("先生","")));  
  var checkdayofweek = data.filter(value => value.dayofweek.match(msg.replace("曜日","")));  
  var checktoday = data01.filter(value => value.date.match(today)); 

  // console.log(checktoday);

// クイックリプライ
  var quickReply ={ // ②
      "items": [
        {
          "type": "action",
          "action": {
            "type": "message",
            "label": "先生はいついる？",
            "text": "先生はいついる？"
          }
        },
        {
            "type": "action", // ③
            "action": {
              "type": "message",
              "label": "曜日の先生を探す",
              "text": "曜日の先生を探す"
           }
        }
      ]
    };



  // 先生の時間を探す
  if(checkname.length>0 && msg !== "水" && msg !== "木" && msg !== "金"){
    
    var test = [];
    var test02 = [];
    for (var i=0; i<checkname.length; i++){
      
      var speciality = checkname[i].speciality;
      var dayofweek = checkname[i].dayofweek;
      var classification = checkname[i].classification || "";
      var amstart = checkname[i].amstart || "";
      var amend = checkname[i].amend || "";
      var pmstart = checkname[i].pmstart || "";
      var pmend = checkname[i].pmend || "";
      
      test[i] = "\n【"+dayofweek+"曜日】"+classification+"外来\n"+"午前"+amstart+"-"+amend+"\n午後"+pmstart + "-" + pmend
      test02[i] = speciality
  
    }
    
    var name = checkname[0].name;
    var doctorinfo01 = "専門："+Array.from(new Set(test02));
    var doctorinfo02 =name+"先生\n"+doctorinfo01+test+"\nです。";
   
    replyText = doctorinfo02
    
  }  

  // 曜日の先生を探す
  else if(checkdayofweek.length>0){
      var test03 = [];
      for (var i=0; i<checkdayofweek.length; i++){
          var name = checkdayofweek[i].name;
          test03[i] = "\n"+name+"先生"
      }
      
      var d = checkdayofweek[0].dayofweek;

      var doctorweek = d +"曜日の先生は"+Array.from(new Set(test03))+"\nです。"
      
      replyText = doctorweek
  }
  
  else if(msg === "日曜日" || msg === "日" ){
  
    replyText = "日曜日、祝日はお休みです。"
  
  }
  

  // 今日の担当医

  else if(msg==="今日の担当医は？"){

    var am = checktoday[0].am.replace(/(\n)\1+/g, '$1');
    var pm = checktoday[0].pm.replace(/(\n)\1+/g, '$1');
    var todaydoctor = "今日の担当医は\n午前\n"+am+"\n午後\n"+pm+"です。"
    
    replyText = todaydoctor
  }
    
  else if(event.message.text == "こんにちは")
  return client.replyMessage(event.replyToken, quickReply);


  // 先生は誰？

  else if(msg==="曜日の先生を探す"){
    return client.replyMessage(event.replyToken, {
      type: 'text',
      text: "何曜日に来院しますか？", 
      "quickReply": { // ②
        "items": [
          {
            "type": "action", // ③
            "action": {
              "type": "message",
              "label": "月曜日",
              "text": "月曜日"
            }
          },
          {
            "type": "action",
            "action": {
              "type": "message",
              "label": "火曜日",
              "text": "火曜日"
            }
          },
          {
              "type": "action", // ③
              "action": {
                "type": "message",
                "label": "水曜日",
                "text": "水曜日"
              }
          },
          {
            "type": "action", // ③
            "action": {
              "type": "message",
              "label": "木曜日",
              "text": "木曜日"
            }
          },
          {
            "type": "action",
            "action": {
              "type": "message",
              "label": "金曜日",
              "text": "金曜日"
            }
          },
          {
              "type": "action", // ③
              "action": {
                "type": "message",
                "label": "土曜日",
                "text": "土曜日"
              }
          },
          {
            "type": "action", // ③
            "action": {
              "type": "message",
              "label": "日曜日",
              "text": "日曜日"
            }
          }
        ]}
      })
    
    }

  // 先生はいついるのか？

  else if(msg==="先生はいついる？"){

    replyText =  "気になる先生の名前を送ってください。"+"\nA先生\nB先生　…など"
  
  }

// -----------------------------------------------
  else{
    replyText = "もう一度やり直してください"
  }


    return client.replyMessage(event.replyToken, {
      type: 'text',
      text: replyText,
      "quickReply": quickReply

    }
    ); 
};

app.listen(PORT);
console.log(`Server running at ${PORT}`);

```

# おわりに
読んでコピペすればできる記事を書く予定でしたが、説明が少ない記事になってしまいました。
同じ名字の先生はどちらか一方しか出てこないなどまだ不備はありますが、完成に向けて取り組んでいきます。
理事長に「これ、すごいっすね！」って言っていただけたので一旦満足です！
読んでいただいてありがとうございました。

## 参考
[Stein/docs](https://docs.steinhq.com/read-data)
[JavaScriptで配列から値を検索し、取得や判定ができるfindの使い方](https://kinocolog.com/javascript_find/)
[配列から条件に合うものはfind()で見つけよう。](https://ginpen.com/2018/12/04/array-find/)
[[JavaScript] 配列の存在チェック（空判定）は if (array.length) {...} でいいよって話](https://qiita.com/kozzzz/items/42108bc96b3a0e7c53d5)
[【GAS】スプレッドシートの値を別のスプレッドシートにコピペするGAS](https://futureobs.xyz/2021/04/25/gas_spreadsheet_copy_and_paste/)
[Google Apps Script(GAS)入門 スプレッドシートのセル取得・書き込み方法(getValue,setValue)](https://auto-worker.com/blog/?p=655)
[JavaScript で配列から重複を削除する](https://www.delftstack.com/ja/howto/javascript/javascript-remove-duplicates-from-an-array/)
[Google Apps Scriptで隣のセルを取得、選択、アクティブセルを隣に移動する](https://www.relief.jp/docs/google-spreadsheet-gas-offset.html)
[js で完全一致をまとめてやりたい時](https://qiita.com/att55/items/a9c276ad0d67da926a4d)
[INDEXとMATCH関数の組み合わせで行列の交差値が一瞬で取得出来るよ](https://xn--t8j3bz04sl3w.xyz/spreadsheet/index-match/3248/)
[【javascript】連想配列で”key”だけや”value”だけを取得する方法](https://twotone.me/web/5329/)
[【JavaScript入門】連想配列(Dictionary)の取得/追加/ソートまとめ](https://www.sejuku.net/blog/27965)