Skip to content

Commit

Permalink
feat: bring back twitter routes
Browse files Browse the repository at this point in the history
  • Loading branch information
DIYgod committed Nov 29, 2023
1 parent 25d9917 commit 9032153
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 324 deletions.
10 changes: 2 additions & 8 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const TRUE_UA = 'RSSHub/1.0 (+http://github.com/DIYgod/RSSHub; like FeedFetcher-

const calculateValue = () => {
const bilibili_cookies = {};
const twitter_tokens = {};
const email_config = {};
const discuz_cookies = {};
const medium_cookies = {};
Expand All @@ -16,9 +15,6 @@ const calculateValue = () => {
if (name.startsWith('BILIBILI_COOKIE_')) {
const uid = name.slice(16);
bilibili_cookies[uid] = envs[name];
} else if (name.startsWith('TWITTER_TOKEN_')) {
const id = name.slice(14);
twitter_tokens[id] = envs[name];
} else if (name.startsWith('EMAIL_CONFIG_')) {
const id = name.slice(13);
email_config[id] = envs[name];
Expand Down Expand Up @@ -295,10 +291,8 @@ const calculateValue = () => {
cookie: envs.TOPHUB_COOKIE,
},
twitter: {
consumer_key: envs.TWITTER_CONSUMER_KEY,
consumer_secret: envs.TWITTER_CONSUMER_SECRET,
tokens: twitter_tokens,
authorization: envs.TWITTER_WEBAPI_AUTHORIZAION && envs.TWITTER_WEBAPI_AUTHORIZAION.split(','),
oauthTokens: envs.TWITTER_OAUTH_TOKEN && envs.TWITTER_OAUTH_TOKEN.split(','),
oauthTokenSecrets: envs.TWITTER_OAUTH_TOKEN_SECRET && envs.TWITTER_OAUTH_TOKEN_SECRET.split(','),
},
weibo: {
app_key: envs.WEIBO_APP_KEY,
Expand Down
4 changes: 1 addition & 3 deletions lib/v2/twitter/keyword.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const devApiImpl = require('./developer-api/search');
const webApiImpl = require('./web-api/search');
const apiFallback = require('./api_fallback_common');

module.exports = (ctx) => apiFallback(ctx, devApiImpl, webApiImpl);
module.exports = async (ctx) => await webApiImpl(ctx);
6 changes: 1 addition & 5 deletions lib/v2/twitter/media.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
// const config = require('@/config').value;
// const devApiImpl = require('./developer-api/user');
const webApiImpl = require('./web-api/media');

module.exports = async (ctx) => {
await webApiImpl(ctx);
};
module.exports = async (ctx) => await webApiImpl(ctx);
4 changes: 1 addition & 3 deletions lib/v2/twitter/user.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const devApiImpl = require('./developer-api/user');
const webApiImpl = require('./web-api/user');
const apiFallback = require('./api_fallback_common');

module.exports = (ctx) => apiFallback(ctx, devApiImpl, webApiImpl);
module.exports = (ctx) => webApiImpl(ctx);
8 changes: 5 additions & 3 deletions lib/v2/twitter/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
quoteInTitle += `${author.name}: ${formatText(quoteData)}`;

if (readable) {
quote += `<br><small>Link: <a href='https://twitter.com/${author.screen_name}/status/${quoteData.id_str}' target='_blank' rel='noopener noreferrer'>https://twitter.com/${author.screen_name}/status/${quoteData.id_str}</a></small>`;
quote += `<br><small>Link: <a href='https://twitter.com/${author.screen_name}/status/${quoteData.id_str || quoteData.conversation_id_str}' target='_blank' rel='noopener noreferrer'>https://twitter.com/${
author.screen_name
}/status/${quoteData.id_str || quoteData.conversation_id_str}</a></small>`;
}
if (showTimestampInDescription) {
quote += '<br><small>' + parseDate(quoteData.created_at);
Expand Down Expand Up @@ -370,7 +372,7 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
author: authorName,
description,
pubDate: parseDate(item.created_at),
link: `https://twitter.com/${item.user.screen_name}/status/${item.id_str}`,
link: `https://twitter.com/${item.user.screen_name}/status/${item.id_str || item.conversation_id_str}`,

_extra:
(isRetweet && {
Expand All @@ -383,7 +385,7 @@ const ProcessFeed = (ctx, { data = [] }, params = {}) => {
(item.is_quote_status && {
links: [
{
url: `https://twitter.com/${item.quoted_status?.user?.screen_name}/status/${item.quoted_status?.id_str}`,
url: `https://twitter.com/${item.quoted_status?.user?.screen_name}/status/${item.quoted_status?.id_str || item.quoted_status?.conversation_id_str}`,
type: 'quote',
},
],
Expand Down
148 changes: 80 additions & 68 deletions lib/v2/twitter/web-api/constants.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,89 @@
// https://github.com/zedeus/nitter/issues/919#issuecomment-1619067142
// https://git.sr.ht/~cloutier/bird.makeup/tree/4b2495bf2908c648ae9250f353bfdacf2464b98d/item/src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs#L36
const auth = 'Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF';
const tokens = [
auth, // tweetdeck new
// 'CjulERsDeqhhjSme66ECg:IQWdVyqFxghAtURHGeGiWAsmCAGmdW3WmbEx6Hck', // iPad, no mixed videos and photos support
// valid, but endpoints differ:
// 'IQKbtAYlXLripLGPWd0HUA:GgDYlkSvaPxGxC4X8liwpUoqKwwr3lCADbz8A7ADU', // iPhone
// '3nVuSoBZnx6U4vzUxf5w:Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys', // Android
// '3rJOl1ODzm9yZy63FACdg:5jPoQ5kQvMJFDYRNE8bQ4rHuds4xJqhvgNJM4awaE8', // Mac
];
const baseUrl = 'https://api.twitter.com';

const consumerKey = '3nVuSoBZnx6U4vzUxf5w';
const consumerSecret = 'Bcs59EFbbsdF6Sl9Ng71smgStWEGwXXKSjYvPVt7qys';

const graphQLEndpointsPlain = [
'/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName',
'/graphql/Lxg1V9AiIzzXEiP2c8dRnw/UserByRestId',
'/graphql/3XDB26fBve-MmjHaWTUZxA/TweetDetail',
'/graphql/QqZBEqganhHwmU9QscmIug/UserTweets',
'/graphql/wxoVeDnl0mP7VLhe6mTOdg/UserTweetsAndReplies',
'/graphql/Az0-KW6F-FyYTc2OJmvUhg/UserMedia',
'/graphql/kgZtsNyE46T3JaEf2nF9vw/Likes',
// these endpoints are not available if authenticated as other clients
// FYI, endpoints for Android: https://gist.github.com/ScamCast/2e40befbd1b61c4a80cda2745d4df1f4
'/graphql/u7wQyGi6oExe8_TRWGMq4Q/UserResultByScreenNameQuery',
'/graphql/oPppcargziU1uDQHAUmH-A/UserResultByIdQuery',
'/graphql/3JNH4e9dq1BifLxAa3UMWg/UserWithProfileTweetsQueryV2',
'/graphql/8IS8MaO-2EN6GZZZb8jF0g/UserWithProfileTweetsAndRepliesQueryV2',
'/graphql/PDfFf8hGeJvUCiTyWtw4wQ/MediaTimelineV2',
'/graphql/q94uRCEn65LZThakYcPT6g/TweetDetail',
'/graphql/sITyJdhRPpvpEjg4waUmTA/TweetResultByIdQuery',
'/graphql/gkjsKepM6gl_HmFWoWKfgg/SearchTimeline',
'/graphql/iTpgCtbdxrsJfyx0cFjHqg/ListByRestId',
'/graphql/-kmqNvm5Y-cVrfvBy6docg/ListBySlug',
'/graphql/P4NpVZDqUD_7MEM84L-8nw/ListMembers',
'/graphql/BbGLL1ZfMibdFNWlk7a0Pw/ListTimeline',
];

const graphQLMap = Object.fromEntries(graphQLEndpointsPlain.map((endpoint) => [endpoint.split('/')[3], endpoint]));
const gqlMap = Object.fromEntries(graphQLEndpointsPlain.map((endpoint) => [endpoint.split('/')[3].replace(/V2$|Query$|QueryV2$/, ''), endpoint]));

const gqlFeatures = JSON.stringify({
android_graphql_skip_api_media_color_palette: false,
blue_business_profile_image_shape_enabled: false,
creator_subscriptions_subscription_count_enabled: false,
creator_subscriptions_tweet_preview_api_enabled: true,
freedom_of_speech_not_reach_fetch_enabled: false,
graphql_is_translatable_rweb_tweet_is_translatable_enabled: false,
hidden_profile_likes_enabled: false,
highlights_tweets_tab_ui_enabled: false,
interactive_text_enabled: false,
longform_notetweets_consumption_enabled: true,
longform_notetweets_inline_media_enabled: false,
longform_notetweets_richtext_consumption_enabled: true,
longform_notetweets_rich_text_read_enabled: false,
responsive_web_edit_tweet_api_enabled: false,
responsive_web_enhance_cards_enabled: false,
responsive_web_graphql_exclude_directive_enabled: true,
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
responsive_web_graphql_timeline_navigation_enabled: false,
responsive_web_media_download_video_enabled: false,
responsive_web_text_conversations_enabled: false,
responsive_web_twitter_article_tweet_consumption_enabled: false,
responsive_web_twitter_blue_verified_badge_is_enabled: true,
rweb_lists_timeline_redesign_enabled: true,
spaces_2022_h2_clipping: true,
spaces_2022_h2_spaces_communities: true,
standardized_nudges_misinfo: false,
subscriptions_verification_info_enabled: true,
subscriptions_verification_info_reason_enabled: true,
subscriptions_verification_info_verified_since_enabled: true,
super_follow_badge_privacy_enabled: false,
super_follow_exclusive_tweet_notifications_enabled: false,
super_follow_tweet_api_enabled: false,
super_follow_user_api_enabled: false,
tweet_awards_web_tipping_enabled: false,
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: false,
tweetypie_unmention_optimization_enabled: false,
unified_cards_ad_metadata_container_dynamic_card_content_query_enabled: false,
verified_phone_label_enabled: false,
vibe_api_enabled: false,
view_counts_everywhere_api_enabled: false,
});

// captured from Twitter web
const featuresMap = {
UserByScreenName: JSON.stringify({
hidden_profile_likes_enabled: false,
responsive_web_graphql_exclude_directive_enabled: true,
verified_phone_label_enabled: false,
subscriptions_verification_info_verified_since_enabled: true,
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,
}),
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,
verified_phone_label_enabled: false,
creator_subscriptions_tweet_preview_api_enabled: true,
responsive_web_graphql_timeline_navigation_enabled: true,
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
tweetypie_unmention_optimization_enabled: true,
responsive_web_edit_tweet_api_enabled: true,
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
view_counts_everywhere_api_enabled: true,
longform_notetweets_consumption_enabled: true,
responsive_web_twitter_article_tweet_consumption_enabled: false,
tweet_awards_web_tipping_enabled: false,
freedom_of_speech_not_reach_fetch_enabled: true,
standardized_nudges_misinfo: true,
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
longform_notetweets_rich_text_read_enabled: true,
longform_notetweets_inline_media_enabled: true,
responsive_web_media_download_video_enabled: false,
responsive_web_enhance_cards_enabled: false,
}),
const timelineParams = {
include_can_media_tag: 1,
include_cards: 1,
include_entities: 1,
include_profile_interstitial_type: 0,
include_quote_count: 0,
include_reply_count: 0,
include_user_entities: 0,
include_ext_reply_count: 0,
include_ext_media_color: 0,
cards_platform: 'Web-13',
tweet_mode: 'extended',
send_error_codes: 1,
simple_quoted_tweet: 1,
};

module.exports = {
auth,
tokens,
graphQLMap,
featuresMap,
baseUrl,
consumerKey,
consumerSecret,
gqlMap,
gqlFeatures,
timelineParams,
};
Loading

1 comment on commit 9032153

@yindaheng98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

666感谢大佬

Please sign in to comment.