Web ページが更新されたら Slack Bot で通知する機能を GAS で実装する話

# 概要
Slack Bot と Google Apps Script を用いて，特定の Web ページが更新されたことを通知する簡易な機能を実装してみる．

# 詳細
ふと[東方我楽多叢誌](https://touhougarakuta.com/)の更新頻度を知りたくなったので

1. 1日おきに Web ページの情報を取ってくる
2. 取ってきた情報と，以前のページ情報を比較する
3. 2で比較したものが異なる場合は通知し，そうでない場合は通知しない

という流れの機能を作る．

```:簡単な流れ図
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  東方我楽多叢誌 strange article of the outer world
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
   情報の取得 ↑                      ↓ 情報の保存
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
     Google Apps Script + Google スプレッドシート
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
     更新通知 ↓
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
                    Slack Bot
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```

## Slack Bot の作成
1. https://api.slack.com/apps にアクセスし **Create New App** をクリック
2. アプリ名とワークスペースを設定し **Create App** をクリック
3. 左側のメニューから **Bot Users** をクリック
4. **Add a Bot User** をクリック
5. 表示名とデフォルトユーザー名を設定し **Add Bot User** をクリック[^1]
6. 左側のメニューから **Install App** をクリック
7. **Install App to Workspace** をクリック
8. アプリがワークスペースへのアクセス権限をリクエストしてくるので，許可する
9. 表示されるトークンのうち **Bot User OAuth Access Token** をメモしておく

[^1]: Always Show My Bot as Online は On でも Off でも良い……はず．

## スプレッドシートの作成
1. Google ドライブ上にスプレッドシートを作成しておく
2. スプレッドシート ID をメモしておく（下記参照）

`https://docs.google.com/spreadsheets/d/[スプレッドシートID]/edit`

## GAS でのコーディング
1. 作成したスプレッドシートのメニューバーから **ツール>スクリプト エディタ** をクリック
2. エディタ画面の **リソース>ライブラリ** から[こちら](https://github.com/soundTricker/SlackApp)のライブラリを追加する
3. 次のコードを入力して保存する

```javascript:コード.gs
function updateCheck() {
  // 東方我楽多叢誌のページ情報を取得
  var targetUrl = "https://touhougarakuta.com/";
  var response = UrlFetchApp.fetch(targetUrl);
  var hash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, response.getContentText()).toString();
  
  // データベース（スプレッドシート）の情報を取得
  var sheet = SpreadsheetApp.openById("スプレッドシートID");
  var data = sheet.getRange("A1").getValue();
  
  // Slack API でメッセージ送信
  var slack_token = "xoxb-******"; // Bot User OAuth Access Token
  var slackApp = SlackApp.create(slack_token);
  var channelId = "ABCDE1234";
  var message = "東方我楽多叢誌に更新があるようです。\n" + targetUrl;
  var result = "Not Posted";
  if(data != hash) {
    sheet.getRange("A1").setValue(hash);
    result = slackApp.chatPostMessage(channelId, message, {});
  }
  
  Logger.log(result);
}
```

## テスト
1. スクリプトエディタの画面で **実行ボタン** をクリック
2. Google アカウントへのアクセスをリクエストされるので，許可する[^2]
3. スクリプトが実行され，Slack Bot に通知が来たら成功

[^2]: 「このアプリは確認されていません」という画面が出たら「詳細」をクリックした後「～（安全ではないページ）に移動」をクリックすると先に進める．

## 運用開始
1. スクリプトエディタの画面で **編集>現在のプロジェクトのトリガー** をクリック
2. **トリガーを追加** をクリック
3. イベントのソースを **時間主導型** にし，例えば1日おきに更新確認を行いたい場合は，トリガーのタイプを **日付ベースのタイマー** にして時刻を選択する

東方我楽多叢誌に更新があれば，下記のように通知がなされる．

![更新通知の画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478774/b9f0ac7d-e03e-d860-1e7e-881ec814fb58.png "更新通知の画像")

# 発展編: 更新内容も一緒に通知する
上記の機能では「Web ページが更新された」ということしか通知されない．なぜなら，更新通知の際に Web ページ全体をハッシュ値に変換して比較しており，差分の検出を行うことができないからである．そこで以下では，具体的な更新内容も併せて通知させてみたい．

## スプレッドシートの書き換え
前節の[スプレッドシートの作成](#%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)で既に作成したスプレッドシートに以下の通り記入する[^3]．

[^3]: セルに入力する `IMPORTXML` の引数は，通知を得たい Web ページに応じて変更する必要がある．

- セル A1 に `=IMPORTXML("https://touhougarakuta.com", "//a[contains(@class, 'card')]/@href")` を記入
- セル B1 に `=IMPORTXML("https://touhougarakuta.com" & A4, "//h2")` を記入

![書き換えたスプレッドシートの画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478774/74c47184-b4f4-c3c8-8432-40f7a622eb22.png "書き換えたスプレッドシートの画像")

## GAS コードの書き換え
前節の [GAS でのコーディング](#gas-%E3%81%A7%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0)の手順に従ってスクリプトエディタを開き，コードを次のように書き換える．

```javascript:コード.gs
function updateCheck() {
  // データベース（スプレッドシート）の情報を取得
  var sheet = SpreadsheetApp.openById("スプレッドシートID");
  var url = "https://touhougarakuta.com";
  var oldtitle = sheet.getRange("E4").getValue();
  var href = url + sheet.getRange("A4").getValue();
  var title = sheet.getRange("B1").getValue();
  
  // Slack API でメッセージを送信する準備
  var slack_token = "xoxb-******"; // Bot User OAuth Access Token
  var slackApp = SlackApp.create(slack_token);
  var channelId = "ABCDE1234";
  var message = "新着情報:\n" + title + "\n" + href;
  var result = "Not Posted";
  
  // 更新されていたらメッセージ送信＆スプレッドシート書き換え
  if(oldtitle != title) {
    result = slackApp.chatPostMessage(channelId, message, {});
    sheet.getRange("E4").setValue(title);
  }
  
  Logger.log(result);
}
```

## テスト・運用開始
前節の[テスト](#%E3%83%86%E3%82%B9%E3%83%88)や[運用開始](#%E9%81%8B%E7%94%A8%E9%96%8B%E5%A7%8B)と同様である．
東方我楽多叢誌に更新があれば，下記のように通知がなされる．

![更新内容も含む更新通知の画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478774/33224592-d8e2-8455-1438-9723ea49e354.png "更新内容も含む更新通知の画像")


# 参考
- [Google Apps Script で Slack Botを作ってみた。(お勉強編) - Qiita](https://qiita.com/Quikky/items/9de56c049304885a4f4f)
- [【GAS】エラー「ArrayをObject[][]に変換できません。」に遭遇したら・・・ - Qiita](https://qiita.com/ShishidoToru/items/0ab9de4ea281df9358f4)
- [クローラ作成に必須！XPATHの記法まとめ - Qiita](https://qiita.com/rllllho/items/cb1187cec0fb17fc650a)
- [XPath Cheat Sheet](http://blog.bangboo.com/sub/xpath.html)