YouTube without the slot machine.
No Shorts. No home feed. No recommendations. No comments. You pick five videos a day, lock the list, and that is your YouTube until midnight.
Made by Bishowdeep Bhusal aka Limitless.
FocusTube replaces YouTube's decision surface with a fixed daily budget. The mechanism is a content script injected at document_start on every youtube.com page, plus a hardened Windows guard that keeps the extension from being quietly switched off.
Blocked outright
| Surface | Result |
|---|---|
Shorts (/shorts/*) |
Full-screen statement wall, video never plays |
| Home feed | Statement wall — no infinite scroll |
| Recommendations / related | Removed from watch pages |
| Comments | Removed |
| Search results | Visible only while picking; locked after |
| Any video not in today's 5 | Statement wall |
The daily flow
- Picking — search, paste a link, or hit Add on any video page. Up to five.
- Lock in — manual, or automatic on the fifth pick. Final until midnight.
- Watching — only the picked five play. Everything else hits a wall.
- Midnight — state resets, five fresh slots.
State lives in chrome.storage.local under a single key, dated YYYY-M-D. A date mismatch wipes the list on the next navigation.
extension/ Chrome MV3 extension — this is the actual product
manifest.json v2.0.0, stable key → fixed extension ID
shared.js state model: today(), get/set, add/remove/lock, idFromUrl, oembed meta
content.js routing, walls, pick bar, player hook, Shorts sweep
content.css Opal UI — Instrument Serif / Inter / JetBrains Mono
popup.html/js toolbar popup: dots, list, add-by-URL, lock button
bg.js native-messaging heartbeat (guard only)
guard/ Windows enforcement layer
host.py native messaging host — writes heartbeat timestamp
watchdog.ps1 SYSTEM task, 10s loop, hosts-file block when heartbeat is stale
tools/ build + test harness
build_v2.py installs fonts, swaps in ui_v2.css, bumps manifest
guard_setup.py full installer (admin): ext copy, HKLM policies, SYSTEM task, shortcuts
patch_and_test.py Playwright: verifies Shorts chips/links are gone, screenshots
test_live.py Playwright: drives the whole flow, renders icons
ui_v2.css the Opal stylesheet
docs/screenshots/ verification shots from the Playwright runs
chrome://extensions→ Developer mode → Load unpacked- Select
extension/ - Open YouTube. Pick five. Lock in.
Unpacked mode gives you all the blocking, none of the lock-in. Deleting the extension resets everything.
python tools/guard_setup.py
This does six things:
- Stable extension ID. Generates an RSA-2048 key, derives the ID from SHA-256 of the public key, bakes the public key into
manifest.json.idfnchgcpcmghclgihmlhkhlegjdpgbnis the ID that results. - Copies the extension to
C:\ProgramData\FocusTube\extand grants Users modify on the heartbeat folder. - Native messaging host registered in
HKLMfor both Chrome and Edge, scoped tochrome-extension://<id>/only. - Browser policies in
HKLM: incognito off, guest mode off, new-profile creation off, DoH off (so the hosts block can't be routed around), YouTube blocked outright in Edge. - SYSTEM watchdog scheduled task, every 10 seconds. If
hb\chrome.txtis older than 75 seconds — extension disabled, uninstalled, or browser closed — it writes0.0.0.0entries for YouTube and related domains into the hosts file and flushes DNS. - Desktop shortcuts: Request Unlock (24h) and Cancel Unlock.
The loop: extension alive → heartbeat fresh → no hosts block → YouTube works under FocusTube rules. Extension killed → heartbeat stale within 75s → YouTube dies at the DNS layer. You cannot get YouTube back without either re-enabling the extension or waiting out a 24-hour unlock.
Unlock teardown removes the hosts entries, the policies, the native messaging registration, and the scheduled task itself.
Walls, not warnings. Blocked pages get a full-viewport statement, not a banner you scroll past. overflow: hidden on <html>, wall appended to documentElement, re-appended if YouTube's SPA router removes it.
Text-based Shorts sweep. YouTube renames Shorts UI constantly. Rather than chase selectors, sweepShorts() runs every 500ms and hides any chip, tab, nav item, or button whose trimmed text is exactly shorts. Selector-independent, survives redesigns.
No innerHTML anywhere in the content script. YouTube enforces Trusted Types; a single innerHTML write throws and takes the whole script with it. Everything is built through h() — a five-line createElement wrapper.
Single state key, date-stamped. No migrations, no schema. If the date doesn't match today, the state is blank. Reset is free.
Guard is honest about its limits. It stops impulse, not determination. Anyone with admin rights can delete the scheduled task, clear the hosts file, and remove the policies in under a minute. It is a speed bump with teeth, sized for the person who installed it.
From tools/patch_and_test.py against live YouTube:
{
"visible_shorts_chips": 0,
"visible_shorts_links": 0,
"hidden_short_found": "/shorts/M93h41hyPsM",
"real_short": { "playing": false },
"errors": []
}Shorts links still exist in YouTube's DOM — they are hidden, and direct navigation hits the wall with the video never starting. That is the intended behaviour: block the surface, not the URL.
Screenshots in docs/screenshots/ cover home picking, Shorts blocked, search locked, off-list blocked, picked video playing, and the post-patch sweep.
FocusTube.apk ships as a release asset, not in the tree. Package com.focustube.app. Same five-a-day model, enforced on-device:
MainActivity— pick, lock, unlock requestGuardService— accessibility service, watches the YouTube appAdminReceiver— device admin, makes uninstall non-trivial- 24-hour unlock delay, same as the desktop guard
v2.0.0 — desktop extension + Windows guard, verified against live YouTube. Android APK at v1.0.
Not a YouTube product. Not affiliated with Google. Does not modify YouTube's servers, only what your browser renders.
MIT — see LICENSE.
Made by Bishowdeep Bhusal aka Limitless.