2025-04-24 17:06:14 -04:00

174 lines
6.1 KiB
JavaScript

import "./protobuf.min.js";
import "./license_protocol.min.js";
const { SignedMessage, LicenseRequest } = protobuf.roots.default.license_protocol;
function uint8ArrayToBase64(uint8Array) {
const binaryString = Array.from(uint8Array)
.map(b => String.fromCharCode(b))
.join('');
return btoa(binaryString);
}
function parseFetch(fetchString) {
// Remove `await` if it exists in the string
fetchString = fetchString.replace(/^await\s+/, "");
// Use a more lenient regex to capture the fetch pattern (including complex bodies)
const fetchRegex = /fetch\(['"](.+?)['"],\s*(\{.+?\})\)/s; // Non-greedy match for JSON
const lines = fetchString.split('\n').map(line => line.trim()).filter(Boolean);
const result = {
method: 'UNDEFINED',
url: '',
headers: {},
body: null,
};
// Try matching the regex
const fetchMatch = fetchString.match(fetchRegex);
if (!fetchMatch) {
console.log(fetchString);
throw new Error("Invalid 'Copy as fetch' string.");
}
// Extract URL from the match
result.url = fetchMatch[1];
// Parse the options JSON from the match (this will include headers, body, etc.)
const optionsString = fetchMatch[2];
const options = JSON.parse(optionsString);
// Assign method, headers, and body if available
if (options.method) result.method = options.method;
if (options.headers) result.headers = options.headers;
if (options.body) result.body = options.body;
return result;
}
const WIDEVINE_SYSTEM_ID = new Uint8Array([0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed]);
const PLAYREADY_SYSTEM_ID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
const PSSH_MAGIC = new Uint8Array([0x70, 0x73, 0x73, 0x68]);
function intToUint8Array(num, endian) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, num, endian);
return new Uint8Array(buffer);
}
function shortToUint8Array(num, endian) {
const buffer = new ArrayBuffer(2);
const view = new DataView(buffer);
view.setUint16(0, num, endian);
return new Uint8Array(buffer);
}
function psshDataToPsshBoxB64(pssh_data, system_id) {
const dataLength = pssh_data.length;
const totalLength = dataLength + 32;
const pssh = new Uint8Array([
...intToUint8Array(totalLength, false),
...PSSH_MAGIC,
...new Uint8Array(4),
...system_id,
...intToUint8Array(dataLength, false),
...pssh_data
]);
return uint8ArrayToBase64(pssh);
}
function wrmHeaderToPlayReadyHeader(wrm_header){
const playready_object = new Uint8Array([
...shortToUint8Array(1, true),
...shortToUint8Array(wrm_header.length, true),
...wrm_header
]);
return new Uint8Array([
...intToUint8Array(playready_object.length + 2 + 4, true),
...shortToUint8Array(1, true),
...playready_object
]);
}
function encodeUtf16LE(str) {
const buffer = new Uint8Array(str.length * 2);
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
buffer[i * 2] = code & 0xff;
buffer[i * 2 + 1] = code >> 8;
}
return buffer;
}
function stringToUint8Array(string) {
return Uint8Array.from(string.split("").map(x => x.charCodeAt()));
}
export async function readTextFromClipboard() {
try {
// Request text from the clipboard
const clipboardText = await navigator.clipboard.readText();
const result = parseFetch(clipboardText);
let pssh_data_string;
let payload_string;
if (result.body.startsWith("<")) {
// If body starts with "<", process it as PlayReady content
payload_string = result.body;
const wrmHeaderMatch = payload_string.match(/.*(<WRMHEADER.*<\/WRMHEADER>).*/);
const wrmHeader = wrmHeaderMatch ? wrmHeaderMatch[1] : null;
const encodedWrmHeader = encodeUtf16LE(wrmHeader);
const playreadyHeader = wrmHeaderToPlayReadyHeader(encodedWrmHeader);
pssh_data_string = psshDataToPsshBoxB64(playreadyHeader, PLAYREADY_SYSTEM_ID);
} else {
// If body is in a different format, process as Widevine content
const uint8Array = stringToUint8Array(result.body);
let signed_message;
let license_request;
try {
signed_message = SignedMessage.decode(uint8Array);
license_request = LicenseRequest.decode(signed_message.msg);
} catch (decodeError) {
// If error occurs during decoding, return an empty pssh
console.error('Decoding failed, returning empty pssh', decodeError);
pssh_data_string = ''; // Empty pssh if decoding fails
}
if (license_request && license_request.contentId && license_request.contentId.widevinePsshData) {
const pssh_data = license_request.contentId.widevinePsshData.psshData[0];
pssh_data_string = psshDataToPsshBoxB64(pssh_data, WIDEVINE_SYSTEM_ID);
}
// Check if the body contains binary data (non-UTF-8 characters)
if (isBinary(uint8Array)) {
payload_string = uint8ArrayToBase64(uint8Array);
} else {
// If it's text, return it as is
payload_string = result.body;
}
}
// Output the result
document.getElementById("licurl").value = result.url;
document.getElementById("headers").value = JSON.stringify(result.headers);
document.getElementById("pssh").value = pssh_data_string;
document.getElementById("data").value = payload_string;
} catch (error) {
console.error('Failed to read clipboard contents:', error);
}
}
// Helper function to check if the data is binary
function isBinary(uint8Array) {
// Check for non-text (non-ASCII) bytes (basic heuristic)
return uint8Array.some(byte => byte > 127); // Non-ASCII byte indicates binary
}