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