vivekchakraverty Claude Opus 4.8 commited on
Commit
29c4ada
·
1 Parent(s): dc57844

Add PO Token Grabber browser extension (one-click visitor data + gvs PO token)

Browse files

Chrome/Edge MV3 extension under extension/: a read-only webRequest listener captures the
gvs `pot` from googlevideo videoplayback requests, and the popup reads `visitor_data` from
the active YouTube tab's ytcfg (MAIN-world scripting). Formats the PO token as
`web.gvs+<token>` for one-click copy into the Space's PO-token / visitor-data fields.
README points users to it as the easy alternative to manual DevTools extraction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

README.md CHANGED
@@ -68,6 +68,15 @@ start failing.
68
  Paste the values into the app's **"YouTube access — cookies / proxy"** panel:
69
  **PO token** field and **Visitor data** field.
70
 
 
 
 
 
 
 
 
 
 
71
  ### Get them manually (web client, ~2 min)
72
 
73
  1. Open a fresh **Incognito** window and go to <https://www.youtube.com> (a throwaway
 
68
  Paste the values into the app's **"YouTube access — cookies / proxy"** panel:
69
  **PO token** field and **Visitor data** field.
70
 
71
+ ### Easiest: the PO Token Grabber extension (one click)
72
+
73
+ This repo ships a tiny Chrome/Edge extension in [`extension/`](extension/) that reads the
74
+ PO token + visitor data for you. Download that folder, load it unpacked
75
+ (`chrome://extensions` → Developer mode → Load unpacked), open a YouTube video, press
76
+ play, click the extension, and **Copy** each value into the Space. See
77
+ [`extension/README.md`](extension/README.md). (It's unpublished/sideloaded and may need
78
+ updates when YouTube changes; the manual steps below always work as a fallback.)
79
+
80
  ### Get them manually (web client, ~2 min)
81
 
82
  1. Open a fresh **Incognito** window and go to <https://www.youtube.com> (a throwaway
extension/README.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TutorialMaker — PO Token Grabber (browser extension)
2
+
3
+ A tiny **Chrome/Edge (Manifest V3)** extension that reads the two values the
4
+ [TutorialMaker Space](https://huggingface.co/spaces/vivekchakraverty/TutorialMaker) needs
5
+ to get past YouTube's bot-check **without a proxy**:
6
+
7
+ - **`visitor_data`** — read from the YouTube page's `ytcfg`.
8
+ - **gvs `pot`** (Proof-of-Origin token) — captured from the player's
9
+ `googlevideo.com/videoplayback` request.
10
+
11
+ It just surfaces these for one-click copy; it sends nothing anywhere.
12
+
13
+ ## Install (unpacked)
14
+
15
+ 1. Open `chrome://extensions` (or `edge://extensions`).
16
+ 2. Turn on **Developer mode** (top-right).
17
+ 3. Click **Load unpacked** and select this `extension/` folder.
18
+
19
+ > Firefox: it's MV3-compatible in spirit, but Firefox needs a `background.scripts` entry
20
+ > and a `browser_specific_settings` block instead of `service_worker`. Minor manifest
21
+ > tweaks required.
22
+
23
+ ## Use
24
+
25
+ 1. **Use a throwaway Google account**, not your main one.
26
+ 2. Open a YouTube **video** and press **play** (this makes the player fetch
27
+ `videoplayback`, which carries the `pot`).
28
+ 3. Click the extension icon.
29
+ 4. **Copy PO token** → paste into the Space's **"PO token"** field.
30
+ **Copy visitor data** → paste into the Space's **"Visitor data"** field.
31
+ 5. Run the Space. Tokens expire within hours — re-grab when downloads start failing.
32
+
33
+ ## How it works
34
+
35
+ - `background.js` — a read-only `webRequest` listener on `*://*.googlevideo.com/videoplayback*`
36
+ pulls the `pot` (and `c`/client) query params and stores the latest in
37
+ `chrome.storage.session`. It prefers `c=WEB` tokens (what yt-dlp's web client uses).
38
+ - `popup.js` — on open, runs a MAIN-world script via `chrome.scripting` on the active
39
+ YouTube tab to read `visitor_data`, reads the captured `pot`, and formats the PO token
40
+ as `web.gvs+<token>`.
41
+
42
+ ## Caveats
43
+
44
+ - **Unpublished / sideloaded** — you load it yourself; trust accordingly. The code is
45
+ small and dependency-free on purpose.
46
+ - **YouTube changes break it** — the `pot`/`visitor_data` locations move over time; this
47
+ may need updates. The authoritative manual method lives in the
48
+ [yt-dlp PO Token Guide](https://github.com/yt-dlp/yt-dlp/wiki/PO-Token-Guide).
49
+ - **Per session** — tokens are short-lived; this is a one-click refresh, not a permanent fix.
50
+ - Automating bot-check bypass is the same YouTube-ToS gray area as the rest of this tooling.
extension/background.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Service worker: watch the player's videoplayback requests and capture the gvs PO token.
2
+ //
3
+ // YouTube's web player fetches video data from *.googlevideo.com/videoplayback?...&pot=...
4
+ // where `pot` is the gvs Proof-of-Origin token and `c` is the client (e.g. WEB). We read
5
+ // it (read-only; no blocking) and stash the latest into session storage for the popup.
6
+
7
+ const FILTER = { urls: ["*://*.googlevideo.com/videoplayback*"] };
8
+
9
+ chrome.webRequest.onBeforeRequest.addListener(
10
+ (details) => {
11
+ try {
12
+ const u = new URL(details.url);
13
+ const pot = u.searchParams.get("pot");
14
+ if (!pot) return;
15
+ const client = u.searchParams.get("c") || "";
16
+ // Prefer WEB tokens (what yt-dlp's web client needs); still record others as backup.
17
+ chrome.storage.session.get(["gvsPot", "gvsClient"]).then((cur) => {
18
+ const haveWeb = cur.gvsClient === "WEB";
19
+ if (!haveWeb || client === "WEB") {
20
+ chrome.storage.session.set({
21
+ gvsPot: pot,
22
+ gvsClient: client,
23
+ potTime: Date.now(),
24
+ });
25
+ }
26
+ });
27
+ } catch (e) {
28
+ // ignore malformed URLs
29
+ }
30
+ },
31
+ FILTER
32
+ );
extension/manifest.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "TutorialMaker PO Token Grabber",
4
+ "version": "1.0.0",
5
+ "description": "Reads your YouTube visitor data and gvs PO token so you can paste them into the TutorialMaker Space.",
6
+ "permissions": ["webRequest", "storage", "scripting", "tabs"],
7
+ "host_permissions": ["*://*.youtube.com/*", "*://*.googlevideo.com/*"],
8
+ "background": { "service_worker": "background.js" },
9
+ "action": {
10
+ "default_popup": "popup.html",
11
+ "default_title": "PO Token Grabber"
12
+ }
13
+ }
extension/popup.html ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <style>
6
+ body { font-family: system-ui, sans-serif; width: 340px; margin: 0; padding: 12px; }
7
+ h1 { font-size: 14px; margin: 0 0 8px; }
8
+ label { display: block; font-size: 11px; color: #444; margin: 8px 0 2px; }
9
+ textarea { width: 100%; box-sizing: border-box; font-family: monospace; font-size: 11px;
10
+ resize: vertical; }
11
+ .row { display: flex; gap: 6px; margin-top: 6px; }
12
+ button { flex: 1; padding: 6px; font-size: 12px; cursor: pointer; }
13
+ #status { margin-top: 10px; font-size: 11px; min-height: 14px; }
14
+ #status.ok { color: #1a7f37; }
15
+ #status.warn { color: #9a6700; }
16
+ .hint { font-size: 10px; color: #666; margin-top: 8px; line-height: 1.4; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <h1>PO Token Grabber</h1>
21
+
22
+ <label for="potField">PO token (paste into the Space's "PO token" field)</label>
23
+ <textarea id="potField" rows="2" readonly placeholder="web.gvs+…"></textarea>
24
+
25
+ <label for="visField">Visitor data (paste into the Space's "Visitor data" field)</label>
26
+ <textarea id="visField" rows="2" readonly placeholder="Cgt…%3D%3D"></textarea>
27
+
28
+ <div class="row">
29
+ <button id="copyPot">Copy PO token</button>
30
+ <button id="copyVis">Copy visitor data</button>
31
+ </div>
32
+ <div class="row">
33
+ <button id="refresh">Refresh</button>
34
+ </div>
35
+
36
+ <div id="status"></div>
37
+ <div class="hint">
38
+ Tip: open a YouTube video, press <b>play</b> (so the token is generated), then click
39
+ this icon. Use a throwaway account. Tokens expire within hours.
40
+ </div>
41
+
42
+ <script src="popup.js"></script>
43
+ </body>
44
+ </html>
extension/popup.js ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Popup: read visitor_data from the active YouTube tab (main world) and the captured gvs
2
+ // PO token from session storage, then offer ready-to-paste values for the Space.
3
+
4
+ // Runs in the page's MAIN world via chrome.scripting; must be self-contained.
5
+ function readVisitorData() {
6
+ try {
7
+ if (window.ytcfg && typeof window.ytcfg.get === "function") {
8
+ const v = window.ytcfg.get("VISITOR_DATA");
9
+ if (v) return v;
10
+ const ctx = window.ytcfg.get("INNERTUBE_CONTEXT");
11
+ if (ctx && ctx.client && ctx.client.visitorData) return ctx.client.visitorData;
12
+ }
13
+ } catch (e) {}
14
+ try {
15
+ const r = window.ytInitialPlayerResponse;
16
+ if (r && r.responseContext && r.responseContext.visitorData) {
17
+ return r.responseContext.visitorData;
18
+ }
19
+ } catch (e) {}
20
+ return null;
21
+ }
22
+
23
+ function setStatus(msg, kind) {
24
+ const el = document.getElementById("status");
25
+ el.textContent = msg;
26
+ el.className = kind || "";
27
+ }
28
+
29
+ async function getActiveTab() {
30
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
31
+ return tab;
32
+ }
33
+
34
+ function isYouTube(url) {
35
+ return /^https?:\/\/([^/]*\.)?youtube\.com\//.test(url || "");
36
+ }
37
+
38
+ async function load() {
39
+ const potEl = document.getElementById("potField");
40
+ const visEl = document.getElementById("visField");
41
+
42
+ const tab = await getActiveTab();
43
+ let visitor = null;
44
+ if (tab && isYouTube(tab.url)) {
45
+ try {
46
+ const res = await chrome.scripting.executeScript({
47
+ target: { tabId: tab.id },
48
+ world: "MAIN",
49
+ func: readVisitorData,
50
+ });
51
+ visitor = res && res[0] ? res[0].result : null;
52
+ } catch (e) {}
53
+ }
54
+
55
+ const { gvsPot, gvsClient, potTime } = await chrome.storage.session.get([
56
+ "gvsPot",
57
+ "gvsClient",
58
+ "potTime",
59
+ ]);
60
+
61
+ visEl.value = visitor || "";
62
+ potEl.value = gvsPot ? "web.gvs+" + gvsPot : "";
63
+
64
+ const notes = [];
65
+ if (!isYouTube(tab && tab.url)) {
66
+ notes.push("Open a YouTube video tab, then reopen this popup.");
67
+ } else {
68
+ if (!visitor) notes.push("Couldn't read visitor data — refresh the video page.");
69
+ if (!gvsPot) notes.push("No PO token yet — press play on a video, then reopen.");
70
+ }
71
+ if (gvsPot && gvsClient && gvsClient !== "WEB") {
72
+ notes.push("Captured client is '" + gvsClient + "', not WEB — play in the normal web player.");
73
+ }
74
+ if (potTime) {
75
+ const mins = Math.round((Date.now() - potTime) / 60000);
76
+ notes.push("Token captured ~" + mins + " min ago (they expire within hours).");
77
+ }
78
+ setStatus(notes.join(" ") || "Ready — copy the values into the Space.", notes.length ? "warn" : "ok");
79
+ }
80
+
81
+ async function copy(id, label) {
82
+ const val = document.getElementById(id).value;
83
+ if (!val) {
84
+ setStatus("Nothing to copy yet for " + label + ".", "warn");
85
+ return;
86
+ }
87
+ try {
88
+ await navigator.clipboard.writeText(val);
89
+ setStatus("Copied " + label + " ✓", "ok");
90
+ } catch (e) {
91
+ setStatus("Copy failed — select the text and copy manually.", "warn");
92
+ }
93
+ }
94
+
95
+ document.addEventListener("DOMContentLoaded", () => {
96
+ load();
97
+ document.getElementById("copyPot").addEventListener("click", () => copy("potField", "PO token"));
98
+ document.getElementById("copyVis").addEventListener("click", () => copy("visField", "visitor data"));
99
+ document.getElementById("refresh").addEventListener("click", load);
100
+ });