LINE BOTからGoogleカレンダーに予定の追加＆一週間の予定の確認を行う

# はじめに
この記事はプログラミングほぼ未経験だった僕が、プログラミングを学び、アウトプットとして作ったLINEBOTに関するものです。

初めての投稿なので、改善点やアドバイス、感想などあればコメントお願いします。

# 目次
1.作ろうと思った経緯
2.機能説明
3.予定追加・一週間の予定確認の実際のトーク画面
4.ソースコード
5.今後の改善点
6.参考にした記事

# 1.作ろうと思った経緯
就職活動がかなり忙しくなってきたときに、
使用頻度が高いLINEで予定追加と確認ができれば楽だな、と思ったからです。
また、inteeリアルカレッジのポートフォリオとして作成しました！

# 2.機能説明
リッチメニューの「予定追加」を押すと予定を追加。
「一週間の予定確認」を押すと、Googleカレンダーに記入されている一週間の予定を日付・時間帯・予定名でリスト化して返す。
「最初からやり直し」を押すと、予定追加を中断し、最初からやり直せる。

# 3.予定追加・一週間の予定確認の実際のトーク画面
##予定追加
###予定日→開始時刻→終了時刻→予定名→確認　の順に登録する。

![ポートフォリオ１.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1190145/e10e4da9-25bd-7603-8a0f-fe8427836aee.jpeg)
![ポートフォリオ２.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1190145/65669b7f-1212-97d8-b43f-8d4de26289c2.jpeg)

## 一週間の予定確認

![Inkedポートフォリオ用LINE画像_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1190145/ac3e91a8-41c3-bb3c-6719-c1cd0a1692ac.jpeg)

# 4.ソースコード
チャネルアクセストークン、スプレッドシートのID、Googleアカウントはご自身の物を使用されてください！

```javascript:
var CHANNEL_ACCESS_TOKEN = "チャネルアクセストークン";
var ERROR_SHEET_ID = "スプレッドシートのID";

//日付、時刻のフォーマット設定
var dateExp = /(\d{2})\/(\d{2})\s(\d{2}):(\d{2})/;
var dayExp = /(\d+)[\/月](\d+)/;
var hourMinExp = /(\d+)[:時](\d+)*/;


function doPost(e) {
  try {
    handleMessage(e);
  } catch(error) {
    logging("ToCalendarFromLineBot");
    logging(JSON.stringify(e));
    logging(JSON.stringify(error));
    var replyToken = JSON.parse(e.postData.contents).events[0].replyToken;
    reply(replyToken, error.message);
  }
}


function logging(str) {
  var sheet = SpreadsheetApp.openById("スプレッドシートのID").getActiveSheet();
  var ts = new Date().toLocaleString("japanese", {timeZone: "Asia/Osaka"});
  sheet.appendRow([ts, str]);
}

function handleMessage(e) {
  var replyToken = JSON.parse(e.postData.contents).events[0].replyToken;
  var lineType = JSON.parse(e.postData.contents).events[0].type
  if (typeof replyToken === "undefined" || lineType === "follow") {
    return;
  }
  var userMessage = JSON.parse(e.postData.contents).events[0].message.text;
  var cache = CacheService.getScriptCache();
  var type = cache.get("type");

  if (type === null) {
    if (userMessage === "予定追加") {
      cache.put("type", 1);
      reply(replyToken, "予定日を教えてください！\n「06/17, 6月17日」などの形式なら大丈夫です！");
    } else if (userMessage === "一週間の予定確認") {
      reply(replyToken, getEventss());
    } else {
      reply(replyToken, "リッチメニューの「予定追加」で予定追加を、「一週間の予定確認」で一週間の予定参照ができるので気軽に話しかけてくださいね！");
    }
  } else {
    if (userMessage === "キャンセル") {
      cache.remove("type");
      reply(replyToken, "キャンセルしました！");
      return;
    }

    switch(type) {
      case "1":
        // 予定日
        var [matched, month, day] = userMessage.match(dayExp); //dayexpの型に合う文字列を取り出す。
        cache.put("type", 2);
        cache.put("month", month);
        cache.put("day", day);
        reply(replyToken, month + "/" + day + "ですね！　次に開始時刻を教えてください。「13:00, 13時, 13:20, 13時20分」などの形式なら大丈夫です！");
        break;

      case "2":
        // 開始時刻
        var [matched, startHour, startMin] = userMessage.match(hourMinExp);
        cache.put("type", 3);
        cache.put("start_hour", startHour);
        if (startMin == null) startMin = "00";
        cache.put("start_min", startMin);
        reply(replyToken, startHour + ":" + startMin + "ですね！　次に終了時刻を教えてください。");
        break;
        
      case "3":
        // 終了時刻
        var [matched, endHour, endMin] = userMessage.match(hourMinExp);
        cache.put("type", 4);
        cache.put("end_hour", endHour);
        if (endMin == null) endMin = "00";
        cache.put("end_min", endMin);
        reply(replyToken, endHour + ":" + endMin + "ですね！　最後に予定名を教えてください！");
        break;

      case "4":
        // 予定名
        cache.put("type", 5);
        cache.put("title", userMessage);
        var [title, startDate, endDate] = createEventData(cache);
        reply(replyToken, toEventFormat(title, startDate, endDate) + "\nで間違いないでしょうか？ よろしければ「はい」をやり直す場合は「いいえ」をお願いいたします！");
        break;

      case "5":
        // 確認の回答がはい or いいえ
        cache.remove("type");
        if (userMessage === "はい") {
          var [title, startDate, endDate] = createEventData(cache);
          CalendarApp.getDefaultCalendar().createEvent(title, startDate, endDate);
          reply(replyToken, "追加しました！\nお疲れ様でした！");
        } else {
          reply(replyToken, "お手数ですがもう一度お願いいたします！");
        }
        break;
      default:
        reply(replyToken, "申し訳ありません。\n形式に誤りがないか確認してみて、なければ「キャンセル」で予定入力をキャンセルすることができるので、そちらを試していただけますか？");
        break;
    }
  }
}

function createEventData(cache) {
  var year = new Date().getFullYear();
  var title = cache.get("title");
  var startDate = new Date(year, cache.get("month") - 1, cache.get("day"), cache.get("start_hour"), cache.get("start_min"));
  var endDate = new Date(year, cache.get("month") - 1, cache.get("day"), cache.get("end_hour"), cache.get("end_min"));
  return [title, startDate, endDate];
}

function toEventFormat(title, startDate, endDate) {
  var start = Utilities.formatDate(startDate, "JST", "MM/dd HH:mm");
  var end = Utilities.formatDate(endDate, "JST", "MM/dd HH:mm");
  var str = title + ": " + start + " ~ " + end;
  return str;
}

function reply(replyToken, message) {
  var url = "https://api.line.me/v2/bot/message/reply";
  UrlFetchApp.fetch(url, {
    "headers": {
      "Content-Type": "application/json; charset=UTF-8",
      "Authorization": "Bearer " + CHANNEL_ACCESS_TOKEN,
    },
    "method": "post",
    "payload": JSON.stringify({
      "replyToken": replyToken,
      "messages": [{
        "type": "text",
        "text": message,
      }],
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({"content": "post ok"})).setMimeType(ContentService.MimeType.JSON);
}

function get_Calendar() {
  var arrCals=[];
  arrCals.push(CalendarApp.getCalendarById('使いたいGoogleカレンダーのアカウントのメールアドレス'));
  return arrCals;
}

//1週間の予定を取得するメインの関数
function get_Week_Schedule(){
  var arrCals = get_Calendar();
  var dateNow = new Date();
  var date = new Date();
  var strBody ='';
  var tmpBody ='';
  var strIntro = "おつかれさまです。\n1週間の予定です。\n" ;
  for (var j = 1; j < 7 ; j++ ){
    date.setDate(dateNow.getDate()+j);
    for (var i = 0 ; i < arrCals.length ; i++){
      tmpBody = tmpBody + getEvents(arrCals[i],date);
    }
    if (tmpBody){
      strBody = strBody + date.getDate() + '日\n' + tmpBody;
    }
    tmpBody = '';
  }
  return (strIntro + strBody);
}

//予定を取得する関数
function getEvents(Cals,getDate){
  var arrEvents = Cals.getEventsForDay(getDate);//カレンダーの予定取得
  var strEvents ='';
  for (var i=0; i<arrEvents.length; i++){
    var strTitle = arrEvents[i].getTitle();
    var strStart = _HHmm(arrEvents[i].getStartTime());
    var strEnd = _HHmm(arrEvents[i].getEndTime());
    if (strStart == strEnd){
      strEvents = strEvents + '終日イベント：' + strTitle +  '\n';
    }else{
      strEvents = strEvents + strStart + '～' + strEnd+ '：'  + strTitle  + '\n';
    }
  }
  return strEvents;
}

//予定確認時の関数
function getEventss() {
  var events = CalendarApp.getDefaultCalendar().getEventsForDay(new Date());
  var body;

  if (events.length === 0) {
    body = "今週の予定はありません!";
    return body;
  } else {
    body = get_Week_Schedule();
    return body;
  }
}

function toHHmm(date){
  return Utilities.formatDate(date, "JST", "HH:mm");
}

function _HHmm(str){
  return Utilities.formatDate(str,'JST','HH:mm');
}

```

# 5.今後の改善点
* ソースコードに書いたアカウントでしかGoogleカレンダーと連携できない。
→LINEトーク画面でアカウント切り替え・追加できるようにすればよいかも？
→また、個人で使うほかに、組織内共通のアカウントで使うとよいかもしれない。

* 一週間の予定確認をする際に、月を表示できるようにしたら見やすい。

* 予定追加の際、開始・終了時刻が24:00を超えた場合の処理
   →入力し直せるようにする。

* 予定を削除する機能を付ける。

* 前日の夜や当日の朝に予定を連絡するリマインド機能を付ける。

* 今年の予定しか登録できない。

# 6.参考にした記事
①GAS上で一週間の予定確認する関数について書かれた記事
https://cyuraharuto.com/gas-googlecalender-week-get/

②Googleカレンダーと連携して予定追加・一日の予定確認をするLINEBOTの記事
https://qiita.com/gawawa124/items/384496ac31fac531f1f9

③リッチメニュー作成について書かれた記事
https://qiita.com/bow_arrow/items/32ac5d2b4c67bd0c1dc2

④タイムゾーン変更方法について
https://stabucky.com/wp/archives/13825

④については、自分が躓いたので、載せてます。