Skip to content

Instantly share code, notes, and snippets.

@kuylar
Last active July 31, 2025 21:04
Show Gist options
  • Select an option

  • Save kuylar/bff978e469961819660ed8ec0e4b2883 to your computer and use it in GitHub Desktop.

Select an option

Save kuylar/bff978e469961819660ed8ec0e4b2883 to your computer and use it in GitHub Desktop.
Small Firefox (and Chrome, maybe, I didn't test it) extension code (content-script) for downloading videos on YouTube.
/**
* Small Firefox (and Chrome, maybe, I didn't test it) extension code
* (content-script) for downloading videos on YouTube. Currently only
* shows a default HTML download button on the top left of the page
* and opens up the format 18 (360p muxed MP4) on a new tab.
*
* It "works", has a "working" poToken implementation (untested for
* downloading videos and unused in this version of the script) and
* can decipher playback URLs automatically.
*
* This was mainly a quick challenge for myself and I don't have any
* intention to finish this project as I have a lot of other stuff to
* work on at the moment. If anyone wants, they can pick from where I
* left off and finish this.
*
* P.S.: I didn't license this code in any way because most of it is
* stolen from NewPipeExtractor & yt-dlp. If you decide to use
* this code as a starting point and publish a finished version,
* keep that in mind.
*
* - kuylar, 17/02/2025
*/
// Taken from NewPipe. Love y'all <3
const poTokenScript = `function loadBotGuard(n){if(this.vm=this[n.globalName],this.program=n.program,this.vmFunctions={},this.syncSnapshotFunction=null,!this.vm)throw Error("[BotGuardClient]: VM not found in the global object");if(!this.vm.a)throw Error("[BotGuardClient]: Could not load program");let t=function(n,t,o,e){this.vmFunctions={asyncSnapshotFunction:n,shutdownFunction:t,passEventFunction:o,checkCameraFunction:e}};return this.syncSnapshotFunction=this.vm.a(this.program,t,!0,this.userInteractionElement,function(){},[[],[]])[0],new Promise(function(n,t){i=0,refreshIntervalId=setInterval(function(){this.vmFunctions.asyncSnapshotFunction&&(n(this),clearInterval(refreshIntervalId)),i>=1e4&&(t("asyncSnapshotFunction is null even after 10 seconds"),clearInterval(refreshIntervalId)),i+=1},1)})}function snapshot(n){return new Promise(function(t,o){if(!this.vmFunctions.asyncSnapshotFunction)return o(Error("[BotGuardClient]: Async snapshot function not found"));this.vmFunctions.asyncSnapshotFunction(function(n){t(n)},[n.contentBinding,n.signedTimestamp,n.webPoSignalOutput,n.skipPrivacyBuffer])})}function runBotGuard(n){let t=n.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;if(t)Function(t)();else throw Error("Could not load VM");let o=[];return loadBotGuard({globalName:n.globalName,globalObj:this,program:n.program}).then(function(n){return n.snapshot({webPoSignalOutput:o})}).then(function(n){return{webPoSignalOutput:o,botguardResponse:n}})}function obtainPoToken(n,t,o){let e=n[0];if(!e)throw Error("PMD:Undefined");let a=e(t);if(!(a instanceof Function))throw Error("APF:Failed");let s=a(o);if(!s)throw Error("YNJ:Undefined");if(!(s instanceof Uint8Array))throw Error("ODM:Invalid");return s};function makeBotguardServiceRequest(e,t){return new Promise(async(r,a)=>{await fetch(e,{headers:{Accept:"application/json","Content-Type":"application/json+protobuf","x-goog-api-key":"AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw","x-user-agent":"grpc-web-javascript/0.1"},method:"POST",body:t}).then(e=>{if(200!=e.status)throw Error("BotGuard request failed");r(e)}).catch(e=>{console.error(e),a(e)})})}function descramble(e){return Array.from((byteArray=Uint8Array.fromBase64(e)).map(e=>e+97)).map(e=>String.fromCharCode(e)).join("")}function parseChallengeData(e){var t="";t=e.length>1&&"string"==typeof e[1]?descramble(e[1]):e[1];let r=JSON.parse(t);var a=r[0],n=r[3],o=r[4],i=r[5],p=r[7],s=r[1]?.find(e=>"string"==typeof e),c=r[2]?.find(e=>"string"==typeof e);return{messageId:a,interpreterJavascript:{privateDoNotAccessOrElseSafeScriptWrappedValue:s,privateDoNotAccessOrElseTrustedResourceUrlWrappedValue:c},interpreterHash:n,program:o,globalName:i,clientExperimentsStateBlob:p}}`;
// Template script to inject signatureCipher and nParam deciphering code
const sigTemplate = `const %%helperObjName%%={%%helperObjBody%%};function decipherSignature(%%sigParam%%){%%sigFuncBody%%};function decipherNParam%%nParamCode%%`;
let signatureTimestamp = 0;
/**
* Downloads a file and returns the result as a string
* @param {String} url
* @returns {String} Contents of the given URL
*/
function downloadFile(url) {
return new Promise((res, rej) => {
fetch(url)
.then((x) => x.text())
.then(res)
.catch(rej);
});
}
/**
* Tries to match input with every pattern inside the regexes array and returns the first one that matches
* @param {String[]} regexes List of RegEx patterns to match input with
* @param {String} input String to check the regexes with
* @returns {RegExpMatchArray | null}
*/
function regexExtractMultiple(regexes, input) {
for (const pattern of regexes) {
const result = input.match(pattern);
if (result) return result;
}
return null;
}
/**
* Injects the given JS code into the HTML
*/
function injectScript(id, js) {
if (document.getElementById(`injectedScript_${id}`)) {
console.error(`Script with ID '${id}' is already injected`);
return;
}
const scr = document.createElement("script");
scr.id = `injectedScript_${id}`;
scr.src = URL.createObjectURL(new Blob([js], { type: "text/javascript" }));
document.body.appendChild(scr);
}
/**
* Downloads and extracts the required parameters to decipher URL signatures
*/
async function getDecipheringStuff() {
const playerHash = await downloadFile(
"https://www.youtube.com/iframe_api"
).then((x) => {
console.log("dlfile", x, typeof x);
return x.match("player\\\\/(.+?)\\\\/")[1];
});
const playerUrl = `https://www.youtube.com/s/player/${playerHash}/player_ias.vflset/en_US/base.js`;
const playerScript = await downloadFile(playerUrl);
console.log("Downloaded player");
signatureTimestamp = parseInt(
playerScript.match("signatureTimestamp[=:] ?(\\d+)")[1]
);
// finds the signature code idk which one until i see the result
const sigFuncName = regexExtractMultiple(
[
"\\b(?<var>[a-zA-Z0-9_$]+)&&\\(\\1=(?<sig>[a-zA-Z0-9_$]{2,})\\(decodeURIComponent\\(\\1\\)\\)",
'(?<sig>[a-zA-Z0-9_$]+)\\s*=\\s*function\\(\\s*(?<arg>[a-zA-Z0-9_$]+)\\s*\\)\\s*{\\s*\\1\\s*=\\s*\\1\\.split\\(\\s*""\\s*\\)\\s*;\\s*[^}]+;\\s*return\\s+\\1\\.join\\(\\s*""\\s*\\)',
'(?:\\b|[^a-zA-Z0-9_$])(?<sig>[a-zA-Z0-9_$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*{\\s*a\\s*=\\s*a\\.split\\(\\s*""\\s*\\)(?:;[a-zA-Z0-9_$]{2}\\.[a-zA-Z0-9_$]{2}\\(a,\\d+\\))?',
// Old patterns
"\\b[cs]\\s*&&\\s*[adf]\\.set\\([^,]+\\s*,\\s*encodeURIComponent\\s*\\(\\s*(?<sig>[a-zA-Z0-9$]+)\\(",
"\\b[a-zA-Z0-9]+\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*encodeURIComponent\\s*\\(\\s*(?<sig>[a-zA-Z0-9$]+)\\(",
"\\bm=(?<sig>[a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)",
// Obsolete patterns
"(\"|\\')signature\\1\\s*,\\s*(?<sig>[a-zA-Z0-9$]+)\\(",
"\\.sig\\|\\|(?<sig>[a-zA-Z0-9$]+)\\(",
"yt\\.akamaized\\.net/\\)\\s*\\|\\|\\s*.*?\\s*[cs]\\s*&&\\s*[adf]\\.set\\([^,]+\\s*,\\s*(?:encodeURIComponent\\s*\\()?\\s*(?<sig>[a-zA-Z0-9$]+)\\(",
"\\b[cs]\\s*&&\\s*[adf]\\.set\\([^,]+\\s*,\\s*(?<sig>[a-zA-Z0-9$]+)\\(",
"\\bc\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*\\([^)]*\\)\\s*\\(\\s*(?<sig>[a-zA-Z0-9$]+)\\(",
],
playerScript
).groups["sig"];
const sigFuncBodyMatch = playerScript.match(
sigFuncName.replace("$", "\\$") +
'=function\\((?<param>.+?)\\){(?<body>.+?;return \\1\\.join\\(\\"\\"\\))};'
);
const sigFuncBody = sigFuncBodyMatch.groups["body"];
const sigFuncHelperObjectName = sigFuncBody.match(";(.+?)\\.")[1];
const sigFuncHelperObject = playerScript.match(
new RegExp(
`${sigFuncHelperObjectName.replace(
"$",
"\\$"
)}=\{(?<body>.+?\})\};`,
"s"
)
).groups["body"];
const nParamMatch = playerScript.match(
/(?:(?:"nn"\[.+?\]|String\.fromCharCode\(110\)),c=a\.get\(b\)|\.get\("n"\)|null)\)&&\(?\S=(?:(?<arr>.+?)\[(?<idx>.+?)\]|(?<func>.+))\(\S+?\)/
);
const nParamArrName = nParamMatch.groups["arr"];
const nParamArrIndex = nParamMatch.groups["idx"];
const nParamRegex = `var ${nParamArrName.replace(
"$",
"\\$"
)}\\s*=\\s*\\[(.+?)][;,]`;
const nParamArr = playerScript.match(nParamRegex)[1].split(",");
const nParamFuncName = nParamArr[parseInt(nParamArrIndex)];
const nParamFuncCode = playerScript.match(
new RegExp(
`${nParamFuncName}=\\s*function(?<body>\\(\\w\\)\\s*\\{.+_w8_.+?\\}\\s*return\\s*\\w+.join\\(\\"\\"\\)};)`,
"ms"
)
);
const sigFunc = sigTemplate
.replace("%%helperObjName%%", sigFuncHelperObjectName)
.replace("%%helperObjBody%%", sigFuncHelperObject)
.replace("%%sigParam%%", sigFuncBodyMatch.groups["param"])
.replace("%%sigFuncBody%%", sigFuncBody)
.replace(
"%%nParamCode%%",
nParamFuncCode.groups["body"].replace(
/if\s?\(\s?typeof\s+.+?\s?=+\s?(["'])undefined\1\s?\)\s?return\s+.+?;/,
";"
)
);
console.log("Extracted player functions", {
signatureTimestamp,
sigFunc,
});
injectScript("decipheringScript", sigFunc);
}
/**
* Deciphers a format to have a valid streaming URL
* @param {Object} format Format taken from InnerTube /player request
* @returns Same format, with the URL key set to a valid URL
*/
function getDecipheredFormat(format) {
let url = format.url;
if (format.signatureCipher) {
const params = new URLSearchParams(format.signatureCipher);
const urlParams = new URLSearchParams(params.get("url").split("?")[1]);
urlParams.append(
params.get("sp"),
this.window.eval("decipherSignature")(params.get("s"))
);
url = params.get("url").split("?")[0] + "?" + urlParams;
}
let urlParams = new URLSearchParams(url.split("?")[1]);
url = url.split("?")[0];
if (urlParams.has("n"))
urlParams.set(
"n",
this.window.eval("decipherNParam")(urlParams.get("n"))
);
format.url = url + "?" + urlParams;
return format;
}
async function downloadVideo() {
const videoId = new URLSearchParams(window.location.search.slice(1)).get(
"v"
);
if (!videoId) {
alert("video id not found D:");
return;
}
const player = await fetch(
"https://www.youtube.com/youtubei/v1/player?prettyPrint=false",
{
headers: {
"User-Agent":
"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0",
Accept: "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Content-Type": "application/json",
"X-Youtube-Client-Name": "1",
"X-Youtube-Client-Version": "2.20250213.05.00",
"X-Origin": "https://www.youtube.com",
},
referrer: "https://www.youtube.com/watch?v=" + videoId,
body: JSON.stringify({
context: {
client: {
hl: "en",
gl: "TR",
// TODO: poToken
// visitorData: "",
userAgent: navigator.userAgent,
clientName: "WEB",
clientVersion: "2.20250213.05.00",
originalUrl:
"https://www.youtube.com/watch?v=" + videoId,
platform: "DESKTOP",
clientFormFactor: "UNKNOWN_FORM_FACTOR",
},
user: {
lockedSafetyMode: false,
},
},
videoId: videoId,
playbackContext: {
contentPlaybackContext: {
currentUrl: "/watch?v=" + videoId,
vis: 0,
splay: false,
autoCaptionsDefaultOn: false,
autonavState: "STATE_OFF",
html5Preference: "HTML5_PREF_WANTS",
signatureTimestamp: signatureTimestamp,
lactMilliseconds: "-1",
watchAmbientModeContext: {
hasShownAmbientMode: true,
hasToggledOffAmbientMode: true,
},
},
},
racyCheckOk: true,
contentCheckOk: true,
// TODO: poToken
//serviceIntegrityDimensions: {
// poToken: ""
//},
}),
method: "POST",
mode: "cors",
}
).then((x) => x.json());
const formats = player.streamingData.formats.map(getDecipheredFormat);
const adaptiveFormats =
player.streamingData.adaptiveFormats.map(getDecipheredFormat);
console.log({
formats,
adaptiveFormats,
});
console.log("opening " + formats[0].url);
window.open(formats[0].url);
}
injectScript("poTokenScript", poTokenScript);
getDecipheringStuff();
const dlButton = document.createElement("button");
dlButton.innerText = "download :3";
dlButton.addEventListener("click", downloadVideo);
dlButton.style.position = "fixed";
dlButton.style.top = "0";
dlButton.style.left = "0";
dlButton.style.zIndex = "999999";
this.document.body.appendChild(dlButton);
console.log(this.document);
console.log("init!! yay");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment