How Nitter Identifies and Marks Pinned Tweets in Timeline Parsing
Nitter detects pinned tweets by parsing the TimelinePinEntry instruction in Twitter's GraphQL response and setting a boolean pinned flag on the Tweet object before rendering it with a distinct "Pinned" label.
The open-source Twitter alternative Nitter (zedeus/nitter) parses Twitter's GraphQL timeline JSON to extract tweets and metadata. When processing timeline data, the parser specifically looks for a TimelinePinEntry instruction to identify content that users have pinned to their profiles. This detection happens early in the parsing pipeline, ensuring the pinned status propagates through the type system and into the rendered HTML views.
Detecting Pinned Tweets in the GraphQL Response
In src/parser.nim, the timeline parser walks through the list of instructions returned by Twitter's GraphQL API. The parser checks for pinned content only when processing the first instruction set—specifically when after.len == 0—since pinned tweets always appear at the top of a user's timeline according to the source code.
The TimelinePinEntry Instruction
When the parser encounters an instruction with the type name "TimelinePinEntry", it extracts the embedded tweet and marks it accordingly:
if after.len == 0:
if i.getTypeName == "TimelinePinEntry":
let tweets = extractTweetsFromEntry(i{"entry"})
if tweets.len > 0:
var tweet = tweets[0]
tweet.pinned = true # ← mark the tweet as pinned
result.pinned = some tweet # ← expose the pinned tweet to callers
This logic, found at lines 1008–1014 of src/parser.nim, ensures that only the first tweet in a TimelinePinEntry receives the pinned flag, and the result is stored in the result.pinned option field for downstream consumption.
Storing the Pinned Flag in the Tweet Type
The Tweet type defined in src/types.nim includes a dedicated pinned*: bool field that carries this metadata throughout the application. This boolean allows the view layer to query tweet.pinned when deciding whether to render a pinned indicator, without needing to re-parse the original JSON or check instruction types again.
Rendering the Pinned Label in Views
The view layer consumes the pinned boolean to generate appropriate UI indicators. Two primary locations handle this: the tweet header renderer and the timeline layout manager.
Header Rendering Logic
In src/views/tweet.nim, the renderHeader procedure receives the pinned boolean and conditionally injects a "Pinned" banner:
proc renderHeader(tweet: Tweet; retweet: string; pinned: bool; prefs: Prefs; path = ""): VNode =
buildHtml(tdiv):
if pinned:
let pinnedLabel =
if "/i/communities/" in path: "Pinned by Community mods"
else: "Pinned Tweet"
tdiv(class="pinned"):
span: icon("pin", pinnedLabel)
...
This code, located at lines 29–38, generates a <div class="pinned"> element containing a pin icon and localized text. The logic also differentiates between standard pinned tweets and community-pinned content by checking the request path.
Timeline Layout Adjustments
The timeline view in src/views/timeline.nim adjusts the container styling based on pinned status. At line 80, the code adds a with-header CSS class when a tweet is pinned or is a retweet:
let header = if tweet.pinned or tweet.retweet.isSome: "with-header " else: ""
This class allows the front-end stylesheets—specifically src/sass/timeline.scss at line 197—to apply distinct spacing or border treatments to visually separate pinned content from the chronological stream. The timeline view also respects user preferences to hide pinned tweets entirely.
Summary
- Nitter identifies pinned tweets via the
TimelinePinEntryinstruction type in Twitter's GraphQL response, processed insrc/parser.nimlines 1008–1014. - The parser sets
tweet.pinned = trueonly whenafter.len == 0, ensuring only the initial timeline entry qualifies. - The
Tweettype insrc/types.nimstores the pinned status as a boolean field for type-safe access. - View rendering in
src/views/tweet.nimgenerates a "Pinned Tweet" label using the.pinnedCSS class, with special handling for community pins. - The timeline layout applies a
with-headerclass to pinned tweets insrc/views/timeline.nim, enabling distinct styling and honoring user preferences to hide pinned content.
Frequently Asked Questions
What is TimelinePinEntry in Nitter?
TimelinePinEntry is a specific instruction type in Twitter's GraphQL timeline response that indicates a pinned tweet at the top of a user's profile. Nitter's parser in src/parser.nim detects this entry by checking i.getTypeName == "TimelinePinEntry" and extracts the embedded tweet to mark it as pinned before processing the remaining chronological entries.
How does Nitter distinguish between pinned tweets and regular tweets?
Nitter distinguishes pinned tweets by checking for the TimelinePinEntry instruction type only when parsing the first batch of timeline results (after.len == 0). Regular tweets appear under different instruction types like TimelineAddEntries. Once detected, the parser sets tweet.pinned = true, and downstream code treats this boolean flag as the source of truth for rendering decisions.
Where is the pinned tweet flag stored in Nitter's codebase?
The pinned flag is stored in the Tweet record defined in src/types.nim as a pinned*: bool field. This field is populated during parsing in src/parser.nim and consumed by the view layer in src/views/tweet.nim and src/views/timeline.nim to conditionally render pinned indicators and apply layout classes.
Can users hide pinned tweets in Nitter?
Yes, Nitter respects user preferences to hide pinned tweets. The timeline view in src/views/timeline.nim checks these preferences when constructing the timeline, allowing users to filter out pinned content. When hidden, the pinned tweet is excluded from the rendered timeline entirely, regardless of the TimelinePinEntry detection in the parser.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →