From 67d4a7ed3f877a8ceac6caebe874c4ce5c210bd8 Mon Sep 17 00:00:00 2001
From: Ethan Shen <42264778+nczitzk@users.noreply.github.com>
Date: Wed, 4 Oct 2023 21:06:52 +0800
Subject: [PATCH] feat(route): add Metacritic (#13447)
* feat(route): add Metacritic
* fix typo
* Update lib/v2/metacritic/index.js
---------
---
lib/router.js | 2 +-
lib/v2/metacritic/index.js | 121 ++++++++++++++++++++
lib/v2/metacritic/maintainer.js | 5 +
lib/v2/metacritic/radar.js | 43 +++++++
lib/{routes => v2}/metacritic/release.js | 0
lib/v2/metacritic/router.js | 3 +
lib/v2/metacritic/templates/description.art | 19 +++
lib/v2/metacritic/util.js | 42 +++++++
website/docs/routes/game.md | 26 -----
website/docs/routes/new-media.md | 56 +++++++++
10 files changed, 290 insertions(+), 27 deletions(-)
create mode 100644 lib/v2/metacritic/index.js
create mode 100644 lib/v2/metacritic/maintainer.js
create mode 100644 lib/v2/metacritic/radar.js
rename lib/{routes => v2}/metacritic/release.js (100%)
create mode 100644 lib/v2/metacritic/router.js
create mode 100644 lib/v2/metacritic/templates/description.art
create mode 100644 lib/v2/metacritic/util.js
diff --git a/lib/router.js b/lib/router.js
index 3abc28d7c..f44c806c1 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -992,7 +992,7 @@ router.get('/polimi/news/:language?', lazyloadRouteHandler('./routes/polimi/news
router.get('/dekudeals/:type', lazyloadRouteHandler('./routes/dekudeals'));
// Metacritic
-router.get('/metacritic/release/:platform/:type/:sort?', lazyloadRouteHandler('./routes/metacritic/release'));
+// router.get('/metacritic/release/:platform/:type/:sort?', lazyloadRouteHandler('./routes/metacritic/release'));
// 快科技(原驱动之家)
// router.get('/kkj/news', lazyloadRouteHandler('./routes/kkj/news'));
diff --git a/lib/v2/metacritic/index.js b/lib/v2/metacritic/index.js
new file mode 100644
index 000000000..519fb6729
--- /dev/null
+++ b/lib/v2/metacritic/index.js
@@ -0,0 +1,121 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const { parseDate } = require('@/utils/parse-date');
+const { art } = require('@/utils/render');
+const path = require('path');
+
+const { sorts, types } = require('./util');
+
+module.exports = async (ctx) => {
+ const { type = 'game', sort = 'new', filter } = ctx.params;
+ const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : 50;
+
+ const rootUrl = 'https://www.metacritic.com';
+ const rootApiUrl = 'https://fandom-prod.apigee.net';
+ const apiUrl = new URL('v1/xapi/finder/metacritic/web', rootApiUrl).href;
+
+ const currentUrlObject = new URL(`/browse/${type}/all/all/all-time/${sort}/${filter ? `?${filter}` : ''}`, rootUrl);
+ const currentUrlParams = currentUrlObject.searchParams;
+ const currentUrl = currentUrlObject.href;
+
+ const { data: currentResponse } = await got(currentUrl);
+
+ const apiKey = currentResponse.match(/apiKey=(.*?)&/)[1];
+
+ const searchParams = {
+ sortBy: `-${sorts[sort].id}`,
+ productType: types[type].id,
+ limit,
+ apiKey,
+ };
+
+ const genres = currentUrlParams.getAll('genre').join(',').toLowerCase();
+ const releaseTypes = currentUrlParams.getAll('releaseType').join(',');
+
+ if (genres) {
+ searchParams.genres = genres;
+ }
+
+ if (releaseTypes) {
+ searchParams.releaseType = releaseTypes;
+ }
+
+ const platforms = currentUrlParams.getAll('platform');
+ const networks = currentUrlParams.getAll('network');
+
+ if (platforms.length || networks.length) {
+ const labels = {};
+ const labelPattern = '{label:"([^"]+)",value:(\\d+),href:a,meta:{mcDisplayWeight';
+
+ for (const m of currentResponse.match(new RegExp(labelPattern, 'g'))) {
+ const matches = m.match(new RegExp(labelPattern));
+
+ labels[
+ matches[1]
+ .toLowerCase()
+ .split(/(\s\(|\\u002f(?!\s))/)[0]
+ .replace(/-/g, '---')
+ .replace(/\s\/\s/g, '-or-')
+ .replace(/\+/g, '-plus')
+ .replace(/\s/g, '-')
+ ] = matches[2];
+ }
+
+ if (platforms.length) {
+ searchParams.gamePlatformIds = platforms
+ .map((p) => (labels.hasOwnProperty(p) ? labels[p] : undefined))
+ .filter((p) => p)
+ .join(',');
+ }
+
+ if (networks.length) {
+ searchParams.streamingNetworkIds = networks
+ .map((p) => (labels.hasOwnProperty(p) ? labels[p] : undefined))
+ .filter((p) => p)
+ .join(',');
+ }
+ }
+
+ const { data: response } = await got(apiUrl, {
+ searchParams,
+ });
+
+ const items = response.data.items.slice(0, limit).map((item) => ({
+ title: item.title,
+ link: new URL(`${type}/${item.slug}`, rootUrl).href,
+ description: art(path.join(__dirname, 'templates/description.art'), {
+ image: item.image
+ ? {
+ src: new URL(`a/img/catalog${item.image.bucketPath}`, rootUrl).href,
+ alt: item.image.alt,
+ }
+ : undefined,
+ description: item.description,
+ score: item.criticScoreSummary?.score ?? undefined,
+ }),
+ category: item.genres.map((c) => c.name),
+ guid: `metacritic-${item.id}`,
+ pubDate: parseDate(item.releaseDate),
+ upvotes: item.criticScoreSummary?.positiveCount ? parseInt(item.criticScoreSummary?.positiveCount, 10) : 0,
+ downvotes: item.criticScoreSummary?.negativeCount ? parseInt(item.criticScoreSummary?.negativeCount, 10) : 0,
+ comments: item.criticScoreSummary?.reviewCount ? parseInt(item.criticScoreSummary?.reviewCount, 10) : 0,
+ }));
+
+ const $ = cheerio.load(currentResponse);
+
+ const icon = new URL($('meta[data-hid="msapplication-task-metacritic"]').prop('content').split('icon-uri=').pop(), rootUrl).href;
+
+ ctx.state.data = {
+ item: items,
+ title: $('title').text(),
+ link: currentUrl,
+ description: $('meta[name="description"]').prop('content'),
+ language: $('html').prop('lang'),
+ image: $('link[rel="icon"]').prop('content'),
+ icon,
+ logo: icon,
+ subtitle: $('meta[name="msapplication-tooltip"]').prop('content'),
+ author: $('meta[name="twitter:site"]').prop('content'),
+ allowEmpty: true,
+ };
+};
diff --git a/lib/v2/metacritic/maintainer.js b/lib/v2/metacritic/maintainer.js
new file mode 100644
index 000000000..800f9838c
--- /dev/null
+++ b/lib/v2/metacritic/maintainer.js
@@ -0,0 +1,5 @@
+module.exports = {
+ '/game/:sort?/:filter?': ['HenryQW', 'nczitzk'],
+ '/movie/:sort?/:filter?': ['nczitzk'],
+ '/tv/:sort?/:filter?': ['nczitzk'],
+};
diff --git a/lib/v2/metacritic/radar.js b/lib/v2/metacritic/radar.js
new file mode 100644
index 000000000..1f26d8901
--- /dev/null
+++ b/lib/v2/metacritic/radar.js
@@ -0,0 +1,43 @@
+module.exports = {
+ 'metacritic.com': {
+ _name: 'Metacritic',
+ '.': [
+ {
+ title: 'Games',
+ docs: 'https://docs.rsshub.app/routes/new-media#metacritic-games',
+ source: ['/browse/game/:params*'],
+ target: (params, url) => {
+ url = new URL(url);
+ const sort = params.params.split(/\//).pop();
+ const filter = url.searchParams.toString();
+
+ return `/metacritic/game${sort ? `/${sort}${filter ? `/${filter}` : ''}` : ''}`;
+ },
+ },
+ {
+ title: 'Movies',
+ docs: 'https://docs.rsshub.app/routes/new-media#metacritic-movies',
+ source: ['/browse/movie/:params*'],
+ target: (params, url) => {
+ url = new URL(url);
+ const sort = params.params.split(/\//).pop();
+ const filter = url.searchParams.toString();
+
+ return `/metacritic/movie${sort ? `/${sort}${filter ? `/${filter}` : ''}` : ''}`;
+ },
+ },
+ {
+ title: 'TV Shows',
+ docs: 'https://docs.rsshub.app/routes/new-media#metacritic-tv-shows',
+ source: ['/browse/tv/:params*'],
+ target: (params, url) => {
+ url = new URL(url);
+ const sort = params.params.split(/\//).pop();
+ const filter = url.searchParams.toString();
+
+ return `/metacritic/tv${sort ? `/${sort}${filter ? `/${filter}` : ''}` : ''}`;
+ },
+ },
+ ],
+ },
+};
diff --git a/lib/routes/metacritic/release.js b/lib/v2/metacritic/release.js
similarity index 100%
rename from lib/routes/metacritic/release.js
rename to lib/v2/metacritic/release.js
diff --git a/lib/v2/metacritic/router.js b/lib/v2/metacritic/router.js
new file mode 100644
index 000000000..3990e3947
--- /dev/null
+++ b/lib/v2/metacritic/router.js
@@ -0,0 +1,3 @@
+module.exports = (router) => {
+ router.get('/:type?/:sort?/:filter?', require('./'));
+};
diff --git a/lib/v2/metacritic/templates/description.art b/lib/v2/metacritic/templates/description.art
new file mode 100644
index 000000000..1ee8bd077
--- /dev/null
+++ b/lib/v2/metacritic/templates/description.art
@@ -0,0 +1,19 @@
+{{ if image }}
+
+
{{ description }}
+{{ /if }} + +{{ if score }} + Metascore: + {{ score }} +{{ /if }} \ No newline at end of file diff --git a/lib/v2/metacritic/util.js b/lib/v2/metacritic/util.js new file mode 100644 index 000000000..4ed8130a1 --- /dev/null +++ b/lib/v2/metacritic/util.js @@ -0,0 +1,42 @@ +const sorts = { + metascore: { + id: 'metaScore', + name: 'Metascore', + }, + userscore: { + id: 'userScore', + name: 'User Score', + }, + popular: { + id: 'popularityCount', + name: 'Most Popular', + }, + new: { + id: 'releaseDate', + name: 'Releases', + }, +}; + +const types = { + game: { + id: 'games', + name: 'Games', + }, + movie: { + id: 'movies', + name: 'Movies', + }, + tv: { + id: 'tv', + name: 'TV Shows', + }, + albums: { + id: 'albums', + name: 'Music', + }, +}; + +module.exports = { + sorts, + types, +}; diff --git a/website/docs/routes/game.md b/website/docs/routes/game.md index 6df19a1bf..b1fcff9d9 100644 --- a/website/docs/routes/game.md +++ b/website/docs/routes/game.md @@ -332,32 +332,6 @@ So the route is [`/itch/devlogs/teamterrible/the-baby-in-yellow`](https://rsshub