Responsive Design
This commit is contained in:
parent
73723741b5
commit
8172c2122e
89
cdrm-frontend/package-lock.json
generated
89
cdrm-frontend/package-lock.json
generated
@ -11,7 +11,9 @@
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-router-dom": "^7.5.2",
|
||||
"shaka-player": "^4.14.9",
|
||||
"tailwindcss": "^4.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -1872,6 +1874,12 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/eme-encryption-scheme-polyfill": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.2.1.tgz",
|
||||
"integrity": "sha512-GzgrLuZPYGijd8oaKuBqYv3Tc2oruDZM3V2982KOuy/PA1N3zwMe+/oIXJYfZ3BH3PwW5nONdBBE+VY6jlwbrw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
@ -2358,7 +2366,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@ -2696,6 +2703,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@ -2758,6 +2777,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@ -2897,6 +2925,17 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@ -2928,6 +2967,42 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-helmet": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
|
||||
"integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-fast-compare": "^3.1.1",
|
||||
"react-side-effect": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-helmet/node_modules/react-side-effect": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
|
||||
"integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
@ -3048,6 +3123,18 @@
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shaka-player": {
|
||||
"version": "4.14.9",
|
||||
"resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-4.14.9.tgz",
|
||||
"integrity": "sha512-0YZJ+UUHBz3meAzN/eOvjNoLH7eCG1yHP3BFH8ZnFbGf3K50DgLWNMoV6bm6pH4cndvXem+SdcQRXabItD4RBA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"eme-encryption-scheme-polyfill": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
@ -13,7 +13,9 @@
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-router-dom": "^7.5.2",
|
||||
"shaka-player": "^4.14.9",
|
||||
"tailwindcss": "^4.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import Home from "./components/Pages/HomePage";
|
||||
import Cache from "./components/Pages/Cache";
|
||||
import API from "./components/Pages/API";
|
||||
import TestPlayer from "./components/Pages/TestPlayer";
|
||||
import NavBar from "./components/NavBar";
|
||||
import NavBarMain from "./components/NavBarMain";
|
||||
import SideMenu from "./components/SideMenu"; // Add this import
|
||||
@ -13,18 +16,21 @@ function App() {
|
||||
{/* The SideMenu should be visible when isMenuOpen is true */}
|
||||
<SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
|
||||
|
||||
<div id="navbarcontainer" className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 shadow-lg shadow-blue-900/50">
|
||||
<div id="navbarcontainer" className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 0">
|
||||
<NavBar />
|
||||
</div>
|
||||
|
||||
<div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col">
|
||||
<div id="navbarmaincontainer" className="w-full lg:hidden h-16 bg-gray-950/10 border-b border-white/5 shadow-md shadow-blue-900/35 sticky top-0 z-10">
|
||||
<div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col grow">
|
||||
<div id="navbarmaincontainer" className="w-full lg:hidden h-16 bg-gray-950/10 border-b border-white/5 sticky top-0 z-10">
|
||||
<NavBarMain setIsMenuOpen={setIsMenuOpen} />
|
||||
</div>
|
||||
|
||||
<div id="maincontentcontainer" className="w-full grow">
|
||||
<div id="maincontentcontainer" className="w-full grow overflow-y-auto">
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/cache" element={<Cache />} />
|
||||
<Route path="/api" element={<API />} />
|
||||
<Route path="/testplayer" element={<TestPlayer />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
173
cdrm-frontend/src/components/Functions/ParseChallenge.jsx
Normal file
173
cdrm-frontend/src/components/Functions/ParseChallenge.jsx
Normal file
@ -0,0 +1,173 @@
|
||||
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
|
||||
}
|
||||
|
||||
|
1
cdrm-frontend/src/components/Functions/license_protocol.min.js
vendored
Normal file
1
cdrm-frontend/src/components/Functions/license_protocol.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
cdrm-frontend/src/components/Functions/protobuf.min.js
vendored
Normal file
8
cdrm-frontend/src/components/Functions/protobuf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
134
cdrm-frontend/src/components/Pages/API.jsx
Normal file
134
cdrm-frontend/src/components/Pages/API.jsx
Normal file
@ -0,0 +1,134 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||
|
||||
const { protocol, hostname, port } = window.location;
|
||||
|
||||
let fullHost = `${protocol}//${hostname}`;
|
||||
if (
|
||||
(protocol === 'http:' && port !== '80') ||
|
||||
(protocol === 'https:' && port !== '443' && port !== '')
|
||||
) {
|
||||
fullHost += `:${port}`;
|
||||
}
|
||||
|
||||
function API() {
|
||||
const [deviceInfo, setDeviceInfo] = useState({
|
||||
device_type: '',
|
||||
system_id: '',
|
||||
security_level: '',
|
||||
host: '',
|
||||
secret: '',
|
||||
device_name: ''
|
||||
});
|
||||
|
||||
const [prDeviceInfo, setPrDeviceInfo] = useState({
|
||||
security_level: '',
|
||||
host: '',
|
||||
secret: '',
|
||||
device_name: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch Widevine info
|
||||
fetch('/remotecdm/widevine/deviceinfo')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setDeviceInfo({
|
||||
device_type: data.device_type,
|
||||
system_id: data.system_id,
|
||||
security_level: data.security_level,
|
||||
host: data.host,
|
||||
secret: data.secret,
|
||||
device_name: data.device_name
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error fetching Widevine info:', error));
|
||||
|
||||
// Fetch PlayReady info
|
||||
fetch('/remotecdm/playready/deviceinfo')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setPrDeviceInfo({
|
||||
security_level: data.security_level,
|
||||
host: data.host,
|
||||
secret: data.secret,
|
||||
device_name: data.device_name
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error fetching PlayReady info:', error));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full overflow-y-auto p-4 text-white">
|
||||
<Helmet>
|
||||
<title>API</title>
|
||||
</Helmet>
|
||||
<details open className='w-full list-none'>
|
||||
<summary className='text-2xl'>Sending a decryption request</summary>
|
||||
<div className='mt-5 p-5 rounded-lg border-2 border-indigo-500/50'>
|
||||
<pre className='rounded-lg font-mono whitespace-pre-wrap text-white overflow-auto'>
|
||||
{`import requests
|
||||
|
||||
print(requests.post(
|
||||
url='${fullHost}/api/decrypt',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
json={
|
||||
'pssh': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==',
|
||||
'licurl': 'https://cwip-shaka-proxy.appspot.com/no_auth',
|
||||
'headers': str({
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0',
|
||||
'Accept': '*/*',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
})
|
||||
}
|
||||
).json()['message'])`}
|
||||
</pre>
|
||||
</div>
|
||||
</details>
|
||||
<details open className='w-full list-none mt-5'>
|
||||
<summary className='text-2xl'>Sending a search request</summary>
|
||||
<div className='mt-5 border-2 border-indigo-500/50 p-5 rounded-lg'>
|
||||
<pre className="rounded-lg font-mono whitespace-pre text-white overflow-x-auto max-w-full p-5">
|
||||
{`import requests
|
||||
|
||||
print(requests.post(
|
||||
url='${fullHost}/api/cache/search',
|
||||
json={
|
||||
'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
|
||||
}
|
||||
).json())`}
|
||||
</pre>
|
||||
</div>
|
||||
</details>
|
||||
<details open className='w-full list-none mt-5'>
|
||||
<summary className='text-2xl'>PyWidevine RemoteCDM info</summary>
|
||||
<div className='mt-5 border-2 border-indigo-500 p-5 rounded-lg overflow-x-auto'>
|
||||
<p>
|
||||
<strong>Device Type:</strong> '{deviceInfo.device_type}'<br />
|
||||
<strong>System ID:</strong> {deviceInfo.system_id}<br />
|
||||
<strong>Security Level:</strong> {deviceInfo.security_level}<br />
|
||||
<strong>Host:</strong> {fullHost}/remotecdm/widevine<br />
|
||||
<strong>Secret:</strong> {deviceInfo.secret}<br />
|
||||
<strong>Device Name:</strong> {deviceInfo.device_name}
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
<details open className='w-full list-none mt-5'>
|
||||
<summary className='text-2xl'>PyPlayready RemoteCDM info</summary>
|
||||
<div className='mt-5 border-2 border-indigo-500 p-5 rounded-lg overflow-x-auto'>
|
||||
<p>
|
||||
<strong>Security Level:</strong> {prDeviceInfo.security_level}<br />
|
||||
<strong>Host:</strong> {fullHost}/remotecdm/playready<br />
|
||||
<strong>Secret:</strong> {prDeviceInfo.secret}<br />
|
||||
<strong>Device Name:</strong> {prDeviceInfo.device_name}
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default API;
|
107
cdrm-frontend/src/components/Pages/Cache.jsx
Normal file
107
cdrm-frontend/src/components/Pages/Cache.jsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||
|
||||
function Cache() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [cacheData, setCacheData] = useState([]);
|
||||
const [keyCount, setKeyCount] = useState(0); // New state to store the key count
|
||||
const debounceTimeout = useRef(null);
|
||||
|
||||
// Fetch the key count when the component mounts
|
||||
useEffect(() => {
|
||||
const fetchKeyCount = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/cache/keycount');
|
||||
const data = await response.json();
|
||||
setKeyCount(data.count); // Update key count
|
||||
} catch (error) {
|
||||
console.error('Error fetching key count:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchKeyCount();
|
||||
}, []); // Run only once when the component mounts
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const query = event.target.value;
|
||||
setSearchQuery(query); // Update the search query
|
||||
|
||||
// Clear the previous timeout
|
||||
if (debounceTimeout.current) {
|
||||
clearTimeout(debounceTimeout.current);
|
||||
}
|
||||
|
||||
// Set a new timeout to send the API call after 1 second of no typing
|
||||
debounceTimeout.current = setTimeout(() => {
|
||||
if (query.trim() !== '') {
|
||||
sendApiCall(query); // Only call the API if the query is not empty
|
||||
} else {
|
||||
setCacheData([]); // Clear results if query is empty
|
||||
}
|
||||
}, 1000); // 1 second delay
|
||||
};
|
||||
|
||||
const sendApiCall = (text) => {
|
||||
fetch('/api/cache/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ input: text }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => setCacheData(data)) // Update cache data with the results
|
||||
.catch((error) => console.error('Error:', error));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full overflow-y-auto p-4">
|
||||
<Helmet>
|
||||
<title>Cache</title>
|
||||
</Helmet>
|
||||
<div className="flex flex-col lg:flex-row w-full lg:h-12 items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={handleInputChange}
|
||||
placeholder={`Search ${keyCount} keys...`} // Dynamic placeholder
|
||||
className="lg:grow w-full border-2 border-emerald-500/25 rounded-xl h-10 self-center m-2 text-white p-1 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all duration-200 ease-in-out"
|
||||
/>
|
||||
<a
|
||||
href="/api/cache/download"
|
||||
className="bg-emerald-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-10 truncate w-full text-center flex items-center justify-center m-2"
|
||||
>
|
||||
Download Cache
|
||||
</a>
|
||||
</div>
|
||||
<div className="w-full grow p-4 border-2 border-emerald-500/50 rounded-2xl mt-5 overflow-y-auto">
|
||||
<table className="min-w-full text-white">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-2 border border-black">PSSH</th>
|
||||
<th className="p-2 border border-black">KID</th>
|
||||
<th className="p-2 border border-black">Key</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{cacheData.length > 0 ? (
|
||||
cacheData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td className="p-2 border border-black">{item.PSSH}</td>
|
||||
<td className="p-2 border border-black">{item.KID}</td>
|
||||
<td className="p-2 border border-black">{item.Key}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="3" className="p-2 border border-black text-center">
|
||||
No data found
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Cache;
|
@ -1,10 +1,192 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { readTextFromClipboard } from '../Functions/ParseChallenge'
|
||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||
|
||||
function HomePage() {
|
||||
const [pssh, setPssh] = useState('');
|
||||
const [licurl, setLicurl] = useState('');
|
||||
const [proxy, setProxy] = useState('');
|
||||
const [headers, setHeaders] = useState('');
|
||||
const [cookies, setCookies] = useState('');
|
||||
const [data, setData] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const bottomRef = useRef(null);
|
||||
const messageRef = useRef(null); // Reference to result container
|
||||
|
||||
const handleReset = () => {
|
||||
if (isVisible) {
|
||||
setIsVisible(false);
|
||||
}
|
||||
setPssh('');
|
||||
setLicurl('');
|
||||
setProxy('');
|
||||
setHeaders('');
|
||||
setCookies('');
|
||||
setData('');
|
||||
};
|
||||
|
||||
const handleSubmitButton = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
fetch('/api/decrypt', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pssh: pssh,
|
||||
licurl: licurl,
|
||||
proxy: proxy,
|
||||
headers: headers,
|
||||
cookies: cookies,
|
||||
data: data
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const resultMessage = data['message'].replace(/\n/g, '<br />');
|
||||
setMessage(resultMessage);
|
||||
setIsVisible(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error during decryption request:', error);
|
||||
setMessage('Error: Unable to process request.');
|
||||
setIsVisible(true);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCopy = (event) => {
|
||||
event.preventDefault();
|
||||
if (messageRef.current) {
|
||||
const textToCopy = messageRef.current.innerText; // Grab the plain text (with visual line breaks)
|
||||
navigator.clipboard.writeText(textToCopy).catch(err => {
|
||||
alert('Failed to copy!');
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full overflow-y-auto p-4">
|
||||
</div>
|
||||
)
|
||||
const handleFetchPaste = () => {
|
||||
event.preventDefault();
|
||||
readTextFromClipboard().then(() => {
|
||||
setPssh(document.getElementById("pssh").value);
|
||||
setLicurl(document.getElementById("licurl").value);
|
||||
setHeaders(document.getElementById("headers").value);
|
||||
setData(document.getElementById("data").value);
|
||||
}).catch(err => {
|
||||
alert('Failed to paste from fetch!');
|
||||
});
|
||||
}
|
||||
|
||||
export default HomePage
|
||||
useEffect(() => {
|
||||
if (isVisible && bottomRef.current) {
|
||||
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}, [message, isVisible]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col w-full overflow-y-auto p-4 min-h-full">
|
||||
<Helmet>
|
||||
<title>CDRM-Project</title>
|
||||
</Helmet>
|
||||
<form className="flex flex-col w-full h-full bg-black/5 p-4 overflow-y-auto">
|
||||
<label htmlFor="pssh" className="text-white w-8/10 self-center">PSSH: </label>
|
||||
<input
|
||||
type="text"
|
||||
id="pssh"
|
||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
||||
value={pssh}
|
||||
onChange={(e) => setPssh(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="licurl" className="text-white w-8/10 self-center">License URL: </label>
|
||||
<input
|
||||
type="text"
|
||||
id="licurl"
|
||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
||||
value={licurl}
|
||||
onChange={(e) => setLicurl(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="proxy" className="text-white w-8/10 self-center">Proxy: </label>
|
||||
<input
|
||||
type="text"
|
||||
id="proxy"
|
||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
||||
value={proxy}
|
||||
onChange={(e) => setProxy(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="headers" className="text-white w-8/10 self-center">Headers: </label>
|
||||
<textarea
|
||||
id="headers"
|
||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
||||
value={headers}
|
||||
onChange={(e) => setHeaders(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="cookies" className="text-white w-8/10 self-center">Cookies: </label>
|
||||
<textarea
|
||||
id="cookies"
|
||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
||||
value={cookies}
|
||||
onChange={(e) => setCookies(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="data" className="text-white w-8/10 self-center">Data: </label>
|
||||
<textarea
|
||||
id="data"
|
||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
||||
value={data}
|
||||
onChange={(e) => setData(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-sky-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
|
||||
onClick={handleSubmitButton}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<button onClick={handleFetchPaste} className="bg-yellow-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0">
|
||||
Paste from fetch
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-red-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0"
|
||||
onClick={handleReset}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{isVisible && (
|
||||
<div id="main_content" className="flex-col w-full h-full p-10 items-center justify-center self-center">
|
||||
<div className="flex flex-col w-full h-full overflow-y-auto items-center">
|
||||
<div className='w-8/10 grow p-4 text-white text-bold text-center text-xl md:text-3xl border-2 border-sky-500/25 rounded-xl bg-black/5'>
|
||||
<p className="w-full border-b-2 border-white/75 pb-2">Results:</p>
|
||||
<p
|
||||
className="w-full grow pt-10 break-words overflow-y-auto"
|
||||
ref={messageRef}
|
||||
dangerouslySetInnerHTML={{ __html: message }}
|
||||
/>
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
||||
<button
|
||||
className="bg-green-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
Copy Results
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default HomePage;
|
||||
|
158
cdrm-frontend/src/components/Pages/TestPlayer.jsx
Normal file
158
cdrm-frontend/src/components/Pages/TestPlayer.jsx
Normal file
@ -0,0 +1,158 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import shaka from 'shaka-player';
|
||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||
|
||||
function TestPlayer() {
|
||||
const [mpdUrl, setMpdUrl] = useState(''); // State to hold the MPD URL
|
||||
const [kids, setKids] = useState(''); // State to hold KIDs (separated by line breaks)
|
||||
const [keys, setKeys] = useState(''); // State to hold Keys (separated by line breaks)
|
||||
const [headers, setHeaders] = useState(''); // State to hold request headers
|
||||
|
||||
const videoRef = useRef(null); // Ref for the video element
|
||||
const playerRef = useRef(null); // Ref for Shaka Player instance
|
||||
|
||||
// Function to update the MPD URL state
|
||||
const handleInputChange = (event) => {
|
||||
setMpdUrl(event.target.value);
|
||||
};
|
||||
|
||||
// Function to update KIDs and Keys
|
||||
const handleKidsChange = (event) => {
|
||||
setKids(event.target.value);
|
||||
};
|
||||
|
||||
const handleKeysChange = (event) => {
|
||||
setKeys(event.target.value);
|
||||
};
|
||||
|
||||
const handleHeadersChange = (event) => {
|
||||
setHeaders(event.target.value);
|
||||
};
|
||||
|
||||
// Function to initialize Shaka Player
|
||||
const initializePlayer = () => {
|
||||
if (videoRef.current) {
|
||||
// Initialize Shaka Player only if it's not already initialized
|
||||
if (!playerRef.current) {
|
||||
const player = new shaka.Player(videoRef.current);
|
||||
playerRef.current = player;
|
||||
|
||||
// Add error listener
|
||||
player.addEventListener('error', (event) => {
|
||||
console.error('Error code', event.detail.code, 'object', event.detail);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Function to handle submit and configure player with DRM keys and headers
|
||||
const handleSubmit = () => {
|
||||
if (mpdUrl && kids && keys) {
|
||||
// Split the KIDs and Keys by new lines
|
||||
const kidsArray = kids.split("\n").map((k) => k.trim());
|
||||
const keysArray = keys.split("\n").map((k) => k.trim());
|
||||
|
||||
if (kidsArray.length !== keysArray.length) {
|
||||
console.error("The number of KIDs and Keys must be the same.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Shaka Player only when the submit button is pressed
|
||||
const player = new shaka.Player(videoRef.current);
|
||||
|
||||
// Widevine DRM configuration with the provided KIDs and Keys
|
||||
const config = {
|
||||
drm: {
|
||||
clearKeys: {},
|
||||
},
|
||||
};
|
||||
|
||||
// Map KIDs to Keys
|
||||
kidsArray.forEach((kid, index) => {
|
||||
config.drm.clearKeys[kid] = keysArray[index];
|
||||
});
|
||||
|
||||
console.log("Configuring player with the following DRM config and headers:", config);
|
||||
|
||||
// Configure the player with ClearKey DRM and custom headers
|
||||
player.configure(config);
|
||||
|
||||
// Load the video stream with MPD URL
|
||||
player.load(mpdUrl).then(() => {
|
||||
console.log('Video loaded');
|
||||
}).catch((error) => {
|
||||
console.error('Error loading the video', error);
|
||||
});
|
||||
} else {
|
||||
console.error('MPD URL, KIDs, and Keys are required.');
|
||||
}
|
||||
};
|
||||
|
||||
// Load the video stream whenever the MPD URL changes
|
||||
useEffect(() => {
|
||||
initializePlayer(); // Initialize the player if it's not initialized already
|
||||
}, []); // This effect runs only once on mount
|
||||
|
||||
// Helper function to parse headers from the textarea input
|
||||
const parseHeaders = (headersText) => {
|
||||
const headersArr = headersText.split('\n');
|
||||
const headersObj = {};
|
||||
headersArr.forEach((line) => {
|
||||
const [key, value] = line.split(':');
|
||||
if (key && value) {
|
||||
headersObj[key.trim()] = value.trim();
|
||||
}
|
||||
});
|
||||
return headersObj;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full p-4">
|
||||
<Helmet>
|
||||
<title>Test Player</title>
|
||||
</Helmet>
|
||||
<div className="w-full flex flex-col">
|
||||
<video
|
||||
ref={videoRef}
|
||||
width="100%"
|
||||
height="auto"
|
||||
controls
|
||||
className="h-96"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={mpdUrl}
|
||||
onChange={handleInputChange}
|
||||
placeholder="MPD URL"
|
||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||
/>
|
||||
<textarea
|
||||
placeholder="KIDs (one per line)"
|
||||
value={kids}
|
||||
onChange={handleKidsChange}
|
||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Keys (one per line)"
|
||||
value={keys}
|
||||
onChange={handleKeysChange}
|
||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||
/>
|
||||
<textarea
|
||||
placeholder="Headers (one per line)"
|
||||
value={headers}
|
||||
onChange={handleHeadersChange}
|
||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
||||
/>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="mt-4 p-2 bg-blue-500 text-white rounded"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TestPlayer;
|
@ -1 +1,10 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
details summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user