Skip to content

Commit 5175368

Browse files
committed
add post hiding with overlay, whitelist, and following protection
Features: - Replace element removal with overlay approach for post hiding - 50% opacity background matching X.com theme with 8px blur - Preserves original post and all click handlers - Unhidden notice appears below post after unhiding - Add whitelist functionality to never mute specific users - Add following protection setting (default: don't mute followed users) - Combine getUserId and isFollowingUser into single getUserData API call - Add statusScanner for post detail pages with replies - Add unmute API integration when whitelisting users Technical: - Convert cache to accept TTL parameter, add followingCache (5min TTL) - Create hiddenPostNotice and unhiddenNotice UI components - Update scanners to only process mutation.addedNodes - Add in-flight request tracking to prevent race conditions - Fix cache early return bug that prevented post hiding - Update button labels for clarity (Undo→Unhide, Whitelist→Unmute and whitelist) Closes issues with duplicate processing and improves UX with immediate post hiding
1 parent bc7e5f2 commit 5175368

22 files changed

+1367
-157
lines changed

README.md

Lines changed: 108 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ Automatically mute X.com (Twitter) users from specified countries using X's new
44

55
## Features
66

7+
- **Instant Post Hiding**: Posts from blacklisted users are hidden immediately with a 50% opacity overlay and 8px blur effect that adapts to your X.com theme
78
- **Automatic Country Detection**: Uses X.com's AboutAccountQuery API to detect users' originating countries
89
- **Customizable Blacklist**: Specify which countries to automatically mute
9-
- **Multi-Page Support**: Works on timeline, profiles, search results, and notifications
10+
- **Whitelist Support**: Whitelist specific users to never mute them, regardless of country
11+
- **Following Protection**: Optional setting to skip muting users you follow (enabled by default)
12+
- **Multi-Page Support**: Works on timeline, profiles, search results, post detail pages (with replies), and notifications
13+
- **Persistent Cache**: Country and following status cached to reduce API calls
1014
- **Mute Database**: Tracks all automatically muted users with username, country, and timestamp
1115
- **CSV Export**: Export your muted users list to CSV format
1216
- **Cross-Browser**: Supports both Chrome/Edge (Manifest V3) and Firefox (Manifest V2)
@@ -15,11 +19,19 @@ Automatically mute X.com (Twitter) users from specified countries using X's new
1519

1620
When you browse X.com, the extension:
1721

18-
1. Scans pages for user profiles (timeline, search, profiles, notifications)
19-
2. Queries X.com's AboutAccountQuery API to get the user's originating country
20-
3. Checks if the country is in your blacklist
21-
4. Automatically mutes the user if their country matches
22-
5. Saves the muted user to a local database for tracking
22+
1. Scans pages for user profiles (timeline, search, profiles, post detail pages, notifications)
23+
2. Fetches user ID and following status from X.com's UserByScreenName API
24+
3. Checks if user is whitelisted (skips if true)
25+
4. Checks if you follow the user and if "mute following" is disabled (skips if true)
26+
5. Queries X.com's AboutAccountQuery API to get the user's originating country (cached for 24 hours)
27+
6. Checks if the country is in your blacklist
28+
7. **Immediately overlays the post** with a 50% opacity background (matching your theme) and 8px blur, while keeping the original post intact
29+
8. Automatically mutes the user via X.com's API if first time seeing them
30+
9. Saves the muted user to a local database for tracking
31+
32+
The overlay approach preserves all post functionality - click handlers remain intact so you can interact with the post after unhiding it.
33+
34+
All checks use persistent caching to minimize API requests and improve performance.
2335

2436
## Installation
2537

@@ -81,17 +93,35 @@ For permanent installation of unsigned extensions:
8193
- Enter a country name (e.g., "United States", "Antarctica")
8294
- Click "Add" to add it to your blacklist
8395

84-
2. **Browse X.com**
96+
2. **Manage Whitelist**
97+
- Open the extension popup
98+
- In the "Whitelisted Users" section, enter a username
99+
- Click "Add" to whitelist them (they'll never be muted, regardless of country)
100+
- Click the "×" next to a username to remove them from whitelist
101+
102+
3. **Configure Settings**
103+
- Open the extension popup and go to "Settings"
104+
- Toggle "Show country flags" to display flags next to usernames
105+
- Toggle "Also mute users you are following" (disabled by default means you won't mute users you follow)
106+
107+
4. **Browse X.com**
85108
- The extension automatically scans for users as you browse
86-
- When a user from a blacklisted country is found, they're automatically muted
87-
- A toast notification appears when a user is muted
109+
- When a user from a blacklisted country is found, their posts are **immediately hidden** with a blurred overlay
110+
- A toast notification appears when a user is muted via X.com's API
88111

89-
3. **View Muted Users**
112+
5. **Interact with Hidden Posts**
113+
- **Unhide**: Click "Unhide" on the overlay to reveal the post
114+
- A notice appears below the unhidden post: "Post unhidden, but user is still muted"
115+
- Click "Unmute and whitelist" to unmute via X.com API and add user to whitelist (unhides all their posts)
116+
- Click "×" to dismiss the notice
117+
- **Unmute and whitelist from overlay**: Click "Unmute and whitelist" on the overlay to immediately unmute the user via X.com API, add them to whitelist, and unhide all their posts
118+
119+
6. **View Muted Users**
90120
- Open the extension popup to see all muted users
91121
- Sort by username, country, or mute date
92122
- View statistics about total muted users and top countries
93123

94-
4. **Export Data**
124+
7. **Export Data**
95125
- Click "Export to CSV" in the popup
96126
- Download a CSV file with all muted users
97127

@@ -129,11 +159,33 @@ xblockorigin/
129159
├── packages/extension/
130160
│ ├── src/
131161
│ │ ├── Api/ # X.com GraphQL API client
162+
│ │ │ ├── countryQuery.ts # Fetch user country
163+
│ │ │ ├── muteQuery.ts # Mute user via API
164+
│ │ │ ├── unmuteQuery.ts # Unmute user via API
165+
│ │ │ ├── userDataQuery.ts # Combined userId + following fetch
166+
│ │ │ └── schemas.ts # Valibot schemas
132167
│ │ ├── Background/ # Background service worker
133168
│ │ ├── Content/ # Content scripts & scanners
134-
│ │ ├── Popup/ # React popup UI
135-
│ │ ├── Storage/ # IndexedDB database
136-
│ │ └── Utils/ # Cache, rate limiter, CSV exporter
169+
│ │ │ ├── orchestrator.ts # Main processing logic
170+
│ │ │ ├── postHider.ts # Post hiding with overlay
171+
│ │ │ ├── hiddenPostNotice.ts # Hidden post UI components
172+
│ │ │ ├── timelineScanner.ts # Scan timeline
173+
│ │ │ ├── searchScanner.ts # Scan search results
174+
│ │ │ ├── statusScanner.ts # Scan post detail pages
175+
│ │ │ ├── replyScanner.ts # Scan notifications/replies
176+
│ │ │ └── profileScanner.ts # Scan profiles
177+
│ │ ├── Popup/ # Preact popup UI
178+
│ │ │ ├── WhitelistManager.tsx # Whitelist UI
179+
│ │ │ └── Settings.tsx # Settings UI
180+
│ │ ├── Storage/ # Data persistence
181+
│ │ │ ├── database.ts # Muted users storage
182+
│ │ │ ├── whitelist.ts # Whitelist storage
183+
│ │ │ ├── settings.ts # Settings storage
184+
│ │ │ └── schema.ts # Valibot schemas
185+
│ │ └── Utils/ # Utilities
186+
│ │ ├── cache.ts # Persistent cache (24h + 5m TTL)
187+
│ │ ├── rateLimit.ts # API request queue
188+
│ │ └── csvExporter.ts # CSV export
137189
│ ├── manifest.v2.json # Firefox manifest
138190
│ └── manifest.v3.json # Chrome manifest
139191
├── dist/
@@ -146,20 +198,38 @@ xblockorigin/
146198

147199
### API Endpoints
148200

201+
- **UserByScreenName**: `https://x.com/i/api/graphql/-oaLodhGbbnzJBACb1kk2Q/UserByScreenName`
202+
- Returns user ID and following status in a single call
203+
- Used to fetch `rest_id` (user ID) and `relationship_perspectives.following`
149204
- **AboutAccountQuery**: `https://x.com/i/api/graphql/XRqGa7EeokUU5kppkh13EA/AboutAccountQuery`
150205
- Returns user's `account_based_in` field (originating country)
151206
- **MuteUser**: `https://x.com/i/api/graphql/mCclF7Y-cdl87NyYin5M_A/CreateMute`
152207
- Mutes a user by their user ID
208+
- **UnmuteUser**: `https://x.com/i/api/1.1/mutes/users/destroy.json`
209+
- Unmutes a user by their user ID
210+
- Called when "Unmute and whitelist" is clicked
153211

154212
### Storage
155213

156-
- **IndexedDB**: Stores muted users (username, userId, country, mutedAt)
157-
- **chrome.storage.sync**: Stores country blacklist (syncs across devices)
214+
All data is stored using Chrome's storage APIs:
215+
216+
- **chrome.storage.sync**: Stores country blacklist and settings (syncs across devices)
217+
- **chrome.storage.local**: Stores all local data:
218+
- Muted users (username, userId, country, mutedAt)
219+
- Whitelisted users (userId, username, whitelistedAt)
220+
- Persistent cache with TTL:
221+
- User IDs (username → userId, 24 hour TTL)
222+
- Countries (username → country, 24 hour TTL)
223+
- Following status (userId → boolean, 5 minute TTL)
158224

159-
### Rate Limiting
225+
### Request Queue & Optimization
160226

161-
- API requests are rate-limited to 1 request per second
162-
- Country lookups are cached for 24 hours to reduce API load
227+
- API requests are queued and processed sequentially (no concurrent requests)
228+
- Country lookups cached for 24 hours
229+
- Following status cached for 5 minutes
230+
- User ID lookups cached for 24 hours
231+
- In-flight request tracking prevents duplicate API calls for same user
232+
- Combined UserByScreenName call fetches both userId and following status in single request
163233

164234
### Browser Support
165235

@@ -176,19 +246,30 @@ xblockorigin/
176246

177247
- The extension requires an active X.com session to work
178248
- X.com's API may change, requiring updates to query IDs
179-
- Country detection is based on the App Store region where the account was created
249+
- Country detection is based on the geolocation of the user's IP and, according to X, is updated every 30 days
180250

181251
## CI/CD and Releases
182252

183253
This project uses GitLab CI/CD for automated builds and unsigned releases.
184254

255+
### Setup
256+
257+
Install the pre-commit hook to enable automatic version bumping:
258+
259+
```bash
260+
bun run scripts/setup-hooks.ts
261+
```
262+
263+
This installs a pre-commit hook that automatically increments the major version on every commit (e.g., 1.0.0 → 2.0.0 → 3.0.0).
264+
185265
### How Releases Work
186266

187-
Every push to the main branch automatically:
188-
1. Runs linting and type checks
189-
2. Builds both Chrome and Firefox versions
190-
3. Updates extension versions to match the release number (r1 → 1.0.0, r2 → 2.0.0, etc.)
191-
4. Packages both extensions as downloadable ZIP files
192-
5. Creates a GitLab release with auto-incremented version (r1, r2, r3...)
267+
When you push to the main branch with an incremented version:
268+
269+
1. **Verify**: CI checks that version was incremented (fails if pre-commit hook didn't run)
270+
2. **Lint**: Runs type checking and linting
271+
3. **Build**: Builds both Chrome and Firefox versions, injects version into manifests
272+
4. **Package**: Creates ZIP files for both browsers
273+
5. **Release**: Creates a GitLab release with tag derived from major version (1.0.0 → r1, 2.0.0 → r2, etc.)
193274

194-
Releases follow a simple versioning scheme: **r1, r2, r3, r4...** where each number represents a pipeline run.
275+
All extensions are **unsigned** for personal/testing use only.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "xblockorigin",
3-
"version": "22.0.0",
3+
"version": "23.0.0",
44
"type": "module",
55
"scripts": {
66
"postinstall": "bun run setup-hooks",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { makeGraphQLRequest } from './client'
2+
import * as v from 'valibot'
3+
4+
// query id for UserByScreenName from HAR analysis
5+
const USER_BY_SCREEN_NAME_QUERY_ID = '-oaLodhGbbnzJBACb1kk2Q'
6+
7+
// combined schema for userId and following status
8+
const userDataResponseSchema = v.object({
9+
data: v.object({
10+
user: v.object({
11+
result: v.object({
12+
__typename: v.literal('User'),
13+
rest_id: v.string(),
14+
relationship_perspectives: v.optional(
15+
v.object({
16+
following: v.optional(v.boolean())
17+
})
18+
)
19+
})
20+
})
21+
})
22+
})
23+
24+
export type UserData = {
25+
userId: string
26+
following: boolean
27+
}
28+
29+
// combined function to get both userId and following status in single API call
30+
export async function getUserData(username: string): Promise<UserData | null> {
31+
try {
32+
const response = await makeGraphQLRequest(
33+
USER_BY_SCREEN_NAME_QUERY_ID,
34+
'UserByScreenName',
35+
{
36+
screen_name: username,
37+
withSafetyModeUserFields: true
38+
},
39+
userDataResponseSchema
40+
)
41+
42+
return {
43+
userId: response.data.user.result.rest_id,
44+
following:
45+
response.data.user.result.relationship_perspectives
46+
?.following ?? false
47+
}
48+
} catch (error) {
49+
return null
50+
}
51+
}

packages/extension/src/Api/userQuery.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

packages/extension/src/Content/extractors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// shared username extraction utilities
22

3-
export type UsernameCallback = (username: string) => void
3+
export type UsernameCallback = (username: string, element?: Element) => void
44

55
// extract username from href path like /username or /username/status/123
66
function extractUsernameFromHref(href: string): string | null {

0 commit comments

Comments
 (0)