-
-
Notifications
You must be signed in to change notification settings - Fork 538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for displaying Community Notes #1023
base: guest_accounts
Are you sure you want to change the base?
Conversation
Thank you! I really appreciate all the effort you put into this.
It should work by simply changing twitter.com/i/api to api.twitter.com. With that said, there's a better way to get this data which has no rate limit and doesn't use any authorization:
This is indeed a pain in the butt, but I would definitely go with a different solution to keep it simple and less intrusive. Similar logic is done elsewhere by splitting up the API calls, and only fetching what is needed. In this case the communityNote option can be an empty tweet (just Similar-ish behavior: Lines 308 to 309 in d7ca353
More similar from the profile/timeline route (the little photo gallery on profile pages only gets fetched if it's enabled in user preferences, and the user isn't accessing the Media tab): nitter/src/routes/timeline.nim Lines 48 to 50 in d7ca353
Alternatively, you could extend this logic: Lines 110 to 113 in d7ca353
by adding something like this: if not result.tweet.isNil and result.tweet.communityNote.isSome():
result.tweet.communityNote = await getCommunityNote(id) This wouldn't cover replies or other tweets in the thread, but I'd consider that a bit overkill anyway. As a last note, I'd definitely want to put this behind a user preference, but I can take care of that if you're not feeling too adventurous. |
CommunityNote* = object | ||
title*: string | ||
subtitle*: string | ||
footer*: string | ||
url*: string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that the only important thing which changes is subtitle
, the rest can just be hardcoded, turning this into just communityNote*: Option[string]
Unfortunately, that didn't work in my local testing - the api.twitter.com endpoints never seem to contain
Oh, that's very nice! I'll adjust the PR to use that instead
That was the main reason I went with the |
It would still be best to process the thread and replies separately instead of stuffing it into the parser code. Getting the community note for the tweet being viewed seems good enough as a first step. |
Something to consider is showing this content in Nitter's RSS feed pages also. |
Demo
I tested this with tweet http://localhost:8080/elonmusk/status/1692558493255942213#m (with reply http://localhost:8080/teslaownersSV/status/1692597508109684813#m, which still shows the Community Note on the parent tweet in the conversation).
With this pull request:
On Twitter.com:
Retrieving the Community Note
Unfortunately, none of the (undocumented) https://api.twitter.com endpoints used by Nitter seem to include Community Notes contents. However, when setting
withBirdwatchNotes: true
, thegraphTweet
(https://api.twitter.com/graphql/q94uRCEn65LZThakYcPT6g/TweetDetail) API includes ahas_birdwatch_notes
field, which istrue
when a Community Note is present on a tweet. This works both for 'main' selected tweets, and for other tweets in the same conversation.When viewing a tweet while not signed in on twitter.com, the browser makes a call to https://twitter.com/i/api/graphql/DJS3BdhUhcaEpZ7B7irJDg/TweetResultByRestId . This endpoint actually contains Community Notes for the given tweet (if they exist), under a "birdwatch_pivot" key:
However, this endpoint doesn't work with normal OAuth tokens (probably because it's a twitter.com endpoint instead of api.twitter.com). Instead, it uses a
Bearer
token with a hardcoded value, and passes in a "guest token" in anx-guest-token
header. I grabbed this token directly from the Network dev tool in my browser, but it should also be possible to get it from one of the account-creation scripts floating around on the issue tracker.Passing in these values, we get a response containing the above JSON object (along with other tweet data that we don't need), which is everything required to render a Community Note
Implementation
guest_accounts.json
now requires aguest_token
field (alongside theoauth_token
andoauth_token_secret
). This guest token will be passed in as thex-guest-token
header only when retrieving a Community NoteparseGraphTweet
, we check ifhas_birdwatch_notes
istrue
. If it is, we then invoke theTweetDetail
API described above in order to get the Community Note. Since the overwhelming majority of tweets do not have a Community Note, we should callTweetDetail
fairly infrequently (unless Nitter users like to view tweets with Community Notes on them).community-note.scss
file. I'm not a web designer, so I just copy-pastedcard.scss
, removed the ellipses logic, and renamed/removed classes. The result isn't perfect, but it includes all of the elements of a Community Note that display on twitter.com (the link to the note feedback pages, any external links, and the 'Find out more' in the footer).Other notes
I ended up making the request to the
TweetDetail
from withinparseTweet
, which requires making lots ofparse
functions async. This doesn't seem great, but the only alternatives I came up with seemed worse:Future
directly, and onlyawait
it later from some alreadyasync
function. Unfortunately, this breaks theflatty
serialization inredis_cache
, since aFuture
can't be serialized. I didn't see a clear way to make this work withflatty
.I didn't include the Community Notes icon - it's an inline SVG on Twitter.com, and I felt somewhat nervous about copyright issues.
This is my first time using Nim, so please let me know if any of this could be done in a more idiomatic way.