Created
November 1, 2024 01:46
-
-
Save ten4dinosaur/6fdba5ae41d4190d1d0022c8ead108ce to your computer and use it in GitHub Desktop.
This file contains 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
// ==UserScript== | |
// @name YoutubeVideoInfoAdapter | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description YoutubeVideoInfoAdapter | |
// @author You | |
// @grant GM_xmlhttpRequest | |
// @grant GM.xmlHttpRequest | |
// @connect www.youtube.com | |
// @connect googlevideo.com | |
// @run-at document-start | |
// @match https://www.youtube.com/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// set cookies | |
const account = { | |
sid: '...', | |
hsid: '...', | |
ssid: '...', | |
apisid: '...', | |
sapisid: '...', | |
psidts: '...' | |
} | |
const clients = { | |
ios: { | |
clientName: "IOS", | |
clientVersion: "19.09.3", | |
deviceModel: "iPhone14,3" | |
}, | |
web: { | |
clientName: "WEB", | |
clientVersion: "2.20220203.04.00", | |
clientScreen: "WATCH" | |
}, | |
tv: { | |
clientName: "TVHTML5_SIMPLY_EMBEDDED_PLAYER", | |
clientVersion: "2.0", | |
} | |
} | |
const root = typeof unsafeWindow !== "undefined" ? unsafeWindow : window; | |
const getConfigValue = (name) => { | |
const config = root.ytcfg; | |
return (config !== undefined && config !== null) ? config.get(name) : undefined; | |
} | |
const sha1 = async (string) => | |
Array.from(new Uint8Array(await window.crypto.subtle.digest("SHA-1", new TextEncoder().encode(string)))) | |
.map(b => ('00' + b.toString(16)).slice(-2)).join(''); | |
const generateSidBasedAuth = async () => { | |
const timestamp = Math.floor(new Date().getTime() / 1000); | |
return `SAPISIDHASH ${timestamp}_${await sha1(timestamp + " " + account.sapisid + " " + "https://www.youtube.com")}`; | |
} | |
const generateApiRequestHeaders = async (auth) => { | |
const headers = { | |
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36", | |
"content-type": "application/json", | |
"origin": "https://www.youtube.com", | |
} | |
return (auth === true) ? Object.assign({ | |
"cookie": `SID=${account.sid}; HSID=${account.hsid}; SSID=${account.ssid}; APISID=${account.apisid}; SAPISID=${account.sapisid}; __Secure-1PSIDTS=${account.psidts};`, | |
"authorization": await generateSidBasedAuth(), | |
}, headers) : headers; | |
} | |
const generateApiRequestData = (client, videoId) => { | |
return { | |
"videoId": videoId, | |
"context": { | |
"client": Object.assign({ | |
"hl": getConfigValue("HL"), | |
"gl": "US", | |
}, client), | |
}, | |
"playbackContext": { | |
"contentPlaybackContext": { | |
"signatureTimestamp": getConfigValue("STS"), | |
} | |
}, | |
"racyCheckOk": true, | |
"contentCheckOk": true, | |
"startTimeSecs": 0 | |
} | |
} | |
const sendUnauthPlayerRequest = async function(client, videoId) { | |
const response = await fetch(`https://www.youtube.com/youtubei/v1/player?key=${getConfigValue('INNERTUBE_API_KEY')}&prettyPrint=false`, { | |
method: 'POST', | |
headers: await generateApiRequestHeaders(false), | |
body: JSON.stringify(generateApiRequestData(client, videoId)), | |
signal: AbortSignal.timeout(5000), | |
}); | |
return response.json(); | |
} | |
const sendAuthPlayerRequest = async function(client, videoId) { | |
const headers = await generateApiRequestHeaders(true); | |
return await new Promise((resolve, reject) => { | |
GM_xmlhttpRequest({ | |
method: "POST", | |
url: `https://www.youtube.com/youtubei/v1/player?key=${getConfigValue('INNERTUBE_API_KEY')}&prettyPrint=false`, | |
data: JSON.stringify(generateApiRequestData(client, videoId)), | |
headers: headers, | |
onload: (res) => resolve(JSON.parse(res.responseText)), | |
onabort: () => reject(new DOMException("Aborted", "AbortError")), | |
ontimeout: () => reject(new TypeError("Network request failed, timeout")), | |
onerror: (err) => reject(new TypeError("Failed to fetch: " + err.finalUrl)) | |
}) | |
}); | |
} | |
root.getYoutubeVideoInfo = async (videoId, name) => { | |
const client = clients[name]; | |
const video = await sendUnauthPlayerRequest(client, videoId); | |
return (video.playabilityStatus.status !== "LOGIN_REQUIRED" && video.playabilityStatus.status !== "UNPLAYABLE") ? | |
video : await sendAuthPlayerRequest(client, videoId); | |
} | |
let tokenId = undefined; | |
let updateFunction = undefined; | |
Object.defineProperty(root, "forcePoTokenId", { | |
get: () => tokenId, | |
set: (value) => { | |
tokenId = value; | |
if (typeof updateFunction === "function") updateFunction(); | |
}, | |
}); | |
const initPoTokenMocker = (flags) => { | |
const interval = flags.html5_session_po_token_interval_time_ms - 0 || 0; | |
flags.html5_session_po_token_interval_time_ms = "529453054"; | |
const setInterval = root.setInterval; | |
root.setInterval = function(func, delay, ...args) { | |
if (delay !== 529453054) | |
return setInterval.call(this, func, delay, ...args); | |
if (typeof func === "function") { | |
updateFunction = function() { | |
flags.html5_mock_content_binding_for_session_token = | |
tokenId; | |
func(); | |
}; | |
updateFunction(); | |
} | |
return interval !== 0 ? | |
setInterval.call(this, func, interval, ...args) : | |
undefined; | |
}; | |
}; | |
Object.defineProperty(Object.prototype, "html5_web_po_request_key", { | |
get: function() { | |
delete Object.prototype.html5_web_po_request_key; | |
initPoTokenMocker(this); | |
return undefined; | |
}, | |
set: function(value) { | |
delete Object.prototype.html5_web_po_request_key; | |
initPoTokenMocker(this); | |
return (this.html5_web_po_request_key = value); | |
}, | |
enumerable: false, | |
configurable: true, | |
}); | |
// window.getYoutubeVideoInfo | |
// window.forcePoTokenId | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment