Adds ad-skip.mjs: connects over CDP and injects a watcher into every tab (current and future) that clicks "Skip ad" the moment it appears, closes overlay ads, and fast-forwards unskippable ads (seek-to-end + 16x + mute) so they clear in ~1s. Self-contained (no extension, no hosts/network changes) and reconnects across Chrome restarts. Documented in the README. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
stream-test
Operational scripts for manually verifying the selfbot Go-Live broadcast with a real browsing session captured from the X display.
Files
stream-hold.ts- joins the voice channel and keeps the Go-Live stream up until stopped. All params from.env(DISCORD_SELFBOT_TOKEN,DISCORD_GUILD_ID,DISCORD_VOICE_CHANNEL_ID,VNC_RESOLUTION,VNC_FRAMERATE,VNC_BITRATE_KBPS,STREAM_HW,VNC_DISPLAY).human.mjs- human-like interaction helpers. Input is injected into the X server withxdotool(synthetic X input, not a physical HID device, but the browser and the captured screen see genuine pointer/keyboard events with a visibly moving cursor); Playwright only locates elements. Every action is such input: address-bar navigation (Ctrl+L + typing), search typing, clicking the video / settings menu / autoplay toggle / play button, fullscreen via thefkey, and scrolling. Elements are brought into view with a real wheel scroll (no DOM scrollIntoView); if an element has no on-screen box the click fails rather than falling back to a synthetic click. The CDP/DOM API is used only to read state for verification, never to act.scenario.mjs- the browse scenario (YouTube -> IU live -> 1080p -> fullscreen -> Naver -> 나무위키), driven with the human helpers. Connects to a Chrome already running with--remote-debugging-port(CDP_PORT, default 9222) on the streamed display. Captions default OFF, auto-enabling a Korean track when one exists.ad-skip.mjs- persistent YouTube ad auto-skipper. Connects over CDP and injects a watcher into every tab (current and future) that clicks "Skip ad" the instant it appears, closes overlay ads, and fast-forwards unskippable ads (seek-to-end + 16x + mute). Run it alongside the broadcast. Reconnects across Chrome restarts.
Run
# keep the broadcast up (separate process / service)
bun bot/scripts/stream-test/stream-hold.ts
# Chrome on the streamed display with remote debugging, then:
node bot/scripts/stream-test/scenario.mjs
# keep YouTube ads auto-skipped for the whole broadcast (separate process):
node bot/scripts/stream-test/ad-skip.mjs
Recommended Chrome flags on the streamed display (avoids the "restore pages?" bubble after an unclean exit and keeps a single clean window):
google-chrome --remote-debugging-port=9222 --start-maximized \
--hide-crash-restore-bubble --disable-session-crashed-bubble \
--autoplay-policy=no-user-gesture-required <url>
Smooth capture (VNC keepalive)
TigerVNC only refreshes its framebuffer while a VNC client is attached. The
Discord broadcast reads the framebuffer with x11grab (not as a VNC client), so
with no viewer attached the captured screen idles at ~1.5 fps and the stream
looks badly choppy while the cursor still moves smoothly (x11grab overlays the
live cursor each frame). SelfbotStreamer fixes this automatically: it keeps a
tiny headless RFB client (vnc-keepalive.ts) connected for the life of the
stream, requesting incremental updates at the stream framerate. Measured: 3/30
distinct frames without it, ~57/60 with it. The keepalive authenticates with
VNC_PASSWORD (or the ~/.config/tigervnc/passwd file) and is fail-open.
A/B framerate/resolution
Lower settings to compare what Discord actually delivers to viewers, e.g.:
VNC_RESOLUTION=1280x720 VNC_FRAMERATE=30 bun bot/scripts/stream-test/stream-hold.ts
Notes
- Selfbot streaming violates Discord ToS; use a burner account.
- Requires
xdotool, an X display, and a systemffmpegwithx11grab/nvenc. - Prereqs (
playwright, system Chrome) are not bot dependencies; install separately where you run the scenario.