diff --git a/PSSH Capture/background.js b/PSSH Capture/background.js new file mode 100644 index 0000000..7b27534 --- /dev/null +++ b/PSSH Capture/background.js @@ -0,0 +1,156 @@ +const tabIDs = {}; +const textDecoder = new TextDecoder(); + +function requestToClipboard(tabId) { + chrome.tabs.get(tabId, (details) => { + const lic_headers = tabIDs[details.id].license_request[0]?.license_headers; + const lic_url = tabIDs[details.id].license_url; + const lic_data_json = tabIDs[details.id].license_data; + const widevine_pssh = tabIDs[details.id].pssh; + if (!lic_headers) + return; + + // Fetching the user's ip for setting the header X-Forwarded-For. + // This might help to bypass regional restrictions when performing the license request in some cases. + + const ip_retrieve_link = "https://ipinfo.io/ip"; + + var get_ip = new XMLHttpRequest(); + get_ip.open('GET', ip_retrieve_link, true); + get_ip.onload = function () { + var ip_resposnse = this.responseText; + console.log(ip_resposnse); + + var i = 0; + let curl_license_data = "curl "; + curl_license_data += `'${lic_url}' \\`; + for (; i < lic_headers.length; ++i) + curl_license_data += `\n -H '${lic_headers[i].name.toLowerCase()}: ${lic_headers[i].value}' \\`; + curl_license_data += `\n -H 'x-forwarded-for: ${ip_resposnse}' \\`; + curl_license_data += "\n --data-raw "; + + if (lic_data_json.includes("u0008")) { + curl_license_data += `${lic_data_json} \\`; + } else { + curl_license_data += `'${lic_data_json}' \\`; /* It is not the same as above line. Note the additional ' symbol at the start and end! */ + } + + curl_license_data += "\n --compressed"; + + // Generating the curl license text link for https://t.me/drm_downloader_robot + const license_gen_link = "https://drm-bot.herokuapp.com/gen.php"; + var data = new FormData(); + data.append('playlist', curl_license_data); + data.append('api', 'api'); + + var gen_link = new XMLHttpRequest(); + gen_link.open('POST', license_gen_link, true); + gen_link.onload = function () { + var gen_link_resposnse = this.responseText; + let json_resp = JSON.parse(gen_link_resposnse); + console.log(json_resp); + let generated_license_link = json_resp.data; + + const final = `${widevine_pssh}^${generated_license_link}`; + console.log(final); + + const copyText = document.createElement("textarea"); + copyText.style.position = "absolute"; + copyText.style.left = "-5454px"; + copyText.style.top = "-5454px"; + copyText.style.opacity = 0; + document.body.appendChild(copyText); + copyText.value = final; + copyText.select(); + document.execCommand("copy"); + document.body.removeChild(copyText); + + chrome.browserAction.setBadgeBackgroundColor({color: "#FF0000", tabId: details.id}); + chrome.browserAction.setBadgeText({text: "📋", tabId: details.id}); + console.log("The pssh and the generated link of widevine license curl data has been copied to your clipboard successfully!\n\nNow go to https://t.me/drm_downloader_robot and paste it and send it to the bot."); + + } + gen_link.send(data); + } + get_ip.send(); + }); +} + +function getLicenseRequestData(details) { + tabIDs[details.tabId] = tabIDs[details.tabId] || {}; + if (details.requestBody && details.requestBody.raw && details.method == "POST") { + for (var j = 0; j < details.requestBody.raw.length; ++j) { + try { + const decodedString = textDecoder.decode(details.requestBody.raw[j].bytes); + const encodedString = btoa(unescape(encodeURIComponent(decodedString))); + + // If the license request does not uses json payloads the data has been sent in raw format. + // But the base64 encoded format of it will have the characters "CAES". + if (encodedString.includes("CAES")) { + tabIDs[details.tabId].license_data = `$'\\u0008\\u0004'`; + tabIDs[details.tabId].license_url = details.url; + tabIDs[details.tabId].req_id = details.requestId; + + // If the license request uses json payloads the charcters "CAES" will be there in almost all cases. + } else if (decodedString.includes("CAES") || details.url.includes("license") && decodedString.includes("token") && decodedString.length > 4000 || decodedString.includes("8,1,18")) { + tabIDs[details.tabId].license_data = decodedString; + tabIDs[details.tabId].license_url = details.url; + tabIDs[details.tabId].req_id = details.requestId; + + } else { + return; + } + } catch (e) { + console.error(e); + } + } + } else if (details.requestBody && details.requestBody.formData && details.method == "POST") { + try { + if (details.requestBody.formData.widevine2Challenge) { + const challenge = String(details.requestBody.formData.widevine2Challenge) + if (challenge.includes("CAES")) { + const decodedString = `widevine2Challenge=${challenge}&includeHdcpTestKeyInLicense=true`; + tabIDs[details.tabId].license_data = decodedString; + tabIDs[details.tabId].license_url = details.url; + tabIDs[details.tabId].req_id = details.requestId; + } + } else { + return; + } + } catch (e) { + console.error(e); + } + } +} +chrome.webRequest.onBeforeRequest.addListener( + getLicenseRequestData, + { urls: [""], types: ["xmlhttprequest"] }, + ["requestBody"] +); + +function getLicenseRequestHeaders(details) { + if (details.method == "POST" && tabIDs[details.tabId] && tabIDs[details.tabId].license_url === details.url && tabIDs[details.tabId].req_id === details.requestId) { + console.log(details.url); + tabIDs[details.tabId].license_request.push({license_headers: details.requestHeaders}); + requestToClipboard(details.tabId); + + // Some license requests can hold one time token values in their headers or payload. So blocking such requests before they are sent to the server and capturing them! + // If your license request also holds one time values add them in the below line, seprating by an || operator. Remember to disable the extension if you actually need to play the video of these services! + if (details.url.includes("api2.hbogoasia.com/onwards-widevine") || details.requestHeaders.includes("prepladder.com") || details.url.includes("scvm1sc0.anycast.nagra.com")) { + return { cancel: true }; + } + } +} +chrome.webRequest.onBeforeSendHeaders.addListener( + getLicenseRequestHeaders, + { urls: [""], types: ["xmlhttprequest"] }, + ["requestHeaders", "blocking", "extraHeaders"] +); + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (!request || !sender.tab) + return; + tabIDs[sender.tab.id] = tabIDs[sender.tab.id] || {}; + tabIDs[sender.tab.id] = {license_data: "", license_request: [], license_url: "", req_id: "", pssh: request}; + console.log(`PSSH:- ${tabIDs[sender.tab.id].pssh}`); +}); \ No newline at end of file diff --git a/PSSH Capture/content-script.js b/PSSH Capture/content-script.js new file mode 100644 index 0000000..ff4544d --- /dev/null +++ b/PSSH Capture/content-script.js @@ -0,0 +1,21 @@ +(() => { + "use strict"; + + window.addEventListener("message", (event) => { + if (event.source != window) + return; + + if (event.data.type && event.data.type === "38405bbb-36ef-454d-8b32-346f9564c978") { + if (event.data.log) + chrome.runtime.sendMessage(event.data.log); + } + }, false); + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.defer = false; + script.async = false; + script.src = chrome.extension.getURL("/eme-logger-mod.js"); + (document.head || document.documentElement).appendChild(script); + script.remove(); +})(); \ No newline at end of file diff --git a/PSSH Capture/eme-logger-mod.js b/PSSH Capture/eme-logger-mod.js new file mode 100644 index 0000000..3b8f3ff --- /dev/null +++ b/PSSH Capture/eme-logger-mod.js @@ -0,0 +1,25 @@ +(async () => { + const b64 = { + decode: s => Uint8Array.from(atob(s), c => c.charCodeAt(0)), + encode: b => btoa(String.fromCharCode(...new Uint8Array(b))) + }; + + const fnproxy = (object, func) => new Proxy(object, { apply: func }); + + const proxy = (object, key, func) => Object.defineProperty(object, key, { + value: fnproxy(object[key], func) + }); + + proxy(MediaKeySession.prototype, 'generateRequest', async (_target, _this, _args) => { + const [initDataType, initData] = _args; + const pssh = b64.encode(initData); + console.groupCollapsed( + `PSSH: ${b64.encode(initData)}` + ); + console.trace(); + console.groupEnd(); + if (pssh) + window.postMessage({ type: "38405bbb-36ef-454d-8b32-346f9564c978", log: pssh }, "*"); + return _target.apply(_this, _args); + }); +})(); \ No newline at end of file diff --git a/PSSH Capture/icon.png b/PSSH Capture/icon.png new file mode 100644 index 0000000..1914d6f Binary files /dev/null and b/PSSH Capture/icon.png differ diff --git a/PSSH Capture/icon128.png b/PSSH Capture/icon128.png new file mode 100644 index 0000000..906e4a4 Binary files /dev/null and b/PSSH Capture/icon128.png differ diff --git a/PSSH Capture/manifest.json b/PSSH Capture/manifest.json new file mode 100644 index 0000000..56945a2 --- /dev/null +++ b/PSSH Capture/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "PSSH Capture", + "version": "3.0", + "manifest_version": 2, + "background": { + "scripts": [ + "background.js" + ] + }, + "content_scripts": [ + { + "matches": [ + "*://*/*" + ], + "js": [ + "content-script.js" + ], + "all_frames": false, + "run_at": "document_start" + } + ], + "browser_action": {"default_icon": "icon.png"}, + "permissions": [ + "*://*/*", + "clipboardWrite", + "tabs", + "webRequest", + "activeTab", + "storage", + "webRequestBlocking" + ], + "web_accessible_resources": [ + "eme-logger-mod.js" + ] +} \ No newline at end of file