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

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

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

画像はRSSをSlackで読ませてみたものです。
<img width="299" alt="rss.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44992/dd6c00ac-b132-fe82-8164-68dab4fca898.png">

中身がHTMLだと判定してくれるRSSの場合は各ライブラリのリンクにジャンプもできます。
<img width="944" alt="rss2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44992/5b760ff4-f296-44cb-389b-dcd596766eb3.png">


似たものとして、GitHub Releaseを使った更新通知を受け取る方法もあります。
[GitHubのNotificationsで、androidxライブラリのリリースの通知を受け取れるリポジトリを作りました | stsnブログ](https://satoshun.github.io/2019/07/androidx-releases/)

# 環境
- 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
<?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で返す方が多いそうです。知らなかった。