費用0でLINE向け英単語自動通知Botを作ってみた(Slack向け付き)

# 前書き

メモした英単語を、定期的に、よく見る媒体（ここでは`LINE`と`Slack`）
に通知するBotを作成してみました。

# 完成イメージ

<img width=50% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/c3752b4a-989d-80bb-7ab1-048531d343b4.jpeg" />

![スクリーンショット 2020-12-11 12.39.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/747c55e0-f3fc-9430-6942-b35863d434f8.png)

# 機能要件

* スプレッドシートに英単語と和訳を記録（手動）
* 記録した単語のうちランダムに数個選んで、LINE&Slackの特定のチャンネルに英単語と和訳を通知

# 使用するもの

* 共通
    * Google SpreadSheet（以下スプレッドシート）
    * Google Apps Script（以下GAS）
* LINEのみ
    * LINE Messaging API
        *  プッシュメッセージ機能
* Slackのみ
    * Slack API
        * Incoming Webhooks機能 

# アーキテクト図

![English Bot (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/08dae3f9-bfe3-9fe6-2b7f-1f1523eb9c78.png)

# 実装手順

1. スプレッドシートを用意
2. LINE編
 1. LINE Developersのアカウントを作成
 2. Messaging APIを作成
3. Slack編
 1. Slack APIを作成
4. GASを作成＆デプロイ

## 1. スプレッドシートを用意

任意のGoogleアカウントで、英単語を記録するスプレッドシートを作成してください。
ここでは、表の形式は以下として作成しています。
※GASのコードの中で列を指定している部分があるので変更する場合はGASコードも適宜変更してください。

（例）
![スクリーンショット 2020-12-10 21.00.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/5862a561-2fb7-a7d7-542f-e39f8c4bf188.png)

## 2. LINE編

###  1. LINE Developersのアカウントを作成

LINE Developersサイト（https://developers.line.biz/ja/）
から、アカウントを作成。
ラインアカウントかビジネスアカウントを用いて作成ができるので、お好みで。
ビジネスアカウント→任意のメールアドレスで作成できる。（自分はビジネスアカウントで作成しました。）

###  2. Messaging APIを作成

#### ①コンソール画面からProvider（後のBotとなるアカウント）を作成

![スクリーンショット 2020-12-10 21.15.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/383f99ca-9226-a203-7a58-fd7b40309b02.png)

Channel typeを`Messaging API`に選択するのを忘れずに。

#### ②作成後、`Channel access token`を控えておいてください。表示されていなかったらIssueボタンを押せば表示されます。

![スクリーンショット 2020-12-11 11.19.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/d80b75db-ee6b-777f-06db-85cdb6abefe4.png)

#### ③同画面のQRコードから作成したBotを友達追加する。



## 3. Slack編

### 1. Slack APIを作成

#### ①以下のサイトから新規アプリを作成する。

https://api.slack.com/

その際、通知を流したいSlackのワークスペースを選択してください。（チャンネルはこの後別途設定）
※Slack API内のアプリとワークスペースは１対の模様

![スクリーンショット 2020-12-11 11.28.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/36f0f2ad-c09a-107f-f98b-b29a5ed45995.png)

#### ②API毎のコンソールへ移動し、`Incoming Webhooks`の設定画面へ行く。

`Incoming Webhooks`：簡単に言えば、Slack APIへ通信を送る入り口のこと。

![スクリーンショット 2020-12-11 11.32.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/408227ba-f516-eda3-b1eb-3d724e625e9f.png)

#### ③ActiveをONにし、「Add New Webhook to Workspace」ボタンを押してWebhookを設定する。

ここで通知を送りたいチャンネルを設定します。

#### ④作成後発行される`Webhook URL`を控えておいてください。

## 4. GASを作成＆デプロイ

#### ①任意のGoogleアカウントのドライブから、`Google Apps Script`PJを新規作成。

なお、スプレッドシートと同じオーナーで作成することをオススメします。後々認証周りで引っかかるので。

#### ②コーディングを行う。

以下、コードになります。
なお、GASに記述する言語は`JavaScript`となります。

```JavaScript
// 英単語シートのID
const SHEET_ID = 'xxxx';

// LINE用設定
// アクセストークン(2項で控えたID)
const LINE_CHANNEL_ACCESS_TOKEN = 'xxxx'; 
// Messaging API用のPush URL、仕様なので固定値、説明は後述
const LINE_ENDPOINT = 'https://api.line.me/v2/bot/message/push';
// 送信先のラインユーザーID
const LINE_USERID = 'xxxx';

// Slack用設定
// SlackメンションユーザーID
const USER_MENTION_ID = '<@ + xxxx + >'; // 書式→`<@xxxxx>`
// 通知先Slack Webhook(3項で控えたSlack Webhook URL)
const SLACK_WEBHOOK_URL = 'xxxx';

// 日付文字列を生成する
function getDateString(date){
  return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');
}

// Bot生成トリガー
function createBot() {
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('英単語');
  // ２行目から
  var min = 2;
  var max = sheet.getLastRow();
  // 個数
  var ofNumber = 10;
  // 送信される単語のIDを格納する配列
  var randoms = [];
  
  /** 重複チェックしながら乱数作成 */
  for(var i = min; i <= (ofNumber + 1); i++){
    while(true){
      var tmp = intRandom(min, max);
      if(!randoms.includes(tmp)){
        randoms.push(tmp);
        break;
      }
    }
  }
  
  var slackText = USER_MENTION_ID + "\n";
  var lineText = "";
  var word;
  var meaning;
  var count = 0;
  for(var row of randoms){
    count++;
    word = sheet.getRange("B" + row).getValue();
    meaning = sheet.getRange("C" + row).getValue();
    slackText += "`" + word + "` : " + meaning;
    lineText += "" + word + " : 「" + meaning + "」";
    if(count !== ofNumber){
      slackText += "\n";
      lineText += "\n";
    }
  }
  
  recordWordLog(randoms);
  postSlack(slackText);
  postLine(lineText);
  
}

// 発信した英単語のログを記録
function recordWordLog(randoms){
  var logSheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('英単語ログ')
  var lastRow = logSheet.getLastRow();
  
  var wordIds = [];
  // 乱数は行番号を表しているので、単語IDにするために-1する
  for(var i in randoms){
    wordIds.push(randoms[i] - 1)
  }
  logSheet.getRange("A" + (lastRow + 1)).setValue(getDateString(new Date()));
  logSheet.getRange("B" + (lastRow + 1)).setValue(wordIds.join(','));
}

// 乱数生成
function intRandom(min, max){
  //Math.random() * ( 最大値 - 最小値 ) + 最小値;
  return Math.floor( Math.random() * (max - min + 1)) + min;
}

// Slackへメッセージ送信
function postSlack(text){
  var jsonData =
      {
        "text" : text
      };
  var payload = JSON.stringify(jsonData);
  var options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };
  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
}

// LINEへメッセージ送信
function postLine(text){
  var jsonData =
      {
        "to":LINE_USERID,
        "messages" : [
          {
            "type":"text",
            "text":text
          }
        ],
        "notificationDisabled":false //通知させるか、デフォルトfalse
      };
  var payload = JSON.stringify(jsonData);
  var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN,
  };
  var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : payload
  };
  UrlFetchApp.fetch(LINE_ENDPOINT, options);
  
}

```

##### 一部変数解説

* SHEET_ID：`https://docs.google.com/spreadsheets/d/xxxxxx/edit#gid=0`URLの`xxxxxx`部分
* USER_MENTION_ID：`<@xxxxxx>`の書式で送信文に加えるとメンションが飛ぶようになる。IDはプロフィールに書いてあります。
* LINE_ENDPOINT：Messaging APIの仕様で定められたURLです。詳細は公式リファレンスにて。（https://developers.line.biz/ja/reference/messaging-api/#send-push-message）
* LINE_USERID：各ラインアカウントを識別するID。取得法は後述。

##### LINEユーザーIDの取得方法

こちらの記事で紹介されているラインアカウントを使用すれば取得できると思います。
（https://qiita.com/kotamatsuoka/items/472b455e5f9a6315d499）
※自分はこちらを使用していないので自己責任でお願いします。

以下余談
自分は以下の方針で取得しました。（もっと上手いやり方があれば教えてください。。）

1. Botアカウントにメッセージを送ったら、GASに内容が送信されるようMessaging APIのWebhook設定を行う。
2. GASに、送られた内容をスプレッドシートに書き起こす処理を実装する。
3. Botアカウントにメッセージを送る。
4. スプレッドシートにIDが記されている。

#### ③デプロイを行う。

「公開」 > 「WEBアプリケーションとして導入」　をクリックする。

以下のように設定してください。

* Project version:`New`
* Execute the app as:`Me`
* Who has access to the app:`Anyone,even anonymous`

これでWEB上にGASが配置されました。

#### ④定期実行のトリガーを設定する。

「編集」 > 「現在のプロジェクトのトリガー」　をクリックする。
別タブに遷移するので、その画面から新規トリガーを作成してください。

* 実行する関数は`createBot`
* デプロイは`Head`
* 実行スケジュールはお好みで。（結構細かく設定できます。）


## 完了

お疲れ様でした。
トリガーにした関数を実行して上手くいけば、完成イメージのような形で通知が飛んでくるはずです。
表示文言や単語の数などは、各自お好みでカスタマイズしてみてください。

ちなみにログはこんな形で溜まっていきます。

<img width=70% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/53570ba9-9ec4-c19f-ce2a-6e2d7dd336ad.png" />

# 後書き

英単語Botを作成してみました。
従来だとHerokuなど使ってサーバ側実装してる印象でしたが、今はGASでお手軽に作れるのが良いですね。
言葉足らずな部分もあると思うので、ご不明点やご指摘があればコメントいただけましたら幸いです。

一番大きいのは、これら全てを無料で実装できることですね！
便利な世の中になったなあ

# 参考

* LINE API公式リファレンス：https://developers.line.biz/ja/reference/messaging-api/#send-push-message

* ランダム取得アルゴリズム：https://www.sejuku.net/blog/22432