n8nでRSSの新着アイテムをwebhookに投げる
昔はSleipnirのRSSリーダーとかGoogle Readerとかを使っていて、Feedlyもちょっとは使っていた。けれどもいつのまにかTwitterで話題になったやつとか、Google Discoverで出てきたニュースをちょろっと読むだけになってしまった。もうちょっと能動的にキーワードで引っ掛けたりして自分にとって重要そうなニュースを早めに読みたいという気持ちはあり、n8nのself-hostingコミュニティ版を使ってチャットに流すことにした。

最初はすんなりいくかとおもって、特にメモを書くこともないなぁと思ってたけど、しっかりあったので書いておく。
まず、RSSフィードを扱ってくれるRSS Readノードは、RSSのURLを1個しか受け取ってくれない。なので複数サイトのフィードを一気に扱いたいときは、さっそく他のノードとの連携が必要になってくる。幸いこのトピックそのものの公式ドキュメントがあるので、Split In Batches
ノードでループを作ればいいことがわかるのだが、このループを作成するときにフィードのリストを渡すための「リストを定義する」みたいなノードが存在しない。
仕方がないので、先ほどの公式ドキュメントにあるように、別途任意のJavascriptコードを書けるCodeノードを用意して、下記のようにして次に実行されるノードにリストが渡るようにする。
return [
{json: {url: 'https://news1.example.com/feed'}},
{json: {url: 'https://news2.example.com/feed'}},
{json: {url: 'https://news3.example.com/feed'}},
{json: {url: 'https://news4.example.com/feed'}},
{json: {url: 'https://news5.example.com/feed'}},
];
ここまでで、ワークフローが実行されたときに全部のサイトを巡回して、RSS内のアイテムを取ってきてくれるのだが、ここから「前回実行時からの新着アイテム」に絞り込むことがRSSノードではできない。このままWebhookへのPOSTにつなげてしまうと、ワークフローが実行されるたびに古い記事も含めて全部のアイテムがチャットに投げられてしまうので悲劇。そこでまずはワークフローの実行間隔を、cronに相当するSchedule triggerノードで設定する。ここでは例として157分間隔に設定。

続いて、Split In Batches
ノードの後ろにFilterノードをつなげて、次のようなフィルターを設定する。

ここで、RSS内のアイテムに紐付いている日時{{$json.isoDate}}
と、現在時刻から157分前の日時{{$now.minus({minutes:157}).toISO()}}
を比べて、記事が前回の実行時より新しいかどうかを無理やり判定する。
これ以外にいい方法がないか調べたけども、公式のノードだけではこれが限界っぽい。157分をハードコードすると、後々間隔を変更したときにこの部分を変更し忘れそうなので変数を使って
$('Schedule Trigger').params["rule"]["interval"][0]["minutesInterval"]
に変更しておくとよさそう。後は$now
を使うとワークフロー全体が開始された日時ではなくて、コード自体が呼び出された日時が入りそうなので、フィードの取得に時間がかかったときなどは空白の時間が生まれて新着判定が上手くいかなそうなので、ここも変数を使ってワークフローが開始された日時そのものを使うために
$('Schedule Trigger').item.json.timestamp
としておく。
最終的には新着判定のために使う基準日時は
{{
DateTime.fromISO($("Schedule Trigger").item.json.timestamp)
.minus({
minutes:
$("Schedule Trigger").params["rule"]["interval"][0][
"minutesInterval"
],
})
.toISO();
}}
となる、けど読みにくいですね。
このあたりは、何分間隔という指定ではなくて、毎時決まった時間に、とした方がすっきりすると思う。だけど、毎時決まった時間にフィードを取りに行くのはなんかしっくりこないんですよね。
ここまでで新着判定はできているはずなので、後はWebhookに向かって情報を投げるだけ。HTTP Requestノードを使って、サービスに合わせて整形した上で投げる。例えばMattermostの場合は、text
にリンクを入れてあげれば自動でプレビューされるので十分。

新着アイテムを全部投稿するのではなくて、特定のキーワードに合致するものだけに絞りたければ、POSTする前にもう一つFilterノードを追加してRegexが書ける。

「RSSの新着アイテムをキーワードで絞ってからPOSTしておいて」っていう簡単ぽく聞こえる作業が、ここまでの長文になってしまった。