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

Extension: auto-fill the Space's PO-token / visitor-data fields

Browse files

- app.py: stable elem_ids (tm_pot_token, tm_visitor_data) on the Gradio inputs
- extension: "Fill the Space tab" injects values into the Space (all frames, so it works
at *.hf.space and embedded in huggingface.co/spaces) using the native value setter +
input/change events so Gradio's Svelte bindings update; visitor_data cached in session
storage so fill works when the Space tab is focused; add *://*.hf.space/* host permission
- READMEs updated with the auto-fill flow

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

README.md CHANGED
@@ -72,8 +72,10 @@ Paste the values into the app's **"YouTube access β€” cookies / proxy"** panel:
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
 
 
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 and press
76
+ play, then click the extension **while on the YouTube tab** to capture the values. Open
77
+ this Space in another tab and hit **‡ Fill the Space tab** to inject them straight into
78
+ the fields below (or use the Copy buttons and paste). See
79
  [`extension/README.md`](extension/README.md). (It's unpublished/sideloaded and may need
80
  updates when YouTube changes; the manual steps below always work as a fallback.)
81
 
app.py CHANGED
@@ -297,9 +297,11 @@ def build_ui():
297
  with gr.Row():
298
  po_token_in = gr.Textbox(
299
  label="PO token (see README) β€” CLIENT.CONTEXT+TOKEN", lines=2, scale=3,
 
300
  placeholder="web.gvs+AbC…, web.player+XyZ…")
301
  visitor_data_in = gr.Textbox(
302
  label="Visitor data (pairs with the PO token)", scale=2,
 
303
  placeholder="Cgt...%3D%3D")
304
  proxy_url = gr.Textbox(
305
  label="Proxy URL (optional)", type="password",
 
297
  with gr.Row():
298
  po_token_in = gr.Textbox(
299
  label="PO token (see README) β€” CLIENT.CONTEXT+TOKEN", lines=2, scale=3,
300
+ elem_id="tm_pot_token",
301
  placeholder="web.gvs+AbC…, web.player+XyZ…")
302
  visitor_data_in = gr.Textbox(
303
  label="Visitor data (pairs with the PO token)", scale=2,
304
+ elem_id="tm_visitor_data",
305
  placeholder="Cgt...%3D%3D")
306
  proxy_url = gr.Textbox(
307
  label="Proxy URL (optional)", type="password",
extension/README.md CHANGED
@@ -25,10 +25,15 @@ It just surfaces these for one-click copy; it sends nothing anywhere.
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
 
@@ -36,8 +41,13 @@ It just surfaces these for one-click copy; it sends nothing anywhere.
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
 
 
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 **while on the YouTube tab** β€” this captures the PO token and
29
+ reads your visitor data.
30
+ 4. Open the **TutorialMaker Space** in another tab (the direct `https://<space>.hf.space`
31
+ app URL is the most reliable target).
32
+ 5. Click the extension icon again, then **‡ Fill the Space tab** β€” it injects both values
33
+ straight into the Space's "PO token" and "Visitor data" fields (expand the "YouTube
34
+ access" panel to see them).
35
+ - Or use **Copy PO token** / **Copy visitor data** and paste manually.
36
+ 6. Run the Space. Tokens expire within hours β€” re-grab when downloads start failing.
37
 
38
  ## How it works
39
 
 
41
  pulls the `pot` (and `c`/client) query params and stores the latest in
42
  `chrome.storage.session`. It prefers `c=WEB` tokens (what yt-dlp's web client uses).
43
  - `popup.js` β€” on open, runs a MAIN-world script via `chrome.scripting` on the active
44
+ YouTube tab to read `visitor_data` (cached in session storage), reads the captured
45
+ `pot`, and formats the PO token as `web.gvs+<token>`.
46
+ - **Auto-fill** β€” "Fill the Space tab" injects `fillSpaceFields` into the Space tab (all
47
+ frames, so it works both at `*.hf.space` and when embedded in `huggingface.co/spaces`).
48
+ It sets `#tm_pot_token` / `#tm_visitor_data` using the native value setter plus `input`
49
+ events so Gradio's Svelte bindings register the change. Requires the `*://*.hf.space/*`
50
+ host permission.
51
 
52
  ## Caveats
53
 
extension/manifest.json CHANGED
@@ -4,7 +4,11 @@
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",
 
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": [
8
+ "*://*.youtube.com/*",
9
+ "*://*.googlevideo.com/*",
10
+ "*://*.hf.space/*"
11
+ ],
12
  "background": { "service_worker": "background.js" },
13
  "action": {
14
  "default_popup": "popup.html",
extension/popup.html CHANGED
@@ -26,10 +26,11 @@
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
 
 
26
  <textarea id="visField" rows="2" readonly placeholder="Cgt…%3D%3D"></textarea>
27
 
28
  <div class="row">
29
+ <button id="fillSpace"><b>‡ Fill the Space tab</b></button>
 
30
  </div>
31
  <div class="row">
32
+ <button id="copyPot">Copy PO token</button>
33
+ <button id="copyVis">Copy visitor data</button>
34
  <button id="refresh">Refresh</button>
35
  </div>
36
 
extension/popup.js CHANGED
@@ -1,5 +1,5 @@
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() {
@@ -20,6 +20,33 @@ function readVisitorData() {
20
  return null;
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  function setStatus(msg, kind) {
24
  const el = document.getElementById("status");
25
  el.textContent = msg;
@@ -40,7 +67,10 @@ async function load() {
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({
@@ -48,34 +78,43 @@ async function load() {
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) {
@@ -92,9 +131,41 @@ async function copy(id, label) {
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
  });
 
1
+ // Popup: read visitor_data from the active YouTube tab (main world) + the captured gvs PO
2
+ // token from session storage, then either copy the values or auto-fill the Space's fields.
3
 
4
  // Runs in the page's MAIN world via chrome.scripting; must be self-contained.
5
  function readVisitorData() {
 
20
  return null;
21
  }
22
 
23
+ // Injected into the Space tab (default/isolated world) to fill the Gradio inputs by their
24
+ // elem_id. Uses the native value setter so Gradio's Svelte bindings register the change.
25
+ function fillSpaceFields(poToken, visitorData) {
26
+ function setVal(id, val) {
27
+ if (!val) return false;
28
+ const root = document.getElementById(id);
29
+ if (!root) return false;
30
+ const el = root.querySelector("textarea, input");
31
+ if (!el) return false;
32
+ const proto =
33
+ el.tagName === "TEXTAREA"
34
+ ? window.HTMLTextAreaElement.prototype
35
+ : window.HTMLInputElement.prototype;
36
+ const setter = Object.getOwnPropertyDescriptor(proto, "value").set;
37
+ setter.call(el, val);
38
+ el.dispatchEvent(new Event("input", { bubbles: true }));
39
+ el.dispatchEvent(new Event("change", { bubbles: true }));
40
+ return true;
41
+ }
42
+ let n = 0;
43
+ if (setVal("tm_pot_token", poToken)) n++;
44
+ if (setVal("tm_visitor_data", visitorData)) n++;
45
+ return n;
46
+ }
47
+
48
+ const SPACE_RE = /(\.hf\.space)|(huggingface\.co\/spaces\/)/i;
49
+
50
  function setStatus(msg, kind) {
51
  const el = document.getElementById("status");
52
  el.textContent = msg;
 
67
  const visEl = document.getElementById("visField");
68
 
69
  const tab = await getActiveTab();
70
+
71
+ // Read visitor_data live if we're on YouTube, and remember it for later (e.g. when the
72
+ // Space tab is the one in focus during fill).
73
+ let liveVisitor = null;
74
  if (tab && isYouTube(tab.url)) {
75
  try {
76
  const res = await chrome.scripting.executeScript({
 
78
  world: "MAIN",
79
  func: readVisitorData,
80
  });
81
+ liveVisitor = res && res[0] ? res[0].result : null;
82
  } catch (e) {}
83
+ if (liveVisitor) await chrome.storage.session.set({ visitorData: liveVisitor });
84
  }
85
 
86
+ const store = await chrome.storage.session.get([
87
  "gvsPot",
88
  "gvsClient",
89
  "potTime",
90
+ "visitorData",
91
  ]);
92
+ const visitor = liveVisitor || store.visitorData || "";
93
 
94
+ visEl.value = visitor;
95
+ potEl.value = store.gvsPot ? "web.gvs+" + store.gvsPot : "";
96
+
97
+ // Is the Space open in some tab? Enable the fill button if so.
98
+ const all = await chrome.tabs.query({});
99
+ const spaceTabs = all.filter((t) => SPACE_RE.test(t.url || ""));
100
+ document.getElementById("fillSpace").disabled = spaceTabs.length === 0;
101
 
102
  const notes = [];
103
+ if (!isYouTube(tab && tab.url) && !store.gvsPot) {
104
+ notes.push("Open a YouTube video, press play, then reopen this popup.");
105
  } else {
106
+ if (!visitor) notes.push("No visitor data yet β€” open the popup once on the YouTube tab.");
107
+ if (!store.gvsPot) notes.push("No PO token yet β€” press play on a video, then reopen.");
108
  }
109
+ if (store.gvsPot && store.gvsClient && store.gvsClient !== "WEB") {
110
+ notes.push("Captured client is '" + store.gvsClient + "', not WEB β€” use the normal web player.");
111
  }
112
+ if (spaceTabs.length === 0) notes.push("Open the TutorialMaker Space in a tab to enable auto-fill.");
113
+ if (store.potTime) {
114
+ const mins = Math.round((Date.now() - store.potTime) / 60000);
115
  notes.push("Token captured ~" + mins + " min ago (they expire within hours).");
116
  }
117
+ setStatus(notes.join(" ") || "Ready.", notes.length ? "warn" : "ok");
118
  }
119
 
120
  async function copy(id, label) {
 
131
  }
132
  }
133
 
134
+ async function fillSpace() {
135
+ const poToken = document.getElementById("potField").value;
136
+ const visitorData = document.getElementById("visField").value;
137
+ if (!poToken && !visitorData) {
138
+ setStatus("Nothing to fill yet β€” capture the values first.", "warn");
139
+ return;
140
+ }
141
+ const all = await chrome.tabs.query({});
142
+ const spaceTabs = all.filter((t) => SPACE_RE.test(t.url || ""));
143
+ if (spaceTabs.length === 0) {
144
+ setStatus("No TutorialMaker Space tab found β€” open it first.", "warn");
145
+ return;
146
+ }
147
+ let filled = 0;
148
+ for (const t of spaceTabs) {
149
+ try {
150
+ const results = await chrome.scripting.executeScript({
151
+ target: { tabId: t.id, allFrames: true },
152
+ func: fillSpaceFields,
153
+ args: [poToken, visitorData],
154
+ });
155
+ for (const r of results) filled += r.result || 0;
156
+ } catch (e) {}
157
+ }
158
+ if (filled > 0) {
159
+ setStatus("Filled " + filled + " field(s) in the Space βœ“ (expand the panel to see).", "ok");
160
+ } else {
161
+ setStatus("Couldn't find the fields β€” make sure the Space finished loading.", "warn");
162
+ }
163
+ }
164
+
165
  document.addEventListener("DOMContentLoaded", () => {
166
  load();
167
  document.getElementById("copyPot").addEventListener("click", () => copy("potField", "PO token"));
168
  document.getElementById("copyVis").addEventListener("click", () => copy("visField", "visitor data"));
169
+ document.getElementById("fillSpace").addEventListener("click", fillSpace);
170
  document.getElementById("refresh").addEventListener("click", load);
171
  });