Edited at

AWS Lambdaを使ってAndroid JetpackのリリースをRSSで受け取れるようにした

AndroidのJetPack便利だし種類も更新も多いのでよくチェックしているのですが、毎日チェックするのは手間なので、RSS形式にして更新を受け取れたらいいなと思い作りました。

https://0i5xv1zj7e.execute-api.ap-northeast-1.amazonaws.com/prod/rss

画像はRSSをSlackで読ませてみたものです。

rss.png

中身がHTMLだと判定してくれるRSSの場合は各ライブラリのリンクにジャンプもできます。

rss2.png

似たものとして、GitHub Releaseを使った更新通知を受け取る方法もあります。

GitHubのNotificationsで、androidxライブラリのリリースの通知を受け取れるリポジトリを作りました | stsnブログ


環境


  • AWS Lambda

  • Node.js 10

今回は https://developer.android.com/jetpack/androidx/releases のページをパースしてRSSのXMLに変換するアプリをAWS Lambda上に作成しました。

HTTPリクエストが簡単できる「request」、

HTMLのパーサーに「node-html-parser」、

XMLの作成に「rss」

をライブラリとして使いました。


package.json

{

"private": true,
"name": "androidx",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"node-html-parser": "^1.1.16",
"request": "^2.88.0",
"request-promise": "^4.2.4",
"rss": "^1.2.2"
}
}

AWS LambdaのNode.jsバージョンがあがり、async-awaitが使えるようになったので、requestをpromiseでラップしたライブラリも導入しました。


コード


index.js

const parser = require('node-html-parser');

const request = require('request-promise');
const RSS = require('rss');

exports.handler = async (event) => {
const htmlString = await request.get('https://developer.android.com/jetpack/androidx/versions/all-channel');
const releaseNotes = parseItems(htmlString);

const response = {
statusCode: 200,
headers: { 'Content-Type': 'text/html' }, // RSS形式として返すためContent-Typeを明示的に指定
body: createRSS(releaseNotes),
};

return response;
};

function parseItems(html) {
const root = parser.parse(html);
const releases = root.querySelector('.devsite-article-body');
const headers = releases.querySelectorAll('h3');
const lists = releases.querySelectorAll('ul');

return headers.map((header, index) => {
const updateDate = header.text;

// 更新内容をHTMLのリスト形式に変換
const updates = lists[index].querySelectorAll('a');
const packages = updates.map((package) => {
// リンクは相対パスなので絶対パスに変換
const link = `https://developer.android.com${package.attributes.href}`;
const packageName = package.text;
return `<li><a href="${link}">${packageName}</a></li>`;
});

return {
title: updateDate,
date: updateDate,
packages: `<ul>${packages.join('\n')}</ul>` // Slackは装飾系タグが無視されるので改行で整える
};
});
}

function createRSS(items) {
let feed = new RSS({
title: 'Recent Release Notes',
feed_url: 'hoge',
site_url: 'https://developer.android.com/jetpack/androidx/versions/all-channel',
});

items.forEach((item) => {
feed.item({
title: item.title,
date: item.date,
description: item.packages,
// August 7, 2019 -> august_7_2019 に変換
url: `https://developer.android.com/jetpack/androidx/versions/all-channel#${item.title.toLowerCase().replace(/,? /g, '_')}`
});
});

return feed.xml();
}


基本的にはHTMLをパースしRSSのXML形式に落とし込んでいく作業です。

特に難しいことはしていませんが、HTMLのパース作業は地味に重労働です。

こうしてできあがるRSSはこのような形になりました。

<?xml version="1.0" encoding="UTF-8"?>

<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title><![CDATA[Recent Release Notes]]></title>
<description><![CDATA[Recent Release Notes]]></description>
<link>https://developer.android.com/jetpack/androidx/versions/all-channel</link>
<generator>RSS for Node</generator>
<lastBuildDate>Sat, 10 Aug 2019 13:35:09 GMT</lastBuildDate>
<atom:link href="hoge" rel="self" type="application/rss+xml" />
<item>
<title><![CDATA[August 7, 2019]]></title>
<description><![CDATA[<ul><li><a href="https://developer.android.com/jetpack/androidx/versions/all-channel/activity#1.1.0-alpha02">Activity 1.1.0-alpha02</a></li>
<li><a href="https://developer.android.com/jetpack/androidx/versions/all-channel/ads#1.0.0-alpha01">Ads 1.0.0-alpha01</a></li>
<li><a href="https://developer.android.com/jetpack/androidx/versions/all-channel/autofill#1.0.0-alpha02">Autofill 1.0.0-alpha02</a></li>

<!-- 長いので省略 -->

</ul>]]></description>
<link>https://developer.android.com/jetpack/androidx/versions/all-channel#august_7_2019</link>
<guid isPermaLink="true">https://developer.android.com/jetpack/androidx/versions/all-channel#august_7_2019</guid>
<pubDate>Wed, 07 Aug 2019 00:00:00 GMT</pubDate>
</item>
<item>
<title><![CDATA[July 30, 2019]]></title>
<description><![CDATA[<ul><li><a href="https://developer.android.com/jetpack/androidx/versions/all-channel/work#2.2.0-rc01">WorkManager 2.2.0-rc01</a></li></ul>]]></description>
<link>https://developer.android.com/jetpack/androidx/versions/all-channel#july_30_2019</link>
<guid isPermaLink="true">https://developer.android.com/jetpack/androidx/versions/all-channel#july_30_2019</guid>
<pubDate>Tue, 30 Jul 2019 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>

全部の更新を取っているので近々直近10件とかに絞ろうかと思います。

あとRSSってContent-Typeがapplication/xmlかと思いきやtext/htmlで返す方が多いそうです。知らなかった。