DTPab

DTPにまつわるあれこれ

RSSフィードの新着情報を自分宛てにメールするGAS

【2019/8/20 9:00】コードを一部修正しました。それに伴い、記事も加筆修正しています。
【2019/9/17 21:00】コードを一部修正しました。それに伴い、記事も加筆修正しています。

はじめに

Adobeのコミュニティフォーラムをたまーに見に行くのですが、たまーにしか見に行かないのでホットな情報を見逃すことがしばしば。
もちろんRSSリーダー*1で読みに行くこともあるのですが、フィードの中に埋もれてしまうこともあって困ってました。

コミュニティフォーラムが2019年9月に刷新され、これまで利用していたRSSが全部だめになりました。UIが大きく変わり、言語に関係なくアプリケーション1つに対して1つのフォーラムという形で統合されており、特に日本語の記事に関しては非常に少ないため秒で埋もれていきます。

なので思い立ってGoogleAppsScript(以下、GAS)でRSSフィードを読み取り、日本語の新着情報だけを自分宛てにメールするものを作ってみました。

やらないこと

この記事では以下の内容は扱っていません。

  • GASの新規プロジェクト作成方法
  • セキュリティ警告の承認方法
  • GASの定期実行トリガーの設定方法
  • GASとスプレッドシートとの連携
  • その他API(CharworkやSlackなど)との連携
  • JavaScriptの構文的な解説

できあがったもの

新着記事が見つかればこんな感じでGmailが届きます。
(画像は更新日時を緩くしているのでかなり広く記事の情報を受け取っています)

f:id:uske_S:20190818170837p:plain

CSS的なものを何も設定していない、素のHTMLをメールとして送っている感じなので、見た目はアレですが。

コード

function mailRSSFeeds(url, title) {
  var result = ['<!doctype html><html lang="ja"><head><meta charset="UTF-8"></head><body>'];
  var data = UrlFetchApp.fetch(url);
  //Logger.log(XmlService.parse(data.getContentText()).getRootElement().getChildren('channel')[0]); return;
  var xmlItems = XmlService.parse(data.getContentText()).getRootElement().getChildren('channel')[0].getChildren('item');
  xmlItems.map(function(el){
    var D = new Date(el.getChild("pubDate").getText());
    Logger.log(/[ぁ-ゞ]/.test(el.getChild("description").getText()))
    if (filterDate(D, 2) && /[ぁ-ゞ]/.test(el.getChild("description").getText())) {
      result.push("[<a href='" + el.getChild("link").getText()+"'>" + el.getChild("title").getText() + "</a>]<br />●Date: " + el.getChild("pubDate").getText() + "<br />●Description: " + el.getChild("description").getText()+"<br />---<br />");
    }
  });
  result.push("</body></html>");
  Logger.log(result.join("\n"));
  if (result.length > 2) sendMail("<メールアドレス>", "RSS Report: "+title, result.join("\n"));
};

function filterDate(D, beforeDays) {
  var now = new Date();
  var curYear = now.getFullYear();
  var curMonth = now.getMonth();
  var curDate = now.getDate();
  var difYear = D.getFullYear();
  var difMonth = D.getMonth();
  var difDate = D.getDate();
  var now = new Date(curYear, curMonth, curDate); //時間を考慮しない日付のみのDateオブジェクトを再作成
  var dif = new Date(difYear, difMonth, difDate);
  return beforeDays > (now - dif)/86400000 && 0 < (now - dif);
};

function sendMail(mailto,subject,body) {
  MailApp.sendEmail({
        to: mailto,
        subject: subject,
        htmlBody: body
  });
};

function main() {
  var urls = [
    ["https://community.adobe.com/havfw69955/rss/board?board.id=indesign", "Adobe InDesignコミュニティフォーラム 日本語新着記事"]
  ];
  for (var i=0; i<urls.length; i++) {
    mailRSSFeeds(urls[i][0], urls[i][1]);
  }
};

解説

他にGoogleアプリを何も連携させないため、基本的にGASで完結します(Gmailを呼び出すのみ)。
前半の変数宣言ですが、以下のような変数として定義しています。

  • result変数:メール本文の内容をString型の要素(HTML)を羅列した配列で記述します
  • url変数:fetchしたい(RSSフィードを取得したい)URLです
  • data変数:url変数をfetchした戻り値です
  • xmlItems変数:data変数をXMLとしてパースし、必要なもの(Item)を取ってきています

xmlItems変数の中身

最初は分かりにくいかと思うのでxmlItemsだけ少し補足しておきます。
これはfetch先のURLを開いて、そこから必要になるところまでXMLの要素をたどっています。

  1. XmlService.parse(data.getContentText())
  2. .getRootElement()
  3. .getChildren('channel')[0]
  4. .getChildren('item');

を順に見ていくと、1でdata変数の内容をXMLとしてパースしています。続いて2でXMLのRootを捕まえます*2
そうしたらgetChildrenメソッドで必要な要素まで繰り返してアクセスします。なのでここで一度fetch先のURLを開いて構造を確認しておきましょう。

f:id:uske_S:20190818172344p:plain

ご覧のように、欲しい情報"item"にたどり着くには、
"Root">"channel">"item"
とたどればいいことがわかります。
XMLの構造として、ある要素が複数の情報を持つとき、[n]のよう(JavaScriptでいうところの配列風)にアクセスすればOKです。なのでgetChildren('channel')[0].getChildren('item')とすれば連なった"item"要素が取得できます。
この結果がこの変数xmlItemsに代入されている内容です。

"item"をmapする

見つかった"item"要素をArray.mapメソッドで総当たりにします。
このとき、result変数に内容を追加するかどうか、記事の日付を取得して判断します。日付は"pubDate"要素に書かれています(前掲の画像を参照)ので、getChild("pubDate")でそれを取得します。
取得した値はString型なので、これを改めてDateコンストラクタに渡してDateオブジェクトにします。この日付(Dateオブジェクト)と、現在の日付を比較しているのがfilterDateメソッドです。

日付の差分を確認する関数

あんまりうまい実装方法が思いつかなくて少し泥臭くなりました…(ほかにいい方法があれば教えてほしい)。年・月・日だけを指定してDateオブジェクトを再生成して、現在の日付と日数の差分を取ります。
1日は 24h * 60m * 60s = 86,400s になるわけですが、Dateオブジェクト同士の引き算で求められるのはミリ秒なので*3、 86,400 * 1,000 = 86,400,000 で割ることで日数換算としています。
この関数は、第一引数が比較対象のDateオブジェクト、第二引数が許容する日数です。なのでfilterDate(D, 2)とすると、D(現在の時間と差分を取りたいDateオブジェクト)が第二引数2日以内であればtrueを返します。このコードでは2日以内の新着記事をメールで送るようになっていますが、当日のみ(差が1日以内)や5日以内など、好きな間隔を設定して下さい。

mapしてresult変数に書き込む情報

日付の差分を確認した結果、指定した日数以内で更新された記事ならresult変数=メール文面に記事の情報を追加していきます。
簡単なHTMLを書いているだけなので詳細は割愛します。

result変数に情報を書き加えていればメール送信

最後に、result変数に記事の情報を書き込んだらメールを自分宛てに送信します。{自分のメールアドレス}にご自分のメールアドレスを記述して下さい。

以上がコードの概説です。

トリガー設定

コードができたので、今度はトリガーの設定をしましょう*4

そうすれば、1日のうちでだいたい決まった時間に(記事の更新が見つかれば)メールが届くようになります。

おわりに

ということで、RSSフィードを読み取り新着記事をメールしてくれるGoogleAppsScriptでした。

*1:僕はfeedlyを使っています

*2:XMLの構造上、Rootが最も上位の階層にあたるため、ここから順番に枝葉をたどっていきます

*3:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date

*4:トリガーの設定のやり方は、探すとほかのブログ記事でたくさん取り上げられているのでここでは説明しません