Google Apps Scriptで百人一首を検索するLINE Botを作った

# 作ったもの

[**スマホ向けリンク**](https://lin.ee/NuJeXmD)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/b3f56496-a79b-a694-9acf-08a63729d61b.png" width="200px">

### 機能
以下のメッセージに反応して，百人一首の歌・番号・作者名・決まり字を返します。

- 歌番号
- 決まり字
- 上の句の5文字目くらいまで(一部でも可)
- 作者名
- 取り札の上３文字  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/3727fdeb-fbcd-4a2e-fe30-41407f170d4e.png)

候補が複数ある場合には，決まり字の一覧を返します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/4d9430b4-be95-2e91-7d4c-9029ae251fb8.png)


### 作った経緯
趣味で競技かるたをしており、百人一首に触れる機会が多いです。
「ちはやぶる〜の歌の作者って誰だっけ？」「歌番号40の歌って何だっけ？」
そんなときすぐ調べられたら便利だと思い，最近勉強していたGASの練習も兼ねて作ってみました。


# 準備
サーバーを用意する必要がなく，ブラウザ上で簡単に作れそうなので，LINE BotとGoogle Apps Script(GAS)を組み合わせて作ります。

### LINE Developersに登録
登録は前にしていたので，省略します。
以下のリンクが参考になります。
[参考：LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)

「Messaging API設定」の下にある，チャンネルアクセストークン(長期)を取得しておきます。
![スクリーンショット_2021-03-22_16_08_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/8a9965b5-b505-e690-fb63-706b678c761d.png)


### Google Apps Scriptを開く

歌のデータベース代わりにスプレッドシートを使います。

Googleドライブにログインして，Googleスプレッドシートを選択。
![スクリーンショット_2021-03-22_16_14_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/5124f2cf-df6f-3157-983a-586eeb1bd3e3.png)

スプレッドシートから「ツール」→「スクリプト エディタ」を選択。
![スクリーンショット_2021-03-22_16_16_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/c5e131ac-abf2-e359-a6a1-b8e74e53cf72.png)

エディタが開くので，コードを書いていきましょう。
![スクリーンショット_2021-03-22_16_17_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/f54eba47-c58d-67f7-8149-852ab9fb408c.png)


# コードを書く
言語はJavaScriptがベースになっています。

### メッセージを受け取って返信する関数
メッセージを受け取り，返信メッセージを作り，送信する doPost 関数を書きます。
チャンネルアクセストークンを，`ここにアクセストークンを入れます`の部分にコピペします。
返信メッセージを作成する部分以外は，以下のリンク先のコードを参考にさせていただきました。

[参考：Google Apps ScriptでLINE BOTつくったら30分で動かせた件](https://qiita.com/hakshu/items/55c2584cf82718f47464)

```javascript:main.gs
// アクセストークン
const ACCESS_TOKEN = 'ここにアクセストークンを入れます';

function doPost(e) {
  // WebHookで受信した応答用Token
  const replyToken = JSON.parse(e.postData.contents).events[0].replyToken;
  // ユーザーのメッセージを取得
  const userMsg = JSON.parse(e.postData.contents).events[0].message.text;
  // 応答メッセージ用のAPI URL
  const url = 'https://api.line.me/v2/bot/message/reply';

  //返信メッセージを作成
  if (1 <= parseInt(userMsg) && parseInt(userMsg) <= 100) {
    var post_msg = numToMessage(parseInt(userMsg));
  } else { 
    var post_msg = wordToMessage(userMsg);
  }

  //変数post_msg を本文として返信を送る
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': post_msg ,
      }],
    }),
  });
}
```


### 歌番号から返信メッセージを作成する関数
受信メッセージが1から100までの数値の場合は，以下の処理を行います。
1. 歌番号から該当する歌の情報を取得する
1. 返信メッセージを返す
そのための numToMessage 関数を用意します。

予め，以下のようなシートを作っておきます。
(シート名は「tanka_list」としました)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/68c0c418-a095-4fa7-863c-ed85c6c037e3.png)

【追記】閲覧用リンクを貼っておきます。

https://docs.google.com/spreadsheets/d/111w5OB3fbZ4gvFWpYc0cKdBLfjP7sAsYA2cNW2xz6_c/edit?usp=sharing

numToMessage 関数のコードを書きます。

```javascript:message.gs
//1から100までの歌番号から，メッセージを作成
function numToMessage(msg) {

  //tanka_listシートを取得
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('tanka_list');

  //歌番号からシートの行番号を取得
  const row = get_row(msg, sheet, 'A');

  //歌番号，上の句，下の句，詠み人，決まり字を取得
  const num = sheet.getRange('A'+ row).getValue();
  const kami = sheet.getRange('B'+ row).getValue();
  const shimo = sheet.getRange('C'+ row).getValue();
  const yomibito = sheet.getRange('D'+ row).getValue();
  const kimariji = sheet.getRange('E'+ row).getValue();

  //メッセージを作成
  return kami + '\n' + shimo +'\n\n歌番号：' + num + '\n詠み人：' + yomibito + '\n決まり字：' + kimariji;
}
```

コード内で出てくる get_row 関数は，以下の記事のものを使わせていただきました。
[参考：GASで列内で特定の値に一致する行番号を取得する - shikaku's blog](https://blog.systemjp.net/entry/2019/05/01/231003)

```javascript:get_row.gs
//keyに一致する値をもつ行番号を返す
function get_row(key, sh, col){
  const array = get_array(sh, col);
  const row = array.indexOf(key) + 1;
  return row;
}

//シートの1列を全て配列に入れる
function get_array(sh, col){
  const last_row = sh.getLastRow();
  const range = sh.getRange(col + '1:' + col + last_row)
  const values = range.getValues();
  const array = [];
  for(let i = 0; i < values.length; i++){
    array.push(values[i][0]);
  }
  return array;
}
```


(例) 受信メッセージが40の場合，以下のようなメッセージを返り値として返します

> しのぶれど 色に出でにけり 我が恋は
> 物や思ふと 人の問ふまで
>
> 歌番号：40
> 詠み人：平兼盛
> 決まり字：しの


### 決まり字や作者名から歌番号を取得する
受信メッセージが歌番号の数字以外の場合には，２つの場合が考えられます。

- 歌が特定できる場合(決まり字や歌人名)は，まず歌番号を取得し，numToMessage 関数に送る
- 歌が特定できない場合は，候補が複数あるのか，全くないのかの判定をする

そのための wordToMessage 関数を用意します。

予め，以下のようなシートを作っておきます。
(シート名は「word_list」としました)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/b9a1f884-6d9d-cff6-5c59-075350664cf1.png)
仮名遣いによる表記揺れや作者の別名(wiki調べ)などに対応するため，800行くらい用意しました。
(本当は正規表現とかでもっと行数減らせるかもしれないですが，未確定のときにも反応してしまいそうなのでやめました)

wordToMessage 関数のコードを書きます。

```javascript:message.gs
//決まり字や上の句,歌人名から，メッセージを作成
function wordToMessage(msg) {

  //word_listシートを取得
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('word_list');

  //メッセージからシートの行番号を取得
  const row = get_row(msg, sheet, 'A');

  //メッセージを作成
  if (row === 0) {
    return partToMessage(msg);
  } else { 
    const num = sheet.getRange('B'+ row).getValue();
    return numToMessage(num);
  };
}
```

もしここでも一致しない場合には，後述する　partToMessage 関数で候補を調べます。


### 候補が複数あるときのメッセージを作成する
受信メッセージから歌番号が確定できない場合は，以下の2通りの場合が考えられます。

- 歌の候補が複数ある場合には，その候補を返す
- ない場合には，`'見つかりませんでした…'`を返す。

そのための partToMessage 関数を用意します。

予め，以下のようなシートを作っておきます。
(シート名は「part_list」としました)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/d757ac72-6f48-d42c-20c1-d7b73b189f0c.png)
(上の句の出だしだけでなく，「六歌仙」なども入れてあります)

partToMessage 関数のコードを書きます。
行ごとに最終列の位置が異なるため，以下のようにコードを書いています。

1. get_row 関数で該当する行番号を見つける
1. 『「ひ」は3枚あります』のような文字列 kouho を作る
1. 該当行の最終列の値を取得し，定数 lastCol に代入  
[参考：もりさんのプログラミング手帳](https://moripro.net/gas-get-specified-lastcol-lastrow/#i-2)
1. for文で3列目から最終列までの値を kouho の後ろに加えていく
1. kouho の値を返す

```javascript:message.gs
//確定できない場合に，候補の札を示すメッセージを作成
function partToMessage(msg) {

  //part_listシートを取得
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('part_list');

  //メッセージからシートの行番号を取得
  const row = get_row(msg, sheet, 'A');

  //メッセージを作成
  if (row === 0) {
    return '見つかりませんでした…';
  } else { 
    const num = sheet.getRange('B'+ row).getValue();
    let kouho = '「' + msg + '」は，' + num + '枚あります。';
    const lastCol = sheet.getRange(row, 3).getNextDataCell(SpreadsheetApp.Direction.NEXT).getColumn();

    for (i = 3; i <= lastCol; i++) {
      let fuda = sheet.getRange(row, i).getValue();
      kouho = kouho.concat('\n' + fuda);
    }

    return kouho;
  };
}
```

# アプリを公開する
公開して，LINE Botとして使えるようにするには，デプロイして利用できる状態にしたあと，LINE DevelopersでWebhookの設定をする必要があります。

### デプロイする
右上の「デプロイ」から「新しいデプロイ」を選択します。
![スクリーンショット_2021-03-22_18_06_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/756f7eb6-f9ef-f3fd-7a82-5463945287d7.png)

種類を「ウェブアプリ」に，アクセスできるユーザーを「全員」に設定してから「デプロイ」します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/850a7ee4-6e21-0c97-f934-77f56a560177.png)

(初回は，アクセス権の承認画面が出てきます)

出てきたウェブアプリのURLをコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/496dadf6-2364-5aac-91da-7f7ca1cadacf.png)


### Webhookの設定をする
LINE DevelopersのMessaging API設定に，Webhook設定という項目があります。
編集をクリックして，先ほどのウェブアプリのURLを貼り付けます。
また，Webhookの利用をオンにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/6eb829c2-8995-4dcd-48cf-f38994352e91.png)

検証をクリックし「成功」と表示されれば正しく設定できています。

あとは実際にLINEで友達登録して返信できるかテストするだけです。


# 今後やりたいことなど
初めてBotを作ってみましたが，非常に楽しかったです。
制作に10時間ほどかかりましたが，時間が経つのを忘れて作ってしまいました。

画像を送ったり，これから百人一首を覚えたい人に向けてクイズを出すなど，文章を返信する以外の機能を加えたBotなども作れたら面白いなと思っています。


# 追記：画像送信するものも作りました。

ゴロ合わせの覚え方をイラストとともに紹介します。

[**スマホ向けリンク**](https://line.me/R/ti/p/%40890nfadl)

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632954/6e65e8b8-ce3c-40e8-337a-e35f9d683c68.png" width="200px">