RSSリーダーはやめてGoogle Apps Scriptでchatworkにニュースを流してみた

この記事はNIFTY Advent Calendar 2017の12日目の記事です。

弊社エンジニア向けにchatworkへIT関連のニュースを拾ってきて流したりしているのですが、今月退職したエンジニアから役に立っていたと初めて言われたりしたので、特に珍しい話でもないですが書いてみることにしました。

# 概要
1. Google Alertに収集したい情報のキーワードを登録してRSS feedで出力させる
2. Google Apps Scriptで出力したRSS Feedを定期的に読み込む
3. Feedを上から順にチェックして、chatworkに最後に投稿した記事のURLと異なる（=新しいもの）ならchatworkのAPIを使ってニュース用のチャネルに投稿する

RSS Feedを提供していないところが増えてきたことやGoogle AlertでピックアップされるサイトがそれほどひどくなかったこともあってFeedは基本的にGoogle Alertだけにしています。（はてぶなどはそのまま使っていたりしますが）

# Google Alertの登録
 あえて説明することもないですが、Google Alertでもgoogleの検索オプションがそのまま使えますので、不要な情報を拾ってきたら適宜調整するようにしています。（参考：[ウェブ検索の精度を高める](https://support.google.com/websearch/answer/2466433)）
 ITと関係ないですが、弊社親会社の情報でサポートしているスポーツクラブや求人情報以外のものを拾うのに以下のような設定をしていたりします。

「ノジマ -"ノジマステラ" -"ノジマエンジニアリング" -"ノジマ相模原ライズ" -site:townwork.net -site:www.froma.com -site:www.nikkansports.com」


# Google Apps Scriptの実装
Feedの読み込みや分析、chatworkへの投稿はおなじみのGoogle Apps Scriptで行っています。FeedのURLと投稿先チャネルのリストは、編集しやすいようにGoogleスプレッドシートに書くようにしました。
chatworkのtoken、GoogleスプレッドシートのID、最後にチェックした記事のURLはスクリプトのプロパティのほうに入れています。

```ga2cw.gs
function GoogleAlert2Chatwork(channelId, feedUrl){
  var properties = PropertiesService.getScriptProperties().getProperties();
 
  this.feedUrl = feedUrl;  
  this.channelId = channelId;
  eval("this.lastChecked = properties.rid" + this.channelId + "_lastChecked;");
}
 
GoogleAlert2Chatwork.prototype = {
  constructor: GoogleAlert2Chatwork,
  postMessage: function(message) {
    if (message != ''){
      var payload = {
        'body' : message
      };
      
      var options = {
        'method'  : 'post',
        'headers' : {'X-ChatWorkToken' : properties.token},
        'payload' : payload
      };
      
      UrlFetchApp.fetch('https://api.chatwork.com/v2/rooms/' + this.channelId + '/messages', options);
    }
  },
  parseFeed: function() {
    var atomNS = XmlService.getNamespace('http://www.w3.org/2005/Atom');
    var document = XmlService.parse(UrlFetchApp.fetch(this.feedUrl).getContentText());
    var entry = document.getRootElement().getChildren('entry', atomNS);
     
    var items = new Array();   
    for(var i in entry){
      var item = {
        'title'   : entry[i].getChild('title',atomNS).getText().replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,''),
        'link'    : decodeURIComponent(entry[i].getChild('link',atomNS).getAttribute('href').getValue().match(/&url=(.*)&ct=ga/)[1]),
        'summary' : entry[i].getChild('content',atomNS).getText().replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'').replace(/&nbsp;|&raquo;|and more/g,' '),
        'time'    : Utilities.formatDate(new Date(entry[i].getChild('updated',atomNS).getText()
                    .replace('T',' ').replace('Z',' GMT').replace(/-/g,'/')), 'JST', "yyyy-MM-dd HH:mm:ss")
      };
      
      items.push(item);
      if(i == 0){
        eval("PropertiesService.getScriptProperties().setProperty('rid" + this.channelId + "_lastChecked', item['link']);");
      }
    }
   
    return items;
  }
};
```

```main.gs
function run(){
  var spreadSheet = SpreadsheetApp.openById(PropertiesService.getScriptProperties().getProperty('feedListSheetId'));
  range = spreadSheet.getActiveSheet().getRange(1, 1, 10, 2); // セルの取得範囲は適当に決め打ち
  var feedList = range.getValues();

  // 1つ目のセルにchatworkのchannel id、2つ目にFeed URL 
  for(var i = 0; i < feedList.length; i++){
    if(feedList[i][0]  == '' || feedList[i][1] == ''){
      break;
    }
 
    var ga2c = new GoogleAlert2Chatwork(feedList[i][0],feedList[i][1]);  
    var entries = ga2c.parseFeed();

    // 新着だけを投稿
    var messages = '';
    for(var e in entries) {
      if(entries[e].link == ga2c.lastChecked) {
        break;
      }else{
        messages = '[info][title]' + entries[e].title + '\n['+ entries[e].time + '][/title]' + entries[e].link + '[hr]' + entries[e].summary + '[/info]';
        ga2c.postMessage(messages);
      }
    }
  }
}
```
parseFeedにタイトルなどから余計なタグを取り除く処理がごちゃごちゃと入っていますが、基本的にはFeedからtitle、url、content、updatedを取り出して、chatworkのAPIを使って投稿するだけのいたってシンプルな内容です。
![cap.png](https://qiita-image-store.s3.amazonaws.com/0/218020/db7554b5-65e5-95da-0766-c9f133a44bfb.png)
記事に対するコメントを同じチャネルに書き込むと埋もれますので、chatworkの発言をクリップボードにコピーする機能を使ってチームのチャネルにコピペしたりしています。

この他にもTECH PLAYの勉強会の情報やPR TIMESのプレスリリースをスクレイピングで拾って流したりしています。（GASでスクレイピングは苦行になりがちですので、Google Cloud functionsやAWS Lambdaを使ったほうがよいでしょう）

# 集約してどうなの？
chatwork/slack/stride何でもよいと思いますが、チャット機能のあるツールに集約すると共有したり、話し合ったりするのが楽ですし、脆弱性の話など重要な情報の共有が増えたような気がしています。

次はRSSリーダーの頃から解決していない「拾ってくる情報が多くて読み捨てになる問題」を解決する仕組みを考えたいところです。