forked from tpd94/CDRM-Project
add prettier formatting
This commit is contained in:
parent
a82a3fd106
commit
2828edd6b7
1
cdrm-frontend/.gitignore
vendored
1
cdrm-frontend/.gitignore
vendored
@ -10,6 +10,7 @@ lerna-debug.log*
|
|||||||
node_modules
|
node_modules
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
dist
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
5
cdrm-frontend/.prettierignore
Normal file
5
cdrm-frontend/.prettierignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
src/assets/icons/
|
||||||
|
src/components/Functions/protobuf.min.js
|
||||||
|
src/components/Functions/license_protocol.min.js
|
8
cdrm-frontend/.prettierrc.json
Normal file
8
cdrm-frontend/.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"useTabs": false,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
@ -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 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" class="w-full h-full">
|
<html lang="en" class="w-full h-full">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favico.png" />
|
<link rel="icon" type="image/svg+xml" href="/favico.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="{{ data.description }}"/>
|
<meta name="description" content="{{ data.description }}" />
|
||||||
<meta name="keywords" content="{{ data.keywords }}"/>
|
<meta name="keywords" content="{{ data.keywords }}" />
|
||||||
<meta property='og:title' content="{{ data.opengraph_title }}" />
|
<meta property="og:title" content="{{ data.opengraph_title }}" />
|
||||||
<meta property='og:description' content="{{ data.opengraph_description }}" />
|
<meta property="og:description" content="{{ data.opengraph_description }}" />
|
||||||
<meta property='og:image' content="{{ data.opengraph_image }}" />
|
<meta property="og:image" content="{{ data.opengraph_image }}" />
|
||||||
<meta property='og:url' content="{{ data.opengraph_url }}" />
|
<meta property="og:url" content="{{ data.opengraph_url }}" />
|
||||||
<meta property='og:locale' content='en_US' />
|
<meta property="og:locale" content="en_US" />
|
||||||
<title>{{ data.tab_title }}</title>
|
<title>{{ data.tab_title }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="w-full h-full">
|
<body class="w-full h-full">
|
||||||
<div id="root" class="w-full h-full"></div>
|
<div id="root" class="w-full h-full"></div>
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
7439
cdrm-frontend/package-lock.json
generated
7439
cdrm-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "cdrm-frontend",
|
"name": "cdrm-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.4",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.10.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-router-dom": "^7.5.2",
|
"react-router-dom": "^7.7.0",
|
||||||
"shaka-player": "^4.14.9",
|
"shaka-player": "^4.15.8",
|
||||||
"tailwindcss": "^4.1.4"
|
"tailwindcss": "^4.1.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.31.0",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
"eslint": "^9.22.0",
|
"eslint": "^9.31.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.20",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.3.0",
|
||||||
"vite": "^6.3.1"
|
"vite": "^7.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,34 +10,40 @@ import Account from "./components/Pages/Account";
|
|||||||
import { Routes, Route } from "react-router-dom";
|
import { Routes, Route } from "react-router-dom";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false); // Track if the menu is open
|
const [isMenuOpen, setIsMenuOpen] = useState(false); // Track if the menu is open
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="appcontainer" className="flex flex-row w-full h-full bg-black">
|
<div id="appcontainer" className="flex flex-row w-full h-full bg-black">
|
||||||
{/* The SideMenu should be visible when isMenuOpen is true */}
|
{/* The SideMenu should be visible when isMenuOpen is true */}
|
||||||
<SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
|
<SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
|
||||||
|
|
||||||
<div id="navbarcontainer" className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 shrink-0">
|
<div
|
||||||
<NavBar />
|
id="navbarcontainer"
|
||||||
</div>
|
className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 shrink-0"
|
||||||
|
>
|
||||||
|
<NavBar />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col grow">
|
<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">
|
<div
|
||||||
<NavBarMain setIsMenuOpen={setIsMenuOpen} />
|
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 overflow-y-auto">
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/cache" element={<Cache />} />
|
||||||
|
<Route path="/api" element={<API />} />
|
||||||
|
<Route path="/testplayer" element={<TestPlayer />} />
|
||||||
|
<Route path="/account" element={<Account />} />
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
<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 />} />
|
|
||||||
<Route path="/account" element={<Account />} />
|
|
||||||
</Routes>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -4,11 +4,11 @@ import "./license_protocol.min.js";
|
|||||||
const { SignedMessage, LicenseRequest } = protobuf.roots.default.license_protocol;
|
const { SignedMessage, LicenseRequest } = protobuf.roots.default.license_protocol;
|
||||||
|
|
||||||
function uint8ArrayToBase64(uint8Array) {
|
function uint8ArrayToBase64(uint8Array) {
|
||||||
const binaryString = Array.from(uint8Array)
|
const binaryString = Array.from(uint8Array)
|
||||||
.map(b => String.fromCharCode(b))
|
.map((b) => String.fromCharCode(b))
|
||||||
.join('');
|
.join("");
|
||||||
|
|
||||||
return btoa(binaryString);
|
return btoa(binaryString);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFetch(fetchString) {
|
function parseFetch(fetchString) {
|
||||||
@ -17,10 +17,13 @@ function parseFetch(fetchString) {
|
|||||||
|
|
||||||
// Use a more lenient regex to capture the fetch pattern (including complex bodies)
|
// Use a more lenient regex to capture the fetch pattern (including complex bodies)
|
||||||
const fetchRegex = /fetch\(['"](.+?)['"],\s*(\{.+?\})\)/s; // Non-greedy match for JSON
|
const fetchRegex = /fetch\(['"](.+?)['"],\s*(\{.+?\})\)/s; // Non-greedy match for JSON
|
||||||
const lines = fetchString.split('\n').map(line => line.trim()).filter(Boolean);
|
const lines = fetchString
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
const result = {
|
const result = {
|
||||||
method: 'UNDEFINED',
|
method: "UNDEFINED",
|
||||||
url: '',
|
url: "",
|
||||||
headers: {},
|
headers: {},
|
||||||
body: null,
|
body: null,
|
||||||
};
|
};
|
||||||
@ -47,9 +50,12 @@ function parseFetch(fetchString) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WIDEVINE_SYSTEM_ID = new Uint8Array([
|
||||||
const WIDEVINE_SYSTEM_ID = new Uint8Array([0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed]);
|
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 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]);
|
const PSSH_MAGIC = new Uint8Array([0x70, 0x73, 0x73, 0x68]);
|
||||||
|
|
||||||
function intToUint8Array(num, endian) {
|
function intToUint8Array(num, endian) {
|
||||||
@ -75,44 +81,44 @@ function psshDataToPsshBoxB64(pssh_data, system_id) {
|
|||||||
...new Uint8Array(4),
|
...new Uint8Array(4),
|
||||||
...system_id,
|
...system_id,
|
||||||
...intToUint8Array(dataLength, false),
|
...intToUint8Array(dataLength, false),
|
||||||
...pssh_data
|
...pssh_data,
|
||||||
]);
|
]);
|
||||||
return uint8ArrayToBase64(pssh);
|
return uint8ArrayToBase64(pssh);
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrmHeaderToPlayReadyHeader(wrm_header){
|
function wrmHeaderToPlayReadyHeader(wrm_header) {
|
||||||
const playready_object = new Uint8Array([
|
const playready_object = new Uint8Array([
|
||||||
...shortToUint8Array(1, true),
|
...shortToUint8Array(1, true),
|
||||||
...shortToUint8Array(wrm_header.length, true),
|
...shortToUint8Array(wrm_header.length, true),
|
||||||
...wrm_header
|
...wrm_header,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return new Uint8Array([
|
return new Uint8Array([
|
||||||
...intToUint8Array(playready_object.length + 2 + 4, true),
|
...intToUint8Array(playready_object.length + 2 + 4, true),
|
||||||
...shortToUint8Array(1, true),
|
...shortToUint8Array(1, true),
|
||||||
...playready_object
|
...playready_object,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeUtf16LE(str) {
|
function encodeUtf16LE(str) {
|
||||||
const buffer = new Uint8Array(str.length * 2);
|
const buffer = new Uint8Array(str.length * 2);
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
const code = str.charCodeAt(i);
|
const code = str.charCodeAt(i);
|
||||||
buffer[i * 2] = code & 0xff;
|
buffer[i * 2] = code & 0xff;
|
||||||
buffer[i * 2 + 1] = code >> 8;
|
buffer[i * 2 + 1] = code >> 8;
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringToUint8Array(string) {
|
function stringToUint8Array(string) {
|
||||||
return Uint8Array.from(string.split("").map(x => x.charCodeAt()));
|
return Uint8Array.from(string.split("").map((x) => x.charCodeAt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readTextFromClipboard() {
|
export async function readTextFromClipboard() {
|
||||||
try {
|
try {
|
||||||
// Request text from the clipboard
|
// Request text from the clipboard
|
||||||
const clipboardText = await navigator.clipboard.readText();
|
const clipboardText = await navigator.clipboard.readText();
|
||||||
|
|
||||||
const result = parseFetch(clipboardText);
|
const result = parseFetch(clipboardText);
|
||||||
|
|
||||||
let pssh_data_string;
|
let pssh_data_string;
|
||||||
@ -136,11 +142,15 @@ export async function readTextFromClipboard() {
|
|||||||
license_request = LicenseRequest.decode(signed_message.msg);
|
license_request = LicenseRequest.decode(signed_message.msg);
|
||||||
} catch (decodeError) {
|
} catch (decodeError) {
|
||||||
// If error occurs during decoding, return an empty pssh
|
// If error occurs during decoding, return an empty pssh
|
||||||
console.error('Decoding failed, returning empty pssh', decodeError);
|
console.error("Decoding failed, returning empty pssh", decodeError);
|
||||||
pssh_data_string = ''; // Empty pssh if decoding fails
|
pssh_data_string = ""; // Empty pssh if decoding fails
|
||||||
}
|
}
|
||||||
|
|
||||||
if (license_request && license_request.contentId && license_request.contentId.widevinePsshData) {
|
if (
|
||||||
|
license_request &&
|
||||||
|
license_request.contentId &&
|
||||||
|
license_request.contentId.widevinePsshData
|
||||||
|
) {
|
||||||
const pssh_data = license_request.contentId.widevinePsshData.psshData[0];
|
const pssh_data = license_request.contentId.widevinePsshData.psshData[0];
|
||||||
pssh_data_string = psshDataToPsshBoxB64(pssh_data, WIDEVINE_SYSTEM_ID);
|
pssh_data_string = psshDataToPsshBoxB64(pssh_data, WIDEVINE_SYSTEM_ID);
|
||||||
}
|
}
|
||||||
@ -160,14 +170,12 @@ export async function readTextFromClipboard() {
|
|||||||
document.getElementById("pssh").value = pssh_data_string;
|
document.getElementById("pssh").value = pssh_data_string;
|
||||||
document.getElementById("data").value = payload_string;
|
document.getElementById("data").value = payload_string;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to read clipboard contents:', error);
|
console.error("Failed to read clipboard contents:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to check if the data is binary
|
// Helper function to check if the data is binary
|
||||||
function isBinary(uint8Array) {
|
function isBinary(uint8Array) {
|
||||||
// Check for non-text (non-ASCII) bytes (basic heuristic)
|
// Check for non-text (non-ASCII) bytes (basic heuristic)
|
||||||
return uint8Array.some(byte => byte > 127); // Non-ASCII byte indicates binary
|
return uint8Array.some((byte) => byte > 127); // Non-ASCII byte indicates binary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from "react";
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from "react-router-dom";
|
||||||
import homeIcon from '../assets/icons/home.svg';
|
import homeIcon from "../assets/icons/home.svg";
|
||||||
import cacheIcon from '../assets/icons/cache.svg';
|
import cacheIcon from "../assets/icons/cache.svg";
|
||||||
import apiIcon from '../assets/icons/api.svg';
|
import apiIcon from "../assets/icons/api.svg";
|
||||||
import testPlayerIcon from '../assets/icons/testplayer.svg';
|
import testPlayerIcon from "../assets/icons/testplayer.svg";
|
||||||
import accountIcon from '../assets/icons/account.svg';
|
import accountIcon from "../assets/icons/account.svg";
|
||||||
import discordIcon from '../assets/icons/discord.svg';
|
import discordIcon from "../assets/icons/discord.svg";
|
||||||
import telegramIcon from '../assets/icons/telegram.svg';
|
import telegramIcon from "../assets/icons/telegram.svg";
|
||||||
import giteaIcon from '../assets/icons/gitea.svg';
|
import giteaIcon from "../assets/icons/gitea.svg";
|
||||||
|
|
||||||
function NavBar() {
|
function NavBar() {
|
||||||
const [externalLinks, setExternalLinks] = useState({
|
const [externalLinks, setExternalLinks] = useState({
|
||||||
discord: '#',
|
discord: "#",
|
||||||
telegram: '#',
|
telegram: "#",
|
||||||
gitea: '#',
|
gitea: "#",
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/links')
|
fetch("/api/links")
|
||||||
.then(response => response.json())
|
.then((response) => response.json())
|
||||||
.then(data => setExternalLinks(data))
|
.then((data) => setExternalLinks(data))
|
||||||
.catch(error => console.error('Error fetching links:', error));
|
.catch((error) => console.error("Error fetching links:", error));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -40,8 +40,8 @@ function NavBar() {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`flex flex-row p-3 border-l-3 ${
|
`flex flex-row p-3 border-l-3 ${
|
||||||
isActive
|
isActive
|
||||||
? 'border-l-sky-500/50 bg-black/50'
|
? "border-l-sky-500/50 bg-black/50"
|
||||||
: 'hover:border-l-sky-500/50 hover:bg-white/5'
|
: "hover:border-l-sky-500/50 hover:bg-white/5"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -58,8 +58,8 @@ function NavBar() {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`flex flex-row p-3 border-l-3 ${
|
`flex flex-row p-3 border-l-3 ${
|
||||||
isActive
|
isActive
|
||||||
? 'border-l-emerald-500/50 bg-black/50'
|
? "border-l-emerald-500/50 bg-black/50"
|
||||||
: 'hover:border-l-emerald-500/50 hover:bg-white/5'
|
: "hover:border-l-emerald-500/50 hover:bg-white/5"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -76,8 +76,8 @@ function NavBar() {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`flex flex-row p-3 border-l-3 ${
|
`flex flex-row p-3 border-l-3 ${
|
||||||
isActive
|
isActive
|
||||||
? 'border-l-indigo-500/50 bg-black/50'
|
? "border-l-indigo-500/50 bg-black/50"
|
||||||
: 'hover:border-l-indigo-500/50 hover:bg-white/5'
|
: "hover:border-l-indigo-500/50 hover:bg-white/5"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -94,13 +94,17 @@ function NavBar() {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`flex flex-row p-3 border-l-3 ${
|
`flex flex-row p-3 border-l-3 ${
|
||||||
isActive
|
isActive
|
||||||
? 'border-l-rose-500/50 bg-black/50'
|
? "border-l-rose-500/50 bg-black/50"
|
||||||
: 'hover:border-l-rose-500/50 hover:bg-white/5'
|
: "hover:border-l-rose-500/50 hover:bg-white/5"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
||||||
<img src={testPlayerIcon} alt="Test Player" className="w-1/2 cursor-pointer" />
|
<img
|
||||||
|
src={testPlayerIcon}
|
||||||
|
alt="Test Player"
|
||||||
|
className="w-1/2 cursor-pointer"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
||||||
Test Player
|
Test Player
|
||||||
@ -114,8 +118,8 @@ function NavBar() {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`flex flex-row p-3 border-l-3 ${
|
`flex flex-row p-3 border-l-3 ${
|
||||||
isActive
|
isActive
|
||||||
? 'border-l-yellow-500/50 bg-black/50'
|
? "border-l-yellow-500/50 bg-black/50"
|
||||||
: 'hover:border-l-yellow-500/50 hover:bg-white/5'
|
: "hover:border-l-yellow-500/50 hover:bg-white/5"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -137,7 +141,11 @@ function NavBar() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-950 group"
|
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-950 group"
|
||||||
>
|
>
|
||||||
<img src={discordIcon} alt="Discord" className="w-1/2 group-hover:animate-bounce" />
|
<img
|
||||||
|
src={discordIcon}
|
||||||
|
alt="Discord"
|
||||||
|
className="w-1/2 group-hover:animate-bounce"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={externalLinks.telegram}
|
href={externalLinks.telegram}
|
||||||
@ -145,7 +153,11 @@ function NavBar() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-400 group"
|
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-400 group"
|
||||||
>
|
>
|
||||||
<img src={telegramIcon} alt="Telegram" className="w-1/2 group-hover:animate-bounce" />
|
<img
|
||||||
|
src={telegramIcon}
|
||||||
|
alt="Telegram"
|
||||||
|
className="w-1/2 group-hover:animate-bounce"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={externalLinks.gitea}
|
href={externalLinks.gitea}
|
||||||
|
@ -2,21 +2,21 @@ import { useState } from "react";
|
|||||||
import hamburgerIcon from "../assets/icons/hamburger.svg";
|
import hamburgerIcon from "../assets/icons/hamburger.svg";
|
||||||
|
|
||||||
function NavBarMain({ setIsMenuOpen }) {
|
function NavBarMain({ setIsMenuOpen }) {
|
||||||
const handleMenuToggle = () => {
|
const handleMenuToggle = () => {
|
||||||
setIsMenuOpen((prevState) => !prevState); // Toggle the menu state
|
setIsMenuOpen((prevState) => !prevState); // Toggle the menu state
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row w-full h-full bg-white/1">
|
<div className="flex flex-row w-full h-full bg-white/1">
|
||||||
<button className="w-24 p-4" onClick={handleMenuToggle}>
|
<button className="w-24 p-4" onClick={handleMenuToggle}>
|
||||||
<img src={hamburgerIcon} alt="Menu" className="w-full h-full cursor-pointer" />
|
<img src={hamburgerIcon} alt="Menu" className="w-full h-full cursor-pointer" />
|
||||||
</button>
|
</button>
|
||||||
<p className="grow text-white md:text-2xl font-bold text-center flex items-center justify-center p-4">
|
<p className="grow text-white md:text-2xl font-bold text-center flex items-center justify-center p-4">
|
||||||
CDRM-Project
|
CDRM-Project
|
||||||
</p>
|
</p>
|
||||||
<div className="w-24 p-4"></div>
|
<div className="w-24 p-4"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NavBarMain;
|
export default NavBarMain;
|
||||||
|
@ -1,73 +1,73 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
import { Helmet } from "react-helmet"; // Import Helmet
|
||||||
|
|
||||||
const { protocol, hostname, port } = window.location;
|
const { protocol, hostname, port } = window.location;
|
||||||
|
|
||||||
let fullHost = `${protocol}//${hostname}`;
|
let fullHost = `${protocol}//${hostname}`;
|
||||||
if (
|
if (
|
||||||
(protocol === 'http:' && port !== '80') ||
|
(protocol === "http:" && port !== "80") ||
|
||||||
(protocol === 'https:' && port !== '443' && port !== '')
|
(protocol === "https:" && port !== "443" && port !== "")
|
||||||
) {
|
) {
|
||||||
fullHost += `:${port}`;
|
fullHost += `:${port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function API() {
|
function API() {
|
||||||
const [deviceInfo, setDeviceInfo] = useState({
|
const [deviceInfo, setDeviceInfo] = useState({
|
||||||
device_type: '',
|
device_type: "",
|
||||||
system_id: '',
|
system_id: "",
|
||||||
security_level: '',
|
security_level: "",
|
||||||
host: '',
|
host: "",
|
||||||
secret: '',
|
secret: "",
|
||||||
device_name: ''
|
device_name: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [prDeviceInfo, setPrDeviceInfo] = useState({
|
const [prDeviceInfo, setPrDeviceInfo] = useState({
|
||||||
security_level: '',
|
security_level: "",
|
||||||
host: '',
|
host: "",
|
||||||
secret: '',
|
secret: "",
|
||||||
device_name: ''
|
device_name: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch Widevine info
|
// Fetch Widevine info
|
||||||
fetch('/remotecdm/widevine/deviceinfo')
|
fetch("/remotecdm/widevine/deviceinfo")
|
||||||
.then(response => response.json())
|
.then((response) => response.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setDeviceInfo({
|
setDeviceInfo({
|
||||||
device_type: data.device_type,
|
device_type: data.device_type,
|
||||||
system_id: data.system_id,
|
system_id: data.system_id,
|
||||||
security_level: data.security_level,
|
security_level: data.security_level,
|
||||||
host: data.host,
|
host: data.host,
|
||||||
secret: data.secret,
|
secret: data.secret,
|
||||||
device_name: data.device_name
|
device_name: data.device_name,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => console.error('Error fetching Widevine info:', error));
|
.catch((error) => console.error("Error fetching Widevine info:", error));
|
||||||
|
|
||||||
// Fetch PlayReady info
|
// Fetch PlayReady info
|
||||||
fetch('/remotecdm/playready/deviceinfo')
|
fetch("/remotecdm/playready/deviceinfo")
|
||||||
.then(response => response.json())
|
.then((response) => response.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setPrDeviceInfo({
|
setPrDeviceInfo({
|
||||||
security_level: data.security_level,
|
security_level: data.security_level,
|
||||||
host: data.host,
|
host: data.host,
|
||||||
secret: data.secret,
|
secret: data.secret,
|
||||||
device_name: data.device_name
|
device_name: data.device_name,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => console.error('Error fetching PlayReady info:', error));
|
.catch((error) => console.error("Error fetching PlayReady info:", error));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full overflow-y-auto p-4 text-white">
|
<div className="flex flex-col w-full overflow-y-auto p-4 text-white">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>API</title>
|
<title>API</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<details open className='w-full list-none'>
|
<details open className="w-full list-none">
|
||||||
<summary className='text-2xl'>Sending a decryption request</summary>
|
<summary className="text-2xl">Sending a decryption request</summary>
|
||||||
<div className='mt-5 p-5 rounded-lg border-2 border-indigo-500/50'>
|
<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'>
|
<pre className="rounded-lg font-mono whitespace-pre-wrap text-white overflow-auto">
|
||||||
{`import requests
|
{`import requests
|
||||||
|
|
||||||
print(requests.post(
|
print(requests.post(
|
||||||
url='${fullHost}/api/decrypt',
|
url='${fullHost}/api/decrypt',
|
||||||
@ -84,14 +84,14 @@ print(requests.post(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
).json()['message'])`}
|
).json()['message'])`}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details open className='w-full list-none mt-5'>
|
<details open className="w-full list-none mt-5">
|
||||||
<summary className='text-2xl'>Sending a search request</summary>
|
<summary className="text-2xl">Sending a search request</summary>
|
||||||
<div className='mt-5 border-2 border-indigo-500/50 p-5 rounded-lg'>
|
<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">
|
<pre className="rounded-lg font-mono whitespace-pre text-white overflow-x-auto max-w-full p-5">
|
||||||
{`import requests
|
{`import requests
|
||||||
|
|
||||||
print(requests.post(
|
print(requests.post(
|
||||||
url='${fullHost}/api/cache/search',
|
url='${fullHost}/api/cache/search',
|
||||||
@ -99,36 +99,40 @@ print(requests.post(
|
|||||||
'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
|
'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
|
||||||
}
|
}
|
||||||
).json())`}
|
).json())`}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details open className='w-full list-none mt-5'>
|
<details open className="w-full list-none mt-5">
|
||||||
<summary className='text-2xl'>PyWidevine RemoteCDM info</summary>
|
<summary className="text-2xl">PyWidevine RemoteCDM info</summary>
|
||||||
<div className='mt-5 border-2 border-indigo-500/50 p-5 rounded-lg overflow-x-auto'>
|
<div className="mt-5 border-2 border-indigo-500/50 p-5 rounded-lg overflow-x-auto">
|
||||||
<p>
|
<p>
|
||||||
<strong>Device Type:</strong> '{deviceInfo.device_type}'<br />
|
<strong>Device Type:</strong> '{deviceInfo.device_type}'<br />
|
||||||
<strong>System ID:</strong> {deviceInfo.system_id}<br />
|
<strong>System ID:</strong> {deviceInfo.system_id}
|
||||||
<strong>Security Level:</strong> {deviceInfo.security_level}<br />
|
<br />
|
||||||
<strong>Host:</strong> {fullHost}/remotecdm/widevine<br />
|
<strong>Security Level:</strong> {deviceInfo.security_level}
|
||||||
<strong>Secret:</strong> '{deviceInfo.secret}'<br />
|
<br />
|
||||||
<strong>Device Name:</strong> {deviceInfo.device_name}
|
<strong>Host:</strong> {fullHost}/remotecdm/widevine
|
||||||
</p>
|
<br />
|
||||||
</div>
|
<strong>Secret:</strong> '{deviceInfo.secret}'<br />
|
||||||
</details>
|
<strong>Device Name:</strong> {deviceInfo.device_name}
|
||||||
<details open className='w-full list-none mt-5'>
|
</p>
|
||||||
<summary className='text-2xl'>PyPlayready RemoteCDM info</summary>
|
</div>
|
||||||
<div className='mt-5 border-2 border-indigo-500/50 p-5 rounded-lg overflow-x-auto'>
|
</details>
|
||||||
<p>
|
<details open className="w-full list-none mt-5">
|
||||||
<strong>Security Level:</strong> {prDeviceInfo.security_level}<br />
|
<summary className="text-2xl">PyPlayready RemoteCDM info</summary>
|
||||||
<strong>Host:</strong> {fullHost}/remotecdm/playready<br />
|
<div className="mt-5 border-2 border-indigo-500/50 p-5 rounded-lg overflow-x-auto">
|
||||||
<strong>Secret:</strong> '{prDeviceInfo.secret}'<br />
|
<p>
|
||||||
<strong>Device Name:</strong> {prDeviceInfo.device_name}
|
<strong>Security Level:</strong> {prDeviceInfo.security_level}
|
||||||
</p>
|
<br />
|
||||||
</div>
|
<strong>Host:</strong> {fullHost}/remotecdm/playready
|
||||||
</details>
|
<br />
|
||||||
|
<strong>Secret:</strong> '{prDeviceInfo.secret}'<br />
|
||||||
</div>
|
<strong>Device Name:</strong> {prDeviceInfo.device_name}
|
||||||
);
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default API;
|
export default API;
|
||||||
|
@ -3,36 +3,36 @@ import Register from "./Register";
|
|||||||
import MyAccount from "./MyAccount"; // <-- Import the MyAccount component
|
import MyAccount from "./MyAccount"; // <-- Import the MyAccount component
|
||||||
|
|
||||||
function Account() {
|
function Account() {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(null); // null = loading state
|
const [isLoggedIn, setIsLoggedIn] = useState(null); // null = loading state
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/login/status', {
|
fetch("/login/status", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
credentials: 'include', // Sends cookies with request
|
credentials: "include", // Sends cookies with request
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then((res) => res.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
if (data.message === 'True') {
|
if (data.message === "True") {
|
||||||
setIsLoggedIn(true);
|
setIsLoggedIn(true);
|
||||||
} else {
|
} else {
|
||||||
setIsLoggedIn(false);
|
setIsLoggedIn(false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
console.error("Error checking login status:", err);
|
console.error("Error checking login status:", err);
|
||||||
setIsLoggedIn(false); // Assume not logged in on error
|
setIsLoggedIn(false); // Assume not logged in on error
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isLoggedIn === null) {
|
if (isLoggedIn === null) {
|
||||||
return <div>Loading...</div>; // Optional loading UI
|
return <div>Loading...</div>; // Optional loading UI
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="accountpage" className="w-full h-full flex">
|
<div id="accountpage" className="w-full h-full flex">
|
||||||
{isLoggedIn ? <MyAccount /> : <Register />}
|
{isLoggedIn ? <MyAccount /> : <Register />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Account;
|
export default Account;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
import { Helmet } from "react-helmet"; // Import Helmet
|
||||||
|
|
||||||
function Cache() {
|
function Cache() {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [cacheData, setCacheData] = useState([]);
|
const [cacheData, setCacheData] = useState([]);
|
||||||
const [keyCount, setKeyCount] = useState(0); // New state to store the key count
|
const [keyCount, setKeyCount] = useState(0); // New state to store the key count
|
||||||
const debounceTimeout = useRef(null);
|
const debounceTimeout = useRef(null);
|
||||||
@ -11,11 +11,11 @@ function Cache() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchKeyCount = async () => {
|
const fetchKeyCount = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/cache/keycount');
|
const response = await fetch("/api/cache/keycount");
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setKeyCount(data.count); // Update key count
|
setKeyCount(data.count); // Update key count
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching key count:', error);
|
console.error("Error fetching key count:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ function Cache() {
|
|||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event) => {
|
||||||
const query = event.target.value;
|
const query = event.target.value;
|
||||||
setSearchQuery(query); // Update the search query
|
setSearchQuery(query); // Update the search query
|
||||||
|
|
||||||
// Clear the previous timeout
|
// Clear the previous timeout
|
||||||
if (debounceTimeout.current) {
|
if (debounceTimeout.current) {
|
||||||
clearTimeout(debounceTimeout.current);
|
clearTimeout(debounceTimeout.current);
|
||||||
@ -33,7 +33,7 @@ function Cache() {
|
|||||||
|
|
||||||
// Set a new timeout to send the API call after 1 second of no typing
|
// Set a new timeout to send the API call after 1 second of no typing
|
||||||
debounceTimeout.current = setTimeout(() => {
|
debounceTimeout.current = setTimeout(() => {
|
||||||
if (query.trim() !== '') {
|
if (query.trim() !== "") {
|
||||||
sendApiCall(query); // Only call the API if the query is not empty
|
sendApiCall(query); // Only call the API if the query is not empty
|
||||||
} else {
|
} else {
|
||||||
setCacheData([]); // Clear results if query is empty
|
setCacheData([]); // Clear results if query is empty
|
||||||
@ -42,14 +42,14 @@ function Cache() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sendApiCall = (text) => {
|
const sendApiCall = (text) => {
|
||||||
fetch('/api/cache/search', {
|
fetch("/api/cache/search", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ input: text }),
|
body: JSON.stringify({ input: text }),
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => setCacheData(data)) // Update cache data with the results
|
.then((data) => setCacheData(data)) // Update cache data with the results
|
||||||
.catch((error) => console.error('Error:', error));
|
.catch((error) => console.error("Error:", error));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,248 +1,272 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { readTextFromClipboard } from '../Functions/ParseChallenge';
|
import { readTextFromClipboard } from "../Functions/ParseChallenge";
|
||||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
import { Helmet } from "react-helmet"; // Import Helmet
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
const [pssh, setPssh] = useState('');
|
const [pssh, setPssh] = useState("");
|
||||||
const [licurl, setLicurl] = useState('');
|
const [licurl, setLicurl] = useState("");
|
||||||
const [proxy, setProxy] = useState('');
|
const [proxy, setProxy] = useState("");
|
||||||
const [headers, setHeaders] = useState('');
|
const [headers, setHeaders] = useState("");
|
||||||
const [cookies, setCookies] = useState('');
|
const [cookies, setCookies] = useState("");
|
||||||
const [data, setData] = useState('');
|
const [data, setData] = useState("");
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState("");
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const [devices, setDevices] = useState([]);
|
const [devices, setDevices] = useState([]);
|
||||||
const [selectedDevice, setSelectedDevice] = useState('default');
|
const [selectedDevice, setSelectedDevice] = useState("default");
|
||||||
|
|
||||||
const bottomRef = useRef(null);
|
const bottomRef = useRef(null);
|
||||||
const messageRef = useRef(null); // Reference to result container
|
const messageRef = useRef(null); // Reference to result container
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
setIsVisible(false);
|
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,
|
|
||||||
device: selectedDevice, // Include selected device in the request
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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!');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isVisible && bottomRef.current) {
|
|
||||||
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
}, [message, isVisible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('/login/status', {
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(statusData => {
|
|
||||||
if (statusData.message === 'True') {
|
|
||||||
return fetch('/userinfo', { method: 'POST' });
|
|
||||||
} else {
|
|
||||||
throw new Error('Not logged in');
|
|
||||||
}
|
}
|
||||||
})
|
setPssh("");
|
||||||
.then(res => res.json())
|
setLicurl("");
|
||||||
.then(deviceData => {
|
setProxy("");
|
||||||
const combinedDevices = [
|
setHeaders("");
|
||||||
...deviceData.Widevine_Devices,
|
setCookies("");
|
||||||
...deviceData.Playready_Devices,
|
setData("");
|
||||||
];
|
};
|
||||||
|
|
||||||
// Add default devices if logged in
|
const handleSubmitButton = (event) => {
|
||||||
const allDevices = [
|
event.preventDefault();
|
||||||
"CDRM-Project Public Widevine CDM",
|
|
||||||
"CDRM-Project Public PlayReady CDM",
|
|
||||||
...combinedDevices,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Set devices and select a device if logged in
|
fetch("/api/decrypt", {
|
||||||
setDevices(allDevices.length > 0 ? allDevices : []);
|
method: "POST",
|
||||||
setSelectedDevice(allDevices.length > 0 ? allDevices[0] : 'default');
|
headers: {
|
||||||
})
|
"Content-Type": "application/json",
|
||||||
.catch(() => {
|
},
|
||||||
// User isn't logged in, set default device to 'default'
|
body: JSON.stringify({
|
||||||
setDevices([]); // Don't display devices list
|
pssh: pssh,
|
||||||
setSelectedDevice('default');
|
licurl: licurl,
|
||||||
});
|
proxy: proxy,
|
||||||
}, []);
|
headers: headers,
|
||||||
|
cookies: cookies,
|
||||||
|
data: data,
|
||||||
|
device: selectedDevice, // Include selected device in the request
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const handleCopy = (event) => {
|
||||||
<>
|
event.preventDefault();
|
||||||
<div className="flex flex-col w-full overflow-y-auto p-4 min-h-full">
|
if (messageRef.current) {
|
||||||
<Helmet>
|
const textToCopy = messageRef.current.innerText; // Grab the plain text (with visual line breaks)
|
||||||
<title>CDRM-Project</title>
|
navigator.clipboard.writeText(textToCopy).catch((err) => {
|
||||||
</Helmet>
|
alert("Failed to copy!");
|
||||||
<form className="flex flex-col w-full h-full bg-black/5 p-4 overflow-y-auto">
|
console.error(err);
|
||||||
<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)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Device Selection Dropdown, only show if logged in */}
|
const handleFetchPaste = () => {
|
||||||
{devices.length > 0 && (
|
event.preventDefault();
|
||||||
<>
|
readTextFromClipboard()
|
||||||
<label htmlFor="device" className="text-white w-8/10 self-center">Select Device:</label>
|
.then(() => {
|
||||||
<select
|
setPssh(document.getElementById("pssh").value);
|
||||||
id="device"
|
setLicurl(document.getElementById("licurl").value);
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white bg-black p-1"
|
setHeaders(document.getElementById("headers").value);
|
||||||
value={selectedDevice}
|
setData(document.getElementById("data").value);
|
||||||
onChange={(e) => setSelectedDevice(e.target.value)}
|
})
|
||||||
>
|
.catch((err) => {
|
||||||
{devices.map((device, index) => (
|
alert("Failed to paste from fetch!");
|
||||||
<option key={index} value={device}>{device}</option>
|
});
|
||||||
))}
|
};
|
||||||
</select>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
useEffect(() => {
|
||||||
<button
|
if (isVisible && bottomRef.current) {
|
||||||
type="button"
|
bottomRef.current.scrollIntoView({ behavior: "smooth" });
|
||||||
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}
|
}, [message, isVisible]);
|
||||||
>
|
|
||||||
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 && (
|
useEffect(() => {
|
||||||
<div id="main_content" className="flex-col w-full h-full p-10 items-center justify-center self-center">
|
fetch("/login/status", {
|
||||||
<div className="flex flex-col w-full h-full overflow-y-auto items-center">
|
method: "POST",
|
||||||
<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>
|
.then((res) => res.json())
|
||||||
<p
|
.then((statusData) => {
|
||||||
className="w-full grow pt-10 break-words overflow-y-auto"
|
if (statusData.message === "True") {
|
||||||
ref={messageRef}
|
return fetch("/userinfo", { method: "POST" });
|
||||||
dangerouslySetInnerHTML={{ __html: message }}
|
} else {
|
||||||
/>
|
throw new Error("Not logged in");
|
||||||
<div ref={bottomRef} />
|
}
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((deviceData) => {
|
||||||
|
const combinedDevices = [
|
||||||
|
...deviceData.Widevine_Devices,
|
||||||
|
...deviceData.Playready_Devices,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add default devices if logged in
|
||||||
|
const allDevices = [
|
||||||
|
"CDRM-Project Public Widevine CDM",
|
||||||
|
"CDRM-Project Public PlayReady CDM",
|
||||||
|
...combinedDevices,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set devices and select a device if logged in
|
||||||
|
setDevices(allDevices.length > 0 ? allDevices : []);
|
||||||
|
setSelectedDevice(allDevices.length > 0 ? allDevices[0] : "default");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// User isn't logged in, set default device to 'default'
|
||||||
|
setDevices([]); // Don't display devices list
|
||||||
|
setSelectedDevice("default");
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Device Selection Dropdown, only show if logged in */}
|
||||||
|
{devices.length > 0 && (
|
||||||
|
<>
|
||||||
|
<label htmlFor="device" className="text-white w-8/10 self-center">
|
||||||
|
Select Device:
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="device"
|
||||||
|
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white bg-black p-1"
|
||||||
|
value={selectedDevice}
|
||||||
|
onChange={(e) => setSelectedDevice(e.target.value)}
|
||||||
|
>
|
||||||
|
{devices.map((device, index) => (
|
||||||
|
<option key={index} value={device}>
|
||||||
|
{device}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
{isVisible && (
|
||||||
<button
|
<div
|
||||||
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"
|
id="main_content"
|
||||||
onClick={handleCopy}
|
className="flex-col w-full h-full p-10 items-center justify-center self-center"
|
||||||
>
|
>
|
||||||
Copy Results
|
<div className="flex flex-col w-full h-full overflow-y-auto items-center">
|
||||||
</button>
|
<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">
|
||||||
</div>
|
<p className="w-full border-b-2 border-white/75 pb-2">Results:</p>
|
||||||
</div>
|
<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;
|
export default HomePage;
|
||||||
|
@ -1,262 +1,285 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
|
|
||||||
function MyAccount() {
|
function MyAccount() {
|
||||||
const [wvList, setWvList] = useState([]);
|
const [wvList, setWvList] = useState([]);
|
||||||
const [prList, setPrList] = useState([]);
|
const [prList, setPrList] = useState([]);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState("");
|
||||||
const [apiKey, setApiKey] = useState('');
|
const [apiKey, setApiKey] = useState("");
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState("");
|
||||||
const [passwordError, setPasswordError] = useState('');
|
const [passwordError, setPasswordError] = useState("");
|
||||||
const [newApiKey, setNewApiKey] = useState('');
|
const [newApiKey, setNewApiKey] = useState("");
|
||||||
const [apiKeyError, setApiKeyError] = useState('');
|
const [apiKeyError, setApiKeyError] = useState("");
|
||||||
|
|
||||||
// Fetch user info
|
// Fetch user info
|
||||||
const fetchUserInfo = async () => {
|
const fetchUserInfo = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/userinfo');
|
const response = await axios.post("/userinfo");
|
||||||
setWvList(response.data.Widevine_Devices || []);
|
setWvList(response.data.Widevine_Devices || []);
|
||||||
setPrList(response.data.Playready_Devices || []);
|
setPrList(response.data.Playready_Devices || []);
|
||||||
setUsername(response.data.Styled_Username || '');
|
setUsername(response.data.Styled_Username || "");
|
||||||
setApiKey(response.data.API_Key || '');
|
setApiKey(response.data.API_Key || "");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch user info', err);
|
console.error("Failed to fetch user info", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUserInfo();
|
fetchUserInfo();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle file upload
|
// Handle file upload
|
||||||
const handleUpload = async (event, cdmType) => {
|
const handleUpload = async (event, cdmType) => {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const extension = file.name.split('.').pop();
|
const extension = file.name.split(".").pop();
|
||||||
if ((cdmType === 'PR' && extension !== 'prd') || (cdmType === 'WV' && extension !== 'wvd')) {
|
if (
|
||||||
alert(`Please upload a .${cdmType === 'PR' ? 'prd' : 'wvd'} file.`);
|
(cdmType === "PR" && extension !== "prd") ||
|
||||||
return;
|
(cdmType === "WV" && extension !== "wvd")
|
||||||
}
|
) {
|
||||||
|
alert(`Please upload a .${cdmType === "PR" ? "prd" : "wvd"} file.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append("file", file);
|
||||||
|
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
try {
|
try {
|
||||||
await axios.post(`/upload/${cdmType}`, formData);
|
await axios.post(`/upload/${cdmType}`, formData);
|
||||||
await fetchUserInfo(); // Refresh list after upload
|
await fetchUserInfo(); // Refresh list after upload
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Upload failed', err);
|
console.error("Upload failed", err);
|
||||||
alert('Upload failed');
|
alert("Upload failed");
|
||||||
} finally {
|
} finally {
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle logout
|
// Handle logout
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post('/logout');
|
await axios.post("/logout");
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Logout failed:', error);
|
console.error("Logout failed:", error);
|
||||||
alert('Logout failed!');
|
alert("Logout failed!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle change password
|
// Handle change password
|
||||||
const handleChangePassword = async () => {
|
const handleChangePassword = async () => {
|
||||||
if (passwordError || password === '') {
|
if (passwordError || password === "") {
|
||||||
alert('Please enter a valid password.');
|
alert("Please enter a valid password.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/user/change_password', {
|
const response = await axios.post("/user/change_password", {
|
||||||
new_password: password
|
new_password: password,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.message === 'True') {
|
if (response.data.message === "True") {
|
||||||
alert('Password changed successfully.');
|
alert("Password changed successfully.");
|
||||||
setPassword('');
|
setPassword("");
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to change password.');
|
alert("Failed to change password.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response && error.response.data?.message === 'Invalid password format') {
|
if (error.response && error.response.data?.message === "Invalid password format") {
|
||||||
alert('Password format is invalid. Please try again.');
|
alert("Password format is invalid. Please try again.");
|
||||||
} else {
|
} else {
|
||||||
alert('Error occurred while changing password.');
|
alert("Error occurred while changing password.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle change API key
|
// Handle change API key
|
||||||
const handleChangeApiKey = async () => {
|
const handleChangeApiKey = async () => {
|
||||||
if (apiKeyError || newApiKey === '') {
|
if (apiKeyError || newApiKey === "") {
|
||||||
alert('Please enter a valid API key.');
|
alert("Please enter a valid API key.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post('/user/change_api_key', {
|
const response = await axios.post("/user/change_api_key", {
|
||||||
new_api_key: newApiKey,
|
new_api_key: newApiKey,
|
||||||
});
|
});
|
||||||
if (response.data.message === 'True') {
|
if (response.data.message === "True") {
|
||||||
alert('API key changed successfully.');
|
alert("API key changed successfully.");
|
||||||
setApiKey(newApiKey);
|
setApiKey(newApiKey);
|
||||||
setNewApiKey('');
|
setNewApiKey("");
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to change API key.');
|
alert("Failed to change API key.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('Error occurred while changing API key.');
|
alert("Error occurred while changing API key.");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="myaccount" className="flex flex-col lg:flex-row gap-4 w-full min-h-full overflow-y-auto p-4">
|
<div
|
||||||
<div className="flex-col w-full min-h-164 lg:h-full lg:w-96 border-2 border-yellow-500/50 rounded-2xl p-4 flex items-center overflow-y-auto">
|
id="myaccount"
|
||||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 w-full text-center mb-2">
|
className="flex flex-col lg:flex-row gap-4 w-full min-h-full overflow-y-auto p-4"
|
||||||
{username ? `${username}` : 'My Account'}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{/* API Key Section */}
|
|
||||||
<div className="w-full flex flex-col items-center">
|
|
||||||
<label htmlFor="apiKey" className="text-white font-semibold mb-1">API Key</label>
|
|
||||||
<input
|
|
||||||
id="apiKey"
|
|
||||||
type="text"
|
|
||||||
value={apiKey}
|
|
||||||
readOnly
|
|
||||||
className="w-full p-2 mb-4 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* New API Key Section */}
|
|
||||||
<label htmlFor="newApiKey" className="text-white font-semibold mt-4 mb-1">New API Key</label>
|
|
||||||
<input
|
|
||||||
id="newApiKey"
|
|
||||||
type="text"
|
|
||||||
value={newApiKey}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
const isValid = /^[^\s]+$/.test(value); // No spaces
|
|
||||||
if (!isValid) {
|
|
||||||
setApiKeyError('API key must not contain spaces.');
|
|
||||||
} else {
|
|
||||||
setApiKeyError('');
|
|
||||||
}
|
|
||||||
setNewApiKey(value);
|
|
||||||
}}
|
|
||||||
placeholder="Enter new API key"
|
|
||||||
className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
|
||||||
/>
|
|
||||||
{apiKeyError && <p className="text-red-500 text-sm mb-3">{apiKeyError}</p>}
|
|
||||||
<button
|
|
||||||
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
|
||||||
onClick={handleChangeApiKey}
|
|
||||||
>
|
|
||||||
Change API Key
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Change Password Section */}
|
|
||||||
<label htmlFor="password" className="text-white font-semibold mt-4 mb-1">Change Password</label>
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
const isValid = /^[A-Za-z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]*$/.test(value);
|
|
||||||
if (!isValid) {
|
|
||||||
setPasswordError('Password must not contain spaces or invalid characters.');
|
|
||||||
} else {
|
|
||||||
setPasswordError('');
|
|
||||||
}
|
|
||||||
setPassword(value);
|
|
||||||
}}
|
|
||||||
placeholder="New Password"
|
|
||||||
className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
|
||||||
/>
|
|
||||||
{passwordError && <p className="text-red-500 text-sm mb-3">{passwordError}</p>}
|
|
||||||
<button
|
|
||||||
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
|
||||||
onClick={handleChangePassword}
|
|
||||||
>
|
|
||||||
Change Password
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="mt-auto w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
|
||||||
>
|
>
|
||||||
Log out
|
<div className="flex-col w-full min-h-164 lg:h-full lg:w-96 border-2 border-yellow-500/50 rounded-2xl p-4 flex items-center overflow-y-auto">
|
||||||
</button>
|
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 w-full text-center mb-2">
|
||||||
</div>
|
{username ? `${username}` : "My Account"}
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div className="flex flex-col w-full lg:ml-2 mt-2 lg:mt-0">
|
{/* API Key Section */}
|
||||||
{/* Widevine Section */}
|
<div className="w-full flex flex-col items-center">
|
||||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl lg:p-4 p-2 overflow-y-auto">
|
<label htmlFor="apiKey" className="text-white font-semibold mb-1">
|
||||||
<h1 className="bg-black text-2xl font-bold text-white border-b-2 border-white p-2">Widevine CDMs</h1>
|
API Key
|
||||||
<div className="flex flex-col w-full grow p-2 bg-white/5 rounded-2xl mt-2 text-white text-left">
|
</label>
|
||||||
{wvList.length === 0 ? (
|
<input
|
||||||
<div className="text-white text-center font-bold">No Widevine CDMs uploaded.</div>
|
id="apiKey"
|
||||||
) : (
|
type="text"
|
||||||
wvList.map((filename, i) => (
|
value={apiKey}
|
||||||
<div
|
readOnly
|
||||||
key={i}
|
className="w-full p-2 mb-4 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
||||||
className={`text-center font-bold text-white p-2 rounded ${i % 2 === 0 ? 'bg-black/30' : 'bg-black/60'}`}
|
/>
|
||||||
>
|
|
||||||
{filename}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
|
||||||
{uploading ? 'Uploading...' : 'Upload CDM'}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".wvd"
|
|
||||||
hidden
|
|
||||||
onChange={(e) => handleUpload(e, 'WV')}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Playready Section */}
|
{/* New API Key Section */}
|
||||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl p-2 mt-2 lg:mt-2 overflow-y-auto">
|
<label htmlFor="newApiKey" className="text-white font-semibold mt-4 mb-1">
|
||||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 bg-black">Playready CDMs</h1>
|
New API Key
|
||||||
<div className="flex flex-col w-full bg-white/5 grow rounded-2xl mt-2 text-white text-left p-2">
|
</label>
|
||||||
{prList.length === 0 ? (
|
<input
|
||||||
<div className="text-white text-center font-bold">No Playready CDMs uploaded.</div>
|
id="newApiKey"
|
||||||
) : (
|
type="text"
|
||||||
prList.map((filename, i) => (
|
value={newApiKey}
|
||||||
<div
|
onChange={(e) => {
|
||||||
key={i}
|
const value = e.target.value;
|
||||||
className={`text-center font-bold text-white p-2 rounded ${i % 2 === 0 ? 'bg-black/30' : 'bg-black/60'}`}
|
const isValid = /^[^\s]+$/.test(value); // No spaces
|
||||||
>
|
if (!isValid) {
|
||||||
{filename}
|
setApiKeyError("API key must not contain spaces.");
|
||||||
|
} else {
|
||||||
|
setApiKeyError("");
|
||||||
|
}
|
||||||
|
setNewApiKey(value);
|
||||||
|
}}
|
||||||
|
placeholder="Enter new API key"
|
||||||
|
className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
||||||
|
/>
|
||||||
|
{apiKeyError && <p className="text-red-500 text-sm mb-3">{apiKeyError}</p>}
|
||||||
|
<button
|
||||||
|
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
||||||
|
onClick={handleChangeApiKey}
|
||||||
|
>
|
||||||
|
Change API Key
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Change Password Section */}
|
||||||
|
<label htmlFor="password" className="text-white font-semibold mt-4 mb-1">
|
||||||
|
Change Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
const isValid =
|
||||||
|
/^[A-Za-z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]*$/.test(value);
|
||||||
|
if (!isValid) {
|
||||||
|
setPasswordError(
|
||||||
|
"Password must not contain spaces or invalid characters."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setPasswordError("");
|
||||||
|
}
|
||||||
|
setPassword(value);
|
||||||
|
}}
|
||||||
|
placeholder="New Password"
|
||||||
|
className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
||||||
|
/>
|
||||||
|
{passwordError && <p className="text-red-500 text-sm mb-3">{passwordError}</p>}
|
||||||
|
<button
|
||||||
|
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
||||||
|
onClick={handleChangePassword}
|
||||||
|
>
|
||||||
|
Change Password
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))
|
|
||||||
)}
|
<button
|
||||||
</div>
|
onClick={handleLogout}
|
||||||
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
className="mt-auto w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
||||||
{uploading ? 'Uploading...' : 'Upload CDM'}
|
>
|
||||||
<input
|
Log out
|
||||||
type="file"
|
</button>
|
||||||
accept=".prd"
|
</div>
|
||||||
hidden
|
|
||||||
onChange={(e) => handleUpload(e, 'PR')}
|
<div className="flex flex-col w-full lg:ml-2 mt-2 lg:mt-0">
|
||||||
/>
|
{/* Widevine Section */}
|
||||||
</label>
|
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl lg:p-4 p-2 overflow-y-auto">
|
||||||
|
<h1 className="bg-black text-2xl font-bold text-white border-b-2 border-white p-2">
|
||||||
|
Widevine CDMs
|
||||||
|
</h1>
|
||||||
|
<div className="flex flex-col w-full grow p-2 bg-white/5 rounded-2xl mt-2 text-white text-left">
|
||||||
|
{wvList.length === 0 ? (
|
||||||
|
<div className="text-white text-center font-bold">
|
||||||
|
No Widevine CDMs uploaded.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
wvList.map((filename, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`text-center font-bold text-white p-2 rounded ${i % 2 === 0 ? "bg-black/30" : "bg-black/60"}`}
|
||||||
|
>
|
||||||
|
{filename}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
||||||
|
{uploading ? "Uploading..." : "Upload CDM"}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".wvd"
|
||||||
|
hidden
|
||||||
|
onChange={(e) => handleUpload(e, "WV")}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Playready Section */}
|
||||||
|
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl p-2 mt-2 lg:mt-2 overflow-y-auto">
|
||||||
|
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 bg-black">
|
||||||
|
Playready CDMs
|
||||||
|
</h1>
|
||||||
|
<div className="flex flex-col w-full bg-white/5 grow rounded-2xl mt-2 text-white text-left p-2">
|
||||||
|
{prList.length === 0 ? (
|
||||||
|
<div className="text-white text-center font-bold">
|
||||||
|
No Playready CDMs uploaded.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
prList.map((filename, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`text-center font-bold text-white p-2 rounded ${i % 2 === 0 ? "bg-black/30" : "bg-black/60"}`}
|
||||||
|
>
|
||||||
|
{filename}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
||||||
|
{uploading ? "Uploading..." : "Upload CDM"}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".prd"
|
||||||
|
hidden
|
||||||
|
onChange={(e) => handleUpload(e, "PR")}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyAccount;
|
export default MyAccount;
|
||||||
|
@ -1,117 +1,117 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
|
|
||||||
function Register() {
|
function Register() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState("");
|
||||||
const [status, setStatus] = useState('');
|
const [status, setStatus] = useState("");
|
||||||
|
|
||||||
// Validation functions
|
// Validation functions
|
||||||
const validateUsername = (name) => /^[A-Za-z0-9_-]+$/.test(name);
|
const validateUsername = (name) => /^[A-Za-z0-9_-]+$/.test(name);
|
||||||
const validatePassword = (pass) => /^\S+$/.test(pass); // No spaces
|
const validatePassword = (pass) => /^\S+$/.test(pass); // No spaces
|
||||||
|
|
||||||
const handleRegister = async () => {
|
const handleRegister = async () => {
|
||||||
if (!validateUsername(username)) {
|
if (!validateUsername(username)) {
|
||||||
setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
|
setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!validatePassword(password)) {
|
if (!validatePassword(password)) {
|
||||||
setStatus("Invalid password. Spaces are not allowed.");
|
setStatus("Invalid password. Spaces are not allowed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/register', {
|
const response = await fetch("/register", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ username, password })
|
body: JSON.stringify({ username, password }),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
setStatus(data.message);
|
setStatus(data.message);
|
||||||
} else if (data.error) {
|
} else if (data.error) {
|
||||||
setStatus(data.error);
|
setStatus(data.error);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus('An error occurred while registering.');
|
setStatus("An error occurred while registering.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!validateUsername(username)) {
|
if (!validateUsername(username)) {
|
||||||
setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
|
setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!validatePassword(password)) {
|
if (!validatePassword(password)) {
|
||||||
setStatus("Invalid password. Spaces are not allowed.");
|
setStatus("Invalid password. Spaces are not allowed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/login', {
|
const response = await fetch("/login", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
credentials: 'include', // Important to send cookies
|
credentials: "include", // Important to send cookies
|
||||||
body: JSON.stringify({ username, password })
|
body: JSON.stringify({ username, password }),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
// Successful login - reload the page to trigger Account check
|
// Successful login - reload the page to trigger Account check
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else if (data.error) {
|
} else if (data.error) {
|
||||||
setStatus(data.error);
|
setStatus(data.error);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus('An error occurred while logging in.');
|
setStatus("An error occurred while logging in.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full items-center justify-center p-4">
|
<div className="flex flex-col w-full h-full items-center justify-center p-4">
|
||||||
<div className="flex flex-col w-full h-full lg:w-1/2 lg:h-96 border-2 border-yellow-500/50 rounded-2xl p-4 overflow-x-auto justify-center items-center">
|
<div className="flex flex-col w-full h-full lg:w-1/2 lg:h-96 border-2 border-yellow-500/50 rounded-2xl p-4 overflow-x-auto justify-center items-center">
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<label htmlFor="username" className="text-lg font-bold mb-2 text-white">Username:</label>
|
<label htmlFor="username" className="text-lg font-bold mb-2 text-white">
|
||||||
<input
|
Username:
|
||||||
type="text"
|
</label>
|
||||||
value={username}
|
<input
|
||||||
onChange={e => setUsername(e.target.value)}
|
type="text"
|
||||||
placeholder="Username"
|
value={username}
|
||||||
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
/>
|
placeholder="Username"
|
||||||
<label htmlFor="password" className="text-lg font-bold mb-2 text-white">Password:</label>
|
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
|
||||||
<input
|
/>
|
||||||
type="password"
|
<label htmlFor="password" className="text-lg font-bold mb-2 text-white">
|
||||||
value={password}
|
Password:
|
||||||
onChange={e => setPassword(e.target.value)}
|
</label>
|
||||||
placeholder="Password"
|
<input
|
||||||
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
|
type="password"
|
||||||
/>
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
placeholder="Password"
|
||||||
|
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col lg:flex-row w-8/10 items-center lg:justify-between mt-4">
|
||||||
|
<button
|
||||||
|
onClick={handleLogin}
|
||||||
|
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleRegister}
|
||||||
|
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{status && <p className="text-sm text-white mt-4 p-4">{status}</p>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col lg:flex-row w-8/10 items-center lg:justify-between mt-4">
|
);
|
||||||
<button
|
|
||||||
onClick={handleLogin}
|
|
||||||
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleRegister}
|
|
||||||
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
|
|
||||||
>
|
|
||||||
Register
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{status && (
|
|
||||||
<p className="text-sm text-white mt-4 p-4">
|
|
||||||
{status}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Register;
|
export default Register;
|
||||||
|
@ -1,158 +1,152 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import shaka from 'shaka-player';
|
import shaka from "shaka-player";
|
||||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
import { Helmet } from "react-helmet"; // Import Helmet
|
||||||
|
|
||||||
function TestPlayer() {
|
function TestPlayer() {
|
||||||
const [mpdUrl, setMpdUrl] = useState(''); // State to hold the MPD URL
|
const [mpdUrl, setMpdUrl] = useState(""); // State to hold the MPD URL
|
||||||
const [kids, setKids] = useState(''); // State to hold KIDs (separated by line breaks)
|
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 [keys, setKeys] = useState(""); // State to hold Keys (separated by line breaks)
|
||||||
const [headers, setHeaders] = useState(''); // State to hold request headers
|
const [headers, setHeaders] = useState(""); // State to hold request headers
|
||||||
|
|
||||||
const videoRef = useRef(null); // Ref for the video element
|
const videoRef = useRef(null); // Ref for the video element
|
||||||
const playerRef = useRef(null); // Ref for Shaka Player instance
|
const playerRef = useRef(null); // Ref for Shaka Player instance
|
||||||
|
|
||||||
// Function to update the MPD URL state
|
// Function to update the MPD URL state
|
||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event) => {
|
||||||
setMpdUrl(event.target.value);
|
setMpdUrl(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to update KIDs and Keys
|
// Function to update KIDs and Keys
|
||||||
const handleKidsChange = (event) => {
|
const handleKidsChange = (event) => {
|
||||||
setKids(event.target.value);
|
setKids(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeysChange = (event) => {
|
const handleKeysChange = (event) => {
|
||||||
setKeys(event.target.value);
|
setKeys(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHeadersChange = (event) => {
|
const handleHeadersChange = (event) => {
|
||||||
setHeaders(event.target.value);
|
setHeaders(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to initialize Shaka Player
|
// Function to initialize Shaka Player
|
||||||
const initializePlayer = () => {
|
const initializePlayer = () => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
// Initialize Shaka Player only if it's not already initialized
|
// Initialize Shaka Player only if it's not already initialized
|
||||||
if (!playerRef.current) {
|
if (!playerRef.current) {
|
||||||
const player = new shaka.Player(videoRef.current);
|
const player = new shaka.Player(videoRef.current);
|
||||||
playerRef.current = player;
|
playerRef.current = player;
|
||||||
|
|
||||||
// Add error listener
|
// Add error listener
|
||||||
player.addEventListener('error', (event) => {
|
player.addEventListener("error", (event) => {
|
||||||
console.error('Error code', event.detail.code, 'object', event.detail);
|
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;
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// Function to handle submit and configure player with DRM keys and headers
|
return (
|
||||||
const handleSubmit = () => {
|
<div className="flex flex-col items-center w-full p-4">
|
||||||
if (mpdUrl && kids && keys) {
|
<Helmet>
|
||||||
// Split the KIDs and Keys by new lines
|
<title>Test Player</title>
|
||||||
const kidsArray = kids.split("\n").map((k) => k.trim());
|
</Helmet>
|
||||||
const keysArray = keys.split("\n").map((k) => k.trim());
|
<div className="w-full flex flex-col">
|
||||||
|
<video ref={videoRef} width="100%" height="auto" controls className="h-96" />
|
||||||
if (kidsArray.length !== keysArray.length) {
|
<input
|
||||||
console.error("The number of KIDs and Keys must be the same.");
|
type="text"
|
||||||
return;
|
value={mpdUrl}
|
||||||
}
|
onChange={handleInputChange}
|
||||||
|
placeholder="MPD URL"
|
||||||
// Initialize Shaka Player only when the submit button is pressed
|
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"
|
||||||
const player = new shaka.Player(videoRef.current);
|
/>
|
||||||
|
<textarea
|
||||||
// Widevine DRM configuration with the provided KIDs and Keys
|
placeholder="KIDs (one per line)"
|
||||||
const config = {
|
value={kids}
|
||||||
drm: {
|
onChange={handleKidsChange}
|
||||||
clearKeys: {},
|
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)"
|
||||||
// Map KIDs to Keys
|
value={keys}
|
||||||
kidsArray.forEach((kid, index) => {
|
onChange={handleKeysChange}
|
||||||
config.drm.clearKeys[kid] = keysArray[index];
|
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
|
||||||
console.log("Configuring player with the following DRM config and headers:", config);
|
placeholder="Headers (one per line)"
|
||||||
|
value={headers}
|
||||||
// Configure the player with ClearKey DRM and custom headers
|
onChange={handleHeadersChange}
|
||||||
player.configure(config);
|
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"
|
||||||
|
/>
|
||||||
// Load the video stream with MPD URL
|
<button onClick={handleSubmit} className="mt-4 p-2 bg-blue-500 text-white rounded">
|
||||||
player.load(mpdUrl).then(() => {
|
Submit
|
||||||
console.log('Video loaded');
|
</button>
|
||||||
}).catch((error) => {
|
</div>
|
||||||
console.error('Error loading the video', error);
|
</div>
|
||||||
});
|
);
|
||||||
} 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;
|
export default TestPlayer;
|
||||||
|
@ -1,178 +1,182 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from "react";
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from "react-router-dom";
|
||||||
import closeIcon from '../assets/icons/close.svg';
|
import closeIcon from "../assets/icons/close.svg";
|
||||||
import homeIcon from '../assets/icons/home.svg';
|
import homeIcon from "../assets/icons/home.svg";
|
||||||
import cacheIcon from '../assets/icons/cache.svg';
|
import cacheIcon from "../assets/icons/cache.svg";
|
||||||
import apiIcon from '../assets/icons/api.svg';
|
import apiIcon from "../assets/icons/api.svg";
|
||||||
import testPlayerIcon from '../assets/icons/testplayer.svg';
|
import testPlayerIcon from "../assets/icons/testplayer.svg";
|
||||||
import accountIcon from '../assets/icons/account.svg';
|
import accountIcon from "../assets/icons/account.svg";
|
||||||
import discordIcon from '../assets/icons/discord.svg';
|
import discordIcon from "../assets/icons/discord.svg";
|
||||||
import telegramIcon from '../assets/icons/telegram.svg';
|
import telegramIcon from "../assets/icons/telegram.svg";
|
||||||
import giteaIcon from '../assets/icons/gitea.svg';
|
import giteaIcon from "../assets/icons/gitea.svg";
|
||||||
|
|
||||||
function SideMenu({ isMenuOpen, setIsMenuOpen }) {
|
function SideMenu({ isMenuOpen, setIsMenuOpen }) {
|
||||||
const [externalLinks, setExternalLinks] = useState({
|
const [externalLinks, setExternalLinks] = useState({
|
||||||
discord: '#',
|
discord: "#",
|
||||||
telegram: '#',
|
telegram: "#",
|
||||||
gitea: '#',
|
gitea: "#",
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/links')
|
fetch("/api/links")
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => setExternalLinks(data))
|
.then((data) => setExternalLinks(data))
|
||||||
.catch((err) => console.error('Failed to fetch links:', err));
|
.catch((err) => console.error("Failed to fetch links:", err));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col fixed top-0 left-0 w-full h-full bg-black transition-transform transform ${
|
className={`flex flex-col fixed top-0 left-0 w-full h-full bg-black transition-transform transform ${
|
||||||
isMenuOpen ? 'translate-x-0' : '-translate-x-full'
|
isMenuOpen ? "translate-x-0" : "-translate-x-full"
|
||||||
} z-50`}
|
} z-50`}
|
||||||
style={{ transitionDuration: '0.3s' }}
|
style={{ transitionDuration: "0.3s" }}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col bg-gray-950/55 h-full">
|
<div className="flex flex-col bg-gray-950/55 h-full">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="h-16 w-full border-b-2 border-white/5 flex flex-row">
|
<div className="h-16 w-full border-b-2 border-white/5 flex flex-row">
|
||||||
<div className="w-1/4 h-full"></div>
|
<div className="w-1/4 h-full"></div>
|
||||||
<p className="grow text-white md:text-2xl font-bold text-center flex items-center justify-center p-4">
|
<p className="grow text-white md:text-2xl font-bold text-center flex items-center justify-center p-4">
|
||||||
CDRM-Project
|
CDRM-Project
|
||||||
</p>
|
</p>
|
||||||
<div className="w-1/4 h-full">
|
<div className="w-1/4 h-full">
|
||||||
<button
|
<button
|
||||||
className="w-full h-full flex items-center justify-center"
|
className="w-full h-full flex items-center justify-center"
|
||||||
onClick={() => setIsMenuOpen(false)}
|
onClick={() => setIsMenuOpen(false)}
|
||||||
>
|
>
|
||||||
<img src={closeIcon} alt="Close" className="w-1/2 h-1/2 cursor-pointer" />
|
<img
|
||||||
</button>
|
src={closeIcon}
|
||||||
</div>
|
alt="Close"
|
||||||
|
className="w-1/2 h-1/2 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scrollable Navigation Links */}
|
||||||
|
<div className="overflow-y-auto flex flex-col p-5 w-full flex-grow">
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
<NavLink
|
||||||
|
to="/"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
||||||
|
isActive
|
||||||
|
? "border-l-sky-500/50 bg-black/50 text-white"
|
||||||
|
: "border-transparent hover:border-l-sky-500/50 hover:bg-white/5 text-white/80"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<img src={homeIcon} alt="Home" className="w-5 h-5" />
|
||||||
|
<span className="text-lg">Home</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
to="/cache"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
||||||
|
isActive
|
||||||
|
? "border-l-emerald-500/50 bg-black/50 text-white"
|
||||||
|
: "border-transparent hover:border-l-emerald-500/50 hover:bg-white/5 text-white/80"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<img src={cacheIcon} alt="Cache" className="w-5 h-5" />
|
||||||
|
<span className="text-lg">Cache</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
to="/api"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
||||||
|
isActive
|
||||||
|
? "border-l-indigo-500/50 bg-black/50 text-white"
|
||||||
|
: "border-transparent hover:border-l-indigo-500/50 hover:bg-white/5 text-white/80"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<img src={apiIcon} alt="API" className="w-5 h-5" />
|
||||||
|
<span className="text-lg">API</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
to="/testplayer"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
||||||
|
isActive
|
||||||
|
? "border-l-rose-700/50 bg-black/50 text-white"
|
||||||
|
: "border-transparent hover:border-l-rose-700/50 hover:bg-white/5 text-white/80"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<img src={testPlayerIcon} alt="Test Player" className="w-5 h-5" />
|
||||||
|
<span className="text-lg">Test Player</span>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* My Account Link at the Bottom of Scrollable Area */}
|
||||||
|
<div className="mt-auto pt-4">
|
||||||
|
<NavLink
|
||||||
|
to="/account"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
||||||
|
isActive
|
||||||
|
? "border-l-yellow-500/50 bg-black/50 text-white"
|
||||||
|
: "border-transparent hover:border-l-yellow-500/50 hover:bg-white/5 text-white/80"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<img src={accountIcon} alt="My Account" className="w-5 h-5" />
|
||||||
|
<span className="text-lg">My Account</span>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* External Links */}
|
||||||
|
<div className="h-16 w-full flex flex-row bg-black/5">
|
||||||
|
<a
|
||||||
|
href={externalLinks.discord}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="w-1/3 h-full flex items-center justify-center hover:bg-blue-950 group"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={discordIcon}
|
||||||
|
alt="Discord"
|
||||||
|
className="w-full h-2/3 p-1 cursor-pointer group-hover:animate-bounce"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={externalLinks.telegram}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="w-1/3 h-full flex items-center justify-center hover:bg-blue-400 group"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={telegramIcon}
|
||||||
|
alt="Telegram"
|
||||||
|
className="w-full h-2/3 p-1 cursor-pointer group-hover:animate-bounce"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={externalLinks.gitea}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="w-1/3 h-full flex items-center justify-center hover:bg-green-700 group"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={giteaIcon}
|
||||||
|
alt="Gitea"
|
||||||
|
className="w-full h-2/3 p-1 cursor-pointer group-hover:animate-bounce"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
{/* Scrollable Navigation Links */}
|
|
||||||
<div className="overflow-y-auto flex flex-col p-5 w-full flex-grow">
|
|
||||||
<div className="flex flex-col space-y-2">
|
|
||||||
<NavLink
|
|
||||||
to="/"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
|
||||||
isActive
|
|
||||||
? 'border-l-sky-500/50 bg-black/50 text-white'
|
|
||||||
: 'border-transparent hover:border-l-sky-500/50 hover:bg-white/5 text-white/80'
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
onClick={() => setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
<img src={homeIcon} alt="Home" className="w-5 h-5" />
|
|
||||||
<span className="text-lg">Home</span>
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink
|
|
||||||
to="/cache"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
|
||||||
isActive
|
|
||||||
? 'border-l-emerald-500/50 bg-black/50 text-white'
|
|
||||||
: 'border-transparent hover:border-l-emerald-500/50 hover:bg-white/5 text-white/80'
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
onClick={() => setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
<img src={cacheIcon} alt="Cache" className="w-5 h-5" />
|
|
||||||
<span className="text-lg">Cache</span>
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink
|
|
||||||
to="/api"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
|
||||||
isActive
|
|
||||||
? 'border-l-indigo-500/50 bg-black/50 text-white'
|
|
||||||
: 'border-transparent hover:border-l-indigo-500/50 hover:bg-white/5 text-white/80'
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
onClick={() => setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
<img src={apiIcon} alt="API" className="w-5 h-5" />
|
|
||||||
<span className="text-lg">API</span>
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink
|
|
||||||
to="/testplayer"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
|
||||||
isActive
|
|
||||||
? 'border-l-rose-700/50 bg-black/50 text-white'
|
|
||||||
: 'border-transparent hover:border-l-rose-700/50 hover:bg-white/5 text-white/80'
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
onClick={() => setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
<img src={testPlayerIcon} alt="Test Player" className="w-5 h-5" />
|
|
||||||
<span className="text-lg">Test Player</span>
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* My Account Link at the Bottom of Scrollable Area */}
|
|
||||||
<div className="mt-auto pt-4">
|
|
||||||
<NavLink
|
|
||||||
to="/account"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex flex-row items-center gap-3 p-3 border-l-4 ${
|
|
||||||
isActive
|
|
||||||
? 'border-l-yellow-500/50 bg-black/50 text-white'
|
|
||||||
: 'border-transparent hover:border-l-yellow-500/50 hover:bg-white/5 text-white/80'
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
onClick={() => setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
<img src={accountIcon} alt="My Account" className="w-5 h-5" />
|
|
||||||
<span className="text-lg">My Account</span>
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* External Links */}
|
|
||||||
<div className="h-16 w-full flex flex-row bg-black/5">
|
|
||||||
<a
|
|
||||||
href={externalLinks.discord}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-1/3 h-full flex items-center justify-center hover:bg-blue-950 group"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={discordIcon}
|
|
||||||
alt="Discord"
|
|
||||||
className="w-full h-2/3 p-1 cursor-pointer group-hover:animate-bounce"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={externalLinks.telegram}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-1/3 h-full flex items-center justify-center hover:bg-blue-400 group"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={telegramIcon}
|
|
||||||
alt="Telegram"
|
|
||||||
className="w-full h-2/3 p-1 cursor-pointer group-hover:animate-bounce"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={externalLinks.gitea}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-1/3 h-full flex items-center justify-center hover:bg-green-700 group"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={giteaIcon}
|
|
||||||
alt="Gitea"
|
|
||||||
className="w-full h-2/3 p-1 cursor-pointer group-hover:animate-bounce"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SideMenu;
|
export default SideMenu;
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
details summary::-webkit-details-marker {
|
details summary::-webkit-details-marker {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
details summary {
|
details summary {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from "react-dom/client";
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from "react-router-dom";
|
||||||
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>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
)
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
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({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
})
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user