prettier formatting, add manifest url in console.log

This commit is contained in:
voldemort 2025-07-20 12:18:15 +07:00
parent bafe525510
commit c9ff17558d
18 changed files with 4754 additions and 4436 deletions

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
react/
frontend/dist/
frontend/src/assets/

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"useTabs": false,
"printWidth": 100
}

View File

@ -1,84 +1,84 @@
// Open popout window when the extension icon is clicked // Open popout window when the extension icon is clicked
chrome.browserAction.onClicked.addListener(() => { chrome.browserAction.onClicked.addListener(() => {
chrome.windows.create({ chrome.windows.create({
url: chrome.runtime.getURL("react/index.html"), url: chrome.runtime.getURL("react/index.html"),
type: "popup", // opens as a floating window type: "popup", // opens as a floating window
width: 800, width: 800,
height: 600 height: 600,
}); });
}); });
// Listen for messages and store data in chrome.storage.local // Listen for messages and store data in chrome.storage.local
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
const { type, data } = message; const { type, data } = message;
switch (type) { switch (type) {
case "DRM_TYPE": case "DRM_TYPE":
console.log("DRM Type:", data); console.log("DRM Type:", data);
chrome.storage.local.set({ drmType: data }); chrome.storage.local.set({ drmType: data });
break; break;
case "PSSH_DATA": case "PSSH_DATA":
console.log("Storing PSSH:", data); console.log("Storing PSSH:", data);
chrome.storage.local.set({ latestPSSH: data }); chrome.storage.local.set({ latestPSSH: data });
break; break;
case "KEYS_DATA": case "KEYS_DATA":
console.log("Storing Decryption Keys:", data); console.log("Storing Decryption Keys:", data);
chrome.storage.local.set({ latestKeys: data }); chrome.storage.local.set({ latestKeys: data });
break; break;
case "LICENSE_URL": case "LICENSE_URL":
console.log("Storling License URL " + data); console.log("Storling License URL " + data);
chrome.storage.local.set({licenseURL: data}); chrome.storage.local.set({ licenseURL: data });
break; break;
default: default:
console.warn("Unknown message type received:", type); console.warn("Unknown message type received:", type);
} }
}); });
// Set initial config and injection type on install // Set initial config and injection type on install
chrome.runtime.onInstalled.addListener((details) => { chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === "install") { if (details.reason === "install") {
chrome.storage.local.set({ valid_config: false }, () => { chrome.storage.local.set({ valid_config: false }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Error setting valid_config:", chrome.runtime.lastError); console.error("Error setting valid_config:", chrome.runtime.lastError);
} else { } else {
console.log("valid_config set to false on first install."); console.log("valid_config set to false on first install.");
} }
}); });
chrome.storage.local.set({ injection_type: "LICENSE" }, () => { chrome.storage.local.set({ injection_type: "LICENSE" }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Error setting Injection Type:", chrome.runtime.lastError); console.error("Error setting Injection Type:", chrome.runtime.lastError);
} else { } else {
console.log("Injection type set to LICENSE on first install."); console.log("Injection type set to LICENSE on first install.");
} }
}); });
chrome.storage.local.set({ drm_override: "DISABLED" }, () => { chrome.storage.local.set({ drm_override: "DISABLED" }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Error setting DRM Override type:", chrome.runtime.lastError); console.error("Error setting DRM Override type:", chrome.runtime.lastError);
} else { } else {
console.log("DRM Override type set to DISABLED on first install."); console.log("DRM Override type set to DISABLED on first install.");
} }
}); });
chrome.storage.local.set({ cdrm_instance: null }, () => { chrome.storage.local.set({ cdrm_instance: null }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Error setting CDRM instance:", chrome.runtime.lastError); console.error("Error setting CDRM instance:", chrome.runtime.lastError);
} else { } else {
console.log("CDRM instance set to null."); console.log("CDRM instance set to null.");
} }
}); });
chrome.storage.local.set({ cdrm_api_key: null }, () => { chrome.storage.local.set({ cdrm_api_key: null }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Error setting CDRM API Key:", chrome.runtime.lastError); console.error("Error setting CDRM API Key:", chrome.runtime.lastError);
} else { } else {
console.log("CDRM API Key set."); console.log("CDRM API Key set.");
} }
}); });
} }
}); });

View File

@ -1,67 +1,96 @@
// Inject `inject.js` into the page context // Inject `inject.js` into the page context
(function injectScript() { (function injectScript() {
const script = document.createElement('script'); const script = document.createElement("script");
script.src = chrome.runtime.getURL('inject.js'); script.src = chrome.runtime.getURL("inject.js");
script.type = 'text/javascript'; script.type = "text/javascript";
script.onload = () => script.remove(); // Clean up script.onload = () => script.remove(); // Clean up
// Inject directly into <html> or <head> // Inject directly into <html> or <head>
(document.documentElement || document.head || document.body).appendChild(script); (document.documentElement || document.head || document.body).appendChild(script);
})(); })();
// Listen for messages from the injected script // Listen for messages from the injected script
window.addEventListener("message", function(event) { window.addEventListener("message", function (event) {
if (event.source !== window) return; if (event.source !== window) return;
if (["__DRM_TYPE__", "__PSSH_DATA__", "__KEYS_DATA__", "__LICENSE_URL__"].includes(event.data?.type)) { if (
["__DRM_TYPE__", "__PSSH_DATA__", "__KEYS_DATA__", "__LICENSE_URL__"].includes(
event.data?.type
)
) {
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: event.data.type.replace("__", "").replace("__", ""), type: event.data.type.replace("__", "").replace("__", ""),
data: event.data.data data: event.data.data,
}); });
} }
if (event.data.type === "__GET_CDM_DEVICES__") { if (event.data.type === "__GET_CDM_DEVICES__") {
chrome.storage.local.get(["widevine_device", "playready_device"], (result) => {
const widevine_device = result.widevine_device || null;
const playready_device = result.playready_device || null;
chrome.storage.local.get(["widevine_device", "playready_device"], (result) => { window.postMessage(
const widevine_device = result.widevine_device || null; {
const playready_device = result.playready_device || null; type: "__CDM_DEVICES__",
widevine_device,
window.postMessage( playready_device,
{ },
type: "__CDM_DEVICES__", "*"
widevine_device, );
playready_device });
},
"*"
);
});
} }
if (event.data.type === "__GET_INJECTION_TYPE__") { if (event.data.type === "__GET_INJECTION_TYPE__") {
chrome.storage.local.get("injection_type", (result) => {
const injectionType = result.injection_type || "LICENSE";
chrome.storage.local.get("injection_type", (result) => { window.postMessage(
const injectionType = result.injection_type || "LICENSE"; {
type: "__INJECTION_TYPE__",
window.postMessage( injectionType,
{ },
type: "__INJECTION_TYPE__", "*"
injectionType );
}, });
"*"
);
});
} }
if (event.data.type === "__GET_DRM_OVERRIDE__") { if (event.data.type === "__GET_DRM_OVERRIDE__") {
chrome.storage.local.get("drm_override", (result) => {
const drmOverride = result.drm_override || "DISABLED";
chrome.storage.local.get("drm_override", (result) => { window.postMessage(
const drmOverride = result.drm_override || "DISABLED"; {
type: "__DRM_OVERRIDE__",
drmOverride,
},
"*"
);
});
}
window.postMessage( // Manifest header and URL
{
type: "__DRM_OVERRIDE__", const seenManifestUrls = new Set();
drmOverride
}, if (event.data?.type === "__MANIFEST_URL__") {
"*" const url = event.data.data;
); if (seenManifestUrls.has(url)) return;
}); seenManifestUrls.add(url);
console.log("✅ [Content] Unique manifest URL:", url);
chrome.runtime.sendMessage({
type: "MANIFEST_URL_FOUND",
data: url,
});
}
if (event.data?.type === "__MANIFEST_HEADERS__") {
const { url, headers } = event.data;
console.log("[Content.js] Manifest Headers:", url, headers);
chrome.runtime.sendMessage({
type: "MANIFEST_HEADERS",
url,
headers,
});
} }
}); });

View File

@ -1,33 +1,30 @@
import js from '@eslint/js' import js from "@eslint/js";
import globals from 'globals' import globals from "globals";
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from "eslint-plugin-react-refresh";
export default [ export default [
{ ignores: ['dist'] }, { ignores: ["dist"] },
{ {
files: ['**/*.{js,jsx}'], files: ["**/*.{js,jsx}"],
languageOptions: { languageOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
parserOptions: { parserOptions: {
ecmaVersion: 'latest', ecmaVersion: "latest",
ecmaFeatures: { jsx: true }, ecmaFeatures: { jsx: true },
sourceType: 'module', sourceType: "module",
}, },
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
"no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
}, },
plugins: { ];
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]

View File

@ -1,12 +1,12 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CDRM Decryption Extension</title> <title>CDRM Decryption Extension</title>
</head> </head>
<body class="min-w-full min-h-full w-full h-full"> <body class="min-w-full min-h-full w-full h-full">
<div class="min-w-full min-h-full w-full h-full" id="root"></div> <div class="min-w-full min-h-full w-full h-full" id="root"></div>
<script type="module" src="/src/main.jsx"></script> <script type="module" src="/src/main.jsx"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,30 @@
{ {
"name": "frontend", "name": "frontend",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.7", "@tailwindcss/vite": "^4.1.7",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-router-dom": "^7.6.1", "react-router-dom": "^7.6.1",
"tailwindcss": "^4.1.7" "tailwindcss": "^4.1.7"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.25.0", "@eslint/js": "^9.25.0",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2", "@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.1", "@vitejs/plugin-react": "^4.4.1",
"eslint": "^9.25.0", "eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0", "globals": "^16.0.0",
"vite": "^6.3.5" "vite": "^6.3.5"
} }
} }

View File

@ -1,77 +1,72 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { import { HashRouter as Router, Routes, Route, Navigate } from "react-router-dom";
HashRouter as Router,
Routes,
Route,
Navigate,
} from "react-router-dom";
import TopNav from "./components/topnav"; import TopNav from "./components/topnav";
import SideNav from "./components/sidenav"; import SideNav from "./components/sidenav";
import Results from "./components/results"; import Results from "./components/results";
import Settings from "./components/settings"; import Settings from "./components/settings";
function App() { function App() {
const [isSideNavOpen, setIsSideNavOpen] = useState(false); const [isSideNavOpen, setIsSideNavOpen] = useState(false);
const [validConfig, setValidConfig] = useState(null); // null = loading const [validConfig, setValidConfig] = useState(null); // null = loading
useEffect(() => { useEffect(() => {
chrome.storage.local.get("valid_config", (result) => { chrome.storage.local.get("valid_config", (result) => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Error reading valid_config:", chrome.runtime.lastError); console.error("Error reading valid_config:", chrome.runtime.lastError);
setValidConfig(false); // fallback setValidConfig(false); // fallback
} else { } else {
setValidConfig(result.valid_config === true); setValidConfig(result.valid_config === true);
} }
}); });
}, []); }, []);
if (validConfig === null) {
return (
<div className="flex items-center justify-center h-screen bg-black text-white">
Loading...
</div>
);
}
if (validConfig === null) {
return ( return (
<div className="flex items-center justify-center h-screen bg-black text-white"> <Router>
Loading... <div className="min-w-full min-h-full w-full h-full flex flex-grow bg-black/95 flex-col relative">
</div> <div className="w-full min-h-16 max-h-16 h-16 shrink-0 flex sticky top-0 z-20 border-b border-b-white bg-black overflow-x-hidden">
<TopNav onMenuClick={() => setIsSideNavOpen(true)} />
</div>
<div id="currentpagecontainer" className="w-full grow overflow-y-auto">
<Routes>
{!validConfig ? (
<>
<Route
path="/settings"
element={
<Settings onConfigSaved={() => setValidConfig(true)} />
}
/>
<Route path="*" element={<Navigate to="/settings" replace />} />
</>
) : (
<>
<Route path="/" element={<Navigate to="/results" replace />} />
<Route path="/results" element={<Results />} />
<Route path="/settings" element={<Settings />} />
</>
)}
</Routes>
</div>
<div
className={`fixed top-0 left-0 w-full h-full z-50 bg-black transform transition-transform duration-300 ease-in-out ${
isSideNavOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
<SideNav onClose={() => setIsSideNavOpen(false)} />
</div>
</div>
</Router>
); );
}
return (
<Router>
<div className="min-w-full min-h-full w-full h-full flex flex-grow bg-black/95 flex-col relative">
<div className="w-full min-h-16 max-h-16 h-16 shrink-0 flex sticky top-0 z-20 border-b border-b-white bg-black overflow-x-hidden">
<TopNav onMenuClick={() => setIsSideNavOpen(true)} />
</div>
<div id="currentpagecontainer" className="w-full grow overflow-y-auto">
<Routes>
{!validConfig ? (
<>
<Route
path="/settings"
element={
<Settings onConfigSaved={() => setValidConfig(true)} />
}
/>
<Route path="*" element={<Navigate to="/settings" replace />} />
</>
) : (
<>
<Route path="/" element={<Navigate to="/results" replace />} />
<Route path="/results" element={<Results />} />
<Route path="/settings" element={<Settings />} />
</>
)}
</Routes>
</div>
<div
className={`fixed top-0 left-0 w-full h-full z-50 bg-black transform transition-transform duration-300 ease-in-out ${
isSideNavOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
<SideNav onClose={() => setIsSideNavOpen(false)} />
</div>
</div>
</Router>
);
} }
export default App; export default App;

View File

@ -1,149 +1,139 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
function Results() { function Results() {
const [drmType, setDrmType] = useState(""); const [drmType, setDrmType] = useState("");
const [pssh, setPssh] = useState(""); const [pssh, setPssh] = useState("");
const [licenseUrl, setLicenseUrl] = useState(""); const [licenseUrl, setLicenseUrl] = useState("");
const [keys, setKeys] = useState([]); const [keys, setKeys] = useState([]);
useEffect(() => { useEffect(() => {
chrome.storage.local.get( chrome.storage.local.get(
[ ["drmType", "latestPSSH", "latestLicenseRequest", "latestKeys", "licenseURL"],
"drmType", (result) => {
"latestPSSH", if (result.drmType) setDrmType(result.drmType);
"latestLicenseRequest", if (result.latestPSSH) setPssh(result.latestPSSH);
"latestKeys", if (result.licenseURL) setLicenseUrl(result.licenseURL);
"licenseURL", if (result.latestKeys) {
], try {
(result) => { const parsed = Array.isArray(result.latestKeys)
if (result.drmType) setDrmType(result.drmType); ? result.latestKeys
if (result.latestPSSH) setPssh(result.latestPSSH); : JSON.parse(result.latestKeys);
if (result.licenseURL) setLicenseUrl(result.licenseURL); setKeys(parsed);
if (result.latestKeys) { } catch (e) {
try { console.error("Failed to parse keys:", e);
const parsed = Array.isArray(result.latestKeys) setKeys([]);
? result.latestKeys }
: JSON.parse(result.latestKeys); }
setKeys(parsed); }
} catch (e) {
console.error("Failed to parse keys:", e);
setKeys([]);
}
}
}
);
const handleChange = (changes, area) => {
if (area === "local") {
if (changes.drmType) {
setDrmType(changes.drmType.newValue);
}
if (changes.latestPSSH) {
setPssh(changes.latestPSSH.newValue);
}
if (changes.licenseURL) {
setLicenseUrl(changes.licenseURL.newValue);
}
if (changes.latestKeys) {
setKeys(changes.latestKeys.newValue);
}
}
};
chrome.storage.onChanged.addListener(handleChange);
return () => chrome.storage.onChanged.removeListener(handleChange);
}, []);
const handleCapture = () => {
// Reset stored values
chrome.storage.local.set({
drmType: "None",
latestPSSH: "None",
licenseURL: "None",
latestKeys: [],
});
// Get all normal windows to exclude your popup
chrome.windows.getAll(
{ populate: true, windowTypes: ["normal"] },
(windows) => {
if (!windows || windows.length === 0) {
console.warn("No normal Chrome windows found");
return;
}
// Find the last focused normal window
const lastFocusedWindow = windows.find((w) => w.focused) || windows[0];
if (!lastFocusedWindow) {
console.warn("No focused normal window found");
return;
}
// Find the active tab in that window (that is a regular webpage)
const activeTab = lastFocusedWindow.tabs.find(
(tab) => tab.active && tab.url && /^https?:\/\//.test(tab.url)
); );
if (activeTab?.id) { const handleChange = (changes, area) => {
chrome.tabs.reload(activeTab.id, () => { if (area === "local") {
if (chrome.runtime.lastError) { if (changes.drmType) {
console.error("Failed to reload tab:", chrome.runtime.lastError); setDrmType(changes.drmType.newValue);
}
if (changes.latestPSSH) {
setPssh(changes.latestPSSH.newValue);
}
if (changes.licenseURL) {
setLicenseUrl(changes.licenseURL.newValue);
}
if (changes.latestKeys) {
setKeys(changes.latestKeys.newValue);
}
} }
}); };
} else {
console.warn("No active tab found in the last focused normal window");
}
}
);
};
return ( chrome.storage.onChanged.addListener(handleChange);
<div className="w-full grow flex h-full overflow-y-auto overflow-x-auto flex-col text-white p-4"> return () => chrome.storage.onChanged.removeListener(handleChange);
<button }, []);
onClick={handleCapture}
className="w-full h-10 bg-sky-500 rounded-md p-2 mt-2 text-white cursor-pointer hover:bg-sky-600" const handleCapture = () => {
> // Reset stored values
Capture current tab chrome.storage.local.set({
</button> drmType: "None",
<p className="text-2xl mt-5">DRM Type</p> latestPSSH: "None",
<input licenseURL: "None",
type="text" latestKeys: [],
value={drmType} });
className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white"
placeholder="None" // Get all normal windows to exclude your popup
disabled chrome.windows.getAll({ populate: true, windowTypes: ["normal"] }, (windows) => {
/> if (!windows || windows.length === 0) {
<p className="text-2xl mt-5">PSSH</p> console.warn("No normal Chrome windows found");
<input return;
type="text" }
value={pssh}
className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white" // Find the last focused normal window
placeholder="None" const lastFocusedWindow = windows.find((w) => w.focused) || windows[0];
disabled
/> if (!lastFocusedWindow) {
<p className="text-2xl mt-5">License URL</p> console.warn("No focused normal window found");
<input return;
type="text" }
value={licenseUrl}
className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white" // Find the active tab in that window (that is a regular webpage)
placeholder="None" const activeTab = lastFocusedWindow.tabs.find(
disabled (tab) => tab.active && tab.url && /^https?:\/\//.test(tab.url)
/> );
<p className="text-2xl mt-5">Keys</p>
<div className="w-full min-h-64 h-64 flex items-center justify-center text-center overflow-y-auto bg-slate-800/50 rounded-md p-2 mt-2 text-white whitespace-pre-line"> if (activeTab?.id) {
{Array.isArray(keys) && chrome.tabs.reload(activeTab.id, () => {
keys.filter((k) => k.type !== "SIGNING").length > 0 ? ( if (chrome.runtime.lastError) {
keys console.error("Failed to reload tab:", chrome.runtime.lastError);
.filter((k) => k.type !== "SIGNING") }
.map((k) => `${k.key_id || k.keyId}:${k.key}`) });
.join("\n") } else {
) : ( console.warn("No active tab found in the last focused normal window");
<span className="text-gray-400">None</span> }
)} });
</div> };
</div>
); return (
<div className="w-full grow flex h-full overflow-y-auto overflow-x-auto flex-col text-white p-4">
<button
onClick={handleCapture}
className="w-full h-10 bg-sky-500 rounded-md p-2 mt-2 text-white cursor-pointer hover:bg-sky-600"
>
Capture current tab
</button>
<p className="text-2xl mt-5">DRM Type</p>
<input
type="text"
value={drmType}
className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white"
placeholder="None"
disabled
/>
<p className="text-2xl mt-5">PSSH</p>
<input
type="text"
value={pssh}
className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white"
placeholder="None"
disabled
/>
<p className="text-2xl mt-5">License URL</p>
<input
type="text"
value={licenseUrl}
className="w-full h-10 bg-slate-800/50 rounded-md p-2 mt-2 text-white"
placeholder="None"
disabled
/>
<p className="text-2xl mt-5">Keys</p>
<div className="w-full min-h-64 h-64 flex items-center justify-center text-center overflow-y-auto bg-slate-800/50 rounded-md p-2 mt-2 text-white whitespace-pre-line">
{Array.isArray(keys) && keys.filter((k) => k.type !== "SIGNING").length > 0 ? (
keys
.filter((k) => k.type !== "SIGNING")
.map((k) => `${k.key_id || k.keyId}:${k.key}`)
.join("\n")
) : (
<span className="text-gray-400">None</span>
)}
</div>
</div>
);
} }
export default Results; export default Results;

View File

@ -2,149 +2,140 @@ import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
function Settings({ onConfigSaved }) { function Settings({ onConfigSaved }) {
const [instanceUrl, setInstanceUrl] = useState(""); const [instanceUrl, setInstanceUrl] = useState("");
const [storedUrl, setStoredUrl] = useState(null); const [storedUrl, setStoredUrl] = useState(null);
const [message, setMessage] = useState(null); const [message, setMessage] = useState(null);
const [messageType, setMessageType] = useState(null); const [messageType, setMessageType] = useState(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
chrome.storage.local.get("cdrm_instance", (result) => { chrome.storage.local.get("cdrm_instance", (result) => {
if (chrome.runtime.lastError) {
console.error(
"Error fetching CDRM instance:",
chrome.runtime.lastError
);
} else if (result.cdrm_instance) {
setStoredUrl(result.cdrm_instance);
}
});
}, []);
const handleSave = async () => {
const trimmedUrl = instanceUrl.trim().replace(/\/+$/, "");
if (!trimmedUrl) {
setMessage("Please enter a valid URL.");
setMessageType("error");
return;
}
const endpoint = trimmedUrl + "/api/extension";
setLoading(true);
setMessage(null);
try {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (data.status === true) {
setMessage("Successfully connected to CDRM Instance.");
setMessageType("success");
const widevineRes = await fetch(
`${trimmedUrl}/remotecdm/widevine/deviceinfo`
);
if (!widevineRes.ok)
throw new Error("Failed to fetch Widevine device info");
const widevineData = await widevineRes.json();
const playreadyRes = await fetch(
`${trimmedUrl}/remotecdm/playready/deviceinfo`
);
if (!playreadyRes.ok)
throw new Error("Failed to fetch PlayReady device info");
const playreadyData = await playreadyRes.json();
chrome.storage.local.set(
{
valid_config: true,
cdrm_instance: trimmedUrl,
widevine_device: {
device_type: widevineData.device_type,
system_id: widevineData.system_id,
security_level: widevineData.security_level,
secret: widevineData.secret,
device_name: widevineData.device_name,
host: trimmedUrl,
},
playready_device: {
security_level: playreadyData.security_level,
secret: playreadyData.secret,
device_name: playreadyData.device_name,
host: trimmedUrl,
},
},
() => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error( console.error("Error fetching CDRM instance:", chrome.runtime.lastError);
"Error saving to chrome.storage:", } else if (result.cdrm_instance) {
chrome.runtime.lastError setStoredUrl(result.cdrm_instance);
);
setMessage("Error saving configuration.");
setMessageType("error");
} else {
console.log("Configuration saved.");
setStoredUrl(trimmedUrl);
setInstanceUrl("");
if (onConfigSaved) onConfigSaved();
navigate("/results"); // Automatically redirect after success
} }
} });
); }, []);
} else {
throw new Error("Invalid response from endpoint.");
}
} catch (err) {
console.error("Connection error:", err);
setMessage("Invalid endpoint or device info could not be retrieved.");
setMessageType("error");
} finally {
setLoading(false);
}
};
return ( const handleSave = async () => {
<div className="w-full h-full overflow-y-auto overflow-x-auto flex flex-col p-4"> const trimmedUrl = instanceUrl.trim().replace(/\/+$/, "");
<input if (!trimmedUrl) {
type="text" setMessage("Please enter a valid URL.");
value={instanceUrl} setMessageType("error");
onChange={(e) => setInstanceUrl(e.target.value)} return;
placeholder={
storedUrl
? `Current CDRM Instance: ${storedUrl}`
: "CDRM Instance URL (e.g., https://cdrm-project.com/, http://127.0.0.1:5000/)"
} }
className="w-full p-4 text-lg bg-gray-800 text-white border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mt-4"
/>
<button
onClick={handleSave}
disabled={loading}
className={`mt-4 p-2 ${
loading ? "bg-blue-400" : "bg-blue-600 hover:bg-blue-700"
} text-white rounded-md transition duration-300`}
>
{loading ? "Connecting..." : "Save Settings"}
</button>
{message && ( const endpoint = trimmedUrl + "/api/extension";
<p setLoading(true);
className={`mt-2 text-sm text-center ${ setMessage(null);
messageType === "success" ? "text-green-400" : "text-red-400"
}`} try {
> const response = await fetch(endpoint, {
{message} method: "POST",
</p> headers: {
)} "Content-Type": "application/json",
</div> },
); });
const data = await response.json();
if (data.status === true) {
setMessage("Successfully connected to CDRM Instance.");
setMessageType("success");
const widevineRes = await fetch(`${trimmedUrl}/remotecdm/widevine/deviceinfo`);
if (!widevineRes.ok) throw new Error("Failed to fetch Widevine device info");
const widevineData = await widevineRes.json();
const playreadyRes = await fetch(`${trimmedUrl}/remotecdm/playready/deviceinfo`);
if (!playreadyRes.ok) throw new Error("Failed to fetch PlayReady device info");
const playreadyData = await playreadyRes.json();
chrome.storage.local.set(
{
valid_config: true,
cdrm_instance: trimmedUrl,
widevine_device: {
device_type: widevineData.device_type,
system_id: widevineData.system_id,
security_level: widevineData.security_level,
secret: widevineData.secret,
device_name: widevineData.device_name,
host: trimmedUrl,
},
playready_device: {
security_level: playreadyData.security_level,
secret: playreadyData.secret,
device_name: playreadyData.device_name,
host: trimmedUrl,
},
},
() => {
if (chrome.runtime.lastError) {
console.error(
"Error saving to chrome.storage:",
chrome.runtime.lastError
);
setMessage("Error saving configuration.");
setMessageType("error");
} else {
console.log("Configuration saved.");
setStoredUrl(trimmedUrl);
setInstanceUrl("");
if (onConfigSaved) onConfigSaved();
navigate("/results"); // Automatically redirect after success
}
}
);
} else {
throw new Error("Invalid response from endpoint.");
}
} catch (err) {
console.error("Connection error:", err);
setMessage("Invalid endpoint or device info could not be retrieved.");
setMessageType("error");
} finally {
setLoading(false);
}
};
return (
<div className="w-full h-full overflow-y-auto overflow-x-auto flex flex-col p-4">
<input
type="text"
value={instanceUrl}
onChange={(e) => setInstanceUrl(e.target.value)}
placeholder={
storedUrl
? `Current CDRM Instance: ${storedUrl}`
: "CDRM Instance URL (e.g., https://cdrm-project.com/, http://127.0.0.1:5000/)"
}
className="w-full p-4 text-lg bg-gray-800 text-white border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mt-4"
/>
<button
onClick={handleSave}
disabled={loading}
className={`mt-4 p-2 ${
loading ? "bg-blue-400" : "bg-blue-600 hover:bg-blue-700"
} text-white rounded-md transition duration-300`}
>
{loading ? "Connecting..." : "Save Settings"}
</button>
{message && (
<p
className={`mt-2 text-sm text-center ${
messageType === "success" ? "text-green-400" : "text-red-400"
}`}
>
{message}
</p>
)}
</div>
);
} }
export default Settings; export default Settings;

View File

@ -4,48 +4,45 @@ import settingsIcon from "../assets/settings.svg";
import closeIcon from "../assets/close.svg"; import closeIcon from "../assets/close.svg";
function SideNav({ onClose }) { function SideNav({ onClose }) {
return ( return (
<div className="w-full h-full overflow-y-auto overflow-x-auto flex flex-col bg-black"> <div className="w-full h-full overflow-y-auto overflow-x-auto flex flex-col bg-black">
<div className="w-full min-h-16 max-h-16 h-16 shrink-0 flex sticky top-0 z-20 border-b border-b-white bg-black"> <div className="w-full min-h-16 max-h-16 h-16 shrink-0 flex sticky top-0 z-20 border-b border-b-white bg-black">
<button <button onClick={onClose} className="h-full ml-auto p-3 hover:cursor-pointer">
onClick={onClose} <img src={closeIcon} alt="Close" className="h-full" />
className="h-full ml-auto p-3 hover:cursor-pointer" </button>
> </div>
<img src={closeIcon} alt="Close" className="h-full" />
</button>
</div>
<div className="w-full h-16 flex items-center justify-center mt-2"> <div className="w-full h-16 flex items-center justify-center mt-2">
<NavLink <NavLink
to="/results" to="/results"
onClick={onClose} onClick={onClose}
className="text-white text-2xl font-bold flex flex-row items-center border-l-white hover:border-l-1 w-full hover:bg-black/50 transition duration-300 ease-in-out p-2" className="text-white text-2xl font-bold flex flex-row items-center border-l-white hover:border-l-1 w-full hover:bg-black/50 transition duration-300 ease-in-out p-2"
> >
<img <img
src={homeIcon} src={homeIcon}
alt="Home" alt="Home"
className="h-full w-16 p-2 flex items-center cursor-pointer" className="h-full w-16 p-2 flex items-center cursor-pointer"
/> />
Home Home
</NavLink> </NavLink>
</div> </div>
<div className="w-full h-16 flex items-center justify-center mt-2"> <div className="w-full h-16 flex items-center justify-center mt-2">
<NavLink <NavLink
to="/settings" to="/settings"
onClick={onClose} onClick={onClose}
className="text-white text-2xl font-bold flex flex-row items-center hover:border-l-1 border-l-white w-full hover:bg-black/50 transition duration-300 ease-in-out p-2" className="text-white text-2xl font-bold flex flex-row items-center hover:border-l-1 border-l-white w-full hover:bg-black/50 transition duration-300 ease-in-out p-2"
> >
<img <img
src={settingsIcon} src={settingsIcon}
alt="Settings" alt="Settings"
className="h-full w-16 p-2 flex items-center cursor-pointer" className="h-full w-16 p-2 flex items-center cursor-pointer"
/> />
Settings Settings
</NavLink> </NavLink>
</div> </div>
</div> </div>
); );
} }
export default SideNav; export default SideNav;

View File

@ -2,84 +2,81 @@ import { useEffect, useState } from "react";
import hamburgerIcon from "../assets/hamburger.svg"; import hamburgerIcon from "../assets/hamburger.svg";
function TopNav({ onMenuClick }) { function TopNav({ onMenuClick }) {
const [injectionType, setInjectionType] = useState("LICENSE"); const [injectionType, setInjectionType] = useState("LICENSE");
const [drmOverride, setDrmOverride] = useState("DISABLED"); const [drmOverride, setDrmOverride] = useState("DISABLED");
useEffect(() => { useEffect(() => {
chrome.storage.local.get(["injection_type", "drm_override"], (result) => { chrome.storage.local.get(["injection_type", "drm_override"], (result) => {
if (result.injection_type !== undefined) { if (result.injection_type !== undefined) {
setInjectionType(result.injection_type); setInjectionType(result.injection_type);
} }
if (result.drm_override !== undefined) { if (result.drm_override !== undefined) {
setDrmOverride(result.drm_override); setDrmOverride(result.drm_override);
} }
}); });
}, []); }, []);
const handleInjectionTypeChange = (type) => { const handleInjectionTypeChange = (type) => {
chrome.storage.local.set({ injection_type: type }, () => { chrome.storage.local.set({ injection_type: type }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error( console.error("Error updating injection_type:", chrome.runtime.lastError);
"Error updating injection_type:", } else {
chrome.runtime.lastError setInjectionType(type);
); console.log(`Injection type updated to ${type}`);
} else { }
setInjectionType(type); });
console.log(`Injection type updated to ${type}`); };
}
});
};
const handleDrmOverrideChange = (type) => { const handleDrmOverrideChange = (type) => {
chrome.storage.local.set({ drm_override: type }, () => { chrome.storage.local.set({ drm_override: type }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error("Error updating drm_override:", chrome.runtime.lastError); console.error("Error updating drm_override:", chrome.runtime.lastError);
} else { } else {
setDrmOverride(type); setDrmOverride(type);
console.log(`DRM Override updated to ${type}`); console.log(`DRM Override updated to ${type}`);
} }
}); });
}; };
return ( return (
<div className="w-full h-full flex flex-row overflow-x-hidden"> <div className="w-full h-full flex flex-row overflow-x-hidden">
<img <img
src={hamburgerIcon} src={hamburgerIcon}
alt="Menu" alt="Menu"
className="h-full w-16 p-2 flex items-center cursor-pointer" className="h-full w-16 p-2 flex items-center cursor-pointer"
onClick={onMenuClick} onClick={onMenuClick}
/> />
<div className="flex flex-row h-full justify-center items-center ml-auto mr-2"> <div className="flex flex-row h-full justify-center items-center ml-auto mr-2">
<p className="text-white text-lg p-2 mr-2 border-r-2 border-r-white text-nowrap"> <p className="text-white text-lg p-2 mr-2 border-r-2 border-r-white text-nowrap">
Injection Type: Injection Type:
</p> </p>
<button <button
onClick={() => handleInjectionTypeChange("LICENSE")} onClick={() => handleInjectionTypeChange("LICENSE")}
className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${ className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${
injectionType === "LICENSE" ? "bg-sky-500/70" : "bg-black" injectionType === "LICENSE" ? "bg-sky-500/70" : "bg-black"
}`} }`}
> >
License License
</button> </button>
<button <button
onClick={() => handleInjectionTypeChange("EME")} onClick={() => handleInjectionTypeChange("EME")}
className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${ className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${
injectionType === "EME" ? "bg-green-500/70" : "bg-black" injectionType === "EME" ? "bg-green-500/70" : "bg-black"
}`} }`}
> >
EME EME
</button> </button>
<button <button
onClick={() => handleInjectionTypeChange("DISABLED")} onClick={() => handleInjectionTypeChange("DISABLED")}
className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${ className={`text-white text-lg p-2 rounded-md m-1 cursor-pointer ${
injectionType === "DISABLED" ? "bg-red-500/70" : "bg-black" injectionType === "DISABLED" ? "bg-red-500/70" : "bg-black"
}`} }`}
> >
Disabled Disabled
</button> </button>
</div> </div>
</div> </div>
); );
} }
export default TopNav; export default TopNav;

View File

@ -1,8 +1,10 @@
@import "tailwindcss"; @import "tailwindcss";
html, body, #root { html,
height: 100%; body,
width: 100%; #root {
margin: 0; height: 100%;
padding: 0; width: 100%;
margin: 0;
padding: 0;
} }

View File

@ -1,10 +1,10 @@
import { StrictMode } from 'react' import { StrictMode } from "react";
import { createRoot } from 'react-dom/client' import { createRoot } from "react-dom/client";
import './index.css' import "./index.css";
import App from './App.jsx' import App from "./App.jsx";
createRoot(document.getElementById('root')).render( createRoot(document.getElementById("root")).render(
<StrictMode> <StrictMode>
<App /> <App />
</StrictMode>, </StrictMode>
) );

View File

@ -1,9 +1,9 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
import tailwindcss from '@tailwindcss/vite' import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
base: './', base: "./",
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
}) });

997
inject.js

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,41 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "CDRM Extension 2.0", "name": "CDRM Extension 2.0",
"version": "2.0", "version": "2.0",
"description": "Decrypt DRM Protected content", "description": "Decrypt DRM Protected content",
"permissions": [ "permissions": [
"webRequest", "webRequest",
"webRequestBlocking", "webRequestBlocking",
"<all_urls>", "<all_urls>",
"activeTab", "activeTab",
"storage", "storage",
"tabs", "tabs",
"contextMenus" "contextMenus"
], ],
"background": { "background": {
"scripts": ["background.js"], "scripts": ["background.js"],
"persistent": true "persistent": true
}, },
"content_scripts": [ "content_scripts": [
{ {
"matches": ["<all_urls>"], "matches": ["<all_urls>"],
"js": ["content.js"], "js": ["content.js"],
"run_at": "document_start", "run_at": "document_start",
"all_frames": true "all_frames": true
}
],
"web_accessible_resources": ["inject.js"],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"128": "icons/icon128.png"
} }
],
"web_accessible_resources": ["inject.js"],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"128": "icons/icon128.png"
}
} }