【LINE Messaging API, GAS】LINE Bot 制作でよく使うコード集

## はじめに
LINE Bot 制作でよく使うコードや参照するサイト等をまとめる。（随時更新）

## リンク
+ [Messaging API リファレンス | LINE Developers](https://developers.line.biz/ja/reference/messaging-api/)
+ [Spreadsheet Service | Apps Scriptリファレンス | Google Developers](https://developers.google.com/apps-script/reference/spreadsheet)
+ [Flex Message Simulator](https://developers.line.biz/flex-simulator/)
+ [ツイートのリンク作成 - Qiita](https://qiita.com/probabilityhill/items/8a0870ce2e593de1f584#4-%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E5%86%85%E5%AE%B9%E3%82%92url%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89)


## LINE Bot 完成までの大まかな流れ
LINE Developers アカウント、プロバイダー、チャンネル作成済み、Google アカウント作成済みとする。（[Messaging APIを始めよう | LINE Developers](https://developers.line.biz/ja/docs/messaging-api/getting-started/)）

<details><summary>Channel access token の取得</summary>

[LINE Developers Console](https://developers.line.biz/console/) の「Messaging API」タブを開く。「Channel access token (long-lived)」の「Issue」をクリックし、キーをコピーし、メモしておく。（[プロパティ（環境変数）の設定](#プロパティ環境変数の設定) で使用する）
</details>

<details><summary>Google Apps Scriptの作成</summary>

[Google Drive](https://drive.google.com/drive/my-drive) のマイドライブ上で右クリック → その他 → Google Apps Script を選択
![スクリーンショット (242).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/3ea5e93d-01a8-11b8-d63f-feb63a194d65.png)
</details>

<details><summary>.gsファイルの作成</summary>

コード例：[GitHub](https://github.com/probabilityhill/line-1btn-msg-gas)

</details>

<details><summary>デプロイ</summary>

「デプロイ → 新しいデプロイ」を選択
![スクリーンショット (243).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/28e94615-fa6f-23ac-29fc-c522181cd038.png)

種類は「ウェブアプリ」を選択
![スクリーンショット (244).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/1e4ba5dc-b83c-6c63-faab-547a78a86be4.png)

アクセスできるユーザーを「全員」にする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/e8a6123f-a564-0e55-6f1c-ba2357ca3bd4.png)

URLをコピーしておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/6319bc7f-b5dd-b431-8d40-fe074d97f631.png)

:::note warn
コードを修正するたびに「デプロイ → デプロイの管理 → 編集（鉛筆アイコン）」でバージョンを「新バージョン」にし、デプロイする必要がある。
:::
</details>

<details><summary>Webhook URL の設定</summary>

[LINE Developers Console](https://developers.line.biz/console/) の「Messaging API」タブを開く。コピーした URL を Webhook URL に設定し **「Use webhook」をON** にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/1533e026-e8a8-06d5-28fb-b2ad528470b1.png)
</details>

## プロパティ（環境変数）の設定
### アクセストークンを環境変数として設定
実行すると環境変数が設定される（実行後はコードを削除する）
```javascript
const SCRIPT_PROPERTIES = PropertiesService.getScriptProperties();  // スクリプトプロパティを取得
SCRIPT_PROPERTIES.setProperty("ACCESS_TOKEN", "ここにアクセストークンを入力");
```
[データの保存 | プロパティ サービス | Apps Script](https://developers.google.com/apps-script/guides/properties#saving_data)

### アクセストークンの取得<font color="OliveDrab"></font>
```javascript
const SCRIPT_PROPERTIES = PropertiesService.getScriptProperties();
const ACCESS_TOKEN = SCRIPT_PROPERTIES.getProperty("ACCESS_TOKEN");
```
[データの読み取り | プロパティ サービス | Apps Script](https://developers.google.com/apps-script/guides/properties#reading_data)

## イベントを受け取る
```javascript
// イベントを受け取って実行する
function doPost(e){
  const EVENTS = JSON.parse(e.postData.contents).events;
  for (const event of EVENTS){
    execute(event);
  }
}

// イベントを実行する
function execute(event){
  const EVENT_TYPE = event.type;          // イベントのタイプ
  const USER_ID = event.source.userId;    // 送信元ユーザーのID
  const REPLY_TOKEN = event.replyToken;   // 応答メッセージを送る際に使用する応答トークン

  if(EVENT_TYPE === "follow"){            // フォローイベントの場合
  }
  else if(EVENT_TYPE === "message"){      // メッセージイベントの場合
    if(event.message.type === "text"){    // テキストメッセージの場合
      let text = event.message.text;      // 受け取ったテキスト
    }
  }
  else if(EVENT_TYPE === "postback"){     // ポストバックイベントの場合
    const PB_DATA = event.postback.data;  // ポストバックデータ
  }
}
```
[Webhookイベントオブジェクト | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects)

## メッセージを送信
```javascript
/*
ACCESS_TOKEN: アクセストークン
messages: メッセージオブジェクトの配列（最大件数：5）
*/
function sendReplyMessage(replyToken, messages){
  const URL = "https://api.line.me/v2/bot/message/reply";
  const RES = UrlFetchApp.fetch(URL, {
    "headers": {
      "Content-Type": "application/json; charset=UTF-8",
      "Authorization": "Bearer " + ACCESS_TOKEN,
    },
    "method": "post",
    "payload": JSON.stringify({
      "replyToken": replyToken,
      "messages": messages 
    }),
  });
  return RES;
}
```
[fetch(url, params) | Class UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object))
[メッセージを送信する | LINE Developers](https://developers.line.biz/ja/docs/messaging-api/sending-messages/#methods-of-sending-message)
[応答メッセージを送る | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#send-reply-message)


## メッセージオブジェクト
[メッセージオブジェクト | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#message-objects)

### テキストメッセージを取得
```javascript
function getTextMsg(text){
  return {
    "type": "text",
    "text": text
  };
}
```
[テキストメッセージ | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#text-message)


### 画像メッセージを取得
```javascript
function getImgMsg(url, preUrl){
  return {
    "type": "image",
    "originalContentUrl": url,
    "previewImageUrl": preUrl
  };
}
```
[画像メッセージ | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#image-message)

### Flex Messageを取得
```javascript
function getFlexMsg(label, content){
  return {
    "type": "flex",
    "altText": label,
    "contents": content
  };
}
```
[Flex Message | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#flex-message)

### メッセージ共通プロパティ付きのメッセージを取得

#### 例1：クイックリプライ付きのテキストメッセージを取得
```javascript
/*
QUICK_REPLY: itemsオブジェクト
*/
function getTextMsgQr(text){
  return {
    "type": "text",
    "text": text,
    "quickReply": QUICK_REPLY
  };
}
```
[itemsオブジェクト | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#items-object)


```javascript:QUICK_REPLYの例
/*
items: クイックリプライボタンオブジェクト（最大オブジェクト数：13）
*/
const QUICK_REPLY = {
  "items": [
    {
      "type": "action",
      "action": {
        "type": "message",
        "label": "Yes",
        "text": "Yes"
      }
    },
    {
      "type": "action",
      "action": {
        "type": "postback",
        "label": "Buy",
        "data": "action=buy&itemid=111",
        "displayText": "Buy",
        "inputOption": "openKeyboard",
        "fillInText": "---\nName: \nPhone: \nBirthday: \n---"
      }
    },    
    {
      "type": "action",
      "imageUrl": "https://line.me/menu",
      "action": {
        "type": "uri",
        "label": "メニューを見る",
        "uri": "https://example.com/menu"
      }
    }
  ]
};
```
[クイックリプライを使う | LINE Developers](https://developers.line.biz/ja/docs/messaging-api/using-quick-reply/#page-title)
[クイックリプライ | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#quick-reply)
[クイックリプライボタンオブジェクト | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#quick-reply-button-object)

#### 例2：アイコンおよび表示名の変更を伴うテキストメッセージを取得
```javascript
function getTextMsgChangeSender(text, name, iconUrl){
  return {
    "type": "text",
    "text": text,
    "sender": {
      "name": name,
      "iconUrl": iconUrl
    }
  };
}
```
[アイコンおよび表示名の変更 | Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#icon-nickname-switch)

## スプレッドシート連携
### データを書き込むシートを指定
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/7b5fc326-61f5-3a6c-06ae-75d12eef0f0e.png" alt="スプレッドシートのイメージ" width="450">

シートIDはスプレッドシートのリンクから取得できる。`https://docs.google.com/spreadsheets/d/[シートID]/edit`

```javascript
const SHEET_ID = "シートID";
const SHEET = SpreadsheetApp.openById(SHEET_ID).getSheets()[0];  // シートの1枚目を指定
```
[openById | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#openbyidid)
[getSheets | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getSheets())

### シートにユーザーIDを記録
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/88e06719-49fe-1695-0242-af8ad2c4f308.png" alt="実行後のスプレッドシートのイメージ" width="450">

```javascript
const ROW = SHEET.getLastRow()+1;            // 書き込む行番号を取得
SHEET.getRange(ROW,1).setValue(USER_ID);     // ユーザIDをA列目に記録
SHEET.getDataRange().removeDuplicates([1]);  // ユーザIDが記入済みであれば取り消す
```
[getDataRange() | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/sheet#getdatarange)
[getLastRow() | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/range#getLastRow())
[getRange(row, column) | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/sheet#getRange(Integer,Integer))
[removeDuplicates(columnsToCompare) | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/range#removeDuplicates(Integer))
[setValue(value) | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/developer-metadata#setValue(String))
[友だち追加時にスプレッドシートにユーザIDを記録 - Qiita](https://qiita.com/probabilityhill/items/e691a852d431ac99b125)

### ユーザーIDからユーザー情報を取得し記録する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1946472/d529840c-9994-f347-bd1a-1a14fa4be1c2.png" alt="実行後のスプレッドシートのイメージ" width="450">

```javascript
// ユーザ情報取得
function getUserProfile(){
  const LAST_ROW = SHEET.getLastRow();                            // 最終行を取得
  const USER_LIST = SHEET.getRange(1,1,LAST_ROW,2).getValues();   // 記入されたデータを取得
  let userInfoList = [];                                          // 取得した情報を格納する配列
  for(const USER of USER_LIST){
    // 情報を取得済みの場合は飛ばす
    if(USER[1]){
      continue;
    }
    const URL = "https://api.line.me/v2/bot/profile/" + USER[0];  // 末尾にユーザーIDを追加
    try {
      const USER_PROFILE = JSON.parse(UrlFetchApp.fetch(URL,{
        "headers": {
          "Authorization":  "Bearer " + ACCESS_TOKEN
        }
      }));
      // 取得した情報（表示名、ステータスメッセージ、プロフィール画像のURL）を配列に追加
      userInfoList.push([
        USER_PROFILE.displayName,
        USER_PROFILE.statusMessage, 
        USER_PROFILE.pictureUrl
      ]);      
    }    
    catch {
      // 取得できなかった場合は"Not found."と記入
      userInfoList.push([
        "Not found.",
        "Not found.", 
        "Not found."
      ]);      
    }
  }
  const NUM_ROWS = userInfoList.length;  // 書き込む行数
  // 取得した情報を書き込む
  SHEET.getRange(LAST_ROW-NUM_ROWS+1,2,NUM_ROWS,3).setValues(userInfoList);
}
```
[fetch(url, params) | Class UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object))
[getLastRow() | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/range#getLastRow())
[getRange(row, column, numRows, numColumns) | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/sheet#getRange(Integer,Integer,Integer,Integer))
[getValues() | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/range#getValues())
[setValues(values) | Class SpreadsheetApp](https://developers.google.com/apps-script/reference/spreadsheet/range#setValues(Object))