rsshub
Version:
Make RSS Great Again!
74 lines (66 loc) • 2.19 kB
text/typescript
import type { Data, DataItem, Route } from '@/types';
import { load } from 'cheerio';
import type { Context } from 'hono';
import ofetch from '@/utils/ofetch';
import cache from '@/utils/cache';
import type { NextDataEpisode } from './types';
import { parseDate } from '@/utils/parse-date';
export const route: Route = {
name: '投稿',
categories: ['reading'],
path: '/works/:id',
example: '/kakuyomu/works/1177354054894027232',
parameters: {
id: '投稿 ID',
},
maintainers: ['KarasuShin'],
handler,
features: {
supportRadar: true,
},
radar: [
{
source: ['kakuyomu.jp/works/:id'],
target: '/works/:id',
},
],
};
async function handler(ctx: Context): Promise<Data> {
const id = ctx.req.param('id');
const url = `https://kakuyomu.jp/works/${id}`;
const limit = Number.parseInt(ctx.req.query('limit') || '10');
const $ = load(await ofetch(url));
const nextData = JSON.parse($('#__NEXT_DATA__').text());
const {
props: {
pageProps: { __APOLLO_STATE__ },
},
} = nextData;
const {
[`Work:${id}`]: { title, catchphrase },
} = __APOLLO_STATE__;
const values = Object.values(__APOLLO_STATE__);
const episodes = values.filter((value) => value.__typename === 'Episode') as NextDataEpisode[];
const items = (await Promise.all(
episodes
.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))
.slice(0, limit)
.map((item) => {
const episodeUrl = `https://kakuyomu.jp/works/${id}/episodes/${item.id}`;
return cache.tryGet(episodeUrl, async () => {
const $ = load(await ofetch(episodeUrl));
const description = $('.widget-episodeBody').html();
return {
title: item.title,
description,
pubDate: parseDate(item.publishedAt),
};
});
})
)) as DataItem[];
return {
title,
description: catchphrase,
item: items,
};
}