From 6b302364e82b9013ffcee6bc17bc6d489ebbbc36 Mon Sep 17 00:00:00 2001 From: Goestav <27970303+goestav@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:30:19 +0000 Subject: [PATCH] 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 --- lib/routes/kurogames/namespace.ts | 2 +- .../kurogames/wutheringwaves/constants.ts | 34 +++++++ lib/routes/kurogames/wutheringwaves/news.ts | 93 ++++++++++++------- lib/routes/kurogames/wutheringwaves/utils.ts | 79 ++++++++++++++++ 4 files changed, 174 insertions(+), 34 deletions(-) create mode 100644 lib/routes/kurogames/wutheringwaves/constants.ts create mode 100644 lib/routes/kurogames/wutheringwaves/utils.ts diff --git a/lib/routes/kurogames/namespace.ts b/lib/routes/kurogames/namespace.ts index e51551a1d..ec104a46c 100644 --- a/lib/routes/kurogames/namespace.ts +++ b/lib/routes/kurogames/namespace.ts @@ -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', diff --git a/lib/routes/kurogames/wutheringwaves/constants.ts b/lib/routes/kurogames/wutheringwaves/constants.ts new file mode 100644 index 000000000..9d0ae687c --- /dev/null +++ b/lib/routes/kurogames/wutheringwaves/constants.ts @@ -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; +} diff --git a/lib/routes/kurogames/wutheringwaves/news.ts b/lib/routes/kurogames/wutheringwaves/news.ts index 95e161378..fff5216d6 100644 --- a/lib/routes/kurogames/wutheringwaves/news.ts +++ b/lib/routes/kurogames/wutheringwaves/news.ts @@ -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('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(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
(contentUrl, { query: { t: Date.now() } }); + const $ = cheerio.load(articleContent); + + item.description = $.html() ?? article.articleDesc ?? ''; - item.description = $.html() ?? i.articleDesc ?? ''; return item; }) as Promise; }) ); + + 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), }; }, }; diff --git a/lib/routes/kurogames/wutheringwaves/utils.ts b/lib/routes/kurogames/wutheringwaves/utils.ts new file mode 100644 index 000000000..2138b6bcb --- /dev/null +++ b/lib/routes/kurogames/wutheringwaves/utils.ts @@ -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 => { + if (language === Language.Chinese) { + return ofetch('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 => { + 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}"`); + } +};