diff --git a/docs/en/social-media.md b/docs/en/social-media.md
index a95eafe0c..0cd5ed040 100644
--- a/docs/en/social-media.md
+++ b/docs/en/social-media.md
@@ -494,15 +494,15 @@ generates
### User timeline
-
+
### User media
-
+
### User following timeline
-
+
::: warning
@@ -514,11 +514,11 @@ This route requires Twitter token's corresponding id, therefore it's only availa
### List timeline
-
+
### User likes
-
+
### Keyword
@@ -530,7 +530,7 @@ This route requires Twitter token's corresponding id, therefore it's only availa
### Collection
-
+
::: warning
@@ -542,7 +542,7 @@ This route requires Twitter token's corresponding id, therefore it's only availa
### Tweet Details
-
+
## Vimeo
diff --git a/docs/social-media.md b/docs/social-media.md
index 504134dde..011799ea0 100644
--- a/docs/social-media.md
+++ b/docs/social-media.md
@@ -832,11 +832,11 @@ Instagram Stories 没有可靠的 guid,你的 RSS 阅读器可能将同一条
### 用户时间线
-
+
### 用户媒体时间线
-
+
### 用户关注时间线
@@ -880,7 +880,7 @@ Instagram Stories 没有可靠的 guid,你的 RSS 阅读器可能将同一条
### 推文详情
-
+
## Vimeo
diff --git a/lib/v2/twitter/developer-api/user.js b/lib/v2/twitter/developer-api/user.js
index 4ec54d0cc..b7e0eb613 100644
--- a/lib/v2/twitter/developer-api/user.js
+++ b/lib/v2/twitter/developer-api/user.js
@@ -2,24 +2,32 @@ const utils = require('../utils');
module.exports = async (ctx) => {
const id = ctx.params.id;
-
// For compatibility
const { exclude_replies, include_rts, count } = utils.parseRouteParams(ctx.params.routeParams);
const client = await utils.getAppClient();
- const data = await client.v1.get('statuses/user_timeline.json', {
- screen_name: id,
+ const user_timeline_query = {
tweet_mode: 'extended',
exclude_replies,
include_rts,
count,
- });
+ };
+ let screen_name;
+ if (id.startsWith('+')) {
+ user_timeline_query.user_id = +id.slice(1);
+ } else {
+ user_timeline_query.screen_name = id;
+ screen_name = id;
+ }
+ const data = await client.v1.get('statuses/user_timeline.json', user_timeline_query);
const userInfo = data[0].user;
-
+ if (!screen_name) {
+ screen_name = userInfo.screen_name;
+ }
const profileImageUrl = userInfo.profile_image_url || userInfo.profile_image_url_https;
ctx.state.data = {
title: `Twitter @${userInfo.name}`,
- link: `https://twitter.com/${id}`,
+ link: `https://twitter.com/${screen_name}`,
image: profileImageUrl,
description: userInfo.description,
item: utils.ProcessFeed(ctx, {
diff --git a/lib/v2/twitter/web-api/constants.js b/lib/v2/twitter/web-api/constants.js
index 8f9aa3020..fe18ac2aa 100644
--- a/lib/v2/twitter/web-api/constants.js
+++ b/lib/v2/twitter/web-api/constants.js
@@ -10,6 +10,7 @@ const tokens = [
const graphQLEndpointsPlain = [
'/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName',
+ '/graphql/Lxg1V9AiIzzXEiP2c8dRnw/UserByRestId',
'/graphql/3XDB26fBve-MmjHaWTUZxA/TweetDetail',
'/graphql/QqZBEqganhHwmU9QscmIug/UserTweets',
'/graphql/wxoVeDnl0mP7VLhe6mTOdg/UserTweetsAndReplies',
@@ -21,6 +22,7 @@ const graphQLEndpointsPlain = [
const graphQLMap = Object.fromEntries(graphQLEndpointsPlain.map((endpoint) => [endpoint.split('/')[3], endpoint]));
+// captured from Twitter web
const featuresMap = {
UserByScreenName: JSON.stringify({
hidden_profile_likes_enabled: false,
@@ -32,6 +34,15 @@ const featuresMap = {
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
responsive_web_graphql_timeline_navigation_enabled: true,
}),
+ UserByRestId: JSON.stringify({
+ hidden_profile_likes_enabled: false,
+ responsive_web_graphql_exclude_directive_enabled: true,
+ verified_phone_label_enabled: false,
+ highlights_tweets_tab_ui_enabled: true,
+ creator_subscriptions_tweet_preview_api_enabled: true,
+ responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
+ responsive_web_graphql_timeline_navigation_enabled: true,
+ }),
UserTweets: JSON.stringify({
rweb_lists_timeline_redesign_enabled: true,
responsive_web_graphql_exclude_directive_enabled: true,
diff --git a/lib/v2/twitter/web-api/media.js b/lib/v2/twitter/web-api/media.js
index 7944834d0..53f03b98e 100644
--- a/lib/v2/twitter/web-api/media.js
+++ b/lib/v2/twitter/web-api/media.js
@@ -13,7 +13,7 @@ module.exports = async (ctx) => {
ctx.state.data = {
title: `Twitter @${userInfo.name}`,
- link: `https://twitter.com/${id}/media`,
+ link: `https://twitter.com/${userInfo.screen_name}/media`,
image: profileImageUrl.replace(/_normal.jpg$/, '.jpg'),
description: userInfo.description,
item: utils.ProcessFeed(ctx, {
diff --git a/lib/v2/twitter/web-api/tweet.js b/lib/v2/twitter/web-api/tweet.js
index 76d2c04e3..a53c09dcb 100644
--- a/lib/v2/twitter/web-api/tweet.js
+++ b/lib/v2/twitter/web-api/tweet.js
@@ -17,7 +17,7 @@ module.exports = async (ctx) => {
ctx.state.data = {
title: `Twitter @${userInfo.name}`,
- link: `https://twitter.com/${id}/status/${status}`,
+ link: `https://twitter.com/${userInfo.screen_name}/status/${status}`,
image: profileImageUrl.replace(/_normal.jpg$/, '.jpg'),
description: userInfo.description,
item,
diff --git a/lib/v2/twitter/web-api/twitter-api.js b/lib/v2/twitter/web-api/twitter-api.js
index 64408334b..e3ad4a9a8 100644
--- a/lib/v2/twitter/web-api/twitter-api.js
+++ b/lib/v2/twitter/web-api/twitter-api.js
@@ -231,22 +231,36 @@ const userByScreenName = (screenName) =>
variables: `{"screen_name":"${screenName}","withHighlightedLabel":true}`,
features: featuresMap.UserByScreenName,
});
-const getUserData = (cache, screenName) => cache.tryGet(`twitter-userdata-${screenName}`, () => userByScreenName(screenName));
-const getUserID = async (cache, screenName) => (await getUserData(cache, screenName)).data.user.result.rest_id;
-const getUser = async (cache, screenName) => (await getUserData(cache, screenName)).data.user.result.legacy;
+const userByRestId = (restId) =>
+ twitterGot(`https://twitter.com/i/api${graphQLMap.UserByRestId}`, {
+ variables: `{"userId":"${restId}","withHighlightedLabel":true}`,
+ features: featuresMap.UserByRestId,
+ });
+const userByAuto = (id) => {
+ if (id.startsWith('+')) {
+ return userByRestId(id.slice(1));
+ }
+ return userByScreenName(id);
+};
+const getUserData = (cache, id) => cache.tryGet(`twitter-userdata-${id}`, () => userByAuto(id));
+const getUserID = async (cache, id) => (await getUserData(cache, id)).data.user.result.rest_id;
+const getUser = async (cache, id) => (await getUserData(cache, id)).data.user.result.legacy;
-const cacheTryGet = async (cache, screenName, params, func) => {
- const id = await getUserID(cache, screenName);
+const cacheTryGet = async (cache, _id, params, func) => {
+ const id = await getUserID(cache, _id);
+ if (id === undefined) {
+ throw Error('User not found');
+ }
const funcName = func.name;
const paramsString = JSON.stringify(params);
return cache.tryGet(`twitter:${id}:${funcName}:${paramsString}`, () => func(id, params), config.cache.routeExpire, false);
};
-const getUserTweets = (cache, screenName, params = {}) => cacheTryGet(cache, screenName, params, getUserTweetsByID);
-const getUserTweetsAndReplies = (cache, screenName, params = {}) => cacheTryGet(cache, screenName, params, getUserTweetsAndRepliesByID);
-const getUserMedia = (cache, screenName, params = {}) => cacheTryGet(cache, screenName, params, getUserMediaByID);
-const getUserLikes = (cache, screenName, params = {}) => cacheTryGet(cache, screenName, params, getUserLikesByID);
-const getUserTweet = (cache, screenName, params) => cacheTryGet(cache, screenName, params, getUserTweetByStatus);
+const getUserTweets = (cache, id, params = {}) => cacheTryGet(cache, id, params, getUserTweetsByID);
+const getUserTweetsAndReplies = (cache, id, params = {}) => cacheTryGet(cache, id, params, getUserTweetsAndRepliesByID);
+const getUserMedia = (cache, id, params = {}) => cacheTryGet(cache, id, params, getUserMediaByID);
+const getUserLikes = (cache, id, params = {}) => cacheTryGet(cache, id, params, getUserLikesByID);
+const getUserTweet = (cache, id, params) => cacheTryGet(cache, id, params, getUserTweetByStatus);
const getSearch = async (keywords, params = {}) => gatherLegacyFromLegacyApiData(await timelineKeywords(keywords, params), 'sq-I-t-');
diff --git a/lib/v2/twitter/web-api/user.js b/lib/v2/twitter/web-api/user.js
index 5eb170834..100fcbf9b 100644
--- a/lib/v2/twitter/web-api/user.js
+++ b/lib/v2/twitter/web-api/user.js
@@ -23,7 +23,7 @@ module.exports = async (ctx) => {
ctx.state.data = {
title: `Twitter @${userInfo.name}`,
- link: `https://twitter.com/${id}`,
+ link: `https://twitter.com/${userInfo.screen_name}`,
image: profileImageUrl.replace(/_normal.jpg$/, '.jpg'),
description: userInfo.description,
item: utils.ProcessFeed(ctx, {