Skip to content

Commit

Permalink
feat: get liked tweets by user
Browse files Browse the repository at this point in the history
Requires authentication, guest auth results in a 404
  • Loading branch information
AtoraSuunva authored and karashiiro committed Mar 6, 2024
1 parent 2dd036a commit bbded4e
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/api-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const endpoints = {
// TODO: Migrate other endpoint URLs here
UserTweets:
'https://twitter.com/i/api/graphql/H8OOoI-5ZE4NxgRr8lfyWg/UserTweets?variables=%7B%22userId%22%3A%2244196397%22%2C%22count%22%3A20%2C%22includePromotedContent%22%3Atrue%2C%22withQuickPromoteEligibilityTweetFields%22%3Atrue%2C%22withVoice%22%3Atrue%2C%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22view_counts_everywhere_api_enabled%22%3Atrue%2C%22longform_notetweets_consumption_enabled%22%3Atrue%2C%22responsive_web_twitter_article_tweet_consumption_enabled%22%3Afalse%2C%22tweet_awards_web_tipping_enabled%22%3Afalse%2C%22freedom_of_speech_not_reach_fetch_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Atrue%2C%22longform_notetweets_rich_text_read_enabled%22%3Atrue%2C%22longform_notetweets_inline_media_enabled%22%3Atrue%2C%22responsive_web_media_download_video_enabled%22%3Afalse%2C%22responsive_web_enhance_cards_enabled%22%3Afalse%7D',
UserLikedTweets:
'https://twitter.com/i/api/graphql/eSSNbhECHHWWALkkQq-YTA/Likes?variables=%7B%22userId%22%3A%222244196397%22%2C%22count%22%3A20%2C%22includePromotedContent%22%3Afalse%2C%22withClientEventToken%22%3Afalse%2C%22withBirdwatchNotes%22%3Afalse%2C%22withVoice%22%3Atrue%2C%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22c9s_tweet_anatomy_moderator_badge_enabled%22%3Atrue%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22view_counts_everywhere_api_enabled%22%3Atrue%2C%22longform_notetweets_consumption_enabled%22%3Atrue%2C%22responsive_web_twitter_article_tweet_consumption_enabled%22%3Atrue%2C%22tweet_awards_web_tipping_enabled%22%3Afalse%2C%22freedom_of_speech_not_reach_fetch_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Atrue%2C%22rweb_video_timestamps_enabled%22%3Atrue%2C%22longform_notetweets_rich_text_read_enabled%22%3Atrue%2C%22longform_notetweets_inline_media_enabled%22%3Atrue%2C%22responsive_web_enhance_cards_enabled%22%3Afalse%7D',
TweetDetail:
'https://twitter.com/i/api/graphql/xOhkmRac04YFZmOzU9PJHg/TweetDetail?variables=%7B%22focalTweetId%22%3A%221237110546383724547%22%2C%22with_rux_injections%22%3Afalse%2C%22includePromotedContent%22%3Atrue%2C%22withCommunity%22%3Atrue%2C%22withQuickPromoteEligibilityTweetFields%22%3Atrue%2C%22withBirdwatchNotes%22%3Atrue%2C%22withVoice%22%3Atrue%2C%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22view_counts_everywhere_api_enabled%22%3Atrue%2C%22longform_notetweets_consumption_enabled%22%3Atrue%2C%22responsive_web_twitter_article_tweet_consumption_enabled%22%3Afalse%2C%22tweet_awards_web_tipping_enabled%22%3Afalse%2C%22freedom_of_speech_not_reach_fetch_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Atrue%2C%22longform_notetweets_rich_text_read_enabled%22%3Atrue%2C%22longform_notetweets_inline_media_enabled%22%3Atrue%2C%22responsive_web_media_download_video_enabled%22%3Afalse%2C%22responsive_web_enhance_cards_enabled%22%3Afalse%7D&fieldToggles=%7B%22withArticleRichContentState%22%3Afalse%7D',
TweetResultByRestId:
Expand Down
11 changes: 11 additions & 0 deletions src/scraper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getTweetAnonymous,
getTweets,
getLatestTweet,
getLikedTweets,
getTweetWhere,
getTweetsWhere,
getTweetsByUserId,
Expand Down Expand Up @@ -248,6 +249,16 @@ export class Scraper {
return getTweets(user, maxTweets, this.auth);
}

/**
* Fetches liked tweets from a Twitter user. Requires authentication.
* @param user The user whose likes should be returned.
* @param maxTweets The maximum number of tweets to return. Defaults to `200`.
* @returns An {@link AsyncGenerator} of liked tweets from the provided user.
*/
public getLikedTweets(user: string, maxTweets = 200): AsyncGenerator<Tweet> {
return getLikedTweets(user, maxTweets, this.auth);
}

/**
* Fetches tweets from a Twitter user using their ID.
* @param userId The user whose tweets should be returned.
Expand Down
9 changes: 9 additions & 0 deletions src/tweets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,12 @@ test('scraper can get tweet thread', async () => {
expect(tweet?.isSelfThread).toBeTruthy();
expect(tweet?.thread.length).toStrictEqual(7);
});

test('scraper can get liked tweets', async () => {
const scraper = await getScraper();
const liked = scraper.getLikedTweets('elonmusk', 10);
const tweet = await liked.next();
expect(tweet.value).not.toBeUndefined();
expect(tweet.done).toBeFalsy();
expect(tweet.value?.id).not.toBeUndefined();
});
53 changes: 53 additions & 0 deletions src/tweets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,59 @@ export function getTweetsByUserId(
});
}

export async function fetchLikedTweets(
userId: string,
maxTweets: number,
cursor: string | undefined,
auth: TwitterAuth,
): Promise<QueryTweetsResponse> {
if (!auth.isLoggedIn()) {
throw new Error('Scraper is not logged-in for fetching liked tweets.');
}

if (maxTweets > 200) {
maxTweets = 200;
}

const userTweetsRequest = apiRequestFactory.createUserLikedTweetsRequest();
userTweetsRequest.variables.userId = userId;
userTweetsRequest.variables.count = maxTweets;
userTweetsRequest.variables.includePromotedContent = false; // true on the website

if (cursor != null && cursor != '') {
userTweetsRequest.variables['cursor'] = cursor;
}

const res = await requestApi<TimelineV2>(
userTweetsRequest.toRequestUrl(),
auth,
);

if (!res.success) {
throw res.err;
}

return parseTimelineTweetsV2(res.value);
}

export function getLikedTweets(
user: string,
maxTweets: number,
auth: TwitterAuth,
): AsyncGenerator<Tweet, void> {
return getTweetTimeline(user, maxTweets, async (q, mt, c) => {
const userIdRes = await getUserIdByScreenName(q, auth);

if (!userIdRes.success) {
throw userIdRes.err;
}

const { value: userId } = userIdRes;

return fetchLikedTweets(userId, mt, c, auth);
});
}

export async function getTweetWhere(
tweets: AsyncIterable<Tweet>,
query: TweetQuery,
Expand Down

0 comments on commit bbded4e

Please sign in to comment.