Chrome MV3 extension for a Nico-style YouTube live chat overlay. It scores messages locally in JavaScript and uses that score to decide whether a comment should move quickly, normally, or slowly across the video.
The goal is to keep useful comments visible without letting short repeated reactions, emoji floods, or low-value bursts bury everything else.
The extension is published on the Chrome Web Store:
https://chromewebstore.google.com/detail/nkphcfhnfjceplpgcjccnpfdkheafohp
- extension ID:
nkphcfhnfjceplpgcjccnpfdkheafohp - store category: Entertainment
- listing languages: English and Japanese
- support and bug reports: https://github.com/hjosugi/smart-youtube-comment/issues
- privacy policy: docs/PRIVACY.md
The store listing tracks the last version that passed Chrome Web Store review,
so it can trail the version in this repository. To run the newest source, load
extension/ unpacked as described in Load In Chrome.
Published on the Chrome Web Store. Implemented:
- Chrome MV3 extension scaffold
- mobile PWA under
web/ - Cloudflare Worker live-chat relay under
worker/ - YouTube live-chat extraction from all frames
- canvas danmaku renderer in an extension stage frame over the player
- right-click menu on a comment: pin and drag it, or hide everything from its author (saved to the NG list)
- text that grows with the player in fullscreen
- comments keep flowing when YouTube's chat is closed (a hidden chat frame stands in, replays included)
- picture-in-picture with comments (beta, Document Picture-in-Picture)
- JavaScript-only local scorer in
extension/scoring.js - settings and filter UI
- local sandbox and renderer performance probes
- release zip packaging
- security/supply-chain checks
Browser extraction and rendering are the practical bottlenecks, so scoring is kept small and local.
The extension never calls a YouTube API. It reads the chat that YouTube already
renders: extension/content.js runs in the watch page and in YouTube's
live-chat frame.
- The chat frame watches YouTube's live-chat renderer nodes.
- Each new message is read from the DOM, checked against the NG lists, and
normalized into a
ScoreInput. extension/scoring.jsscores it locally: emoji and short reactions flow fast, long text flows slow, and a quality score decides which comments win when the screen is full. Nothing is hidden by the score. The rules, with examples, are in docs/SCORING.md.buildRenderPlan()maps the result into fast / normal / slow display timing.- A background service worker relays messages from chat frames to the top video frame.
- The top frame forwards them to
stage.html, an extension frame laid over the YouTube player, where thedanmaku.jscanvas engine renders them in the extension's own process, off YouTube's main thread. Comments are packed into shared sprite-atlas pages, so the steady state allocates nothing and there is no periodic GC jolt (seedocs/PERFORMANCE.md).
The extension does not fetch remote code. The mobile PWA under web/ gets the
same chat by polling YouTube's InnerTube API through the Worker relay under
worker/. Both paths, with the files and functions involved, are described in
ARCHITECTURE.md.
The extension scores, filters, and renders comments locally in the browser. Display, behavior, and performance settings are saved with Chrome Sync storage when available, so Chrome may sync those settings across the signed-in profile. Blocked users and blocked words are saved only in local extension storage on the current device. Chat text, author names, and block lists are not sent to the developer.
- Chrome or Chromium for loading the extension
- Node.js 22+ and npm 10+ for scripts
- Optional: Bun 1.3+ for faster local scripts
Install JS tooling:
npm installRun the security gate, typecheck, web build, unit suites, browser e2e suites when Chromium is installed, and sandbox smoke checks:
npm testRun the security gate directly:
npm run securityBun equivalents:
bun run test:bun
bun run security:bunRenderer performance probe:
npm run test:e2eReal extension smoke test in Chromium:
npm run test:extOpt-in real YouTube smoke:
SYC_REAL_YOUTUBE_URL="https://www.youtube.com/watch?v=..." npm run test:ext:youtubetest:ext opens a real browser locally. In CI it skips by default unless
SYC_REQUIRE_EXTENSION_E2E=1 is set. To make npm test fail instead of
skipping missing Chromium, set SYC_REQUIRE_E2E=1.
The Cloudflare Worker under worker/ relays YouTube InnerTube live-chat calls.
It defaults to the checked-in WEB client version, but production can override it
without a code change:
wrangler deploy --var INNERTUBE_CLIENT_VERSION:2.20260705.00.00GET /health returns the effective and default InnerTube client versions.
GET /health?deep=1&video=<11-char-id> runs a canary next probe with the same
effective client version; use it from an external scheduled monitor or Cloudflare
Cron Trigger to detect client-version rejection before viewers hit it.
Optional relay controls:
ALLOWED_ORIGINS=https://your-pwa.examplerejects browser callers from other origins.RATE_LIMIT_PER_MINUTE=120limits each client IP per Worker isolate; set0to disable.
Run:
npm run sandboxThen open:
http://127.0.0.1:4173/
The sandbox serves sandbox/index.html, loads the shared JS scorer, simulates
live chat, and renders comments over a fake video surface.
Use this path to run the current source instead of the published store build.
- Open
chrome://extensions. - Enable Developer mode.
- Click
Load unpacked. - Select the
extensiondirectory. - Open a YouTube live stream with live chat.
After changing content scripts or manifest files, reload the extension in
chrome://extensions and reload the YouTube tab.
Expected behavior:
- comments appear over the video in danmaku style
- short or spammy messages move faster
- higher-quality or emphasized messages move slower
- the seekbar-area toggle can hide/show danmaku
- default YouTube chat hide/show follows settings
Known limitation:
- YouTube pop-out chat runs in a separate tab without the video player, so this extension does not render pop-out chat messages over the original video tab. Use the normal embedded chat on the watch page for the overlay.
Create a tester zip:
npm run release:zipBun path:
bun run release:zip:bunArtifacts are written to .release/, which is ignored by Git. See
docs/RELEASE.md.
Store releases go out when a version bump lands on main, through
.github/workflows/chrome-webstore-release.yml,
which then tags vX.Y.Z and creates the GitHub Release.
The store listing and publisher setup is already done; the credentials the
workflow needs are in docs/RELEASE.md.
Set package and manifest versions together:
npm run version:set -- 0.1.1This updates the root, web/, worker/, their lockfile root metadata, and
extension/manifest.json.
| Document | Contents |
|---|---|
| ARCHITECTURE.md | how comments are obtained and rendered, code map, repository layout |
| docs/CONTRACT.md | message, poll envelope, and score shapes |
| docs/SCORING.md | how a comment is scored |
| docs/PERFORMANCE.md | measured bottlenecks and their fixes |
| docs/SECURITY.md | threat model and the security gate |
| docs/PRIVACY.md | privacy policy |
| docs/RELEASE.md | release flow and store automation |
| docs/PLAN.md | open work, gates, migration notes |
| CONTRIBUTING.md | setup, checks, boundaries |
0BSD. You can use, copy, modify, and distribute this project for almost any purpose.
This project is not developed or distributed as a business, and does not infringe the patent rights of DWANGO Co., Ltd. in Japan.
The developer accepts no liability for any damage arising from this extension.