mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-24 23:12:34 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
### 用户页面
|
||||
|
||||
@@ -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'],
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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'));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const webApiImpl = require('./web-api/tweet');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
await webApiImpl(ctx);
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user