mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-08-30 16:55:08 +08:00
feat(route): support different languages for the kurogames (wuthering waves) route (#18781)
* feat: add wuthering waves route * refactor: use arrow functions for better readability * refactor: remove redundant nullish fallback * refactor: use Promise.all(...) improve performance * refactor: reduce complexity of limit parameter logic * refactor: extract parse integer parameter to its own function * refactor: add kuro games to namespace name * refactor: make kurogames route for wuthering waves more flexible This commit adds support for different languages. * refactor: remove redundant wuthering waves route * refactor: add language parameter description * refactor: fallback to 30 feed items instead of unlimited * refactor: rename language in description table * refactor: filter articles beforehand and favour map over flatMap * refactor: remove redundant code for article filtering
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: '库洛游戏',
|
||||
name: '库洛游戏 | Kuro Games',
|
||||
url: 'www.kurogames.com',
|
||||
categories: ['game'],
|
||||
lang: 'zh-CN',
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/** The language. */
|
||||
export enum Language {
|
||||
English = 'en',
|
||||
Japanese = 'jp',
|
||||
Korean = 'kr',
|
||||
/** Legacy code to ensure old results don't change. */
|
||||
Chinese = 'zh',
|
||||
ChineseTaiwan = 'zh-tw',
|
||||
Spanish = 'es',
|
||||
French = 'fr',
|
||||
German = 'de',
|
||||
}
|
||||
|
||||
/** Route parameters. */
|
||||
export enum Parameter {
|
||||
Limit = 'limit',
|
||||
Language = 'language',
|
||||
}
|
||||
|
||||
/** The languages supported by the API. */
|
||||
export const SUPPORTED_LANGUAGES = Object.values(Language);
|
||||
|
||||
export interface Article {
|
||||
articleContent: string;
|
||||
articleDesc: string;
|
||||
articleId: number;
|
||||
articleTitle: string;
|
||||
articleType: number;
|
||||
createTime: string;
|
||||
sortingMark: number;
|
||||
startTime: string;
|
||||
suggestCover: string;
|
||||
top: number;
|
||||
}
|
||||
@@ -4,56 +4,83 @@ import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
import * as cheerio from 'cheerio';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
|
||||
interface NewsItem {
|
||||
articleContent: string;
|
||||
articleDesc: string;
|
||||
articleId: number;
|
||||
articleTitle: string;
|
||||
articleType: number;
|
||||
createTime: string;
|
||||
sortingMark: number;
|
||||
startTime: string;
|
||||
suggestCover: string;
|
||||
top: number;
|
||||
}
|
||||
import { Article, Language, Parameter, SUPPORTED_LANGUAGES } from './constants';
|
||||
import { fetchArticles, getArticleContentLink, getArticleLink, getHandlerLanguage, isValidLanguage, parseInteger } from './utils';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/wutheringwaves/news',
|
||||
path: `/wutheringwaves/news/:${Parameter.Language}?`,
|
||||
categories: ['game'],
|
||||
example: '/kurogames/wutheringwaves/news',
|
||||
parameters: {
|
||||
[Parameter.Language]: 'The language to use for the content. Default: `zh`.',
|
||||
},
|
||||
name: '鸣潮 — 游戏公告、新闻与活动',
|
||||
radar: [
|
||||
{
|
||||
source: ['mc.kurogames.com/m/main/news', 'mc.kurogames.com/main'],
|
||||
},
|
||||
{
|
||||
title: 'Wuthering Waves — Game announcements, news and events',
|
||||
source: ['wutheringwaves.kurogames.com/en/main/news', 'wutheringwaves.kurogames.com/en/main'],
|
||||
},
|
||||
],
|
||||
maintainers: ['enpitsulin'],
|
||||
description: '',
|
||||
async handler() {
|
||||
const res = await ofetch<NewsItem[]>('https://media-cdn-mingchao.kurogame.com/akiwebsite/website2.0/json/G152/zh/ArticleMenu.json', { query: { t: Date.now() } });
|
||||
const item = await Promise.all(
|
||||
res.map((i) => {
|
||||
const contentUrl = `https://media-cdn-mingchao.kurogame.com/akiwebsite/website2.0/json/G152/zh/article/${i.articleId}.json`;
|
||||
const item = {
|
||||
title: i.articleTitle,
|
||||
pubDate: timezone(parseDate(i.createTime), +8),
|
||||
link: `https://mc.kurogames.com/main/news/detail/${i.articleId}`,
|
||||
} as DataItem;
|
||||
return cache.tryGet(contentUrl, async () => {
|
||||
const data = await ofetch<NewsItem>(contentUrl, { query: { t: Date.now() } });
|
||||
const $ = cheerio.load(data.articleContent);
|
||||
maintainers: ['goestav', 'enpitsulin'],
|
||||
description: `
|
||||
Language codes for the \`${Parameter.Language}\` parameter:
|
||||
|
||||
| Language | Code |
|
||||
|----------|--------------|
|
||||
| English | en |
|
||||
| 日本語 | jp |
|
||||
| 한국어 | kr |
|
||||
| 简体中文 | zh (default) |
|
||||
| 繁體中文 | zh-tw |
|
||||
| Español | es |
|
||||
| Français | fr |
|
||||
| Deutsch | de |
|
||||
`,
|
||||
async handler(ctx) {
|
||||
const limitParam = ctx.req.query(Parameter.Limit);
|
||||
const languageParam = ctx.req.param(Parameter.Language);
|
||||
|
||||
const limit = parseInteger(limitParam, 30);
|
||||
const language = languageParam || Language.Chinese;
|
||||
|
||||
if (!isValidLanguage(language)) {
|
||||
throw new TypeError(`Language parameter is not valid. Please use one of the following: ${SUPPORTED_LANGUAGES.join(', ')}`);
|
||||
}
|
||||
|
||||
const articles = await fetchArticles(language);
|
||||
const filteredArticles = articles.filter((a) => a.articleType !== 0).slice(0, limit);
|
||||
|
||||
const item = await Promise.all(
|
||||
filteredArticles.map((article) => {
|
||||
const contentUrl = getArticleContentLink(language, article.articleId);
|
||||
const item: DataItem = {
|
||||
title: article.articleTitle,
|
||||
pubDate: timezone(parseDate(article.createTime), +8),
|
||||
link: getArticleLink(language, article.articleId),
|
||||
};
|
||||
|
||||
return cache.tryGet(`wutheringwaves:${language}:${article.articleId}`, async () => {
|
||||
const { articleContent } = await ofetch<Article>(contentUrl, { query: { t: Date.now() } });
|
||||
const $ = cheerio.load(articleContent);
|
||||
|
||||
item.description = $.html() ?? article.articleDesc ?? '';
|
||||
|
||||
item.description = $.html() ?? i.articleDesc ?? '';
|
||||
return item;
|
||||
}) as Promise<DataItem>;
|
||||
})
|
||||
);
|
||||
|
||||
const title = language === Language.Chinese ? '《鸣潮》— 游戏公告、新闻和活动' : 'Wuthering Waves - Announcements, News and Events';
|
||||
const link = language === Language.Chinese ? 'https://mc.kurogames.com/main#news' : `https://wutheringwaves.kurogames.com/${language}/main/#news`;
|
||||
|
||||
return {
|
||||
title: '《鸣潮》— 游戏公告、新闻和活动',
|
||||
link: 'https://mc.kurogames.com/main#news',
|
||||
title,
|
||||
link,
|
||||
item,
|
||||
language: 'zh-cn',
|
||||
language: getHandlerLanguage(language),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { Data } from '@/types';
|
||||
import { Article, Language, SUPPORTED_LANGUAGES } from './constants';
|
||||
|
||||
/**
|
||||
* Parse a number or a number as string.\
|
||||
* **NOTE:** this may return `NaN` if the string is not a number or the value is `undefined` and no {@link fallback} is provided.
|
||||
*/
|
||||
export const parseInteger = (value?: string | number, fallback?: number): number => {
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
return fallback === undefined ? Number.NaN : fallback;
|
||||
}
|
||||
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
|
||||
if (fallback !== undefined && Number.isNaN(parsed)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
};
|
||||
|
||||
/** Type-guard to ensure {@link language} is a valid value of {@link SUPPORTED_LANGUAGES}. */
|
||||
export const isValidLanguage = (language: string): language is Language => SUPPORTED_LANGUAGES.includes(language as Language);
|
||||
|
||||
/** Fetch the articles for a given language in a given category. */
|
||||
export const fetchArticles = (language: Language): Promise<Article[]> => {
|
||||
if (language === Language.Chinese) {
|
||||
return ofetch<Article[]>('https://media-cdn-mingchao.kurogame.com/akiwebsite/website2.0/json/G152/zh/ArticleMenu.json', { query: { t: Date.now() } });
|
||||
}
|
||||
|
||||
return ofetch<{ article: Article[] }>(`https://hw-media-cdn-mingchao.kurogame.com/akiwebsite/website2.0/json/G152/${language}/MainMenu.json`).then((data) => data.article);
|
||||
};
|
||||
|
||||
/** Get the link to the article content. */
|
||||
export const getArticleContentLink = (language: Language, articleId: number): string => {
|
||||
if (language === Language.Chinese) {
|
||||
return `https://media-cdn-mingchao.kurogame.com/akiwebsite/website2.0/json/G152/zh/article/${articleId}.json`;
|
||||
}
|
||||
|
||||
return `https://hw-media-cdn-mingchao.kurogame.com/akiwebsite/website2.0/json/G152/${language}/article/${articleId}.json`;
|
||||
};
|
||||
|
||||
/** Get the link to an article from its ID. */
|
||||
export const getArticleLink = (language: Language, articleId: number): string => {
|
||||
if (language === Language.Chinese) {
|
||||
return `https://mc.kurogames.com/main/news/detail/${articleId}`;
|
||||
}
|
||||
|
||||
return `https://wutheringwaves.kurogames.com/${language}/main/news/detail/${articleId}`;
|
||||
};
|
||||
|
||||
/** Resolve the handler language from the {@link Language}. */
|
||||
export const getHandlerLanguage = (language: Language): Exclude<Data['language'], undefined> => {
|
||||
switch (language) {
|
||||
case Language.English:
|
||||
return 'en';
|
||||
case Language.Chinese:
|
||||
return 'zh-CN';
|
||||
case Language.ChineseTaiwan:
|
||||
return 'zh-TW';
|
||||
case Language.French:
|
||||
return 'fr';
|
||||
case Language.German:
|
||||
return 'de';
|
||||
case Language.Japanese:
|
||||
return 'ja';
|
||||
case Language.Korean:
|
||||
return 'ko';
|
||||
case Language.Spanish:
|
||||
return 'es';
|
||||
default:
|
||||
throw new Error(`Could not resolve handler language from "${language}"`);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user