1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RSSHubで東京科学大学ニュースのルーターを作ってみた

Posted at

背景

旧東京工業大学のウェブサイトには便利なRSS機能があるが、東京科学大学の新しいサイトにはその機能がなくなっている。機能を補完するためにさまざまな実現方法を調査した結果、最終的にRSSHubを使用することに決定した。

RSSHubについて

簡単に言うと、RSSHubはウェブサイトからRSSリンクを生成するアプリである。既に多くのルーターが作成されているため、そのまま利用しても便利である。また、オープンソースプロジェクトであるため、自分でカスタムルーターを作成することも可能である。今回はオフィシャルパンフレットに従ってルーターを作ってみた。

環境構築

普通に、Node.jsをインストールして、pnpm iで構築できる。(ここでpnpmがなければ、先にnpm install pnpm)

Namespaceの作成

オフィシャルパンフレットに従って、簡単にできると思う。
nameは人が読むための名前であり、urlはプロトコルなし(つまりhttpなどなし)のリンクである。また、説明が必要であれば、descriptionに記載できる。
多言語対応もしており、(zhzh-TWja)の言語で多言語ドキュメントを作成可能である。

import type { Namespace } from '@/types';

export const namespace: Namespace = {
    name: 'Institute of Science Tokyo',
    url: 'isct.ac.jp',
    lang: 'ja',
    description: `
:::tip
    支持通过category参数筛选新闻类别。详情请查看[指南](https://docs.rsshub.app/zh/guide/parameters#%E5%86%85%E5%AE%B9%E8%BF%87%E6%BB%A4)。
    You can filter news by category through the category parameter. For more information, please refer to the [guide](https://docs.rsshub.app/guide/parameters#filtering).
:::`,
    ja: {
        name: '東京科学大学',
    },
};

Route本作成

Routeの定義やRadarなど

この部分もオフィシャルパンフレットに従って作ればいい。

    path: '/news/:lang',
    categories: ['university'],
    example: '/isct/news/ja',
    parameters: { lang: 'language, could be ja or en' },
    features: {
        requireConfig: false,
        requirePuppeteer: false,
        antiCrawler: false,
        supportBT: false,
        supportPodcast: false,
        supportScihub: false,
    },
    radar: [
        {
            source: ['www.isct.ac.jp/:lang/news'],
            target: '/news/:lang',
        },
    ],
    name: 'News',
    maintainers: ['catyyy'],

ウェブサイトの解析

ウェブサイト自体がAPIを通じてデータを取得している場合は、可能であればそのAPIを直接使用してデータを取得する方が良い。それができない場合は、HTMLデータを手動で解析しなければならない。最も簡単な方法は、F12を押して開発者ツールを開き、ネットワークオプションに切り替えてからページをリフレッシュすることです。これで、Fetch/XHRのセクションでウェブページのAPIリクエストを確認できる。
今回は運良く、東科大のニュースはこのAPIを使って取得されているニュースデータで、分類もこのAPIから取得されている。

しかし、残念ながら上記のAPIの返す値は一般的なJSON形式ではなく、HTMLテキスト形式である。そのため、解析して利用可能な形式に変換する必要がある。この部分でかなりの時間を費やしてしまった。ここで、私はTSのentitiesにあるHTML解析関数decodeを使った。もしこれを見て役立つ人がいれば、非常に嬉しく思う。

handler: async (ctx) => {
        const { lang = 'ja' } = ctx.req.param();
        const mediaResponse = await ofetch(`https://www.isct.ac.jp/expansion/get_media_list_json.php?lang_cd=${lang}`);
        const tagResponse = await ofetch(`https://www.isct.ac.jp/expansion/get_tag_list_json.php?lang_cd=${lang}`);

        const mediaData = JSON.parse(decode(mediaResponse));
        const tagData = JSON.parse(decode(tagResponse));

        const itemsArray: MediaItem[] = Object.values(mediaData);
        const tagArray: TagItem[] = Object.values(tagData);

        const tagIdNameMapping: { [key: string]: string } = {};

        for (const item of Object.values(tagArray)) {
            tagIdNameMapping[item.TAG_ID] = item.TAG_NAME;
        }

        const items = itemsArray.map((item) => ({
            // タイトル
            title: item.TITLE,
            // リンク
            link: 'news/' + item.MEDIA_CD,
            // 本文
            description: item.META_DESCRIPTION,
            // リリース時間
            pubDate: parseDate(item.PUBLISH_DATE),
            // (オプション)作者
            // author: item.user.login,
            // (オプション)分類
            category: item.MEDIA_TYPES ? [tagIdNameMapping[Number.parseInt(item.MEDIA_TYPES.replaceAll('"', ''), 10)]] : [],
        }));
        return {
            // ウェブサイトのタイトル
            title: `ISCT News - ${lang}`,
            // ウェブサイトのリンク
            link: `https://www.isct.ac.jp/${lang}/news`,
            // 先に作成した内容
            item: items,
        };
    },

最後に作成できたファイル

import { Route } from '@/types';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import { decode } from 'entities';

interface MediaItem {
    ID: string;
    TITLE: string;
    PUBLISH_DATE: string;
    META_DESCRIPTION: string;
    MEDIA_CD: string;
    MEDIA_TYPES: string;
}

interface TagItem {
    TAG_ID: string;
    TAG_NAME: string;
}

export const route: Route = {
    path: '/news/:lang',
    categories: ['university'],
    example: '/isct/news/ja',
    parameters: { lang: 'language, could be ja or en' },
    features: {
        requireConfig: false,
        requirePuppeteer: false,
        antiCrawler: false,
        supportBT: false,
        supportPodcast: false,
        supportScihub: false,
    },
    radar: [
        {
            source: ['www.isct.ac.jp/:lang/news'],
            target: '/news/:lang',
        },
    ],
    name: 'News',
    maintainers: ['catyyy'],
    handler: async (ctx) => {
        const { lang = 'ja' } = ctx.req.param();
        const mediaResponse = await ofetch(`https://www.isct.ac.jp/expansion/get_media_list_json.php?lang_cd=${lang}`);
        const tagResponse = await ofetch(`https://www.isct.ac.jp/expansion/get_tag_list_json.php?lang_cd=${lang}`);

        const mediaData = JSON.parse(decode(mediaResponse));
        const tagData = JSON.parse(decode(tagResponse));

        const itemsArray: MediaItem[] = Object.values(mediaData);
        const tagArray: TagItem[] = Object.values(tagData);

        const tagIdNameMapping: { [key: string]: string } = {};

        for (const item of Object.values(tagArray)) {
            tagIdNameMapping[item.TAG_ID] = item.TAG_NAME;
        }

        const items = itemsArray.map((item) => ({
            // タイトル
            title: item.TITLE,
            // リンク
            link: 'news/' + item.MEDIA_CD,
            // 本文
            description: item.META_DESCRIPTION,
            // リリース時間
            pubDate: parseDate(item.PUBLISH_DATE),
            // (オプション)作者
            // author: item.user.login,
            // (オプション)分類
            category: item.MEDIA_TYPES ? [tagIdNameMapping[Number.parseInt(item.MEDIA_TYPES.replaceAll('"', ''), 10)]] : [],
        }));
        return {
            // ウェブサイトのタイトル
            title: `ISCT News - ${lang}`,
            // ウェブサイトのリンク
            link: `https://www.isct.ac.jp/${lang}/news`,
            // 先に作成した内容
            item: items,
        };
    },
};

提出

提出する前に、必ずScript Standardに従ってください。
テストが完了した後は、pull requestを作成し、要求に従ってフォームに必要事項を記入すれば、通常は1日以内にmergeされるはずである。

ここで終わり、人生初めてOSSに貢献できて嬉しい。
もし興味があれば、ぜひ試してみてください。
また、オフィシャルハンドブックには日本語がないようだので、もし何か質問があれば気軽に聞いてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?