Warning
Please do not test on many profiles, you can cause rate limiting
READ CAREFULLY BEFORE USING
Important
On first run or reload, please hover on the display name or avatar first
Then hover on the username on the hover card to let the extension to work
I still can't solve this problem
- Country Flags: Automatically displays country flags next to usernames in hover cards
- Location Display: Shows the full country/region name alongside the flag (e.g., "🇺🇸 United States")
- Smart Caching: Caches location data for 30 days to minimize API calls
- Hover Detection: Works on both username links and avatars
- Real-time Updates: Automatically updates when new tweets load
- Settings: Toggle extension on/off and view cached locations
The extension consists of several key components:
- Content Script (
entrypoints/content.ts): Main orchestrator that manages DOM interactions and coordinates other modules - Page Script (
public/page.js): Injected into the page context to intercept Twitter API requests and capture authentication headers - Popup UI (
entrypoints/popup/): React-based popup interface for settings and cache management - Utility Modules (
utils/): Modular code for cache, API requests, DOM utilities, flag insertion, and event handling
-
Header Capture: The page script intercepts Twitter's GraphQL API requests to capture authentication headers (authorization tokens, CSRF tokens, etc.)
-
Hover Detection: When you hover over a username or avatar:
- The content script detects the hover event
- It extracts the username from the DOM element
- Checks if location data exists in cache
-
Location Fetching:
- If cached: Displays flag immediately
- If not cached: Makes a GraphQL API request to Twitter's internal API (
UserByScreenNamequery) - Extracts location from
account_based_infield - Caches the result for 30 days
-
Flag Display:
- Waits for Twitter's hover card popup to appear
- Finds the username span within the popup
- Inserts the flag and country name next to the username
- Uses MutationObserver to re-insert flag if Twitter re-renders the popup
-
Rate Limiting: Implements a request queue with:
- Minimum 2-second interval between requests
- Maximum 2 concurrent requests
- Automatic rate limit detection and handling
utils/cache.ts: Manages persistent storage of location data usingbrowser.storage.localutils/api.ts: Handles API request queue, rate limiting, and communication with page scriptutils/dom-utils.ts: DOM manipulation utilities (username extraction, popup finding)utils/flag-insertion.ts: Logic for inserting flags into hover cardsutils/hover-handlers.ts: Event handlers for hover events on usernames and avatarsutils/event-listeners.ts: Sets up event listeners on Twitter's dynamic DOMutils/country.ts: Maps country/region names to flag emojis
- Node.js 18+ (or Bun)
- npm, yarn, pnpm, or bun package manager
-
Clone the repository:
git clone https://github.com/verse91/twitter-location.git cd twitter-location -
Install dependencies:
npm install # or bun install -
Run in development mode:
bun run dev # or for Firefox bun run dev-fThis will:
- Build the extension in watch mode
- Output to
.output/chrome-mv3(or.output/firefox-mv2) - Automatically rebuild on file changes
-
Load the extension:
- Chrome/Edge: Go to
chrome://extensions/, enable "Developer mode", click "Load unpacked", select the.output/chrome-mv3folder - Firefox: Go to
about:debugging, click "This Firefox", click "Load Temporary Add-on", select the.output/firefox-mv2/manifest.jsonfile
- Chrome/Edge: Go to
Build the extension for production:
bun run build
# or for Firefox
bun run build:firefoxThe built extension will be in .output/chrome-mv3 or .output/firefox-mv2.
Create a ZIP file for distribution:
bun run zip
# or for Firefox
bun run zip:firefoxThe ZIP file will be created in the .output directory.
Check TypeScript types without building:
bun run compiletwitter-location/
├── entrypoints/
│ ├── background.ts # Background script (if needed)
│ ├── content.ts # Main content script orchestrator
│ ├── popup/ # Popup UI
│ │ ├── App.tsx # React popup component
│ │ ├── main.tsx # Popup entry point
│ │ └── style.css # Popup styles
│ └── options/ # Options page (if needed)
├── utils/ # Utility modules
│ ├── cache.ts # Cache management
│ ├── api.ts # API request handling
│ ├── dom-utils.ts # DOM utilities
│ ├── flag-insertion.ts # Flag insertion logic
│ ├── hover-handlers.ts # Hover event handlers
│ ├── event-listeners.ts # Event listener setup
│ └── country.ts # Country/flag mapping
├── public/
│ └── page.js # Page script (injected into page context)
├── wxt.config.ts # WXT configuration
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies and scripts
The page script (public/page.js) is injected into the page context (not content script context) to bypass the Same-Origin Policy. This allows it to:
- Intercept
fetchandXMLHttpRequestcalls - Capture Twitter API headers from authenticated requests
- Make GraphQL API calls using captured credentials
Communication between content script and page script uses window.postMessage:
- Content script sends:
{ type: '__fetchLocation', screenName, requestId } - Page script responds:
{ type: '__locationResponse', screenName, location, requestId }
The extension uses MutationObserver to detect:
- New tweets being added to the timeline
- Hover card popups appearing
- Dynamic content updates
- Wait for hover card popup to appear
- Find the username span (
@username) within the popup - Insert flag element after the username span
- Use MutationObserver to re-insert if Twitter re-renders content
- Only insert into hover cards (not profile pages) by checking for
hoverCardParent
- Cache expiry: 30 days (configurable in
utils/cache.ts) - Cache key:
twitter_location_cache
- Minimum request interval: 2 seconds
- Max concurrent requests: 2
- Configurable in
utils/api.ts
- Check browser console for errors
- Verify extension is enabled in popup
- Clear cache and try again
- Check if user has location set in their Twitter profile
If you see rate limit errors:
- The extension automatically handles rate limits
- Wait for the reset time indicated
- Cached locations will still work
- Ensure Node.js 18+ is installed
- Clear
node_modulesand reinstall:rm -rf node_modules && npm install - Run
npm run compileto check for TypeScript errors
- Chromium based browsers (Manifest V3)
- Firefox based browsers (Manifest V2)
This project is freely licensed under the 0BSD License. You may copy it or modify it or anything else you want.
Please follow our Contributing when you make a pull request.


