feat(twitter): media for webapi

This commit is contained in:
DIYgod 2024-03-19 21:41:39 +08:00
parent 4a7c00f274
commit 52edc7e393
No known key found for this signature in database
5 changed files with 72 additions and 19 deletions

View File

@ -1,4 +1,4 @@
import { baseUrl, gqlMap, gqlFeatures, gqlFieldToggles } from './constants';
import { baseUrl, gqlMap, gqlFeatures } from './constants';
import { config } from '@/config';
import cache from '@/utils/cache';
import { twitterGot, paginationTweets, gatherLegacyFromData } from './utils';
@ -12,7 +12,9 @@ const getUserData = (id) =>
withSafetyModeUserFields: true,
}),
features: JSON.stringify(gqlFeatures.UserByRestId),
fieldToggles: JSON.stringify(gqlFieldToggles.UserByScreenName),
fieldToggles: JSON.stringify({
withAuxiliaryUserLabels: false,
}),
});
}
return twitterGot(`${baseUrl}${gqlMap.UserByScreenName}`, {
@ -21,7 +23,9 @@ const getUserData = (id) =>
withSafetyModeUserFields: true,
}),
features: JSON.stringify(gqlFeatures.UserByScreenName),
fieldToggles: JSON.stringify(gqlFieldToggles.UserByScreenName),
fieldToggles: JSON.stringify({
withAuxiliaryUserLabels: false,
}),
});
});
@ -62,7 +66,31 @@ const getUserTweetsAndReplies = (id: string, params?: Record<string, any>) =>
)
);
const getUserMedia = (id: string, params?: Record<string, any>) => cacheTryGet(id, params, async (id, params = {}) => gatherLegacyFromData(await paginationTweets('MediaTimeline', id, params)));
const getUserMedia = (id: string, params?: Record<string, any>) =>
cacheTryGet(id, params, async (id, params = {}) => {
const cursorSource = await paginationTweets('UserMedia', id, {
...params,
count: 20,
includePromotedContent: false,
withClientEventToken: false,
withBirdwatchNotes: false,
withVoice: true,
withV2Timeline: true,
});
const cursor = cursorSource.find((i) => i.content?.cursorType === 'Top').content.value;
return gatherLegacyFromData(
await paginationTweets('UserMedia', id, {
...params,
cursor,
count: 20,
includePromotedContent: false,
withClientEventToken: false,
withBirdwatchNotes: false,
withVoice: true,
withV2Timeline: true,
})
);
});
const getUserLikes = (id: string, params?: Record<string, any>) => cacheTryGet(id, params, async (id, params = {}) => gatherLegacyFromData(await paginationTweets('Likes', id, params)));

View File

@ -61,14 +61,27 @@ const gqlFeatures = {
longform_notetweets_inline_media_enabled: true,
responsive_web_enhance_cards_enabled: false,
},
};
const gqlFieldToggles = {
UserByScreenName: {
withAuxiliaryUserLabels: false,
},
UserByRestId: {
withAuxiliaryUserLabels: false,
UserMedia: {
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,
c9s_tweet_anatomy_moderator_badge_enabled: true,
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: true,
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,
rweb_video_timestamps_enabled: true,
longform_notetweets_rich_text_read_enabled: true,
longform_notetweets_inline_media_enabled: true,
responsive_web_enhance_cards_enabled: false,
},
};
@ -90,4 +103,4 @@ const timelineParams = {
const bearerToken = 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
export { baseUrl, gqlMap, gqlFeatures, gqlFieldToggles, timelineParams, bearerToken };
export { baseUrl, gqlMap, gqlFeatures, timelineParams, bearerToken };

View File

@ -12,7 +12,7 @@ export const twitterGot = async (url, params) => {
config.twitter.cookie
.split(';')
.map((c) => Cookie.parse(c)?.toJSON())
.map((c) => [c.key, c.value])
.map((c) => [c?.key, c?.value])
);
if (!jsonCookie || !jsonCookie.auth_token || !jsonCookie.ct0) {
throw new Error('Twitter cookie is not valid');
@ -66,17 +66,21 @@ export const paginationTweets = async (endpoint: string, userId: number | undefi
instructions = data.user.result.timeline_v2.timeline.instructions;
}
return instructions.find((i) => i.__typename === 'TimelineAddEntries' || i.type === 'TimelineAddEntries').entries;
const entries1 = instructions.find((i) => i.type === 'TimelineAddToModule')?.moduleItems; // Media
const entries2 = instructions.find((i) => i.type === 'TimelineAddEntries').entries;
return entries1 || entries2;
};
export function gatherLegacyFromData(entries, filterNested?: string[], userId?: number | string) {
const tweets = [];
const filteredEntries = [];
export function gatherLegacyFromData(entries: any[], filterNested?: string[], userId?: number | string) {
const tweets: any[] = [];
const filteredEntries: any[] = [];
for (const entry of entries) {
const entryId = entry.entryId;
if (entryId) {
if (entryId.startsWith('tweet-')) {
filteredEntries.push(entry);
} else if (entryId.startsWith('profile-grid-0-tweet-')) {
filteredEntries.push(entry);
}
if (filterNested && filterNested.some((f) => entryId.startsWith(f))) {
filteredEntries.push(...entry.content.items);
@ -98,7 +102,7 @@ export function gatherLegacyFromData(entries, filterNested?: string[], userId?:
}
t.legacy.user = t.core?.user_result?.result?.legacy || t.core?.user_results?.result?.legacy;
t.legacy.id_str = t.rest_id; // avoid falling back to conversation_id_str elsewhere
const quote = t.quoted_status_result?.result;
const quote = t.quoted_status_result?.result?.tweet || t.quoted_status_result?.result;
if (quote) {
t.legacy.quoted_status = quote.legacy;
t.legacy.quoted_status.user = quote.core.user_result?.result?.legacy || quote.core.user_results?.result?.legacy;

View File

@ -17,6 +17,10 @@ export const route: Route = {
name: 'TWITTER_PASSWORD',
description: '',
},
{
name: 'TWITTER_COOKIE',
description: '',
},
],
requirePuppeteer: false,
antiCrawler: false,

View File

@ -21,6 +21,10 @@ export const route: Route = {
name: 'TWITTER_PASSWORD',
description: '',
},
{
name: 'TWITTER_COOKIE',
description: '',
},
],
requirePuppeteer: false,
antiCrawler: false,