食べログのランキングからランチをランダムに提案してくれるSlackボット

#はじめに
本稿は下記の記事から~~パクリ~~着想を得て記載しています。

- [Google Apps ScriptでスクレイピングしてSlackに定期ポストするbotを瞬殺で作った - Qiita](https://qiita.com/fireowl11/items/e703e35073b600528e7c)
- [いい感じにランチを決める Slack ボットを作った - Qiita](https://qiita.com/kimihiro_n/items/7808a56c10700704b9a2)

GASのスクレイピングとSlackボット作成をしたかったので、作ってみました。

#できること
食べログのランキングからランダムにN個を取り出してSlackに投稿する。

#完成したコード

```javascript:Code.gs
//required library
//SlackApp	M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO
//Parser	M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV

//Fisher–Yates shuffle（配列をシャッフルする関数）
function shuffle(array) {
  var n = array.length, t, i;

  while (n) {
    i = Math.floor(Math.random() * n--);
    t = array[n];
    array[n] = array[i];
    array[i] = t;
  }

  return array;
}

//食べログをスクレイピングしてSlackに投稿する。
function postMessage() {
  
  const prop = PropertiesService.getScriptProperties().getProperties();
  const slackApp = SlackApp.create(prop.token); //Script Propertiesに設定したトークンIDを取得
  const slackChannelId = prop.SlackChannel; //Script Propertiesに設定したチャンネルIDを取得
  const message = [ 
         '今日のおすすめランチです。',
         'ランチはここでどうでしょう？',
         'ランチはここで食べてください。'
  ];
  //食べログのURL 下記形式のURLであればそのまま使えそう。https://tabelog.com/*/rstLst/*
  //サンプルは東京駅のランチランキング
  const fetchUrl = 'https://tabelog.com/tokyo/A1302/A130201/R6586/rstLst/lunch/';

  const html = UrlFetchApp.fetch(fetchUrl).getContentText();
  //ランキングのリスト部分をパースする。
  const doc = Parser.data(html)
                    .from('<ul class="js-rstlist-info rstlist-info"')
                    .to('<li class="list-rst__rdimg">')
                    .build()
  //名前、価格etcを配列で格納。
  //現状は名前しか使っていない。できれば結合して投稿したい。
  const name = Parser.data(doc)
                    .from('<div class="list-rst__rst-name">')
                    .to('<span class="list-rst__area-genre cpy-area-genre">')
                    .iterate()
                                 
  const price = Parser.data(doc)
                    .from('<span class="c-rating__val list-rst__budget-val cpy-lunch-budget-val">')
                    .to('</span>')
                    .iterate()
  
  const rating = Parser.data(doc)
                    .from('<span class="c-rating__val c-rating__val--strong list-rst__rating-val">')
                    .to('</span>')
                    .iterate()

  const url = Parser.data(doc)
                    .from('<a class="list-rst__rst-name-target cpy-rst-name" target="_blank" href="')
                    .to('">')
                    .iterate()

  //メッセージとランキングをシャッフル
  var randomMsg = shuffle(message);
  var randomName = shuffle(name);
  
  //3つを取り出してSlackに投稿
  slackApp.postMessage(slackChannelId,randomMsg[0], {username : "ランチロシアンルーレット"});  
  for (var j=0; j<3 ;j++){
    slackApp.postMessage(slackChannelId,j+1+'. '+randomName[j].replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,''), {username : "ランチロシアンルーレット"});
  }
}
```

#コードの説明
```javascript:Code.gs
//required library
//SlackApp	M3W5Ut3Q39AaIwLquryEPMwV62A3znfOO
//Parser	M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV
```
事前準備として、必要なライブラリをインポートします。

```javascript:Code.gs
//Fisher–Yates shuffle（配列をシャッフルする関数）
function shuffle(array) {
  var n = array.length, t, i;

  while (n) {
    i = Math.floor(Math.random() * n--);
    t = array[n];
    array[n] = array[i];
    array[i] = t;
  }

  return array;
}
```
お店をどうやってランダムに取り出すか迷いましたが、配列をシャッフルして先頭から取り出すことにしました。
このため、シャッフルするための関数を用意しておきます。

```javascript:Code.gs
//食べログをスクレイピングしてSlackに投稿する。
function postMessage() {
  
  const prop = PropertiesService.getScriptProperties().getProperties();
  const slackApp = SlackApp.create(prop.token); //Script Propertiesに設定したトークンIDを取得
  const slackChannelId = prop.SlackChannel; //Script Propertiesに設定したチャンネルIDを取得
  const message = [ 
         '今日のおすすめランチです。',
         'ランチはここでどうでしょう？',
         'ランチはここで食べてください。'
  ];
```
Slackの投稿に必要なトークンとチャンネルのIDは、コード内に含めずに、スクリプトのプロパティに定義しておき、`PropertiesService.getScriptProperties().getProperties()`で取り出します。

```javascript:Code.gs
  //食べログのURL 右記形式のURLであればそのまま使えそう。 https://tabelog.com/*/rstLst/*
  //サンプルは東京駅のランチランキング
  const fetchUrl = 'https://tabelog.com/tokyo/A1302/A130201/R6586/rstLst/lunch/';
```
食べログのURLを指定します。
「https://tabelog.com/*/rstLst/*」 という形式のURLであれば、今回のコードがそのまま使えるようです。

```javascript:Code.gs

  const html = UrlFetchApp.fetch(fetchUrl).getContentText();
  //ランキングのリスト部分をパースする。
  const doc = Parser.data(html)
                    .from('<ul class="js-rstlist-info rstlist-info"')
                    .to('<li class="list-rst__rdimg">')
                    .build()
  //名前、価格etcを配列で格納。
  //現状は名前しか使っていない。できれば結合して投稿したい。
  const name = Parser.data(doc)
                    .from('<div class="list-rst__rst-name">')
                    .to('<span class="list-rst__area-genre cpy-area-genre">')
                    .iterate()
                                 
  const price = Parser.data(doc)
                    .from('<span class="c-rating__val list-rst__budget-val cpy-lunch-budget-val">')
                    .to('</span>')
                    .iterate()
  
  const rating = Parser.data(doc)
                    .from('<span class="c-rating__val c-rating__val--strong list-rst__rating-val">')
                    .to('</span>')
                    .iterate()

  const url = Parser.data(doc)
                    .from('<a class="list-rst__rst-name-target cpy-rst-name" target="_blank" href="')
                    .to('">')
                    .iterate()
```
ライブラリ[Parser](http://www.kutil.org/2016/01/easy-data-scrapping-with-google-apps.html)を利用して、ページの情報を取得します。
必要な情報が含まれる箇所のHTMLタグを`from`/`to`で指定して、`build()`で文字列として取得します。名称や星の数は`literate()`で配列として格納します。

なお、このコードでは、名前しか使用していません。
本当は星の数やURLも一緒に投稿したかったのですが、うまい紐づけかたが思い浮かびませんでした。（コメント貰えると嬉しいです）

```javascript:Code.gs
  //メッセージとランキングをシャッフル
  var randomMsg = shuffle(message);
  var randomName = shuffle(name);
```
いつも同じメッセージだとつまらないので、冒頭で定義した`shuffle`ファンクションを利用して、シャッフルしたメッセージを取り出します。
また、お店の名前はこのままだとランキングの順位で並んでいるだけなので、こちらもシャッフルします。

```javascript:Code.gs
  //3つを取り出してSlackに投稿
  slackApp.postMessage(slackChannelId,randomMsg[0], {username : "ランチロシアンルーレット"});  
  for (var j=0; j<3 ;j++){
    slackApp.postMessage(slackChannelId,j+1+'. '+randomName[j].replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,''), {username : "ランチロシアンルーレット"});
  }
}
```
ライブラリ[SlackApp](https://qiita.com/soundTricker/items/43267609a870fc9c7453)を利用してSlackに投稿します。
先ほどシャッフルした配列`randomName`から順番に3つを取り出します。

配列`name`（とそれをシャッフルした`randomName`）には\<a>タグが含まれてしまうので、`replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'')`でタグを削除します。

[JavaScriptでHTMLタグを削除する正規表現 - Qiita](https://qiita.com/miiitaka/items/793555b4ccb0259a4cb8)

#できあがり
![slackbot.png](https://qiita-image-store.s3.amazonaws.com/0/228205/7c0b77f6-5c14-0daf-7a7f-5f7fcec0ba03.png)
#あとがき
本文中にも書きましたが、できれば店の名前とURLや星の数を紐づけて投稿したいなと思っています。

#参考リンク
- [Google Apps ScriptでスクレイピングしてSlackに定期ポストするbotを瞬殺で作った - Qiita](https://qiita.com/fireowl11/items/e703e35073b600528e7c)
- [いい感じにランチを決める Slack ボットを作った - Qiita](https://qiita.com/kimihiro_n/items/7808a56c10700704b9a2)
- [JavaScriptでHTMLタグを削除する正規表現 - Qiita](https://qiita.com/miiitaka/items/793555b4ccb0259a4cb8)