feat(video): share-to-video web route + extension YouTube ingestion#1156
Merged
Conversation
Client-side half of the share-to-video flow paired with zeeguu/api#635. When the user clicks the Zeeguu icon on a YouTube watch page, the extension extracts the caption track on the user's residential IP, POSTs it to /video_upload/create, and opens /shared-video?video_id=<id>, which shows a brief "Preparing <title>..." screen before redirecting to /watch/video. YouTube anti-scraping note: the /api/timedtext baseUrl from playerCaptionsTracklistRenderer now requires a one-shot PO Token (Proof of Origin Token) that we can't reproduce. Direct fetches return empty bodies or HTTP 404 even on a fresh page. The fix is a monkey-patch (youtubePatch.js) injected at document_start into youtube.com tabs via a content script: it wraps fetch and XMLHttpRequest on the page's MAIN world and stores every successful /api/timedtext response on window.__zeeguuCapturedCaptions, keyed by ${videoId}:${langCode}. The share flow then reads from that store. If nothing has been captured yet, the popup prompts the user to enable CC (which triggers the player's own fetch that the patch then intercepts). Pieces: - src/MainAppRouter.js + src/reader/SharedVideoHandler.js: new /shared-video route. Reads ?video_id=, fetches video info, shows the "Preparing <title>..." screen for ~800ms so the title is actually visible, then redirects to the existing /watch/video?id= route (which already has VideoPlayer + TranslateCaptionsControl from #1155). - src/api/userVideos.js: createVideoUpload (JSON body via _postJSON) -- the captions array is awkward as form-encoded data. - src/extension/src/youtube/youtubePatch.js: MAIN-world fetch/XHR wraps that capture timedtext responses, parses JSON3 (the default since 2021) and srv1 XML, scoped per (videoId, langCode) so SPA-nav between videos doesn't confuse captures. - src/extension/src/youtube/youtubeContentScript.js: injects youtubePatch.js via <script src=chrome.runtime.getURL(...)> so YouTube's CSP doesn't reject an inline script. - src/extension/src/shared/sendYouTubeTabToZeeguu.js: orchestrator. Reads captured captions via chrome.scripting.executeScript({ world: "MAIN" }) (synchronous return only -- chrome.scripting does not reliably await async returns across the MAIN-world boundary in MV3). Selects the track for the user's learned_language, preferring manual over auto-generated; surfaces a clear error when the only available captions are in a different language. - src/extension/src/shared/shareYouTubeTab.js: shared entry-point used by both the popup (toolbar click) and background.js (right-click "Read with Zeeguu") -- removes the duplicated branch. - src/extension/manifest.chrome.dev.json: declares the new content_script for *.youtube.com / youtu.be at document_start, plus youtubePatch.js as a web_accessible_resource. - package.json: ext:buildDev now copies the two youtube/* JS files into build/ (and mkdir -p as a small robustness guard). Caveat: this is the same anti-scraping cat-and-mouse that yt-dlp and youtube_transcript_api fight quarterly. The monkey-patch approach sidesteps it cleanly today (YouTube cannot block us reading data the player itself just received), but expect to revisit when YouTube changes the player's caption-fetch path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for voluble-nougat-015dd1 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Client-side half of the share-to-video flow. Pairs with zeeguu/api#635 (already merged + deployed).
End-to-end smoke-tested in prod today: the API side already accepts uploads from the new extension (server log shows POST /video_upload/create → 200 with the captioned video row + topic inferred). This PR adds the missing /shared-video web route that the extension opens on success — the only piece preventing the share flow from completing cleanly.
What happens when a user clicks the Zeeguu icon on a YouTube watch page
isYouTubeTab(tab)matches the URL.shareYouTubeTab(api, tab, WEB_URL)is called from both the popup (toolbar click) andbackground.js(right-click "Read with Zeeguu").chrome.scripting.executeScript({ world: \"MAIN\" })runs an extractor that readswindow.__zeeguuCapturedCaptions[videoId:lang]— populated by the monkey-patch (see below)./video_upload/createalong withvideo_unique_keyandlanguage.${WEB_URL}/shared-video?video_id=<id>.SharedVideoHandlerfetches video info, shows "Preparing <title>…" for ~800ms so the user can read the title, then redirects to/watch/video?id=<id>(existing route → existing VideoPlayer + theTranslateCaptionsControlfrom feat(video): translated subtitles control on shared-video reader (v1.5) #1155).Why a monkey-patch
YouTube's `/api/timedtext` baseUrl from
playerCaptionsTracklistRenderernow requires a one-shot PO Token (Proof of Origin Token) computed by YouTube's BotGuard JS. We cannot reproduce it; direct fetches of the baseUrl (with or withoutfmt=srv1) return empty bodies or HTTP 404. This is the same anti-scraping thatyt-dlpandyoutube_transcript_apifight quarterly.The workaround: capture the player's own successful fetch.
youtubeContentScript.jsruns at `document_start` on `*.youtube.com` / `youtu.be` (ISOLATED world) and injects `youtubePatch.js` via `<script src=chrome.runtime.getURL(...)>` (declared in `web_accessible_resources` so YouTube's CSP accepts it).When nothing has been captured yet, the popup surfaces a clear prompt: "Please turn on subtitles in the YouTube player (CC button) for this video, then click Zeeguu again."
Files
Why `chrome.scripting` returns the data synchronously
In MV3, `chrome.scripting.executeScript({ world: "MAIN" })` does not reliably await async returns across the world boundary — async functions come back with `result: null`. The extractor is therefore synchronous: it only reads from `window.__zeeguuCapturedCaptions` (already populated by the patch) and returns immediately. No fetches happen across the boundary.
Known caveats
Test plan
🤖 Generated with Claude Code