【Google Apps Script】その16　GASとスプレッドシートだけで簡易RSSリーダーを作り、Chatworkに新着通知する

この記事は[Google Apps Scriptを実例交えて基礎からざっくり学ぶ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/gas_beginner)　16日目の記事です。

本アドベントカレンダーは@rt_pの個人プロジェクトですが、筆者は[Ateam Brides Inc. Advent Calendar 2017](https://qiita.com/advent-calendar/2017/a-t-brides)にも参加しています。そちらでも出張版記事を書いているので、覗いていただけると嬉しいです。

# はじめに

今回はGASを使いRSSフィードから最新ニュースを取得、新着記事をChatworkに通知してみたいと思います。

![img2.png](https://qiita-image-store.s3.amazonaws.com/0/199678/1974d07a-aed2-6418-a154-7b1e3d588e22.png)

Chatwork画面
![img11.png](https://qiita-image-store.s3.amazonaws.com/0/199678/28906122-b762-5246-771e-600f5746dacd.png)

# スプレッドシートの準備

まずは通知したいニュースのRSSフィードを探しましょう。

今回は*Yahoo!ニュース - RSS*から選んでみたいと思います。
https://headlines.yahoo.co.jp/rss/list

お好きなRSSフィードを選んだら、新たにスプレッドシートを作成します。
A1、B1、A2の3セルに以下の通り値を貼り付けます。

|セル|値|
|---|---|
|A1|[RSSフィードURL]|
|B1|=A1&"?d="&C1|
|A2|=importfeed(B1, "items", false, 20)|

> IMPORTFEED(URL, [クエリ], [見出し], [アイテム数])
https://support.google.com/docs/answer/3093337?hl=ja

IMPORTFEEED関数を1つ入力するだけで、簡単にRSSフィードをスプレッドシートに展開できます。超便利！

![img2.png](https://qiita-image-store.s3.amazonaws.com/0/199678/1974d07a-aed2-6418-a154-7b1e3d588e22.png)

# GASの設定

スクリプトエディタを開き、以下コードに置き換えて実行します。
スクリプトエディタの開き方や*承認が必要です*メッセージが出た際の対処法が分からない場合は
[アドベントカレンダー1日目のHello, world!記事](https://qiita.com/rf_p/items/0535ec495df8f9d2b894)をご参照ください。

```javascript:get_feeds.gs
var currentTime = new Date();
var newFeeds = [];

function myFunction() {
  var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
  for (var i in sheets) { // 全シートを順番にチェック
    addNewFeeds(sheets[i]);
  }
  Logger.log(newFeeds); // デバッグ用
}

// フィードの新着チェックを行い、新着があれば配列newFeedsに追加する
function addNewFeeds(sheet) {
  var feeds = getFeeds(sheet);
  for (var i in feeds['feeds']) {
    if (isNew(feeds['feeds'][i][2], feeds['lastCheckTime'])) { // 前回起動時間以降の投稿か判定
      feeds['feeds'][i][3] = sheet.getName();
      newFeeds.push(feeds['feeds'][i]);
    }
  }
}

// 最新のフィードを取得する
function getFeeds(sheet) {
  var values = sheet.getRange(1, 1, 1, 3).getValues(); // getValueを2回実行すると都度通信が入るので、A1:C1を習得
  var url = values[0][0];
  var lastCheckTime = values[0][2]; // C1には前回のタスク実行時刻が入っている
  sheet.getRange('C1').setValue(currentTime);
  var lastRow = sheet.getLastRow();
  var feeds = sheet.getRange(2, 1, lastRow - 1, 3).getValues(); // 一括で範囲内全てを取得する
  return {'feeds': feeds, 'lastCheckTime': lastCheckTime};
}

// 前回のチェック以降の投稿か確認
function isNew(date, lastCheckTime) {
  var postTime = new Date(date);
  return (postTime.getTime() > lastCheckTime.getTime());
}
```

成功すると、以下のようにログに新着フィードが吐き出されます。
前回実行日時（C1セル）以降のフィードを一覧取得しているので、もし空の配列が表示されてしまう場合はC1セルの中身を一度消してみてください。

![log.png](https://qiita-image-store.s3.amazonaws.com/0/199678/6294dee0-220e-4ba2-4d17-cd83ba0762c2.png)

## なぜわざわざフィードのURLに`?d=`とパラメータ付きにしているのか

ここが今回のスクリプトのミソなのですが、実はIMPORTFEED関数はリクエストしたURLの結果をキャッシュで保持するらしく、パラメータを付けて別URLとして再計算しないと前回と同じフィード内容が取得されてしまいます。

`rand()`や`time()`もIMPORTFEED側で弾かれて使用できないので、泣く泣くGASで現在の時刻をスプレッドシートに吐き出し、文字列結合でURLを強制的に書き換える手法を取っています。

ここまで成功すれば、フィードからデータを取得することはできましたね。
ではChatworkに飛ばす設定をしましょう。

# Chatwork APIのトークン取得

ChatworkのAPIトークンを取得しましょう。
Chatworkの画面を開き、まず右上メニューからAPI設定を選択します。

![img9.png](https://qiita-image-store.s3.amazonaws.com/0/199678/d5e8bc09-901f-9335-6fc5-3ed9b62c4679.png)

パスワードを入力し「表示」を押せば、トークンが表示されます。
ビジネスプラン、エンタープライズプラン、KDDI ChatWorkの場合はシステム管理者が申請する必要があります。
専用のbotアカウントを作り、そのアカウントで申請をしてもらいましょう。

![img10.png](https://qiita-image-store.s3.amazonaws.com/0/199678/8e6f98da-1e74-eece-4862-6f7b2fc839bc.png)

Linuxコマンドラインでcurlを叩いてみましょう。

> curl -X POST -H "X-ChatWorkToken: 自分のAPIトークン" -d "body=Hello+ChatWork%21" "https://api.chatwork.com/v2/rooms/{room_id}/messages"

http://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-messages

room_idは部屋のIDです。URLの`#!ridXXXXX`となっている、rid以降の数値です。
成功すると指定した部屋にメッセージが投稿されます。

![curl.png](https://qiita-image-store.s3.amazonaws.com/0/199678/70f67da0-28f3-b155-447d-3c625ca76914.png)

トークンを使って投稿できることが確認できたら、最後にGASに置き換えます。

# GASを使って部屋に投稿

念のため全てのコードを貼り付けています。
修正箇所は`myFunction()`の最下部と、`postToChatwork()`、`requestToChatwork()`の追加です（命名が凄くイケてない）。

```javascript:get_feeds_to_chatwork.gs
var currentTime = new Date();
var newFeeds = [];

function myFunction() {
  var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
  for (var i in sheets) { // 全シートを順番にチェック
    addNewFeeds(sheets[i]);
  }
  postToChatwork(); // この行を追加。チャットワークに新規投稿
}

// フィードの新着チェックを行い、新着があれば配列newFeedsに追加する
function addNewFeeds(sheet) {
  var feeds = getFeeds(sheet);
  for (var i in feeds['feeds']) {
    if (isNew(feeds['feeds'][i][2], feeds['lastCheckTime'])) { // 前回起動時間以降の投稿か判定
      feeds['feeds'][i][3] = sheet.getName();
      newFeeds.push(feeds['feeds'][i]);
    }
  }
}

// 最新のフィードを取得する
function getFeeds(sheet) {
  var values = sheet.getRange(1, 1, 1, 3).getValues(); // getValueを2回実行すると都度通信が入るので、A1:C1を習得
  var url = values[0][0];
  var lastCheckTime = values[0][2]; // C1には前回のタスク実行時刻が入っている
  sheet.getRange('C1').setValue(currentTime);
  var lastRow = sheet.getLastRow();
  var feeds = sheet.getRange(2, 1, lastRow - 1, 3).getValues(); // 一括で範囲内全てを取得する
  return {'feeds': feeds, 'lastCheckTime': lastCheckTime};
}

// 前回のチェック以降の投稿か確認
function isNew(date, lastCheckTime) {
  var postTime = new Date(date);
  return (postTime.getTime() > lastCheckTime.getTime());
}

// chatworkに投稿する
function postToChatwork() {
  for (var i in newFeeds) {
    var subject = newFeeds[i][3]
    var body = newFeeds[i][0] + "\r\n" + newFeeds[i][1];
    requestToChatwork(subject, body);
  }
}

// chatworkにリクエストを送る
function requestToChatwork(subject, body) {
  var token = 'YOUR_TOKEN'; // ここにトークンを入力
  var roomId = 'YOUR_ROOM_ID'; // ここに投稿したい部屋のIDを入力
  var body  = '[info][title]' + subject + '[/title]' + body + '[/info]';
  var payload = {
    'body': body
  }
  var headers = {
    'X-ChatWorkToken': token
  }
  var options = {
    'method' : 'POST',
    'payload' : payload,
    'headers' : headers
  }
  var url = 'https://api.chatwork.com/v2/rooms/' + roomId + '/messages';
  UrlFetchApp.fetch(url, options);
}
```

```javascript
  var token = 'YOUR_TOKEN'; // ここにトークンを入力
  var roomId = 'YOUR_ROOM_ID'; // ここに投稿したい部屋のIDを入力
```

この2箇所はご自身のものに置き換えてください。
部屋にトークンの持ち主であるアカウントがいないと発言できないことにだけ注意してください。

コードが合っていると、`myFunction()`の実行で新着ニュースが投稿されます。
![img11.png](https://qiita-image-store.s3.amazonaws.com/0/199678/28906122-b762-5246-771e-600f5746dacd.png)

最後に定期実行を設定します。
GASのツールバーにストップウォッチのようなアイコンがあるので、そちらをクリックして以下の通り設定。

![img13.png](https://qiita-image-store.s3.amazonaws.com/0/199678/8b38fe01-bee5-f33b-d65a-df8e9cacb82f.png)

すると2時間に一度フィードの中身が更新され、新着記事が投稿されます。
本日は以上です。

# おわりに

ChatworkのAPIを使うと自動でメッセージを取得したり、投稿したり、タスクを追加したりメンバーを管理できたりします。
ただしトークンが漏洩すると見られたくない情報を外部からも閲覧できてしまう可能性があるので要注意です。

Chatworkのトークンが各スプレッドシートにばらまかれる形になるのもなんとかしたいですね。
こちらはアドベントカレンダー最終日に一元管理する方法を紹介する予定です。

# 明日

[【Google Apps Script】その17　デイリーでの目標進捗をChatworkの部屋名に自動反映する](https://qiita.com/rf_p/items/828c9f8147131b57e570)
となります。
デイリーレポートをChatworkに通知しつつ、月目標の進捗をChatworkの部屋名に自動設定する方法をご紹介します。

前の記事
[【Google Apps Script】その15　Execution APIを使い、外部からAPIとして叩く](https://qiita.com/rf_p/items/2eeb5e5e863fb3f42860)
次の記事
[【Google Apps Script】その17　デイリーでの目標進捗をChatworkの部屋名に自動反映する](https://qiita.com/rf_p/items/828c9f8147131b57e570)