Remove YouTube Suggestions
repository·main·Indexed 20 days ago
https://github.com/lawrencehook/remove-youtube-suggestionsA browser extension for Firefox and Chrome that allows users to hide YouTube recommendations and customize the interface to mitigate the effects of the recommendation algorithm. The project includes the extension source code and the rys-premium-server (v1.0.0), a subscription server providing API endpoints for authentication, license checking, and Stripe billing management.
What's inside RYS
- RYS (Remove YouTube Suggestions) is a browser extension designed to help users take control of their YouTube experience. It allows you to hide recommendations and customize the YouTube interface to avoid the recommendation algorithm's 'rabbit holes'. The extension is free to use, with optional premium features available.
RYS Premium Server File Structure
mainUnderstanding the project layout:
src/: Contains the core logic.index.js: The server entry point.config.js: Configuration logic.routes/: Express route definitions.services/: Business logic for Stripe, JWT, and email.storage/: File-based storage implementation.
data/: Local data storage.grandfathered.json: List of donor emails.auth-requests/: Directory for pending authentication requests (auto-created).rate-limits/: Directory for rate limit counters (auto-created).
tests/: Test suites.package.json: Project dependencies and scripts.
server/ ├── src/ │ ├── index.js # Entry point │ ├── config.js # Configuration │ ├── routes/ # Express routes │ ├── services/ # Stripe, JWT, email │ └── storage/ # File-based storage ├── data/ │ ├── grandfathered.json # Donor emails (gitignored) │ ├── auth-requests/ # Pending auth (auto-created) │ └── rate-limits/ # Rate limit counters (auto-created) ├── tests/ └── package.jsonTransition Behavior for License and Tier Changes
mainThe following table describes how the extension handles different states to ensure user data is preserved even during connectivity or authentication issues:
Transition Stored preferences Effective behavior Refresh pending or transient error Unchanged Keep the current view when available; otherwise use a non-destructive fallback Confirmed premium Unchanged Reapply all stored preferences Confirmed signed-in free Unchanged Apply the allowed slot budget Sign-out or session 401Unchanged Disable premium behavior in memory Note on Staleness: In the current implementation (Phase 1), tier changes (like sign-out or downgrade) may not propagate instantly to already-open YouTube tabs or options pages. Changes will take effect upon the next page load or context initialization.
Understand the RYS Settings Philosophy: Stored vs. Effective
mainThe extension distinguishes between stored preferences (what the user has toggled in settings) and effective behavior (what the extension actually does based on the user's current subscription tier).
- Stored Preferences: These are the user's actual settings saved in
browser.storage.local. They should never be overwritten by tier-based logic (e.g., a downgrade should not delete a user's preference for a premium feature). - Effective Behavior: This is the in-memory application of settings. If a user is on the free tier, the extension applies a 'slot budget' (e.g., only allowing two premium features to be active) by clamping the settings in memory during the apply/render phase, without modifying the underlying stored data.
This separation ensures that if a user re-upgrades their subscription, their previous premium choices are immediately restored without needing to re-import settings.
- Stored Preferences: These are the user's actual settings saved in
Understand the Settings Reset issue and Root Cause
mainUsers may experience an intermittent 'reset' where locally stored premium preferences are reverted to defaults. This is caused by a destructive write-back mechanism that occurs when the client's premium tier cannot be verified.
Root Cause
Premium tier status is inferred from a locally cached license JWT which expires every 3 days (
LICENSE_TOKEN_LIFETIME_DAYS). If the license token expires and the client cannot immediately refresh it (due to the user not opening the options page, network errors, or transient server issues), the system treats the user as being on afree_signed_intier.When this happens, the function
enforceSlotBudget(settings, 2)is triggered, which generates a write-back map that persistsfalsefor premium settings exceeding the allowed slot budget. This permanently overwrites the user's stored preferences inbrowser.storage.local.Key Failure Scenarios
- Expired License + No Refresh: The content script only reads the license; it does not renew it. Only the options page performs a refresh via
refreshLicense. If a user browses YouTube for >3 days without opening the options page, the expired token triggers the budget enforcement. - Options Page Initialization: The options page (
src/options/main.js) performs synchronous initialization before the asynchronousrefreshLicense(true)can confirm premium status, potentially wiping settings upon opening the page if the token is expired. - Transient Renewal Failures: If
License.checkLicense()encounters a network error, it returns{ isPremium: false, error: true }. The UI does not distinguish this from a confirmed free account and proceeds to callpruneToSlotBudget(), persisting the pruned values. - Session Expiry: When a 30-day session token expires (401 error),
updatePremiumUIcallsdisableAllPremiumFeatures(), which persistsfalsefor all premium features.
- Expired License + No Refresh: The content script only reads the license; it does not renew it. Only the options page performs a refresh via
Verify premium feature clamping and preference persistence
mainWhen testing or implementing logic around premium features, ensure that the distinction between 'effective behavior' and 'stored preferences' is maintained.
Even if a user's premium license has expired or they are in an offline state, the extension should clamp the effective view (the features actually active in the UI/content script) without modifying the stored preference keys in
browser.storage.local. This ensures that once a license is renewed, the user's previously selected preferences are still intact.Planned Background License Renewal Logic
mainFuture updates will implement a background-owned scheduled path for license renewal to improve continuity.
Key Implementation Details:
- Trigger: Uses the
alarmspermission to trigger a check when the token is near expiry (defined byLICENSE_REFRESH_THRESHOLD_MS, currently 24h). - Execution: The background context (Service Worker in Chrome, Background Script in Firefox) will call
License.checkLicense(). - CORS & Permissions: Background fetches use the extension origin, which is permitted by the server's CORS policy. No additional
host_permissionsare required. - Error Handling:
- Transient/Non-401 errors: Leave existing auth tokens untouched.
401errors: Explicitly invokeAuth.signOut()and remove auth tokens.- Crucially: Neither outcome should ever write to any preference key in storage.
- Trigger: Uses the
Install RYS — Remove YouTube Suggestions
mainYou can install the RYS extension on the following browsers:
- Firefox: Download from Firefox Add-ons
- Chrome: Download from Chrome Web Store
Implementation Guide: Preventing Preference Destruction (Phase 1)
mainTo fix the bug where tier demotions were overwriting user preferences, the following architectural changes must be implemented to ensure tier enforcement happens only in memory:
- Content Script (
src/content-script/main.js): KeepenforceSlotBudget(settings, limit)but remove thebrowser.storage.local.set(writeBack)call. This ensures the budget is enforced in the current session without persisting the demotion to storage. - Options Page (
src/options/main.js): Perform the same change: clamp the localsettingscopy without writing the returned map back to storage. - Slot Pruning (
pruneToSlotBudget()): Retain the helper and UI updates, but useupdateSetting(id, false, { write: false })to ensure the change is not persisted. - Feature Disabling (
disableAllPremiumFeatures()): Retain the helper and its callers, but useupdateSetting(id, false, { write: false })to prevent storage writes.
Key Rule: Tier logic should never write to storage. It should only coerce values (e.g.,
value === true) in memory during the application of settings.- Content Script (
Set up a local development environment
mainTo develop the extension locally, clone the repository, install the
web-exttool globally, and use the provideddev.shscript to launch the extension in your preferred browser.Prerequisites
npminstalledgitinstalled
Steps
- Clone the repository.
- Install
web-extglobally via npm. - Run the development script for either Firefox or Chrome.
git clone https://github.com/lawrencehook/remove-youtube-suggestions.git cd remove-youtube-suggestions npm install --global web-ext ./dev.sh firefox # opens Firefox with the extension loaded ./dev.sh chrome # builds dist/chrome/ — load as unpacked in chrome://extensionsQuick Start for RYS Premium Server
mainFollow these steps to set up and run the RYS Premium Server locally:
- Install dependencies: Use npm to install required packages.
- Configure environment: Copy the example environment file to
.envand fill in the necessary values. - Verify installation: Run the test suite to ensure everything is working correctly.
- Run the server: Use the development command for local work or the start command for production mode.
# Install dependencies npm install # Copy env and fill in values cp .env.example .env # Run tests npm test # Start development server npm run dev # Start production server npm startManage Grandfathered Users
mainPast donors can be granted lifetime premium access by adding their email addresses to the
data/grandfathered.jsonfile. This file is gitignored to protect user privacy and should be managed locally or via secure deployment processes.["donor1@example.com", "donor2@example.com"]