プログラム知識0だけどGASでLINE WORKSの「今日の予定を教えてくれるBot」を作ってみる

# 今回のテーマ
プログラムの知識は無いですが、Botを作ってみたい！！という衝動に駆られ、
Botに話しかけるとLINE WORKSの[基本カレンダー](https://guide.worksmobile.com/jp/calendar/calendar-guide/calendar/calendar-type/#i)に登録されている今日一日の予定を教えてくれる。というBotを作成します。

[こちらの記事](https://qiita.com/kunihiros/items/24001310c0473c8531dc)と[こちらの記事](https://qiita.com/kunihiros/items/4cdec5784f21598cfee0)を参考に、LINE WORKSのBotを作成してみようと思います！

参考にした記事のBotとの違いはざっくりと以下の通りです。

|   |  参考記事  | 今回の記事|
| ---- | ---- | ---- |
| LINE WORKS APIバージョン  |  1.0  | 2.0 |
| Botが実行されるタイミング  | GASでスケジュールした時刻に実行される  | Botに話しかけると実行される |
|誰の予定を確認するか|GAS内で特定のアカウント情報を設定し、該当ユーザーの予定を確認　| トークで話しかけたユーザーの予定を確認 |

プログラム知識の無い私が果たしてどのように完成に至ったのか。。。。

## 構想
想定したのは以下のような流れです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/7a4e52ca-81db-5118-98d6-281fa83848b1.png)

① ユーザーがBotに対してトークを送信する
② Botがトークを送信してきたユーザーに挨拶をするため、service accountを使用して[ユーザーの取得API](https://developers.worksmobile.com/jp/reference/user-get?lang=ja)をリクエストする
③ ユーザー情報をレスポンスする
④ トーク送信元ユーザーのuserIdを使用して、service accountを使用して[基本カレンダーの予定リストの取得
](https://developers.worksmobile.com/jp/reference/calendar-default-event-user-list?lang=ja)で当日の予定をリクエストする
⑤ 今日の予定をレスポンスする
⑥ Botがユーザーに今日の予定をトークで送信する

②は別に必須では無いと思うのですが、気分的にやりたいだけですw

## 準備
以下の準備から始めました。

* Googleスプレッドシートの用意
* LINE WORKSのDeveloper ConsoleでBot登録
* LINE WORKSの管理画面でBotを使用可能な状態にする

### Googleスプレッドシートの用意
Googleドライブから新規のGoogleスプレッドシートを作成します。
ファイル名は何でも良いと思いますが、とりあえず今回は「calBot」というファイル名にしました。

メニューから「拡張機能」-「Apps Script」を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/beec867d-b83c-2f6e-b0f7-4f60e2b5f345.png)

とりあえず[参考](https://qiita.com/kunihiros/items/24001310c0473c8531dc)の手順をそのまま実行しました。

>1\.左側メニューの「ライブラリ」にある「＋」をクリック
>2\.「ライブラリを追加」画面でスクリプト ID に 1aLcCr3CWqfenPMyM0_FWIDUgRcTxsit9bO6BTx61NCXrCtkY2zbHBlod を入力して検索ボタンをクリック
>3\.「LINEWORKS」ライブラリが表示されるので、一番新しいバージョンを選んで追加ボタンをクリック

さらに[こちらの記事](https://qiita.com/kunihiros/items/4cdec5784f21598cfee0)に記載されていたコードを全部コピペしました。コピペしたものを色々変更していくためです。

画面の右上にある「デプロイ」から「新しいデプロイ」を選択し、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/330ad720-169a-b1e6-e1cb-5cb4b07bce78.png)

「ウェブアプリ」として、「全員」がアクセスできるように設定して「デプロイ」しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/a5f7ff77-0f24-bc26-87b3-980348f4c31f.png)

「ウェブアプリ」の部分に表示されるURLが「Callback URL」となり、次のLINE WORKSのBot登録で必要となるのでコピーしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/84f16f57-985a-31de-dee0-ed1fca465076.png)

[貼り付けたコード](https://qiita.com/kunihiros/items/4cdec5784f21598cfee0#4-%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F)にはLINE WORKSのBotを登録するファンクションも作られていたのですが、今回はDeveloper ConsoleからのBot登録も操作したかったので、そのファンクションは実行しませんでした。

多少手間ですがそれも勉強ということで。
ではLINE WORKSのBot登録を行います。

### LINE WORKSのDeveloper ConsoleでBot登録

Developer Consoleの[ログイン画面](https://auth.worksmobile.com/login/login?accessUrl=http%3A%2F%2Fdevelopers.worksmobile.com%3A80%2Fconsole%2Fopenapi%2Fv2%2Fapp%2Flist%2Fview)にアクセスしてログインします。

画面左側のメニューから「Bot」を選択し、「登録」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/5232e8a0-c390-488b-5736-73b658b8c1f8.png)

必須項目となる「Bot名」や「説明」に任意の内容を入力して、「Callback URL」をOnにします。
入力欄には先ほどデプロイ時にコピーしたURLを貼り付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/99d2a474-0a37-6bf8-d2cf-7b01130dff57.png)

下部にも必須項目となる「主担当」がありますので、ここは自分のアカウントを登録して保存しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/c7e94bca-81be-cf36-5d28-764ee1c55e29.png)

Botを保存すると、Bot IDが払い出されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/6144ee9b-03b7-ebc6-ee61-74b38d0f4f16.png)

[貼り付けたコード](https://qiita.com/kunihiros/items/4cdec5784f21598cfee0#4-%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F)のgetEnvファンクションにBot IDを入力する部分があるので、BotIDをコピペしておきます。

Developer ConsoleでBotを登録しただけでは、Botの状態は「準備中」になります。
コードを編集しながら正常に動くのか？など確認しつつ作業したいと思ったので、とりあえずBotを使える状態にします。

### LINE WORKSの管理画面でBotを使用可能な状態にする
今度はLINE WORKSの管理画面に入り、左側のメニューから「サービス」から「Bot」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/a94ce195-e7f1-6b0c-c36f-39267cb9afba.png)

画面右上の「Bot追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/7b3b5e9a-672f-afc7-cc8f-034bc455cc79.png)

Developer Consoleで登録したBotを選択し、「Bot追加」をクリックします。
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/b00b75b8-5f38-9e96-7782-31c56911dc22.png)

ここでBotを追加することによって、Developer Consoleで「サービス中」という状態となり、トーク画面でBotとのトークルームを作れるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/953ad5e8-5ea4-b2d6-f831-7446e19c3a4a.png)

では実際にどのようなコードを書いたのか？？？

## まず結論
Bot自体は作成できました。

```js: testcode.js
// POSTデータを受信した時に処理を開始する
function doPost(e) {
  if (e == null || e.postData == null || e.postData.contents == null) return　
　let requestJSON = e.postData.contents　　
  let requestObj = JSON.parse(requestJSON) 
  let env = getEnv_()　
  env.userId = requestObj.source.userId

// APIを実行するためのTokenを取得する
 let token = getAccessToken(env,"user,calendar")
 let accessToken = token.access_token
 let headers = { "authorization": "Bearer " + accessToken }
 let options = { 
    "headers": headers,
    "method": "get"
  }

// ユーザー情報を取得する
  const lwUserUri = "https://www.worksapis.com/v1.0/users/" + env.userId 
  const lwResponse = UrlFetchApp.fetch(lwUserUri,options)
  const lwUser = JSON.parse(lwResponse) 
  let userLastName = lwUser.userName.lastName
  let userFirstName = lwUser.userName.firstName

  let text = `こんにちは${userLastName} ${userFirstName}さん。`
  LINEWORKS.userMessageSend(env, text)

//基本カレンダーの予定リストを取る
  let today = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd") //今日の日付け
  const lwCalUri = "https://www.worksapis.com/v1.0/users/" + env.userId + "/calendar/events?fromDateTime=" + today + "T00:00:00%2B09:00&untilDateTime=" + today + "T23:59:59%2B09:00"
  const calResponse = UrlFetchApp.fetch(lwCalUri,options)
  let lwCal = JSON.parse(calResponse)
  let calCount = lwCal.events.length //予定の件数を取得
  let title
  let startHour
  let startTime
  let endHour
  let endTime
  let eYear
  let eMonth
  let eDay

  text = `今日の予定は${calCount}件です`

  LINEWORKS.userMessageSend(env, text)
 
//それぞれの予定の情報をトークで送信する
for (let i = 0; i < calCount; i ++){　
  if ( lwCal.events[i].eventComponents[0].start.hasOwnProperty('date')=== false){　
    title = lwCal.events[i].eventComponents[0].summary
    startHour = lwCal.events[i].eventComponents[0].start.dateTime.slice(11,13)
    startTime = lwCal.events[i].eventComponents[0].start.dateTime.slice(14,16)
    endHour= lwCal.events[i].eventComponents[0].end.dateTime.slice(11,13)
    endTime= lwCal.events[i].eventComponents[0].end.dateTime.slice(14,16)

  text = `${i + 1}件目の予定です。
          件名：${title}
          開始時間：${startHour}時${startTime}分から
          終了時間：${endHour}時${endTime}分まで`

  LINEWORKS.userMessageSend(env, text)
  }else{
    title = lwCal.events[i].eventComponents[0].summary
    startTime = lwCal.events[i].eventComponents[0].start.date
    endTime = lwCal.events[i].eventComponents[0].end.date

    eYear = endTime.slice(0,4)
    eMonth = endTime.slice(5,7)
    eDay = endTime.slice(8,10)
    eDay = Number(eDay)
    eDay = eDay - 1

  text = `${i + 1}件目の予定です。この予定は【終日の予定】です。
          件名：${title}
          開始日：${startTime}
          終了日：${eYear}-${eMonth}-${eDay}`

  LINEWORKS.userMessageSend(env, text)
  }
}

  text = `以上です`

  LINEWORKS.userMessageSend(env, text)
}

// getAccessTokenとgetJwtはTokenを取得するためのファンクション
function getJwt(ENV){
  const header = Utilities.base64Encode(JSON.stringify({"alg":"RS256","typ":"JWT"}), Utilities.Charset.UTF_8)
  const claimSet = JSON.stringify({
    "iss": ENV.CLIENT_ID,
    "sub": ENV.SERVICE_ACCOUNT,
    "iat": Math.floor(Date.now() / 1000),
    "exp": Math.floor(Date.now() / 1000 + 2000)
  })
  const encodeText = header + "." + Utilities.base64Encode(claimSet, Utilities.Charset.UTF_8)
  const signature = Utilities.computeRsaSha256Signature(encodeText, ENV.PRIVATE_KEY)
  return encodeText + "." + Utilities.base64Encode(signature)
}

function getAccessToken(ENV, scopes) {
  const uri = "https://auth.worksmobile.com/oauth2/v2.0/token"
  const payload = {
    "assertion" : getJwt(ENV),
    "grant_type" : encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
    "client_id": ENV.CLIENT_ID,
    "client_secret": ENV.CLIENT_SECRET,
    "scope": scopes    
  }
  const options = {
    "method": "post",
    "headers": {"Content-Type" : "application/x-www-form-urlencoded"},
    "payload": payload
  }
  return JSON.parse(UrlFetchApp.fetch(uri, options))
}

// 認証情報
function getEnv_() {
  return {
    CLIENT_ID: "*****",
    CLIENT_SECRET: "*****",
    SERVICE_ACCOUNT: "service_account@domain",
    PRIVATE_KEY: "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----",
    DOMAIN_ID: *****,
    ADMIN_ID: "admin@domain",
    BOT_ID: *****
  }
}
```

このような感じで動きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/60aabb7e-9745-a01f-3138-557ce0cd640b.png)

## 各部分の解説
### POSTデータの受信と認証情報の取得
まず冒頭部分から見ていきます。

~~~js: testcode.js
function doPost(e) {
  if (e == null || e.postData == null || e.postData.contents == null) return　
　let requestJSON = e.postData.contents　　
  let requestObj = JSON.parse(requestJSON) 
  let env = getEnv_()　
  env.userId = requestObj.source.userId
~~~
最初のfunction dopostから「？」という感じだったのですが、 **function doPostは、Callback URLがPOSTデータを受け取った場合に、処理を始めるよ。** というということみたいです。

今回、LINE WORKSのユーザーがBotとのトークルームでトークを送る事が処理のトリガーとなりますが、「Botにトークする = Callback URLに対してトークを送る」ということになります。

 **Callback URLに送られたデータは[このようなデータ](https://developers.worksmobile.com/jp/reference/bot-callback-message?lang=ja)** が含まれています。

```js: testcode.js
  if (e == null || e.postData == null || e.postData.contents == null) return　
```

2行目の「if~」の部分で「受け取ったデータが空じゃないよね？」ということを確認します。

```js: testcode.js
　let requestJSON = e.postData.contents　　
  let requestObj = JSON.parse(requestJSON) 
```

受け取ったデータをrequestObjに代入して、requestObjをパースします。
 **[パース](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)って何？？？？** って感じですが、とりあえず先に進めます。

```js: testcode.js
  let env = getEnv_()　
  env.userId = requestObj.source.userId
```
次にgetEnvファンクションを実行し、Client sercretなどの認証情報をenvに代入します。
さらに受け取ったデータの中から、userIdをenvの中にuserIdとして代入します。

### Tokenの取得
次にAPIを実行するためのTokenの取得です。

```js: testcode.js
 let token = getAccessToken(env,"user,calendar")
 let accessToken = token.access_token
 let headers = { "authorization": "Bearer " + accessToken }
 let options = { 
    "headers": headers,
    "method": "get"
  }
```
service accountのToken取得の詳細は[こちらのドキュメント](https://developers.worksmobile.com/jp/reference/authorization-sa?lang=ja)をご覧ください。

ざっくりした流れとしては、
1\.JWTの生成
2\.JWT電子署名
3\.Access Tokenの取得
となるようですが、正直私にはドキュメントが難解すぎて、参考のコードをコピペしただけです。。。。これが無かったらもう詰んでました。

```js: testcode.js
 let token = getAccessToken(env,"user,calendar")
```
getAccessTokenファンクションにenvの認証情報と、Scopeを渡して処理を進めてもらいます。
今回実行するAPIは **「ユーザーの取得」と「基本カレンダーの予定リストの取得」** なので、<font color=red>「user,calendar」</font>を記載しました。

getAccessTokenファンクションの中で、getJwtファンクションが実行されており、getJwtファンクション内で、JWTの生成や電子署名が行われています。
ここは詳細を理解できてないので、実際のコードは折りたたんで省略します。
<details><summary>getAccessTokenファンクションとgetJwtファンクション</summary>


```js: testcode.js
function getJwt(ENV){
  const header = Utilities.base64Encode(JSON.stringify({"alg":"RS256","typ":"JWT"}), Utilities.Charset.UTF_8)
  const claimSet = JSON.stringify({
    "iss": ENV.CLIENT_ID,
    "sub": ENV.SERVICE_ACCOUNT,
    "iat": Math.floor(Date.now() / 1000),
    "exp": Math.floor(Date.now() / 1000 + 2000)
  })
  const encodeText = header + "." + Utilities.base64Encode(claimSet, Utilities.Charset.UTF_8)
  const signature = Utilities.computeRsaSha256Signature(encodeText, ENV.PRIVATE_KEY)
  return encodeText + "." + Utilities.base64Encode(signature)
}

function getAccessToken(ENV, scopes) {
  const uri = "https://auth.worksmobile.com/oauth2/v2.0/token"
  const payload = {
    "assertion" : getJwt(ENV),
    "grant_type" : encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
    "client_id": ENV.CLIENT_ID,
    "client_secret": ENV.CLIENT_SECRET,
    "scope": scopes
    
  }
  const options = {
    "method": "post",
    "headers": {"Content-Type" : "application/x-www-form-urlencoded"},
    "payload": payload
  }
  return JSON.parse(UrlFetchApp.fetch(uri, options))
}
```
</details>

さらにAPIを実行するためのheader情報を設定します。
```js: testcode.js
 let accessToken = token.access_token
 let headers = { "authorization": "Bearer " + accessToken }
 let options = { 
    "headers": headers,
    "method": "get"
  }
```
heder情報は各APIのドキュメントに以下のような説明があるのでそちらを参照ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/561a15b3-3f43-3409-96e0-8173caad78ae.png)

### ユーザーの取得
次に挨拶のメッセージを送るためにユーザーの名前をAPIで取得します。

```js: testcode.js
  const lwUserUri = "https://www.worksapis.com/v1.0/users/" + env.userId 
  const lwResponse = UrlFetchApp.fetch(lwUserUri,options)
  const lwUser = JSON.parse(lwResponse) 
  let userLastName = lwUser.userName.lastName
  let userFirstName = lwUser.userName.firstName

  let text = `こんにちは${userLastName} ${userFirstName}さん。`
  LINEWORKS.userMessageSend(env, text)
```

使用するAPIは[こちら](https://developers.worksmobile.com/jp/reference/user-get?lang=ja)ですね。
GASからAPIを実行するためには **[UrlFetchApp.fetch](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app)を使用する** ことを学びました。

```js: testcode.js
  const lwUserUri = "https://www.worksapis.com/v1.0/users/" + env.userId 
  const lwResponse = UrlFetchApp.fetch(lwUserUri,options)
  const lwUser = JSON.parse(lwResponse) 
```

APIをリクエストするためのURLをlwUserUriに代入します。

ユーザーの取得APIはユーザーを特定するための情報（usserIdなど）をpath parameterに含める必要があります。
Callback URLに送られてきた情報の中にuserIdがあり、envの中に含めていますので、リクエストするURLとuserIdを含めて、lwUserUriに代入します。

UrlFetchApp.fetchでリクエストするURLとヘッダー情報を渡し、レスポンスされた情報をパースしてlwUserへ代入します。

```js: testcode.js
  let userLastName = lwUser.userName.lastName
  let userFirstName = lwUser.userName.firstName

  let text = `こんにちは${userLastName} ${userFirstName}さん。`
  LINEWORKS.userMessageSend(env, text)
```
レスポンスされたデータの中から、ユーザーの姓名を取得して、送信するテキストの中に含めています。
メッセージ送信APIの実行は、追加したライブラリを使って行います。



### 基本カレンダーの予定を取得する
#### 予定を取得して件数をトーク送信する
カレンダーに関する部分は処理が長いので分けて解説します。

```js:testcode.js
  let today = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd") //今日の日付け
  const lwCalUri = "https://www.worksapis.com/v1.0/users/" + env.userId + "/calendar/events?fromDateTime=" + today + "T00:00:00%2B09:00&untilDateTime=" + today + "T23:59:59%2B09:00"
  const calResponse = UrlFetchApp.fetch(lwCalUri,options)
  let lwCal = JSON.parse(calResponse)
  let calCount = lwCal.events.length //予定の件数を取得
  let title
  let startHour
  let startTime
  let endHour
  let endTime
  let eYear
  let eMonth
  let eDay

  text = `今日の予定は${calCount}件です`

  LINEWORKS.userMessageSend(env, text)
```

[基本カレンダーの予定リストの取得API](https://developers.worksmobile.com/jp/reference/calendar-default-event-user-list?lang=ja)は実行するにあたって、以下の情報が必要です。

***
【path parameter】
* ユーザーを特定する情報

【qery parameter】
* 予定の開始日時(YYYY-MM-DDThh：mm：ssTZD形式)
* 予定の終了日時(YYYY-MM-DDThh：mm：ssTZD形式)
<font color = silver>※「mm」の両側を全角コロンで記載しましたが、本来は半角です。全角にするとなんかのマークに変換されてしまったので、、、なんでだろう。。</font>

***

今回のBotは「今日の予定を教えてくれるBot」なので、トークを送った当日の0:00:00から23:59:59を指定しました。

```js:testcode.js
  let today = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd") 
```
まず今日の日付けを取得し、todayへ代入します。

```js:testcode.js
  const lwCalUri = "https://www.worksapis.com/v1.0/users/" + env.userId + "/calendar/events?fromDateTime=" + today + "T00:00:00%2B09:00&untilDateTime=" + today + "T23:59:59%2B09:00"
  const calResponse = UrlFetchApp.fetch(lwCalUri,options)
  let lwCal = JSON.parse(calResponse)
```

リクエストするURLのpath parameterとして必要なユーザー情報(env.userId)を含め、path parameterに必要となる開始時間・終了時間については、日付をtodayから持ってきて、時間については決め打ちで入力しています。

APIの実行結果はパースしてlwCalに代入します。

```js:testcode.js
  let calCount = lwCal.events.length　//予定の件数をカウントする 
  let title
  let startHour
  let startTime
  let endHour
  let endTime
```
取得したレスポンス(lwCal)の中に予定(events)が何件あるか確認するためにlengthを使用しました。
 **[length](https://www.sejuku.net/blog/22120)は文字数や配列の長さを取得できる** プロパティみたいです。
その下でいくつか変数を宣言していますが、これは後ほど使用します。
（ここで宣言するのって書き方として美しいのでしょうか。。。よく分からず）

lengthをcalCountに代入します。

```js:testcode.js
  text = `今日の予定は${calCount}件です`

  LINEWORKS.userMessageSend(env, text)
```
予定の件数をtextに再代入し、ライブラリを使用してメッセージを送ります。

#### 各予定の件名・開始時間・終了時間をトークで送信する


```js:testcode.js
//それぞれの予定の情報をトークで送信する
for (let i = 0; i < calCount; i ++){　
  if ( lwCal.events[i].eventComponents[0].start.hasOwnProperty('date')=== false){　
    title = lwCal.events[i].eventComponents[0].summary
  startHour = lwCal.events[i].eventComponents[0].start.dateTime.slice(11,13)
  startTime = lwCal.events[i].eventComponents[0].start.dateTime.slice(14,16)
  endHour= lwCal.events[i].eventComponents[0].end.dateTime.slice(11,13)
  endTime= lwCal.events[i].eventComponents[0].end.dateTime.slice(14,16)

  text = `${i + 1}件目の予定です。
          件名：${title}
          開始時間：${startHour}時${startTime}分から
          終了時間：${endHour}時${endTime}分まで`

  LINEWORKS.userMessageSend(env, text)
  }else{
    title = lwCal.events[i].eventComponents[0].summary
    startTime = lwCal.events[i].eventComponents[0].start.date 
    endTime = lwCal.events[i].eventComponents[0].end.date

  text = `${i + 1}件目の予定です。この予定は【終日の予定】です。
          件名：${title}
          開始日：${startTime}
          終了日：${endTime}`

  LINEWORKS.userMessageSend(env, text)
  }
}
```

forを使用して予定の件数(calCount)分、繰り返し処理を行います。


```js:testcode.js
//それぞれの予定の情報をトークで送信する
for (let i = 0; i < calCount; i ++){　
  if ( lwCal.events[i].eventComponents[0].start.hasOwnProperty('date')=== false){　
    title = lwCal.events[i].eventComponents[0].summary
```

予定には **「終日の予定」と「時間を指定した予定」の二種類** があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/f6dd3a5a-faa9-36a5-13d8-194664cf39f0.png)

propertyの名称が異なるので、まずはif文で **該当の予定のstart内にdateというpropertyがあるのか無いのか？** 
を確認ます。

 **propertyが存在するかどうかは[hasOwnProperty](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty)** を使用して確認しました。

終日予定なのか時間指定の予定なのかを判別し、deteというpropertyが無い（終日予定ではない）場合に次の処理を行います。



```js:testcode.js
    title = lwCal.events[i].eventComponents[0].summary
  startHour = lwCal.events[i].eventComponents[0].start.dateTime.slice(11,13)
  startTime = lwCal.events[i].eventComponents[0].start.dateTime.slice(14,16)
  endHour= lwCal.events[i].eventComponents[0].end.dateTime.slice(11,13)
  endTime= lwCal.events[i].eventComponents[0].end.dateTime.slice(14,16)

  text = `${i + 1}件目の予定です。
          件名：${title}
          開始時間：${startHour}時${startTime}分から
          終了時間：${endHour}時${endTime}分まで`

  LINEWORKS.userMessageSend(env, text)
  }
```
予定の件名(summary)をtitleに代入します。
予定の開始時間(start.dateTime)から **slice** で文字列を抜き出して、時をstartHourに代入し、分をstartTimeに代入します。
終了時間(end.dateTime)も同様に処理します。

それぞれをtextに代入して、ライブラリを使用してトークを送信します。

当初は単純にstart.dateTimeを表示させるように書いていたのですが、そうすると「yyyy-mm-ddThh：mm：ss」のように表示され、見づらいと思いました。
「今日の予定を確認するBot」として機能が明確なので、「時と分」だけ分かれば良いかなと。
時と分を抜き出す書き方がいまいちよく分からずこのように記載しているのですが、もっとスマートな書き方教えて欲しいです。。。


```js:testcode.js
else{
    title = lwCal.events[i].eventComponents[0].summary
    startTime = lwCal.events[i].eventComponents[0].start.date //終日の予定なのでdate
    endTime = lwCal.events[i].eventComponents[0].end.date

    eYear = endTime.slice(0,4)
    eMonth = endTime.slice(5,7)
    eDay = endTime.slice(8,10)
    eDay = Number(eDay)
    eDay = eDay - 1

  text = `${i + 1}件目の予定です。この予定は【終日の予定】です。
          件名：${title}
          開始日：${startTime}
          終了日：${eYear}-${eMonth}-${eDay}`

  LINEWORKS.userMessageSend(env, text)
  }
}
```

終日予定の場合の処理ですが、ここもちょっと書き方がスマートではない気がしますが上記のように書きました。
ちょっとこの部分は詳細を[後述](https://qiita.com/tnk-tty/items/87b10266a9db91351867#%E7%B5%82%E6%97%A5%E4%BA%88%E5%AE%9A%E3%81%AE%E5%87%A6%E7%90%86%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)します。


```js:testcode.js

  text = `以上です`

  LINEWORKS.userMessageSend(env, text)
}
```

処理が完了したことが分かるように最後にトークを送ります。

以上がGAS内の処理の流れです。

## わざわざトークを送らなければいけないのか？リッチメニューでトーク送信
このBotはトークを受信することで処理が開始されます。

なので「あ」でも「テスト」でも送れば良いのですが、単一の機能しか持っていないBotですし、
 **ユーザー側の「メッセージを入力・送信」という作業も軽減させよう** と思いました。

Botを作ったことが無い私にとって、「そもそもリッチメニューって何？」状態だったのですが、[リッチメニューはこういうものです。](https://www.linebiz.com/jp/column/technique/20180731-01/)
よくLINEの公式アカウントで使われていますね。

リッチメニューを登録し、タップする事だけでBotが動くように設定します。

### 画像の作成・コンテンツアップロード
まず、リッチメニューとして登録できる画像の条件を確認するため[リッチメニュー画像の登録](https://developers.worksmobile.com/jp/reference/bot-richmenu-image-set?lang=ja)を参照しました。

>リッチメニュー画像の条件
>* 画像形式: JPEG または PNG 形式
>* 画像サイズ: 2500x1686 または 2500x843 ピクセル
>* 最大ファイルサイズ: 1MB

サイズは2500x1686で適当な画像をmspaintで作成して、[コンテンツアップロード](https://qiita.com/tnk-tty/items/db07947f3417bd903b17)しました。

### リッチメニューの登録
次に[リッチメニューの登録API](https://developers.worksmobile.com/jp/reference/bot-richmenu-create?lang=ja)でリッチメニューを作成しました。

リクエスト内容は以下のような感じで登録しました。

<details><summary>リクエスト内容</summary>

```
{
  "richmenuName": "Example Richmenu",
  "areas": [
    {
      "action": {
        "type": "message",
        "label": "calBot",
        "displayText": "予定照会",
        "text":"予定照会"
      },
      "bounds": {
        "x": 0,
        "y": 0,
        "width": 2500,
        "height": 843
      }
    }
  ],
  "size": {
    "width": 2500,
    "height": 1686
  }
}
```
</details>

これでリッチメニューのIDが払い出されます。

### リッチメニューの画像の登録
では次にリッチメニューに画像を登録します。
[リッチメニュー画像の登録API](https://developers.worksmobile.com/jp/reference/bot-richmenu-image-set?lang=ja)ではpath parameterに「botId」と「richmenuId」が必要になります。

また、Request Body内で、コンテンツアップロードを行った後に払い出されるfileIdが必要になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/d0362c31-c58a-01df-dc4a-eaea29a8d9f2.png)
このような感じで画像を登録しました。

### 全員に対するリッチメニューかユーザー別のリッチメニューか
今回は特にユーザー別にする必要は無いので、全員に対するリッチメニュー（規定リッチメニュー）としてBotに登録します。

[Botの部分更新API](https://developers.worksmobile.com/jp/reference/bot-update-patch?lang=ja)でdefaultRichmenuIdとして、登録したリッチメニューを設定しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/ad3f9f8e-e673-5769-7a69-58624774268d.png)

### 動作確認
リッチメニューをタップするとトークが送信され、基本カレンダーに登録されている本日の予定をBotが教えてくれます。

![RPReplay_Final1662966277.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/03b84856-66c5-62e6-27f6-1032fdc10497.gif)


### 終日予定の処理について
```js:testcode.js
else{
    title = lwCal.events[i].eventComponents[0].summary
    startTime = lwCal.events[i].eventComponents[0].start.date //終日の予定なのでdate
    endTime = lwCal.events[i].eventComponents[0].end.date

    eYear = endTime.slice(0,4)
    eMonth = endTime.slice(5,7)
    eDay = endTime.slice(8,10)
    eDay = Number(eDay) //日付のデータを文字列から数値へ変換
    eDay = eDay - 1

  text = `${i + 1}件目の予定です。この予定は【終日の予定】です。
          件名：${title}
          開始日：${startTime}
          終了日：${eYear}-${eMonth}-${eDay}`

  LINEWORKS.userMessageSend(env, text)
  }
}
```
当初、終了日はend.dateのみを表示させていましたが、ちょっと疑問を感じました。
例えばLINE WORKSのUI上で以下のように終日予定を作成します。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/29eef957-49ca-c582-be26-d9323723051c.png)

この予定をAPIで取得すると以下のようにresponseされます。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2740500/2793e523-0700-4991-65bf-022c484777e0.png)

UIでは9月8日が終了日と設定していますが、<font color=red>APIで取得したデータは9月9日</font>となっていました。
Botから送信するデータはUIの情報に合わせたいので、「日付部分を-1しよう！」と思いました。

endTimeを「endTime = entTime - 1」って書いて、なんで正しく処理されないんだ！と悩んでいたところ、
 **end.dateは<font color=red>stringなのでそのまま計算できない</font>** と言う事が分かりました。（そのくらい分かれよっていう突っ込みはナシでお願いします）

そのため、
```js:testcode.js
    eYear = endTime.slice(0,4)
    eMonth = endTime.slice(5,7)
    eDay = endTime.slice(8,10)
```
end.dateの情報を **slice** で年,月,日の三つに抜き出して、
```js:testcode.js
    eDay = Number(eDay) //日付のデータを文字列から数値へ変換
    eDay = eDay - 1
```
日を-1するために、 **Number** でstringから数値に変換し、減算して

```js:testcode.js
  text = `${i + 1}件目の予定です。この予定は【終日の予定】です。
          件名：${title}
          開始日：${startTime}
          終了日：${eYear}-${eMonth}-${eDay}`
```
最後に文字列として繋げました。

もっと簡単な処理方法があるんだろうなぁ。。。という気がしつつも、今の自分が思いついてできることはここら辺が限界でした。。。

# まとめ
以下のAPIを使用しました。

・[ユーザーの取得](https://developers.worksmobile.com/jp/reference/user-get?lang=ja)
・[基本カレンダーの予定リストの取得](https://developers.worksmobile.com/jp/reference/calendar-default-event-user-list?lang=ja)
・[リッチメニューの登録](https://developers.worksmobile.com/jp/reference/bot-richmenu-create?lang=ja)
・[リッチメニュー画像の登録](https://developers.worksmobile.com/jp/reference/bot-richmenu-image-set?lang=ja)
・[Botの部分更新](https://developers.worksmobile.com/jp/reference/bot-update-patch?lang=ja)
・[コンテンツアップロード](https://developers.worksmobile.com/jp/reference/bot-attachment-create?lang=ja)

以下の記事を参考にさせていただきました。
・[【GAS × LINE WORKS】今日一日の予定をまとめて教えてくれる Bot を作りました](https://qiita.com/kunihiros/items/24001310c0473c8531dc)
・[[GAS x LINE WORKS] GoogleAppsScriptでLINE WORKSのチャットBOTを作る(API2.0 ver)](https://qiita.com/kunihiros/items/4cdec5784f21598cfee0)


はじめてのBot作成は難しかったですが、「Botで他にもこんなことできないかな？」と思うようになりました。
また何かアイデアが浮かんだらBot作成にチャレンジしたいと思います！