/* Flow console harvester — paste into DevTools Console on the project's Videos page (labs.google/fx/tools/flow/project/ with Videos filter). Auto-scrolls the grid to load every tile, collects every unique video source, and downloads them sequentially as flowvid_NNN.mp4 into the browser's Downloads folder. Grid previews are the full mp4s at ~720p, which is the game's target resolution anyway. Safe: read-only against Flow; no clicks on app buttons. */ (async () => { const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const seen = new Set(); const collect = () => { document.querySelectorAll("video").forEach((v) => { const s = v.currentSrc || v.src || v.querySelector?.("source")?.src; if (s) seen.add(s); }); }; // Find the scrollable container (falls back to window) const scroller = [...document.querySelectorAll("*")].find( (el) => el.scrollHeight > el.clientHeight + 500 && el.clientHeight > 400 ) || document.scrollingElement; console.log("%cHarvest: scrolling to load all tiles…", "color:#0f0"); let lastCount = -1, stable = 0; while (stable < 3) { collect(); scroller.scrollBy?.(0, 900) ?? window.scrollBy(0, 900); await sleep(700); collect(); if (seen.size === lastCount) stable++; else { stable = 0; lastCount = seen.size; } } console.log(`%cHarvest: found ${seen.size} unique videos. Downloading…`, "color:#0f0"); let n = 0; for (const src of seen) { n++; try { const blob = await fetch(src).then((r) => r.blob()); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `flowvid_${String(n).padStart(3, "0")}.mp4`; document.body.appendChild(a); a.click(); a.remove(); console.log(`downloaded ${n}/${seen.size}`); await sleep(1200); // gentle: don't trip the download shelf } catch (e) { console.warn(`miss ${n}: ${e.message}`); } } console.log("%cHarvest complete. Check your Downloads folder.", "color:#0f0"); })();