ALWAYS bump the @version in the userscript header when modifying any .user.js file. This is required for userscript managers (Tampermonkey etc.) to detect updates. Use major.minor.bug format: bump bug for fixes, minor for features, major for breaking changes.
- All scripts are self-contained IIFEs with
'use strict' - Use
@grant none(runs in page context) - Match both
https://twitter.com/*andhttps://x.com/* - Only use
@run-at document-startif intercepting fetch/XHR; otherwise use default (document-end) - Namespace:
https://github.com/digitalby
- Tweets:
article[data-testid="tweet"] - User name container:
[data-testid="User-Name"] - Avatar with handle:
[data-testid^="UserAvatar-Container-"] - Tweet three-dot menu:
[data-testid="caret"] - Back button (soft nav):
[data-testid="app-bar-back"] - Retweet button:
[data-testid="retweet"]/[data-testid="unretweet"] - Timestamp/permalink:
timeelement inside anatag - Dropdown menus:
[role="menu"]with[role="menuitem"]children - Tabs:
[role="tablist"]with[role="tab"]children - j/k focused tweet: walk up from
document.activeElementto find enclosingarticle[data-testid="tweet"]
- Always guard against typing: ignore when activeElement is INPUT, TEXTAREA, or contenteditable
- Always ignore when modifier keys (ctrl/cmd/alt) are held
- Handle Twitter's chord shortcuts (e.g.
g+p): track chord prefix keys and skip interception if a chord is pending - For menu interactions: click the button, poll for menu appearance (50ms intervals, max 10 attempts), then find menu item by text content
- Use
history.back()only as fallback — prefer Twitter's[data-testid="app-bar-back"]for soft SPA navigation
- GraphQL query IDs rotate with each deployment — discover from JS bundles, never hardcode
featureSwitchesarray near query definitions can be extracted and all set totruescreen_namehas moved OUT of thelegacyresponse object — use the screen name from the request parameterfollowers_countis inlegacybut use deep search as fallback- Bearer token is public/shared; CSRF token from
ct0cookie - Rate limit: ~1000 requests per 15-minute window; use escalating delays and 429 backoff
- Use
origFetch(saved before overridingwindow.fetch) for own API calls