diff --git a/docs/en/game.md b/docs/en/game.md
index 3acc0d156..66a24c264 100755
--- a/docs/en/game.md
+++ b/docs/en/game.md
@@ -281,6 +281,52 @@ Steam provides some official RSS feeds:
+## TapTap International
+
+::: warning Warning
+
+Due to the regional restrictions, an RSSHub deployment in China Mainland may not work on accessing the TapTap International Website.
+
+:::
+
+### Game Topics
+
+::: tip Tips
+
+Unlike TapTap China Mainland Website, the International Website has no BBS.
+
+:::
+
+### Game's Changelog
+
+
+
+#### Language Code
+
+| English (US) | 繁體中文 | 한국어 | 日本語 |
+| ----- | ----- | ----- | ----- |
+| en_US | zh_TW | ko_KR | ja_JP |
+
+
+
+### Ratings & Reviews
+
+
+
+#### Sort Method
+
+| Most Relevant | Most Recent |
+| -------------- | ---- |
+| default | new |
+
+#### Language Code
+
+| English (US) | 繁體中文 | 한국어 | 日本語 |
+| ----- | ----- | ----- | ----- |
+| en_US | zh_TW | ko_KR | ja_JP |
+
+
+
## War Thunder
### News
@@ -290,4 +336,4 @@ Steam provides some official RSS feeds:
News data from https://warthunder.com/en/news/
The year, month and day provided under UTC time zone are the same as the official website, so please ignore the specific time!!!
-
\ No newline at end of file
+
diff --git a/docs/game.md b/docs/game.md
index c345f0de4..f6cf3d97a 100644
--- a/docs/game.md
+++ b/docs/game.md
@@ -461,7 +461,8 @@ Steam 新闻中心支持官方的 RSS 订阅:
::: warning 注意
-由于区域限制,需要在有国内 IP 的机器上自建才能正常获取 RSS
+由于区域限制,需要在有国内 IP 的机器上自建才能正常获取 RSS。\
+而对于《TapTap 国际版》则需要部署在具有海外出口的 IP 上才可正常获取 RSS。
:::
@@ -481,7 +482,19 @@ Steam 新闻中心支持官方的 RSS 订阅:
### 游戏更新
-
+
+
+### 游戏更新 - 国际版
+
+
+
+#### 语言代码
+
+| English (US) | 繁體中文 | 한국어 | 日本語 |
+| ------------ | ----- | ----- | ----- |
+| en_US | zh_TW | ko_KR | ja_JP |
+
+
### 游戏评价
@@ -493,6 +506,24 @@ Steam 新闻中心支持官方的 RSS 订阅:
+### 游戏评价 - 国际版
+
+
+
+#### 排序方式
+
+| 最相关 | 最新 |
+| ------- | --- |
+| default | new |
+
+#### 语言代码
+
+| English (US) | 繁體中文 | 한국어 | 日本語 |
+| ------------ | ----- | ----- | ----- |
+| en_US | zh_TW | ko_KR | ja_JP |
+
+
+
## War Thunder
### 英文 News
diff --git a/lib/v2/taptap/changelog.js b/lib/v2/taptap/changelog.js
index 53ab97cf7..3e9318bf6 100644
--- a/lib/v2/taptap/changelog.js
+++ b/lib/v2/taptap/changelog.js
@@ -1,22 +1,23 @@
const got = require('@/utils/got');
-const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
+const { getRootUrl, appDetail, X_UA } = require('./utils');
module.exports = async (ctx) => {
+ const is_intl = ctx.request.url.indexOf('/intl/') === 0;
const id = ctx.params.id;
+ const lang = ctx.params.lang ?? (is_intl ? 'en_US' : 'zh_CN');
- const url = `https://www.taptap.com/app/${id}`;
+ const url = `${getRootUrl(is_intl)}/app/${id}`;
- const app_response = await got.get(url);
- const $ = cheerio.load(app_response.data);
+ const app_detail = await appDetail(id, lang, is_intl);
- const app_img = $('.header-icon-body > img').attr('src');
- const app_name = $('.breadcrumb > li.active').text();
- const app_description = $('.body-description-paragraph').text();
+ const app_img = app_detail.app.icon.original_url;
+ const app_name = app_detail.app.title;
+ const app_description = `${app_name} by ${app_detail.app.developers.map((item) => item.name).join(' & ')}`;
const response = await got({
method: 'get',
- url: `https://www.taptap.com/ajax/apk/v1/list-by-app?app_id=${id}&from=0&limit=10`,
+ url: `${getRootUrl(is_intl)}/webapiv2/apk/v1/list-by-app?app_id=${id}&from=0&limit=10&${X_UA(lang)}`,
headers: {
Referer: url,
},
@@ -30,9 +31,9 @@ module.exports = async (ctx) => {
link: url,
image: app_img,
item: list.map((item) => ({
- title: item.version_label,
+ title: `${app_name} / ${item.version_label}`,
description: item.whatsnew.text,
- pubDate: parseDate(item.update_date),
+ pubDate: parseDate(item.update_date * 1000),
link: url,
guid: item.version_label,
})),
diff --git a/lib/v2/taptap/maintainer.js b/lib/v2/taptap/maintainer.js
index b2caec287..e5dfc51a2 100644
--- a/lib/v2/taptap/maintainer.js
+++ b/lib/v2/taptap/maintainer.js
@@ -1,5 +1,7 @@
module.exports = {
- '/changelog/:id': ['hoilc'],
+ '/changelog/:id/:lang?': ['hoilc', 'ETiV'],
'/review/:id/:order?/:lang?': ['hoilc', 'TonyRL'],
'/topic/:id/:type?/:sort?/:lang?': ['hoilc', 'TonyRL'],
+ '/intl/changelog/:id/:lang?': ['hoilc', 'ETiV'],
+ '/intl/review/:id/:order?/:lang?': ['hoilc', 'TonyRL', 'ETiV'],
};
diff --git a/lib/v2/taptap/radar.js b/lib/v2/taptap/radar.js
index 869ab5375..075c54daf 100644
--- a/lib/v2/taptap/radar.js
+++ b/lib/v2/taptap/radar.js
@@ -22,4 +22,21 @@ module.exports = {
},
],
},
+ 'taptap.io': {
+ _name: 'TapTap International',
+ '.': [
+ {
+ title: 'Changelog',
+ docs: 'https://docs.rsshub.app/game.html#taptap',
+ source: ['/app/:id'],
+ target: '/taptap/intl/changelog/:id',
+ },
+ {
+ title: 'Ratings & Reviews',
+ docs: 'https://docs.rsshub.app/game.html#taptap',
+ source: ['/app/:id/review', '/app/:id'],
+ target: '/taptap/intl/review/:id',
+ },
+ ],
+ },
};
diff --git a/lib/v2/taptap/review.js b/lib/v2/taptap/review.js
index c3c8c65fb..5a50a8c32 100644
--- a/lib/v2/taptap/review.js
+++ b/lib/v2/taptap/review.js
@@ -1,69 +1,131 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
-const { rootUrl, X_UA } = require('./utils');
+const { getRootUrl, appDetail, X_UA } = require('./utils');
const sortMap = {
default: {
+ en_US: 'Default',
zh_CN: '预设',
zh_TW: '預設',
},
new: {
+ en_US: 'Latest',
zh_CN: '最新',
zh_TW: '最新',
},
hot: {
+ en_US: 'Popular',
zh_CN: '热门',
zh_TW: '熱門',
},
spent: {
+ en_US: 'Play Time',
zh_CN: '游戏时长',
zh_TW: '遊戲時長',
},
};
-module.exports = async (ctx) => {
- const id = ctx.params.id;
- const order = ctx.params.order ?? 'default';
- const lang = ctx.params.lang ?? 'zh_CN';
+const intlSortMap = {
+ default: {
+ en_US: 'Most Relevant',
+ zh_TW: '最相關',
+ ja_JP: '関連性が高い',
+ ko_KR: '관련도순',
+ },
+ new: {
+ en_US: 'Most Recent',
+ zh_TW: '最新',
+ ja_JP: '最も最近',
+ ko_KR: '최근순',
+ },
+};
- let url = `${rootUrl}/webapiv2/review/v2/by-app?app_id=${id}&limit=10`;
- let sortParam;
- if (order === 'new' || order === 'hot' || order === 'spent') {
- sortParam = `sort=${order}`;
- url += `&${sortParam}`;
+const makeSortParam = (isIntl, order) => {
+ if (isIntl) {
+ if (order === 'new') {
+ return `sort=${order}`;
+ }
+ } else {
+ if (order === 'new' || order === 'hot' || order === 'spent') {
+ return `sort=${order}`;
+ }
}
+ return '';
+};
+
+const fetchMainlandItems = async (params) => {
+ const id = params.id;
+ const order = params.order ?? 'default';
+ const lang = params.lang ?? 'zh_CN';
+
+ let url = `${getRootUrl(false)}/webapiv2/review/v2/by-app?app_id=${id}&limit=10`;
+ url += `&${makeSortParam(false, order)}`;
url += `&${X_UA(lang)}`;
const reviews_list_response = await got(url);
- const reviews_list = reviews_list_response.data;
+ const reviews_list = reviews_list_response.data.data.list;
- const app_img = reviews_list.data.list[0].moment.app.icon.url;
- const app_name = reviews_list.data.list[0].moment.app.title;
-
- const items = reviews_list.data.list.map((review) => {
+ return reviews_list.map((review) => {
const author = review.moment.author.user.name;
const score = review.moment.extended_entities.reviews[0].score;
return {
title: `${author} - ${score}星`,
author,
description: review.moment.extended_entities.reviews[0].contents.text,
- link: `${rootUrl}/review/${review.moment.extended_entities.reviews[0].id}`,
+ link: `${getRootUrl(false)}/review/${review.moment.extended_entities.reviews[0].id}`,
pubDate: parseDate(review.moment.extended_entities.reviews[0].created_time * 1000),
};
});
+};
+
+const fetchIntlItems = async (params) => {
+ const id = params.id;
+ const order = params.order ?? 'default';
+ const lang = params.lang ?? 'en_US';
+
+ let url = `${getRootUrl(true)}/webapiv2/feeds/v1/app-ratings?app_id=${id}&limit=10`;
+ url += `&${makeSortParam(true, order)}`;
+ url += `&${X_UA(lang)}`;
+
+ const reviews_list_response = await got(url);
+ const reviews_list = reviews_list_response.data.data.list;
+
+ return reviews_list.map((review) => {
+ const author = review.post.user.name;
+ const score = review.post.list_fields.app_ratings[id].score;
+ return {
+ title: `${author} - ${score}星`,
+ author,
+ description: review.post.list_fields.summary || review.post.list_fields.title,
+ link: `${getRootUrl(true)}/post/${review.post.id_str}`,
+ pubDate: parseDate(review.post.published_time * 1000),
+ };
+ });
+};
+
+module.exports = async (ctx) => {
+ const is_intl = ctx.request.url.indexOf('/intl/') === 0;
+ const id = ctx.params.id;
+ const order = ctx.params.order ?? 'default';
+ const lang = ctx.params.lang ?? (is_intl ? 'en_US' : 'zh_CN');
+
+ const app_detail = await appDetail(id, lang, is_intl);
+ const app_img = app_detail.app.icon.original_url;
+ const app_name = app_detail.app.title;
+
+ const items = is_intl ? await fetchIntlItems(ctx.params) : await fetchMainlandItems(ctx.params);
ctx.state.data = {
- title: `TapTap 评价 ${app_name} - ${sortMap[order][lang]}排序`,
- link: `${rootUrl}/app/${id}/review${sortParam ? `?${sortParam}` : ''}`,
+ title: `TapTap 评价 ${app_name} - ${(is_intl ? intlSortMap : sortMap)[order][lang]}排序`,
+ link: `${getRootUrl(is_intl)}/app/${id}/review?${makeSortParam(is_intl, order)}`,
image: app_img,
item: items,
};
ctx.state.json = {
- title: `TapTap 评价 ${app_name} - ${sortMap[order][lang]}排序`,
- link: `${rootUrl}/app/${id}/review${sortParam ? `?${sortParam}` : ''}`,
+ title: `TapTap 评价 ${app_name} - ${(is_intl ? intlSortMap : sortMap)[order][lang]}排序`,
+ link: `${getRootUrl(is_intl)}/app/${id}/review?${makeSortParam(is_intl, order)}`,
image: app_img,
item: items,
- reviews_list,
};
};
diff --git a/lib/v2/taptap/router.js b/lib/v2/taptap/router.js
index 62e7fcdf4..9d21dcff0 100644
--- a/lib/v2/taptap/router.js
+++ b/lib/v2/taptap/router.js
@@ -1,5 +1,8 @@
module.exports = (router) => {
- router.get('/changelog/:id', require('./changelog'));
+ router.get('/changelog/:id/:lang?', require('./changelog'));
router.get('/review/:id/:order?/:lang?', require('./review'));
router.get('/topic/:id/:type?/:sort?/:lang?', require('./topic'));
+ // TapTap.io, the international website
+ router.get('/intl/changelog/:id/:lang?', require('./changelog'));
+ router.get('/intl/review/:id/:order?/:lang?', require('./review'));
};
diff --git a/lib/v2/taptap/topic.js b/lib/v2/taptap/topic.js
index dc489ffcf..c908bb865 100644
--- a/lib/v2/taptap/topic.js
+++ b/lib/v2/taptap/topic.js
@@ -1,6 +1,6 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
-const { rootUrl, X_UA, appDetail, imagePost, topicPost, videoPost } = require('./utils');
+const { getRootUrl, X_UA, appDetail, imagePost, topicPost, videoPost } = require('./utils');
const typeMap = {
feed: {
@@ -30,7 +30,7 @@ module.exports = async (ctx) => {
const groupId = appData.group.id;
const app_img = appData.app.icon.original_url || appData.app.icon.url;
const app_name = appData.app.title;
- const url = `${rootUrl}/webapiv2/feed/v6/by-group?group_id=${groupId}&type=${type}&sort=${sort}&${X_UA(lang)}`;
+ const url = `${getRootUrl(false)}/webapiv2/feed/v6/by-group?group_id=${groupId}&type=${type}&sort=${sort}&${X_UA(lang)}`;
const topics_list_response = await got(url);
const topics_list = topics_list_response.data;
@@ -68,14 +68,14 @@ module.exports = async (ctx) => {
ctx.state.data = {
title: `${app_name} - ${typeMap[type][lang]} - TapTap 论坛`,
- link: `${rootUrl}/app/${appId}/topic?type=${type}&sort=${sort}`,
+ link: `${getRootUrl(false)}/app/${appId}/topic?type=${type}&sort=${sort}`,
image: app_img,
item: out.filter((item) => item !== ''),
};
ctx.state.json = {
title: `${app_name} - ${typeMap[type][lang]} - TapTap 论坛`,
- link: `${rootUrl}/app/${appId}/topic?type=${type}&sort=${sort}`,
+ link: `${getRootUrl(false)}/app/${appId}/topic?type=${type}&sort=${sort}`,
image: app_img,
item: out.filter((item) => item !== ''),
appId,
diff --git a/lib/v2/taptap/utils.js b/lib/v2/taptap/utils.js
index 20a33666e..fcdc02f44 100644
--- a/lib/v2/taptap/utils.js
+++ b/lib/v2/taptap/utils.js
@@ -3,14 +3,15 @@ const cheerio = require('cheerio');
const path = require('path');
const { art } = require('@/utils/render');
-const rootUrl = 'https://www.taptap.com';
// Please do not change %26 to &
const X_UA = (lang = 'zh_CN') => `X-UA=V=1%26PN=WebApp%26VN=0.1.0%26LANG=${lang}%26PLT=PC`;
-const appDetail = async (appId, lang = 'zh_CN') => {
- const { data } = await got(`${rootUrl}/webapiv2/group/v1/detail?app_id=${appId}&${X_UA(lang)}`, {
+const getRootUrl = (isIntl = false) => (isIntl ? 'https://www.taptap.io' : 'https://www.taptap.com');
+
+const appDetail = async (appId, lang = 'zh_CN', isIntl = false) => {
+ const { data } = await got(`${getRootUrl(isIntl)}/webapiv2/group/v1/detail?app_id=${appId}&${X_UA(lang)}`, {
headers: {
- Referer: `${rootUrl}/app/${appId}`,
+ Referer: `${getRootUrl(isIntl)}/app/${appId}`,
},
});
return data.data;
@@ -22,9 +23,9 @@ const imagePost = (images) =>
});
const topicPost = async (appId, topicId, lang = 'zh_CN') => {
- const res = await got(`${rootUrl}/webapiv2/topic/v1/detail?id=${topicId}&${X_UA(lang)}`, {
+ const res = await got(`${getRootUrl(false)}/webapiv2/topic/v1/detail?id=${topicId}&${X_UA(lang)}`, {
headers: {
- Referer: `${rootUrl}/app/${appId}`,
+ Referer: `${getRootUrl(false)}/app/${appId}`,
},
});
const $ = cheerio.load(res.data.data.first_post.contents.text, null, false);
@@ -44,7 +45,7 @@ const videoPost = (video) =>
});
module.exports = {
- rootUrl,
+ getRootUrl,
X_UA,
appDetail,
imagePost,