feat(route): add tweet details for twitter (#12412)

* feat(route): add tweet details for twitter

* docs: add english docs

* fix: sort router

---------
This commit is contained in:
Alex Liu
2023-05-02 04:31:03 +08:00
committed by GitHub
parent e3f3ce8b22
commit 334485899c
8 changed files with 91 additions and 3 deletions
+5 -1
View File
@@ -500,7 +500,7 @@ This route requires Twitter token's corresponding id, therefore it's only availa
### Collection
<RouteEn author="TonyRL" example="/twitter/collection/DIYgod/1527857429467172864" path="/twitter/collection/:uid/:collectionId/:routeParams?" :paramsDesc="['User name, should match the generated token', 'Collection ID, can be found in URL', 'extra parameters, see the table above']" radar="1" rssbud="1" selfhost="1"/>
<RouteEn author="TonyRL" example="/twitter/collection/DIYgod/1527857429467172864" path="/twitter/collection/:uid/:collectionId/:routeParams?" :paramsDesc="['User name, should match the generated token', 'Collection ID, can be found in URL', 'extra parameters, see the table above']" radar="1" rssbud="1" selfhost="1">
::: warning
@@ -510,6 +510,10 @@ This route requires Twitter token's corresponding id, therefore it's only availa
</RouteEn>
### Tweet Details
<Route author="LarchLiu" example="/twitter/tweet/DIYgod/status/1650844643997646852" path="/twitter/tweet/:id/status/:status/:original?" :paramsDesc="['User name', 'Tweet ID', 'extra parameters, data type of return, if the value is not `0`/`false` and `config.isPackage` is `true`, return the original data of twitter']" radar="1" rssbud="1"/>
## Vimeo
### User Profile
+5
View File
@@ -868,6 +868,11 @@ Instagram Stories 没有可靠的 guid,你的 RSS 阅读器可能将同一条
</Route>
### 推文详情
<Route author="LarchLiu" example="/twitter/tweet/DIYgod/status/1650844643997646852" path="/twitter/tweet/:id/status/:status/:original?" :paramsDesc="['用户名', '推文 ID', '额外参数;返回数据类型,当非 `0`/`false` 且 `config.isPackage` 为 `true`时,返回 twitter 原始数据']" radar="1" rssbud="1"/>
## Vimeo
### 用户页面
+1
View File
@@ -6,5 +6,6 @@ module.exports = {
'/list/:id/:name/:routeParams?': ['xyqfer'],
'/media/:id/:routeParams?': ['yindaheng98', 'Rongronggg9'],
'/trends/:woeid?': ['sakamossan'],
'/tweet/:id/status/:status/:original?': ['LarchLiu'],
'/user/:id/:routeParams?': ['DIYgod', 'yindaheng98', 'Rongronggg9'],
};
+10
View File
@@ -68,6 +68,16 @@ module.exports = {
}
},
},
{
title: '推文详情',
docs: 'https://docs.rsshub.app/social-media.html#twitter',
source: '/:id/status/:status',
target: (params) => {
if (params.id !== 'home' && params.id !== 'explore' && params.id !== 'notifications' && params.id !== 'messages' && params.id !== 'explore' && params.id !== 'search') {
return '/twitter/tweet/:id/suatus/:status';
}
},
},
],
},
};
+1
View File
@@ -6,5 +6,6 @@ module.exports = (router) => {
router.get('/list/:id/:name/:routeParams?', require('./list'));
router.get('/media/:id/:routeParams?', require('./media'));
router.get('/trends/:woeid?', require('./trends'));
router.get('/tweet/:id/status/:status/:original?', require('./tweet'));
router.get('/user/:id/:routeParams?', require('./user'));
};
+5
View File
@@ -0,0 +1,5 @@
const webApiImpl = require('./web-api/tweet');
module.exports = async (ctx) => {
await webApiImpl(ctx);
};
+25
View File
@@ -0,0 +1,25 @@
// const config = require('@/config').value;
const { getUser, getUserTweet } = require('./twitter-api');
const utils = require('../utils');
const { fallback, queryToBoolean } = require('@/utils/readable-social');
const config = require('@/config').value;
module.exports = async (ctx) => {
const id = ctx.params.id;
const status = ctx.params.status;
const routeParams = new URLSearchParams(ctx.params.original);
const original = fallback(undefined, queryToBoolean(routeParams.get('original')), false);
const params = { focalTweetId: status };
const userInfo = await getUser(ctx.cache, id);
const data = await getUserTweet(ctx.cache, id, params);
const profileImageUrl = userInfo.profile_image_url || userInfo.profile_image_url_https;
const item = original && config.isPackage ? data : utils.ProcessFeed(ctx, { data });
ctx.state.data = {
title: `Twitter @${userInfo.name}`,
link: `https://twitter.com/${id}/status/${status}`,
image: profileImageUrl.replace(/_normal.jpg$/, '.jpg'),
description: userInfo.description,
item,
};
};
+39 -2
View File
@@ -111,11 +111,44 @@ const timelineKeywords = (keywords, params = {}) =>
pc: 1,
});
// https://github.com/mikf/gallery-dl/blob/a53cfc845e12d9e98fefd07e43ebffaec488c18f/gallery_dl/extractor/twitter.py#L795-L805
const tweetDetail = (userId, params) =>
paginationTweets('/graphql/ItejhtHVxU7ksltgMmyaLA/TweetDetail', userId, {
...params,
with_rux_injections: false,
withCommunity: true,
withQuickPromoteEligibilityTweetFields: false,
withBirdwatchNotes: false,
}, ["threaded_conversation_with_injections"]);
function gatherLegacyFromData(entries, filter = 'tweet-') {
const tweets = [];
const filte_entries = [];
entries.forEach((entry) => {
if (entry.entryId && entry.entryId.includes(filter)) {
const tweet = entry.content.itemContent.tweet_results?.result;
const entryId = entry.entryId;
if (entryId) {
if (filter === 'none') {
if (entryId.startsWith("tweet-")) {
filte_entries.push(entry);
}
else if (entryId.startsWith("homeConversation-") || entryId.startsWith("conversationthread-")) {
filte_entries.push(...entry.content.items);
}
}
else {
if (entryId.startsWith(filter)) {
filte_entries.push(entry);
}
}
}
});
filte_entries.forEach((entry) => {
if (entry.entryId) {
const content = entry.content || entry.item;
let tweet = content?.itemContent?.tweet_results?.result;
if (tweet && tweet.tweet) {
tweet = tweet.tweet;
};
if (tweet) {
const retweet = tweet.legacy?.retweeted_status_result?.result;
for (const t of [tweet, retweet]) {
@@ -175,6 +208,8 @@ const getUserTweetsByID = async (id, params = {}) => gatherLegacyFromData(await
const getUserTweetsAndRepliesByID = async (id, params = {}) => gatherLegacyFromData(await timelineTweetsAndReplies(id, params));
const getUserMediaByID = async (id, params = {}) => gatherLegacyFromData(await timelineMedia(id, params));
const getUserLikesByID = async (id, params = {}) => gatherLegacyFromData(await timelineLikes(id, params));
const getUserTweetByStatus = async (id, params = {}) => gatherLegacyFromData(await tweetDetail(id, params), 'none');
const excludeRetweet = function (tweets) {
const excluded = [];
@@ -206,6 +241,7 @@ const getUserTweets = (cache, screenName, params = {}) => cacheTryGet(cache, scr
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 getSearch = async (keywords, params = {}) => gatherLegacyFromLegacyApiData(await timelineKeywords(keywords, params), 'sq-I-t-');
@@ -217,4 +253,5 @@ module.exports = {
getUserLikes,
excludeRetweet,
getSearch,
getUserTweet,
};