Skip to content

Instantly share code, notes, and snippets.

@AzureFlow
Last active March 21, 2024 05:49
Show Gist options
  • Save AzureFlow/01cff883b9f1b22e8d0c094df90d0747 to your computer and use it in GitHub Desktop.
Save AzureFlow/01cff883b9f1b22e8d0c094df90d0747 to your computer and use it in GitHub Desktop.
Get last N tweets from a user.
<?php declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\RequestOptions;
// ==================== //
$username = '@elonmusk';
$count = 2;
// $count = 40; // default
// ==================== //
// From main.HASH.js bundle
const AUTH = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36';
$client = new Client([
RequestOptions::PROXY => '',
]);
// You can refresh these when you get rate-limited
$csrfToken = getCsrf();
$guestToken = getGuestToken($client);
$userId = getUserId($client, $username, $csrfToken, $guestToken);
if($userId === null)
{
die("Couldn't find user: $username!" . PHP_EOL);
}
$startTime = microtime(true);
$userTweetsBody = getUserTweets($client, $userId, $count, $csrfToken, $guestToken);
$endTime = microtime(true);
// $.data.user.result.timeline_v2.timeline.instructions[1].entries[*].content.itemContent.tweet_results.result.legacy.full_text
$userTweetsBody = $userTweetsBody['data']['user']['result']['timeline_v2']['timeline']['instructions'][1]['entries'];
foreach($userTweetsBody as $entry)
{
if($entry['content']['entryType'] === 'TimelineTimelineItem')
{
echo html_entity_decode($entry['content']['itemContent']['tweet_results']['result']['legacy']['full_text']) . PHP_EOL;
echo '=====' . PHP_EOL;
}
}
$totalTime = round(($endTime - $startTime) * 1000);
echo PHP_EOL . "Finished in: {$totalTime}ms" . PHP_EOL;
/**
* @throws GuzzleException
* @throws JsonException
*/
function getGuestToken(Client $client)
{
$resp = $client->post('https://api.twitter.com/1.1/guest/activate.json', [
RequestOptions::HEADERS => [
'authorization' => 'Bearer ' . AUTH,
'user-agent' => USER_AGENT,
],
]);
$body = json_decode($resp->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
return $body['guest_token'];
}
/**
* @throws Exception
*/
function getCsrf(): string
{
$token = '';
for($i = 0; $i < 32; $i++)
{
$value = random_int(0, 255);
$token .= substr(dechex($value), -1);
}
return $token;
}
/**
* @throws GuzzleException
* @throws JsonException
*/
function getUserId(Client $client, string $username, string $csrfToken, string $guestToken): ?string
{
$username = str_replace('@', '', $username);
// TODO: Cache
$variables = json_encode([
'screen_name' => $username,
'withSafetyModeUserFields' => false,
'withSuperFollowsUserFields' => false,
], JSON_THROW_ON_ERROR);
$features = json_encode([
'responsive_web_twitter_blue_verified_badge_is_enabled' => false,
'verified_phone_label_enabled' => false,
'responsive_web_twitter_blue_new_verification_copy_is_enabled' => false,
'responsive_web_graphql_timeline_navigation_enabled' => false,
], JSON_THROW_ON_ERROR);
// e.exports = {
// queryId: "0dbpv_sJc_YxCb-1RO2NFA",
// operationName: "UserByScreenName",
// operationType: "query",
// metadata: {
// featureSwitches: ["responsive_web_twitter_blue_verified_badge_is_enabled", "verified_phone_label_enabled", "responsive_web_twitter_blue_new_verification_copy_is_enabled", "responsive_web_graphql_timeline_navigation_enabled"]
// }
// }
$resp = $client->get('https://twitter.com/i/api/graphql/0dbpv_sJc_YxCb-1RO2NFA/UserByScreenName', [
RequestOptions::HEADERS => [
'authorization' => 'Bearer ' . AUTH,
'cookie' => "ct0=$csrfToken; gt=$guestToken",
'user-agent' => USER_AGENT,
'x-csrf-token' => $csrfToken,
'x-guest-token' => $guestToken,
],
RequestOptions::QUERY => [
'variables' => $variables,
'features' => $features,
],
]);
$respBody = json_decode($resp->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
if(count($respBody['data']) > 0)
{
return $respBody['data']['user']['result']['rest_id'];
}
return null;
}
/**
* @throws GuzzleException
* @throws JsonException
*/
function getUserTweets(Client $client, string $userId, int $count, string $csrfToken, string $guestToken): array
{
// TODO: Cache
$variables = json_encode([
'userId' => $userId,
'count' => $count,
'includePromotedContent' => false,
// 'includePromotedContent' => true,
'withQuickPromoteEligibilityTweetFields' => true,
'withSuperFollowsUserFields' => true,
'withDownvotePerspective' => false,
'withReactionsMetadata' => false,
'withReactionsPerspective' => false,
'withSuperFollowsTweetFields' => true,
'withVoice' => true,
'withV2Timeline' => true,
], JSON_THROW_ON_ERROR);
// $features = '{"responsive_web_twitter_blue_verified_badge_is_enabled":true,"verified_phone_label_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true,"view_counts_public_visibility_enabled":false,"view_counts_everywhere_api_enabled":false,"tweetypie_unmention_optimization_enabled":true,"responsive_web_uc_gql_enabled":true,"vibe_api_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":false,"interactive_text_enabled":true,"responsive_web_text_conversations_enabled":false,"responsive_web_enhance_cards_enabled":true}';
$features = json_encode([
'responsive_web_twitter_blue_verified_badge_is_enabled' => false,
'verified_phone_label_enabled' => false,
'responsive_web_graphql_timeline_navigation_enabled' => false,
'view_counts_public_visibility_enabled' => false,
'view_counts_everywhere_api_enabled' => false,
'tweetypie_unmention_optimization_enabled' => true,
'responsive_web_uc_gql_enabled' => false,
'vibe_api_enabled' => false,
'responsive_web_edit_tweet_api_enabled' => false,
'graphql_is_translatable_rweb_tweet_is_translatable_enabled' => false,
'standardized_nudges_misinfo' => false,
'tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled' => false,
'interactive_text_enabled' => false,
'responsive_web_text_conversations_enabled' => false,
'responsive_web_enhance_cards_enabled' => false,
], JSON_THROW_ON_ERROR);
// e.exports = {
// queryId: "zEAmYdYUvRuhrr9zjc5Vvg",
// operationName: "UserTweets",
// operationType: "query",
// metadata: {
// featureSwitches: ["responsive_web_twitter_blue_verified_badge_is_enabled", "verified_phone_label_enabled", "responsive_web_graphql_timeline_navigation_enabled", "view_counts_public_visibility_enabled", "view_counts_everywhere_api_enabled", "tweetypie_unmention_optimization_enabled", "responsive_web_uc_gql_enabled", "vibe_api_enabled", "responsive_web_edit_tweet_api_enabled", "graphql_is_translatable_rweb_tweet_is_translatable_enabled", "standardized_nudges_misinfo", "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled", "interactive_text_enabled", "responsive_web_text_conversations_enabled", "responsive_web_enhance_cards_enabled"]
// }
// }
$userTweetsResp = $client->get('https://twitter.com/i/api/graphql/zEAmYdYUvRuhrr9zjc5Vvg/UserTweets', [
RequestOptions::HEADERS => [
'authorization' => 'Bearer ' . AUTH,
'cookie' => "ct0=$csrfToken; gt=$guestToken",
'user-agent' => USER_AGENT,
'x-csrf-token' => $csrfToken,
'x-guest-token' => $guestToken,
],
RequestOptions::QUERY => [
'variables' => $variables,
'features' => $features,
],
]);
return json_decode($userTweetsResp->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment