rsshub
Version:
Make RSS Great Again!
151 lines (133 loc) • 5.14 kB
text/typescript
import { Route } from '@/types';
import got from '@/utils/got';
import getToken from '../_access';
import cache from '@/utils/cache';
import { config } from '@/config';
import { getMangaChapters, getMangaMetaByIds } from '../_feed';
type FollowType = 'reading' | 'plan-to-read' | 'completed' | 'on-hold' | 're-reading' | 'dropped';
type StatusType = 'reading' | 'plan_to_read' | 'completed' | 'on_hold' | 're_reading' | 'dropped';
type LabelType = 'Reading' | 'Plan to Read' | 'Completed' | 'On Hold' | 'Re-reading' | 'Dropped';
const statusMap: Record<FollowType, StatusType> = {
reading: 'reading',
'plan-to-read': 'plan_to_read',
completed: 'completed',
'on-hold': 'on_hold',
're-reading': 're_reading',
dropped: 'dropped',
};
const labelMap: Record<FollowType, LabelType> = {
reading: 'Reading',
'plan-to-read': 'Plan to Read',
completed: 'Completed',
'on-hold': 'On Hold',
're-reading': 'Re-reading',
dropped: 'Dropped',
};
export const route: Route = {
path: '/user/follow/:type?',
name: "Logged User's Followed Mangas Feed",
maintainers: ['chrisis58'],
example: '/mangadex/user/follow/reading',
description: `Fetches the feed of mangas that you follow on MangaDex whick are in the specified status.
CAUTION: With big amount of follows, it may take a long time to load or even fail.
It's recommended to use the \`/mangadex/mdlist/:listId?\` route instead for better performance, though it requires manual configuration.`,
categories: ['anime'],
parameters: {
type: {
description: 'The type of follows to fetch',
default: 'reading',
options: [
{ value: 'reading', label: 'Reading' },
{ value: 'plan-to-read', label: 'Plan to Read' },
{ value: 'completed', label: 'Completed' },
{ value: 'on-hold', label: 'On Hold' },
{ value: 're-reading', label: 'Re-reading' },
{ value: 'dropped', label: 'Dropped' },
],
},
},
radar: [
{
source: ['mangadex.org/titles/follows'],
target: '/user/follow/reading',
},
],
features: {
requireConfig: [
{
name: 'MANGADEX_USERNAME',
description: 'MangaDex Username, required when refresh-token is not set',
optional: true,
},
{
name: 'MANGADEX_PASSWORD',
description: 'MangaDex Password, required when refresh-token is not set',
optional: true,
},
{
name: 'MANGADEX_CLIENT_ID',
description: 'MangaDex Client ID',
optional: false,
},
{
name: 'MANGADEX_CLIENT_SECRET',
description: 'MangaDex Client Secret',
optional: false,
},
{
name: 'MANGADEX_REFRESH_TOKEN',
description: 'MangaDex Refresh Token, required when username and password are not set',
optional: true,
},
],
nsfw: true,
},
handler,
};
async function handler(ctx) {
const userFollowUrl = 'https://api.mangadex.org/manga/status';
const { type } = ctx.req.param();
const followType = (type || 'reading') as FollowType;
const accessToken = await getToken();
const statuses = (await cache.tryGet(
`mangadex:user-follow-${followType}`,
async () => {
const response = await got.get(userFollowUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
'User-Agent': config.trueUA,
},
});
const statuses = response?.data?.statuses;
if (!statuses) {
throw new Error('Failed to retrieve user follows from MangaDex API.');
}
return statuses;
},
config.cache.routeExpire,
false
)) as Record<string, string>;
const mangaIds = filterByValue(statuses, statusMap[followType]);
const mangaMetaMap = await getMangaMetaByIds(mangaIds);
const mangaChapters = await Promise.all(mangaIds.map((id) => getMangaChapters(id, undefined, 10)));
const mangas = mangaChapters.flatMap((chapters, index) => {
const mangaMeta = mangaMetaMap.get(mangaIds[index]);
return chapters.map((chapter) => ({
title: mangaMeta?.title ?? 'Unknown',
link: chapter.link,
pubDate: chapter.pubDate,
description: chapter.title ?? '',
image: mangaMeta?.cover ?? '',
}));
});
return {
title: `User Follows - ${labelMap[followType]} Mangas`,
link: `https://mangadex.org/titles/follows?tab=${followType}`,
description: 'Followed Mangas',
item: mangas,
};
}
const filterByValue = (record: Record<string, string>, value: string): string[] =>
Object.entries(record)
.filter(([, v]) => v === value)
.map(([k]) => k);