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