zedeus / nitter

Alternative Twitter front-end
https://nitter.net
GNU Affero General Public License v3.0
9.93k stars 529 forks source link

R.I.P. Nitter 🪦😭 (...unless?) #919

Closed devgaucho closed 1 year ago

devgaucho commented 1 year ago

https://techcrunch.com/2023/06/30/twitter-now-requires-an-account-to-view-tweets/

the nitter crawler will need to be recreated...

NoodlesStamps commented 1 year ago

It appears that using Togetter, a service that compiles tweets, it can go back to all tweets(probably). https://togetter.com https://togetter.com/create this service is in Japanese, but it seems to be able to trace back tweets from overseas as well.

https://github.com/zedeus/nitter/assets/50985323/bbc85bf2-5461-4c1c-a7e9-3040cd18333e

Login is required but not API restricted, because togetter uses Twitter-API provided by NTT-DATA .(probably) https://nttdata-nazuki.jp/certifiedserviceprogram/index.html

edel79 commented 1 year ago

OK so this is a source for the tweets. But you have to deal with that, know how to use it and how to integrate it to Nitter. And if the whole Nitter community use that source, not sure it will be working for a long time... due to a massive traffic increase.

x1ca3 commented 1 year ago

you can add "showReplies" parameter to the syndication endpoint and will show the user tweet replies also https://syndication.twitter.com/srv/timeline-profile/screen-name/elonmusk?showReplies=true

then to get more info about a tweet with just the tweet id you can use https://syndication.twitter.com/tweet-result?id=1675214274627530754

maddsua commented 1 year ago

https://cdn.discordapp.com/attachments/874455648883052544/1125145584798548119/image.png

"Parse HTML with regex" I feel attacked

Sometimes that's not even a bad option 😉

pozirk commented 1 year ago

Looks like this instance (rss-bridge.org) is modified to be able to parse the syndication (git.twitter-fix-2.be59a1e). I am running my own instance of rss-bridge with latest docker image (git.master.d8bc015). This is not working for syndication (yet).

the twitter issues might resolve itself by its own so im waiting a bit before merging to master.

do you want it merged asap?

Syndication sometimes works, sometimes doesn't. Right now, it is not working. twitter_synd

saqib-ahmed commented 1 year ago

Looks like this instance (rss-bridge.org) is modified to be able to parse the syndication (git.twitter-fix-2.be59a1e). I am running my own instance of rss-bridge with latest docker image (git.master.d8bc015). This is not working for syndication (yet).

the twitter issues might resolve itself by its own so im waiting a bit before merging to master. do you want it merged asap?

Syndication sometimes works, sometimes doesn't. Right now, it is not working. twitter_synd

I'm getting this same issue. Thought my IP is blocked as I've been checking this link a lot, but VPN didn't help either. They seem to be on guard with this syndication thing.

maddsua commented 1 year ago

Looks like this instance (rss-bridge.org) is modified to be able to parse the syndication (git.twitter-fix-2.be59a1e). I am running my own instance of rss-bridge with latest docker image (git.master.d8bc015). This is not working for syndication (yet).

the twitter issues might resolve itself by its own so im waiting a bit before merging to master. do you want it merged asap?

Syndication sometimes works, sometimes doesn't. Right now, it is not working. twitter_synd

I'm getting this same issue. Thought my IP is blocked as I've been checking this link a lot, but VPN didn't help either. They seem to be on guard with this syndication thing.

Twitter employees are reading this thread for sure, this is when openness actually backfires

maddsua commented 1 year ago

I guess at this point the only actual solution is to create *a lot of* accounts for readonly bots and deploy a load balancer so you guys wont rail the Twitter's potato servers

JacobKfromIRC commented 1 year ago

https://cdn.discordapp.com/attachments/874455648883052544/1125145584798548119/image.png

Do Nitter instances pay for captcha solvers?

FoxBlocks commented 1 year ago

I think the coward privated his account, too. EDIT: Nevermind. The reason I don't see anything is because posts are hidden, but the syndication page itself is not. image

andrigamerita commented 1 year ago

I think Nitter can be saved... Look at this browser extension that - while its advertised feature is to bring back the old Twitter UI - due to the "secret APIs" it uses, which seem to be something GraphQL-related similar to Nitter's internal API (I only skimmed trough the source code, haven't really read it), it can bypass rate limits: https://github.com/dimdenGD/OldTwitter.

As said by the developer (on Twitter), it needs an active login session. I tried logging into Twitter.com, installing the extension, and I think I've scrolled far more than the free 1000 posts I would be allowed to, so I'd say this works. If we can look at the extension source, we can maybe see what needs to be fixed in Nitter's source to make it work again (apart from requiring the instance admin to login with their own Twitter account on the Nitter server)

pozirk commented 1 year ago

I'm sure they can see where the traffic goes and just block it, like they are doing with syndication now. So any trick or loophole will be closed as soon as too many people start using it.

YJSoft commented 1 year ago

OldTwitter uses Twitter API 1.1 used by old TweetDeck. It's not secret at all and can be blocked like embed tweet

TempUser13 commented 1 year ago

Has anyone built a tool to get an rss from a syndication link?

@TempUser13 dvikan has linked to rss-bridge in this issue 🙈 [#919 (comment)](https://github.com/zedeus/nitter/issues/919#issuecomment-1615253896

doesn't seem to work but thanks for the reply, ill try to selfhost with the specific commit, nvm syndication is broken i didn't see

ScamCast commented 1 year ago

Their crappy official API changes ruined my small project that checked 1 persons timeline every hour. Now I can't even check it at all.

image

image

The official API is completely useless at the free tier. Wanted to use Nitter as a backup option, but looks like Twitter screwed that up too. Hopefully they fallback on their changes.

devgaucho commented 1 year ago

@devgaucho can that be used to bypass login walls as well as archiving?

good question, besides GoogleBot the twitter robots.txt still allows access from Slurp, Yandex, msnbot and bingbot

is it possible that it doesn't do ip verification on all these bots

but robots.txt seems to be outdated since it is still allowing bots access to /search?q=%23

https://imgur.com/8Bs8fhh.png

Write commented 1 year ago

Does anyone know how bird.makeup still manage to work correctly ? According to it's author it has 40% failure rate but that's still more than enough. https://sr.ht/~cloutier/bird.makeup/

His Twoot : https://social.librem.one/@vincent/110645408061048325

devgaucho commented 1 year ago

Does anyone know how bird.makeup still manage to work correctly ? According to it's author it has 40% failure rate but that's still more than enough. https://sr.ht/~cloutier/bird.makeup/

His Twoot : https://social.librem.one/@vincent/110645408061048325

probably he uses twitter's api and makes a local cache to avoid repeating requests

even so he seems to be suffering with the platform's instability

devgaucho commented 1 year ago

Does anyone know how bird.makeup still manage to work correctly ? According to it's author it has 40% failure rate but that's still more than enough. https://sr.ht/~cloutier/bird.makeup/ His Twoot : https://social.librem.one/@vincent/110645408061048325

probably he uses twitter's paid_ api _and makes a local cache to avoid repeating requests

even so he seems to be suffering with the platform's instability

confirmated.

https://git.sr.ht/~cloutier/bird.makeup/tree/master/item/src/BirdsiteLive.Twitter/TwitterUserService.cs https://imgur.com/m0SDEUC.png

https://git.sr.ht/~cloutier/bird.makeup/tree/master/item/src/BirdsiteLive.Twitter/CachedTwitterService.cs https://imgur.com/h3d1cXo.png

Write commented 1 year ago

Does anyone know how bird.makeup still manage to work correctly ? According to it's author it has 40% failure rate but that's still more than enough. https://sr.ht/~cloutier/bird.makeup/ His Twoot : https://social.librem.one/@vincent/110645408061048325

probably he uses twitter's paid_ api _and makes a local cache to avoid repeating requests even so he seems to be suffering with the platform's instability

confirmated.

But that doesn't make any sense, you can selfhost it, there's no way he just share publicly his paid apikey .. ?

In his documentation you don't have to fill any apikey https://git.sr.ht/~cloutier/bird.makeup/tree/master/item/VARIABLES.md

He seems to use some sort of API 1.1 and / GraphQL combo : https://git.sr.ht/~cloutier/bird.makeup/commit/612637fdc7f667d2e491117ea84cd2e60b5c85e6

EDIT : Nevermind he seems to use some sort of API Key indeed, as you can see in the function private async Task<string> GenerateBearerToken() in src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs

Write commented 1 year ago

Ok this seems interesting, he seems he is able to get a Bearer tokens using built-in tokens from APPS as seens here Screenshot du 2023-07-03 à 21 50 59

https://git.sr.ht/~cloutier/bird.makeup/tree/master/item/src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs#L36

He then picks one randomly and send a post request in GenerateBearerToken() which correctly returns a Bearer Tokens

You can try yourself with curl : curl -X POST "https://api.twitter.com/oauth2/token?grant_type=client_credentials" -u "CjulERsDeqhhjSme66ECg:IQWdVyqFxghAtURHGeGiWAsmCAGmdW3WmbEx6Hck"

You can then activate your fresh access_token :

curl -X POST -H "Authorization: Bearer your_access_token" "https://api.twitter.com/1.1/guest/activate.json"

This will return you a guest_token.

You can then access graphql endpoints :

curl -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" \
-H "x-guest-token: 1675958319943393280" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com/" \
"https://api.twitter.com/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName?variables=%7B%22screen_name%22%3A%22elonmusk%22%2C%22withSafetyModeUserFields%22%3Atrue%7D&features=%7B%22hidden_profile_likes_enabled%22%3Afalse%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D"

where x-guest-token is the token returned when you activated your Bearer.

You can get the user numeric ID from previous request, from twitter name : for elonmusk :

curl -s -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" \
-H "x-guest-token: 1675958319943393280" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com/" \
"https://api.twitter.com/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName?variables=%7B%22screen_name%22%3A%22elonmusk%22%2C%22withSafetyModeUserFields%22%3Atrue%7D&features=%7B%22hidden_profile_likes_enabled%22%3Afalse%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D" \
| jq -r '.data.user.result.rest_id'

Then get all his timeline :

curl -s -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" \
-H "x-guest-token: 1675958319943393280" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com" "https://api.twitter.com/graphql/pNl8WjKAvaegIoVH--FuoQ/UserTweetsAndReplies?variables=%7B%22userId%22%3A%2244196397%22,%22count%22%3A40,%22includePromotedContent%22%3Atrue,%22withCommunity%22%3Atrue,%22withSuperFollowsUserFields%22%3Atrue,%22withDownvotePerspective%22%3Afalse,%22withReactionsMetadata%22%3Afalse,%22withReactionsPerspective%22%3Afalse,%22withSuperFollowsTweetFields%22%3Atrue,%22withVoice%22%3Atrue,%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue,%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue,%22verified_phone_label_enabled%22%3Afalse,%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue,%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse,%22tweetypie_unmention_optimization_enabled%22%3Atrue,%22vibe_api_enabled%22%3Atrue,%22responsive_web_edit_tweet_api_enabled%22%3Atrue,%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue,%22view_counts_everywhere_api_enabled%22%3Atrue,%22longform_notetweets_consumption_enabled%22%3Atrue,%22tweet_awards_web_tipping_enabled%22%3Afalse,%22freedom_of_speech_not_reach_fetch_enabled%22%3Afalse,%22standardized_nudges_misinfo%22%3Atrue,%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse,%22interactive_text_enabled%22%3Atrue,%22responsive_web_text_conversations_enabled%22%3Afalse,%22longform_notetweets_richtext_consumption_enabled%22%3Afalse,%22responsive_web_enhance_cards_enabled%22%3Afalse%7D" \
| jq

You have to replace the userID between the two "%22" tags. Here the user id (of elonmusk) is : 44196397

Which returns All his Tweets and Replies (too long):

https://bin.socialspill.com/ixomusor.json

To access detail of a specific tweet the url is :

https://api.twitter.com/graphql/XjlydVWHFIDaAUny86oh2g/TweetDetail?variables=%7B%22focalTweetId%22%3A% + statusId + %22,%22with_rux_injections%22%3Atrue,%22includePromotedContent%22%3Afalse,%22withCommunity%22%3Afalse,%22withQuickPromoteEligibilityTweetFields%22%3Afalse,%22withBirdwatchNotes%22%3Afalse,%22withSuperFollowsUserFields%22%3Afalse,%22withDownvotePerspective%22%3Afalse,%22withReactionsMetadata%22%3Afalse,%22withReactionsPerspective%22%3Afalse,%22withSuperFollowsTweetFields%22%3Afalse,%22withVoice%22%3Atrue,%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue,%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue,%22verified_phone_label_enabled%22%3Afalse,%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue,%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse,%22tweetypie_unmention_optimization_enabled%22%3Atrue,%22vibe_api_enabled%22%3Atrue,%22responsive_web_edit_tweet_api_enabled%22%3Atrue,%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Afalse,%22view_counts_everywhere_api_enabled%22%3Atrue,%22longform_notetweets_consumption_enabled%22%3Atrue,%22tweet_awards_web_tipping_enabled%22%3Afalse,%22freedom_of_speech_not_reach_fetch_enabled%22%3Afalse,%22standardized_nudges_misinfo%22%3Atrue,%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse,%22interactive_text_enabled%22%3Atrue,%22responsive_web_text_conversations_enabled%22%3Afalse,%22longform_notetweets_richtext_consumption_enabled%22%3Afalse,%22responsive_web_enhance_cards_enabled%22%3Atrue%7D
SuperSonicHub1 commented 1 year ago

Wow, this is a great find! So, can we just implement a Twitter API client and re-implement Nitter's fetching layer?

Write commented 1 year ago

Updated my post, :) (btw, get rekt elon)

SuperSonicHub1 commented 1 year ago

I guess the next step would be finding all the endpoints we can access to. I know the folks at Libreddit were able to do something similar by rummaging through a decompiled version of the Reddit Android client.

mthmcalixto commented 1 year ago

@Write https://github.com/zedeus/nitter/issues/919#issuecomment-1619067142 Is this not limited?

Write commented 1 year ago

@Write #919 (comment) Is this not limited?

Well guessing that there is, hence why birds makeup has 40% failure rate and rotate between keys. Gonna let you try :)

ghost commented 1 year ago

@Write #919 (comment) Is this not limited?

It seems the Bearer Token is the same for all, so the first step can be skipped. But the guest_token is different for all. So probably only this one can get limited. Maybe it is simple to just recreate a new one each few calls. One limitation, according to tests with curl, is that each second or third curl answer is slow (~ 2 to 3 seconds).

Write commented 1 year ago

@Write #919 (comment) Is this not limited?

It seems the Bearer Token is the same for all, so the first step can be skipped. But the guest_token is different for all. So probably only this one can get limited. Maybe it is simple to just recreate a new one each few calls. One limitation, according to tests with curl, is that each 2 or 3 curl answers are slow (~ 2 to 3 seconds).

You gonna have to make your own tests. I just showed an example as how bird.makeup works. I don't know anything more, sorry. For me the Bearer returned is definitely different based on which credential I use.

ghost commented 1 year ago

@Write #919 (comment) Is this not limited?

It seems the Bearer Token is the same for all, so the first step can be skipped. But the guest_token is different for all. So probably only this one can get limited. Maybe it is simple to just recreate a new one each few calls. One limitation, according to tests with curl, is that each 2 or 3 curl answers are slow (~ 2 to 3 seconds).

You gonna have to make your own tests. I just showed an example as how bird.makeup works. I don't know anything more, sorry. For me the Bearer returned is definitely different based on which credential I use.

Yes, it is based on the credentials. But since they are constant, I got the same Bearer as you have shown above. All others probably, too.

Write commented 1 year ago

@Write #919 (comment) Is this not limited?

It seems the Bearer Token is the same for all, so the first step can be skipped. But the guest_token is different for all. So probably only this one can get limited. Maybe it is simple to just recreate a new one each few calls. One limitation, according to tests with curl, is that each 2 or 3 curl answers are slow (~ 2 to 3 seconds).

You gonna have to make your own tests. I just showed an example as how bird.makeup works. I don't know anything more, sorry. For me the Bearer returned is definitely different based on which credential I use.

Yes, it is based on the credentials. But since they are constant, I got the same Bearer as you have shown above. All others probably, too.

There's a possibility they change about some times. I don't know.

zooool commented 1 year ago

I don't know why everyone is so positive about the makeup bird. It has failed for me ever since I tried it first a couple of days ago. No matter which IP or browser (agent) I use, I always get:

Error.
An error occurred while processing your request.
ghost commented 1 year ago

I don't know why everyone is so positive about the makeup bird. It has failed for me ever since I tried it first a couple of days ago. No matter which IP or browser (agent) I use, I always get:

Error.
An error occurred while processing your request.

Yeah, this app does not work. But you can try the curl codes above and it works. There is also not much room for twitter to prevent this without blocking their own official apps or force users to install new versions. The problem with the code above is that it is less legal than the previous approach that was easier to get from the browser, I think. But not sure.

SuperSonicHub1 commented 1 year ago

My goal to find all possible endpoints is starting to go well; just found my first API endpoint:

curl -s -X GET \
-H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" \
-H "x-guest-token: 1675958319943393280" -H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com" \
"https://api.twitter.com/graphql/0c8QtPl8cRsze0fPzS6_zg/TwitterBlueMarketingPage?features=%7B%22subscriptions_annual_subscription_signup_enabled%22%3A%20true%7D"
{"data":{"blue_marketing_page_config":{"card":{"badge":{"text":""},"imageUrl":"https://abs.twimg.com/responsive-web/client-web/verification-card-v2@3x.8ebee01a.png","description":"","title":"Blue subscribers with a verified phone number will get a blue checkmark once approved."},"products":[{"buckets":{"title":"","buckets":[{"imageUrl":"https://abs.twimg.com/responsive-web/client-web/purple-present@3x.5f4d564a.png","description":"Edit Tweet, 1080p video uploads, Reader, custom navigation, Bookmark Folders, Top Articles and more.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"All the existing Blue features","clientEventInfo":{"component":"feature_bucket","element":"existing_blue_features"},"features":[]},{"badge":"NEW","imageUrl":"https://abs.twimg.com/responsive-web/client-web/upranked-replies-feature@3x.68f97c89.png","description":"Tweets from Twitter Blue subscribers will be prioritized in replies, mentions, and search — helping to fight scams and spam.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"Prioritized ranking in conversations and search","clientEventInfo":{"component":"feature_bucket","element":"reply_upranking"},"features":[]},{"badge":"NEW","imageUrl":"https://abs.twimg.com/responsive-web/client-web/less-ads-feature@3x.98d5a999.png","description":"See approximately twice as many Tweets between ads in your For You and Following timelines.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"See half the ads","clientEventInfo":{"component":"feature_bucket","element":"less_ads"},"features":[]},{"badge":"NEW","imageUrl":"https://abs.twimg.com/responsive-web/client-web/longer-video-feature-v3@3x.6c6c531a.png","description":"You’ll finally be able to post longer videos to Twitter.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"Post longer videos","clientEventInfo":{"component":"feature_bucket","element":"longer_video"},"features":[]},{"imageUrl":"https://abs.twimg.com/responsive-web/client-web/early-access-feature@3x.9d1ba0a9.png","description":"Get early access to select new features with Twitter Blue Labs.","learnMoreText":"Learn more","learnMoreTitle":"Get early access","learnMoreDescription":"Twitter Blue subscribers get early access to new features like these through Twitter Blue Labs.","title":"Get early access","clientEventInfo":{"component":"feature_bucket","element":"early_access"},"features":[{"icon":"WriteStroke","description":"Edit a Tweet up to 5 times within 30 minutes.","title":"Edit Tweet"},{"icon":"AccountNft","description":"Show your personal flair and set your profile picture to an NFT you own.","title":"NFT Profile Pictures"},{"icon":"CameraVideoStroke","description":"Share your favorite moments with 1080p (Full HD) video.","title":"1080p video uploads"}]}]},"productCategory":"BlueVerified","title":"Blue"}],"feature_buckets":{"title":"","buckets":[{"imageUrl":"https://abs.twimg.com/responsive-web/client-web/purple-present@3x.5f4d564a.png","description":"Edit Tweet, 1080p video uploads, Reader, custom navigation, Bookmark Folders, Top Articles and more.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"All the existing Blue features","clientEventInfo":{"component":"feature_bucket","element":"existing_blue_features"},"features":[]},{"badge":"NEW","imageUrl":"https://abs.twimg.com/responsive-web/client-web/upranked-replies-feature@3x.68f97c89.png","description":"Tweets from Twitter Blue subscribers will be prioritized in replies, mentions, and search — helping to fight scams and spam.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"Prioritized ranking in conversations and search","clientEventInfo":{"component":"feature_bucket","element":"reply_upranking"},"features":[]},{"badge":"NEW","imageUrl":"https://abs.twimg.com/responsive-web/client-web/less-ads-feature@3x.98d5a999.png","description":"See approximately twice as many Tweets between ads in your For You and Following timelines.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"See half the ads","clientEventInfo":{"component":"feature_bucket","element":"less_ads"},"features":[]},{"badge":"NEW","imageUrl":"https://abs.twimg.com/responsive-web/client-web/longer-video-feature-v3@3x.6c6c531a.png","description":"You’ll finally be able to post longer videos to Twitter.","learnMoreText":"","learnMoreTitle":"","learnMoreDescription":"","title":"Post longer videos","clientEventInfo":{"component":"feature_bucket","element":"longer_video"},"features":[]},{"imageUrl":"https://abs.twimg.com/responsive-web/client-web/early-access-feature@3x.9d1ba0a9.png","description":"Get early access to select new features with Twitter Blue Labs.","learnMoreText":"Learn more","learnMoreTitle":"Get early access","learnMoreDescription":"Twitter Blue subscribers get early access to new features like these through Twitter Blue Labs.","title":"Get early access","clientEventInfo":{"component":"feature_bucket","element":"early_access"},"features":[{"icon":"WriteStroke","description":"Edit a Tweet up to 5 times within 30 minutes.","title":"Edit Tweet"},{"icon":"AccountNft","description":"Show your personal flair and set your profile picture to an NFT you own.","title":"NFT Profile Pictures"},{"icon":"CameraVideoStroke","description":"Share your favorite moments with 1080p (Full HD) video.","title":"1080p video uploads"}]}]},"button":{"detailText":"Limited time offer: {{PRICE}}/{{PERIOD}}","disclaimerText":"By clicking Subscribe, you agree to our @#!. Subscriptions auto-renew until canceled, as described in the Terms. Cancel anytime. A verified phone number is required to subscribe. If you've subscribed on another platform, manage your subscription through that platform.","disclaimerUrl":"https://legal.twitter.com/purchaser-terms","disclaimerUrlText":"Purchaser Terms of Service"}}}}
SuperSonicHub1 commented 1 year ago

Here's all the operations I was able to extract from a decomp (using JADX) of the Twitter APK (version 9.95.0-release.0): operations.txt It would seem that the Android and web clients use different queries, as I was unable to find any @Write mentioned.

It looks like Twitter's Android app makes use of Apollo, so there's a chance we may be able to fully extract all info about the schema from it; it will just require some reverse engineering.

devgaucho commented 1 year ago

Does anyone know how bird.makeup still manage to work correctly ? According to it's author it has 40% failure rate but that's still more than enough. https://sr.ht/~cloutier/bird.makeup/ His Twoot : https://social.librem.one/@vincent/110645408061048325

probably he uses twitter's paid api and makes a local cache to avoid repeating requests even so he seems to be suffering with the platform's instability

confirmated.

But that doesn't make any sense, you can selfhost it, there's no way he just share publicly his paid apikey .. ?

In his documentation you don't have to fill any apikey https://git.sr.ht/~cloutier/bird.makeup/tree/master/item/VARIABLES.md

He seems to use some sort of_ API 1.1 and / GraphQL _combo : https://git.sr.ht/~cloutier/bird.makeup/commit/612637fdc7f667d2e491117ea84cd2e60b5c85e6

EDIT : Nevermind he seems to use some sort of_ API Key indeed, as you can see in the function `private async _Task<_string_> GenerateBearerToken()insrc/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs`

this is a hard coded persistent graphql query:

https://imgur.com/lsRr5CP.png

SuperSonicHub1 commented 1 year ago

Here's how you can get Elon Musk's profile info using the Android API:

$ curl -s -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" -H "x-guest-token: 1675958319943393280" -H "x-twitter-active-user: yes" -H "Referer: https://twitter.com" "https://api.twitter.com/graphql/oPppcargziU1uDQHAUmH-A/UserResultByIdQuery?features=%7B%22blue_business_profile_image_shape_enabled%22%3A%20false%2C%20%22verified_phone_label_enabled%22%3A%20false%2C%20%22super_follow_user_api_enabled%22%3A%20false%2C%20%22super_follow_badge_privacy_enabled%22%3A%20false%2C%20%22subscriptions_verification_info_enabled%22%3A%20false%2C%20%22creator_subscriptions_subscription_count_enabled%22%3A%20false%2C%20%22super_follow_exclusive_tweet_notifications_enabled%22%3A%20false%7D&variables=%7B%22user_id%22%3A%20%2244196397%22%2C%20%22rest_id%22%3A%20%2244196397%22%7D"
{"data":{"user_result":{"result":{"__typename":"User","rest_id":"44196397","has_nft_avatar":false,"is_blue_verified":true,"affiliates_highlighted_label":{"label":{"url":{"urlType":"DeepLink","url":"https://twitter.com/Twitter"},"badge":{"url":"https://pbs.twimg.com/profile_images/1488548719062654976/u6qfBBkF_bigger.jpg"},"userLabelType":"BusinessLabel","userLabelDisplayType":"Badge","description":"Twitter"}},"legacy":{"advertiser_account_service_levels":["analytics"],"advertiser_account_type":"promotable_user","analytics_type":"enabled","created_at":"Tue Jun 02 20:12:29 +0000 2009","description":"","entities":{"description":{"hashtags":[],"symbols":[],"urls":[],"user_mentions":[]}},"fast_followers_count":0,"favourites_count":26922,"followers_count":146412716,"friends_count":342,"geo_enabled":false,"has_custom_timelines":true,"has_extended_profile":true,"id_str":"44196397","is_translator":false,"location":"","media_count":1605,"name":"Elon Musk","normal_followers_count":146412716,"pinned_tweet_ids_str":[],"profile_background_color":"C0DEED","profile_banner_url":"https://pbs.twimg.com/profile_banners/44196397/1576183471","profile_image_url_https":"https://pbs.twimg.com/profile_images/1590968738358079488/IY9Gx6Ok_normal.jpg","profile_interstitial_type":"","profile_link_color":"0084B4","protected":false,"screen_name":"elonmusk","statuses_count":27575,"translator_type_enum":"None","verified":false,"withheld_in_countries":[]},"business_account":{}}}}}
mthmcalixto commented 1 year ago

Here's how you can get Elon Musk's profile info using the Android API:

$ curl -s -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" -H "x-guest-token: 1675958319943393280" -H "x-twitter-active-user: yes" -H "Referer: https://twitter.com" "https://api.twitter.com/graphql/oPppcargziU1uDQHAUmH-A/UserResultByIdQuery?features=%7B%22blue_business_profile_image_shape_enabled%22%3A%20false%2C%20%22verified_phone_label_enabled%22%3A%20false%2C%20%22super_follow_user_api_enabled%22%3A%20false%2C%20%22super_follow_badge_privacy_enabled%22%3A%20false%2C%20%22subscriptions_verification_info_enabled%22%3A%20false%2C%20%22creator_subscriptions_subscription_count_enabled%22%3A%20false%2C%20%22super_follow_exclusive_tweet_notifications_enabled%22%3A%20false%7D&variables=%7B%22user_id%22%3A%20%2244196397%22%2C%20%22rest_id%22%3A%20%2244196397%22%7D"
{"data":{"user_result":{"result":{"__typename":"User","rest_id":"44196397","has_nft_avatar":false,"is_blue_verified":true,"affiliates_highlighted_label":{"label":{"url":{"urlType":"DeepLink","url":"https://twitter.com/Twitter"},"badge":{"url":"https://pbs.twimg.com/profile_images/1488548719062654976/u6qfBBkF_bigger.jpg"},"userLabelType":"BusinessLabel","userLabelDisplayType":"Badge","description":"Twitter"}},"legacy":{"advertiser_account_service_levels":["analytics"],"advertiser_account_type":"promotable_user","analytics_type":"enabled","created_at":"Tue Jun 02 20:12:29 +0000 2009","description":"","entities":{"description":{"hashtags":[],"symbols":[],"urls":[],"user_mentions":[]}},"fast_followers_count":0,"favourites_count":26922,"followers_count":146412716,"friends_count":342,"geo_enabled":false,"has_custom_timelines":true,"has_extended_profile":true,"id_str":"44196397","is_translator":false,"location":"","media_count":1605,"name":"Elon Musk","normal_followers_count":146412716,"pinned_tweet_ids_str":[],"profile_background_color":"C0DEED","profile_banner_url":"https://pbs.twimg.com/profile_banners/44196397/1576183471","profile_image_url_https":"https://pbs.twimg.com/profile_images/1590968738358079488/IY9Gx6Ok_normal.jpg","profile_interstitial_type":"","profile_link_color":"0084B4","protected":false,"screen_name":"elonmusk","statuses_count":27575,"translator_type_enum":"None","verified":false,"withheld_in_countries":[]},"business_account":{}}}}}

It stops working fast.

Write commented 1 year ago

Here's how you can get Elon Musk's profile info using the Android API:

$ curl -s -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" -H "x-guest-token: 1675958319943393280" -H "x-twitter-active-user: yes" -H "Referer: https://twitter.com" "https://api.twitter.com/graphql/oPppcargziU1uDQHAUmH-A/UserResultByIdQuery?features=%7B%22blue_business_profile_image_shape_enabled%22%3A%20false%2C%20%22verified_phone_label_enabled%22%3A%20false%2C%20%22super_follow_user_api_enabled%22%3A%20false%2C%20%22super_follow_badge_privacy_enabled%22%3A%20false%2C%20%22subscriptions_verification_info_enabled%22%3A%20false%2C%20%22creator_subscriptions_subscription_count_enabled%22%3A%20false%2C%20%22super_follow_exclusive_tweet_notifications_enabled%22%3A%20false%7D&variables=%7B%22user_id%22%3A%20%2244196397%22%2C%20%22rest_id%22%3A%20%2244196397%22%7D"
{"data":{"user_result":{"result":{"__typename":"User","rest_id":"44196397","has_nft_avatar":false,"is_blue_verified":true,"affiliates_highlighted_label":{"label":{"url":{"urlType":"DeepLink","url":"https://twitter.com/Twitter"},"badge":{"url":"https://pbs.twimg.com/profile_images/1488548719062654976/u6qfBBkF_bigger.jpg"},"userLabelType":"BusinessLabel","userLabelDisplayType":"Badge","description":"Twitter"}},"legacy":{"advertiser_account_service_levels":["analytics"],"advertiser_account_type":"promotable_user","analytics_type":"enabled","created_at":"Tue Jun 02 20:12:29 +0000 2009","description":"","entities":{"description":{"hashtags":[],"symbols":[],"urls":[],"user_mentions":[]}},"fast_followers_count":0,"favourites_count":26922,"followers_count":146412716,"friends_count":342,"geo_enabled":false,"has_custom_timelines":true,"has_extended_profile":true,"id_str":"44196397","is_translator":false,"location":"","media_count":1605,"name":"Elon Musk","normal_followers_count":146412716,"pinned_tweet_ids_str":[],"profile_background_color":"C0DEED","profile_banner_url":"https://pbs.twimg.com/profile_banners/44196397/1576183471","profile_image_url_https":"https://pbs.twimg.com/profile_images/1590968738358079488/IY9Gx6Ok_normal.jpg","profile_interstitial_type":"","profile_link_color":"0084B4","protected":false,"screen_name":"elonmusk","statuses_count":27575,"translator_type_enum":"None","verified":false,"withheld_in_countries":[]},"business_account":{}}}}}

It stops working fast.

That's expected and normal you just have to refresh your guest_token.

polkaulfield commented 1 year ago

Ok this seems interesting, he seems he is able to get a Bearer tokens using built-in tokens from APPS as seens here Screenshot du 2023-07-03 à 21 50 59

https://git.sr.ht/~cloutier/bird.makeup/tree/master/item/src/BirdsiteLive.Twitter/Tools/TwitterAuthenticationInitializer.cs#L36

He then picks one randomly and send a post request in GenerateBearerToken() which correctly returns a Bearer Tokens

You can try yourself with curl : curl -X POST "https://api.twitter.com/oauth2/token?grant_type=client_credentials" -u "CjulERsDeqhhjSme66ECg:IQWdVyqFxghAtURHGeGiWAsmCAGmdW3WmbEx6Hck"

You can then activate your fresh access_token :

curl -X POST -H "Authorization: Bearer your_access_token" "https://api.twitter.com/1.1/guest/activate.json"

This will return you a guest_token.

You can then access graphql endpoints :

curl -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" \
-H "x-guest-token: 1675958319943393280" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com/" \
"https://api.twitter.com/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName?variables=%7B%22screen_name%22%3A%22elonmusk%22%2C%22withSafetyModeUserFields%22%3Atrue%7D&features=%7B%22hidden_profile_likes_enabled%22%3Afalse%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D"

where x-guest-token is the token returned when you activated your Bearer.

You can get the user numeric ID from previous request, from twitter name : for elonmusk :

curl -s -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" \
-H "x-guest-token: 1675958319943393280" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com/" \
"https://api.twitter.com/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName?variables=%7B%22screen_name%22%3A%22elonmusk%22%2C%22withSafetyModeUserFields%22%3Atrue%7D&features=%7B%22hidden_profile_likes_enabled%22%3Afalse%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D" \
| jq -r '.data.user.result.rest_id'

Then get all his timeline :

curl -s -X GET -H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR" \
-H "x-guest-token: 1675958319943393280" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com" "https://api.twitter.com/graphql/pNl8WjKAvaegIoVH--FuoQ/UserTweetsAndReplies?variables=%7B%22userId%22%3A%2244196397%22,%22count%22%3A40,%22includePromotedContent%22%3Atrue,%22withCommunity%22%3Atrue,%22withSuperFollowsUserFields%22%3Atrue,%22withDownvotePerspective%22%3Afalse,%22withReactionsMetadata%22%3Afalse,%22withReactionsPerspective%22%3Afalse,%22withSuperFollowsTweetFields%22%3Atrue,%22withVoice%22%3Atrue,%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue,%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue,%22verified_phone_label_enabled%22%3Afalse,%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue,%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse,%22tweetypie_unmention_optimization_enabled%22%3Atrue,%22vibe_api_enabled%22%3Atrue,%22responsive_web_edit_tweet_api_enabled%22%3Atrue,%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue,%22view_counts_everywhere_api_enabled%22%3Atrue,%22longform_notetweets_consumption_enabled%22%3Atrue,%22tweet_awards_web_tipping_enabled%22%3Afalse,%22freedom_of_speech_not_reach_fetch_enabled%22%3Afalse,%22standardized_nudges_misinfo%22%3Atrue,%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse,%22interactive_text_enabled%22%3Atrue,%22responsive_web_text_conversations_enabled%22%3Afalse,%22longform_notetweets_richtext_consumption_enabled%22%3Afalse,%22responsive_web_enhance_cards_enabled%22%3Afalse%7D" \
| jq

You have to replace the userID between the two "%22" tags. Here the user id (of elonmusk) is : 44196397

Which returns All his Tweets and Replies (too long):

https://bin.socialspill.com/ixomusor.json

To access detail of a specific tweet the url is :

https://api.twitter.com/graphql/XjlydVWHFIDaAUny86oh2g/TweetDetail?variables=%7B%22focalTweetId%22%3A% + statusId + %22,%22with_rux_injections%22%3Atrue,%22includePromotedContent%22%3Afalse,%22withCommunity%22%3Afalse,%22withQuickPromoteEligibilityTweetFields%22%3Afalse,%22withBirdwatchNotes%22%3Afalse,%22withSuperFollowsUserFields%22%3Afalse,%22withDownvotePerspective%22%3Afalse,%22withReactionsMetadata%22%3Afalse,%22withReactionsPerspective%22%3Afalse,%22withSuperFollowsTweetFields%22%3Afalse,%22withVoice%22%3Atrue,%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue,%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue,%22verified_phone_label_enabled%22%3Afalse,%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue,%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse,%22tweetypie_unmention_optimization_enabled%22%3Atrue,%22vibe_api_enabled%22%3Atrue,%22responsive_web_edit_tweet_api_enabled%22%3Atrue,%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Afalse,%22view_counts_everywhere_api_enabled%22%3Atrue,%22longform_notetweets_consumption_enabled%22%3Atrue,%22tweet_awards_web_tipping_enabled%22%3Afalse,%22freedom_of_speech_not_reach_fetch_enabled%22%3Afalse,%22standardized_nudges_misinfo%22%3Atrue,%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse,%22interactive_text_enabled%22%3Atrue,%22responsive_web_text_conversations_enabled%22%3Afalse,%22longform_notetweets_richtext_consumption_enabled%22%3Afalse,%22responsive_web_enhance_cards_enabled%22%3Atrue%7D

Poor man's nitter:

#!/bin/sh

[ -z "$1" ] && echo "USAGE: $(basename $0) username_to_search" && exit

USERNAME=$1

echo "Searching..."

ACCESS_TOKEN=$(curl -s -X POST "https://api.twitter.com/oauth2/token?grant_type=client_credentials" -u "CjulERsDeqhhjSme66ECg:IQWdVyqFxghAtURHGeGiWAsmCAGmdW3WmbEx6Hck" | jq .access_token | cut -d '"' -f 2)

GUEST_TOKEN=$(curl -s -X POST -H "Authorization: Bearer $ACCESS_TOKEN" "https://api.twitter.com/1.1/guest/activate.json" | jq .guest_token | cut -d '"' -f 2)

USER_ID=$(curl -s -X GET -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-guest-token: $GUEST_TOKEN" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com/" \
"https://api.twitter.com/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName?variables=%7B%22screen_name%22%3A%22$USERNAME%22%2C%22withSafetyModeUserFields%22%3Atrue%7D&features=%7B%22hidden_profile_likes_enabled%22%3Afalse%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22subscriptions_verification_info_verified_since_enabled%22%3Atrue%2C%22highlights_tweets_tab_ui_enabled%22%3Atrue%2C%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%7D" \
| jq -r '.data.user.result.rest_id')

curl -s -X GET -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-guest-token: $GUEST_TOKEN" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com" "https://api.twitter.com/graphql/pNl8WjKAvaegIoVH--FuoQ/UserTweetsAndReplies?variables=%7B%22userId%22%3A%22$USER_ID%22,%22count%22%3A40,%22includePromotedContent%22%3Atrue,%22withCommunity%22%3Atrue,%22withSuperFollowsUserFields%22%3Atrue,%22withDownvotePerspective%22%3Afalse,%22withReactionsMetadata%22%3Afalse,%22withReactionsPerspective%22%3Afalse,%22withSuperFollowsTweetFields%22%3Atrue,%22withVoice%22%3Atrue,%22withV2Timeline%22%3Atrue%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue,%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue,%22verified_phone_label_enabled%22%3Afalse,%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue,%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse,%22tweetypie_unmention_optimization_enabled%22%3Atrue,%22vibe_api_enabled%22%3Atrue,%22responsive_web_edit_tweet_api_enabled%22%3Atrue,%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue,%22view_counts_everywhere_api_enabled%22%3Atrue,%22longform_notetweets_consumption_enabled%22%3Atrue,%22tweet_awards_web_tipping_enabled%22%3Afalse,%22freedom_of_speech_not_reach_fetch_enabled%22%3Afalse,%22standardized_nudges_misinfo%22%3Atrue,%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse,%22interactive_text_enabled%22%3Atrue,%22responsive_web_text_conversations_enabled%22%3Afalse,%22longform_notetweets_richtext_consumption_enabled%22%3Afalse,%22responsive_web_enhance_cards_enabled%22%3Afalse%7D" \
| jq '..|.full_text?|select(length>0)'
Write commented 1 year ago

Poor man's nitter:

🤣

poor_man_nitter.sh

polkaulfield commented 1 year ago

Poor man's nitter:

rofl

muskie shows up Screenshot from 2023-07-04 01-44-33

Write commented 1 year ago

Love your poor_man_nitter.sh Screenshot du 2023-07-04 à 01 45 31

ultrajae commented 1 year ago

Damn . SO syndication isn't working- there's no working way for the plebs to read a TL is there (other than these shell scripts you wizards are toying with now) .. correct? (no rss-bridge either)

githubtefo commented 1 year ago

poor_man_nitter.sh

hahaha what if @ElonMusk monetizes scrapping for ChatGPT+ providing an improved API instead of chasing users?

UPDATE: apologizes for the off topic - won't bother you again.

Write commented 1 year ago

Damn . SO syndication isn't working- there's no working way for the plebs to read a TL is there (other than these shell scripts you wizards are toying with now) .. correct? (no rss-bridge either)

Not that I found

Write commented 1 year ago

@polkaulfield have you find a way to get only tweets without replies ? or at least that it returns more tweets, because for example, for elon we can't see any of his tweet because of all his replies

polkaulfield commented 1 year ago

Idk, ill take a look tomorrow. I just automated your commands and parsed the output for the laughs. Amazing work finding this API :D

Write commented 1 year ago

I tried the rIIwMe1ObkGh_ByBtTCtRQ/UserTweets to no avail.

It always returns The following features cannot be null: [then it list all features].

Not sure what / if I did something wrong.

curl -s -X GET -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-guest-token: $GUEST_TOKEN" \
-H "x-twitter-active-user: yes" \
-H "Referer: https://twitter.com" "https://twitter.com/i/api/graphql/rIIwMe1ObkGh_ByBtTCtRQ/UserTweets?variables=%7B%22userId%22%3A%22$USER_ID%22,%22count%22%3A20,%22includePromotedContent%22%3Atrue,%22withQuickPromoteEligibilityTweetFields%22%3Atrue,%22withVoice%22%3Atrue,%22withV2Timeline%22%3Atrue%7D%26features%3D%7B%22rweb_lists_timeline_redesign_enabled%22%3Atrue,%22responsive_web_graphql_exclude_directive_enabled%22%3Atrue,%22verified_phone_label_enabled%22%3Afalse,%22creator_subscriptions_tweet_preview_api_enabled%22%3Atrue,%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue,%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse,%22tweetypie_unmention_optimization_enabled%22%3Atrue,%22responsive_web_edit_tweet_api_enabled%22%3Atrue,%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue,%22view_counts_everywhere_api_enabled%22%3Atrue,%22longform_notetweets_consumption_enabled%22%3Atrue,%22responsive_web_twitter_article_tweet_consumption_enabled%22%3Afalse,%22tweet_awards_web_tipping_enabled%22%3Afalse,%22freedom_of_speech_not_reach_fetch_enabled%22%3Atrue,%22standardized_nudges_misinfo%22%3Atrue,%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Atrue,%22longform_notetweets_rich_text_read_enabled%22%3Atrue,%22longform_notetweets_inline_media_enabled%22%3Atrue,%22responsive_web_media_download_video_enabled%22%3Afalse,%22responsive_web_enhance_cards_enabled%22%3Afalse%7D%26fieldToggles%3D%7B%22withArticleRichContentState%22%3Afalse%7D" 
ScamCast commented 1 year ago

Eliminating x-guest-token header has been working for me

Edit: Added authenticate(), TweetDetail()

import requests, json
from pprint import pprint as pp

headers = {
    "Authorization": "Bearer AAAAAAAAAAAAAAAAAAAAAGHtAgAAAAAA%2Bx7ILXNILCqkSGIzy6faIHZ9s3Q%3DQy97w6SIrzE7lQwPJEYQBsArEE2fC25caFwRBvAGi456G09vGR",
    "x-guest-token": "",
    "x-twitter-active-user": "yes",
    "Referer": "https://twitter.com",
    "user-agent": "",
}

def authenticate():
    resp1 = requests.post("https://api.twitter.com/oauth2/token", params={"grant_type": "client_credentials"}, auth=("CjulERsDeqhhjSme66ECg", "IQWdVyqFxghAtURHGeGiWAsmCAGmdW3WmbEx6Hck")).json()
    headers["Authorization"] = f"Bearer {resp1['access_token']}"
    resp2 = requests.post("https://api.twitter.com/1.1/guest/activate.json", headers=headers).json()
    headers["x-guest-token"] = resp2["guest_token"]
    return {"access_token": resp1['access_token'], "guest_token": resp2["guest_token"]}

def UserByScreenName(username):
    return requests.get("https://api.twitter.com/graphql/oUZZZ8Oddwxs8Cd3iW3UEA/UserByScreenName", headers=headers, params={
        "features": json.dumps({
            "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
        }),
        "variables": json.dumps({
            "screen_name": username,
            "withSafetyModeUserFields": True
        })
    }).json()

def UserResultByIdQuery(userId):
    return requests.get("https://api.twitter.com/graphql/oPppcargziU1uDQHAUmH-A/UserResultByIdQuery", headers=headers, params={
        "features": json.dumps({
            "blue_business_profile_image_shape_enabled": False,
            "verified_phone_label_enabled": False,
            "super_follow_user_api_enabled": False,
            "super_follow_badge_privacy_enabled": False,
            "subscriptions_verification_info_enabled": False,
            "creator_subscriptions_subscription_count_enabled": False,
            "super_follow_exclusive_tweet_notifications_enabled": False
        }),
        "variables": json.dumps({
            "user_id": f"{userId}",
            "rest_id": f"{userId}"
        })
    }).json()

def UserTweetsAndReplies(userId):
    return requests.get("https://api.twitter.com/graphql/pNl8WjKAvaegIoVH--FuoQ/UserTweetsAndReplies", headers=headers, params={
        "features": json.dumps({
            "responsive_web_twitter_blue_verified_badge_is_enabled": True,
            "responsive_web_graphql_exclude_directive_enabled": True,
            "verified_phone_label_enabled": False,
            "responsive_web_graphql_timeline_navigation_enabled": True,
            "responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
            "tweetypie_unmention_optimization_enabled": True,
            "vibe_api_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,
            "tweet_awards_web_tipping_enabled": False,
            "freedom_of_speech_not_reach_fetch_enabled": False,
            "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,
            "longform_notetweets_richtext_consumption_enabled": False,
            "responsive_web_enhance_cards_enabled":False
        }),
        "variables": json.dumps({
            "userId": f"{user_id}",
            "count": 40,
            "includePromotedContent": True,
            "withCommunity": True,
            "withSuperFollowsUserFields": True,
            "withDownvotePerspective": False,
            "withReactionsMetadata": False,
            "withReactionsPerspective": False,
            "withSuperFollowsTweetFields": True,
            "withVoice": True,
            "withV2Timeline":True
        })
    }).json()

def TweetDetail(tweetId):
    return requests.get("https://api.twitter.com/graphql/XjlydVWHFIDaAUny86oh2g/TweetDetail", headers=headers, params={
        "features": json.dumps({
            "rweb_lists_timeline_redesign_enabled": True,
            "creator_subscriptions_tweet_preview_api_enabled": True,
            "responsive_web_twitter_article_tweet_consumption_enabled": False,
            "longform_notetweets_rich_text_read_enabled": True,
            "longform_notetweets_inline_media_enabled": True,
            "responsive_web_media_download_video_enabled": True,
            "responsive_web_enhance_cards_enabled": True,
            "responsive_web_twitter_blue_verified_badge_is_enabled":True,
            "responsive_web_graphql_exclude_directive_enabled":True,
            "verified_phone_label_enabled":False,
            "responsive_web_graphql_timeline_navigation_enabled":True,
            "responsive_web_graphql_skip_user_profile_image_extensions_enabled":False,
            "tweetypie_unmention_optimization_enabled":True,
            "vibe_api_enabled":True,
            "responsive_web_edit_tweet_api_enabled":True,
            "graphql_is_translatable_rweb_tweet_is_translatable_enabled":False,
            "view_counts_everywhere_api_enabled":True,
            "longform_notetweets_consumption_enabled":True,
            "tweet_awards_web_tipping_enabled":False,
            "freedom_of_speech_not_reach_fetch_enabled":False,
            "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,
            "longform_notetweets_richtext_consumption_enabled":False,
            "responsive_web_enhance_cards_enabled":True

        }),
        "variables": json.dumps({
            "focalTweetId":tweetId,
            "with_rux_injections":True,
            "includePromotedContent":False,
            "withCommunity":False,
            "withQuickPromoteEligibilityTweetFields":False,
            "withBirdwatchNotes":False,
            "withSuperFollowsUserFields":False,
            "withDownvotePerspective":False,
            "withReactionsMetadata":False,
            "withReactionsPerspective":False,
            "withSuperFollowsTweetFields":False,
            "withVoice":True,
            "withV2Timeline":True
        }),
        "fieldToggles": json.dumps({
            "withArticleRichContentState": False
        })
    }).json()

test_username = 'elonmusk'
test_tweet_id = 1675390796718014464

authenticate()

user_id = UserByScreenName(test_username)['data']['user']['result']['rest_id']
tweets = UserTweetsAndReplies(user_id)['data']['user']['result']['timeline_v2']['timeline']['instructions'][1]['entries']
tweet_thread = TweetDetail(test_tweet_id)['data']['threaded_conversation_with_injections_v2']['instructions'][0]['entries']

pp(tweet_thread)
AlexCSDev commented 1 year ago

Has anyone figured out any graphql endpoint for retrieving tweets by id? I have a list of like several thousand tweets I'd like to get asap with as little manual work as possible.