Merge branch 'github' of https://cdm-project.com/tpd94/CDRM-Project into github

This commit is contained in:
TPD94 2025-04-30 04:44:48 -04:00
commit 4415372992
31 changed files with 1208 additions and 343 deletions

View File

@ -2,11 +2,6 @@
## CDRM-Project ## CDRM-Project
![forthebadge](https://forthebadge.com/images/badges/uses-html.svg) ![forthebadge](https://forthebadge.com/images/badges/uses-css.svg) ![forthebadge](https://forthebadge.com/images/badges/uses-javascript.svg) ![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg) ![forthebadge](https://forthebadge.com/images/badges/uses-html.svg) ![forthebadge](https://forthebadge.com/images/badges/uses-css.svg) ![forthebadge](https://forthebadge.com/images/badges/uses-javascript.svg) ![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)
## GITHUB EDITION
> This version **DOES NOT** come with CDM's (Content Decryption Modules) or the link to automatically download them - A simple web search should help you find what you're looking for.
>
## Prerequisites (from source only) ## Prerequisites (from source only)
- [Python](https://www.python.org/downloads/) version [3.12](https://www.python.org/downloads/release/python-3120/)+ with PIP and VENV installed - [Python](https://www.python.org/downloads/) version [3.12](https://www.python.org/downloads/release/python-3120/)+ with PIP and VENV installed

View File

@ -8,6 +8,7 @@ pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
dist
dist-ssr dist-ssr
*.local *.local

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,8 +12,8 @@
<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>
<script type="module" crossorigin src="/assets/index-DN7XJ__W.js"></script> <script type="module" crossorigin src="/assets/index-C2DUB5KK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-COb8XlA9.css"> <link rel="stylesheet" crossorigin href="/assets/index-BXlb7x7c.css">
</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>

View File

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.4", "@tailwindcss/vite": "^4.1.4",
"axios": "^1.9.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
@ -1669,6 +1670,23 @@
"dev": true, "dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -1720,6 +1738,19 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -1788,6 +1819,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1858,6 +1901,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@ -1867,6 +1919,20 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.143", "version": "1.5.143",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.143.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.143.tgz",
@ -1893,6 +1959,51 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.25.3", "version": "0.25.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
@ -2220,6 +2331,41 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -2234,6 +2380,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -2244,6 +2399,43 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -2270,6 +2462,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -2286,6 +2490,45 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -2725,6 +2968,36 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -2936,6 +3209,12 @@
"react-is": "^16.13.1" "react-is": "^16.13.1"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",

View File

@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.4", "@tailwindcss/vite": "^4.1.4",
"axios": "^1.9.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",

View File

@ -6,6 +6,7 @@ import TestPlayer from "./components/Pages/TestPlayer";
import NavBar from "./components/NavBar"; import NavBar from "./components/NavBar";
import NavBarMain from "./components/NavBarMain"; import NavBarMain from "./components/NavBarMain";
import SideMenu from "./components/SideMenu"; // Add this import import SideMenu from "./components/SideMenu"; // Add this import
import Account from "./components/Pages/Account";
import { Routes, Route } from "react-router-dom"; import { Routes, Route } from "react-router-dom";
function App() { function App() {
@ -31,6 +32,7 @@ function App() {
<Route path="/cache" element={<Cache />} /> <Route path="/cache" element={<Cache />} />
<Route path="/api" element={<API />} /> <Route path="/api" element={<API />} />
<Route path="/testplayer" element={<TestPlayer />} /> <Route path="/testplayer" element={<TestPlayer />} />
<Route path="/account" element={<Account />} />
</Routes> </Routes>
</div> </div>
</div> </div>

View File

@ -0,0 +1,13 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" fill="#000000">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">
<defs>
<style>.cls-1{fill:none;stroke:#ffffff;stroke-miterlimit:10;stroke-width:1.91px;}</style>
</defs>
<circle class="cls-1" cx="12" cy="7.25" r="5.73"/>
<path class="cls-1" d="M1.5,23.48l.37-2.05A10.3,10.3,0,0,1,12,13h0a10.3,10.3,0,0,1,10.13,8.45l.37,2.05"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 760 B

View File

@ -4,6 +4,7 @@ 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 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';
@ -24,81 +25,135 @@ function NavBar() {
return ( return (
<div className="flex flex-col w-full h-full bg-white/1"> <div className="flex flex-col w-full h-full bg-white/1">
{/* Header */}
<div> <div>
<p className='text-white text-2xl font-bold p-3 text-center mb-5'> <p className="text-white text-2xl font-bold p-3 text-center mb-5">
<a href='/'>CDRM-Project</a> <a href="/">CDRM-Project</a>
</p> </p>
</div> </div>
<div className='overflow-y-auto grow'> {/* Scrollable navigation area */}
{/* Static routes */} <div className="overflow-y-auto grow flex flex-col">
{[{ {/* Main NavLinks */}
to: '/',
label: 'Home',
icon: homeIcon,
color: 'sky'
}, {
to: '/cache',
label: 'Cache',
icon: cacheIcon,
color: 'emerald'
}, {
to: '/api',
label: 'API',
icon: apiIcon,
color: 'indigo'
}, {
to: '/testplayer',
label: 'Test Player',
icon: testPlayerIcon,
color: 'rose-700'
}].map(({ to, label, icon, color }) => (
<NavLink <NavLink
key={label} to="/"
to={to}
className={({ isActive }) => className={({ isActive }) =>
`flex flex-row p-3 border-l-3 ${ `flex flex-row p-3 border-l-3 ${
isActive isActive
? `border-l-${color}-500/50 bg-black/50` ? 'border-l-sky-500/50 bg-black/50'
: `hover:border-l-${color}-500/50 hover:bg-white/5` : 'hover:border-l-sky-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={icon} alt={label} className='w-1/2 cursor-pointer' /> <img src={homeIcon} alt="Home" 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">
{label} Home
</p> </p>
</NavLink> </NavLink>
))}
<NavLink
to="/cache"
className={({ isActive }) =>
`flex flex-row p-3 border-l-3 ${
isActive
? 'border-l-emerald-500/50 bg-black/50'
: 'hover:border-l-emerald-500/50 hover:bg-white/5'
}`
}
>
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
<img src={cacheIcon} alt="Cache" className="w-1/2 cursor-pointer" />
</button>
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
Cache
</p>
</NavLink>
<NavLink
to="/api"
className={({ isActive }) =>
`flex flex-row p-3 border-l-3 ${
isActive
? 'border-l-indigo-500/50 bg-black/50'
: 'hover:border-l-indigo-500/50 hover:bg-white/5'
}`
}
>
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
<img src={apiIcon} alt="API" className="w-1/2 cursor-pointer" />
</button>
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
API
</p>
</NavLink>
<NavLink
to="/testplayer"
className={({ isActive }) =>
`flex flex-row p-3 border-l-3 ${
isActive
? 'border-l-rose-500/50 bg-black/50'
: 'hover:border-l-rose-500/50 hover:bg-white/5'
}`
}
>
<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" />
</button>
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
Test Player
</p>
</NavLink>
{/* Account link at bottom of scrollable area */}
<div className="mt-auto">
<NavLink
to="/account"
className={({ isActive }) =>
`flex flex-row p-3 border-l-3 ${
isActive
? 'border-l-yellow-500/50 bg-black/50'
: 'hover:border-l-yellow-500/50 hover:bg-white/5'
}`
}
>
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
<img src={accountIcon} alt="Account" className="w-1/2 cursor-pointer" />
</button>
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
My Account
</p>
</NavLink>
</div>
</div> </div>
{/* External links */} {/* External links at very bottom */}
<div className='flex flex-row w-full h-16 self-end bg-black/25'> <div className="flex flex-row w-full h-16 bg-black/25">
<a <a
href={externalLinks.discord} href={externalLinks.discord}
target='_blank' target="_blank"
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}
target='_blank' target="_blank"
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}
target='_blank' target="_blank"
rel='noopener noreferrer' rel="noopener noreferrer"
className='w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-green-700 group' className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-green-700 group"
> >
<img src={giteaIcon} alt="Gitea" className='w-1/2 group-hover:animate-bounce' /> <img src={giteaIcon} alt="Gitea" className="w-1/2 group-hover:animate-bounce" />
</a> </a>
</div> </div>
</div> </div>

View File

@ -0,0 +1,38 @@
import React, { useEffect, useState } from "react";
import Register from "./Register";
import MyAccount from "./MyAccount"; // <-- Import the MyAccount component
function Account() {
const [isLoggedIn, setIsLoggedIn] = useState(null); // null = loading state
useEffect(() => {
fetch('/login/status', {
method: 'POST',
credentials: 'include', // Sends cookies with request
})
.then(res => res.json())
.then(data => {
if (data.message === 'True') {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
})
.catch(err => {
console.error("Error checking login status:", err);
setIsLoggedIn(false); // Assume not logged in on error
});
}, []);
if (isLoggedIn === null) {
return <div>Loading...</div>; // Optional loading UI
}
return (
<div id="accountpage" className="w-full h-full flex">
{isLoggedIn ? <MyAccount /> : <Register />}
</div>
);
}
export default Account;

View File

@ -1,5 +1,5 @@
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() {
@ -11,6 +11,8 @@ function HomePage() {
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 [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
@ -41,7 +43,8 @@ function HomePage() {
proxy: proxy, proxy: proxy,
headers: headers, headers: headers,
cookies: cookies, cookies: cookies,
data: data data: data,
device: selectedDevice, // Include selected device in the request
}), }),
}) })
.then(response => response.json()) .then(response => response.json())
@ -68,7 +71,6 @@ function HomePage() {
} }
}; };
const handleFetchPaste = () => { const handleFetchPaste = () => {
event.preventDefault(); event.preventDefault();
readTextFromClipboard().then(() => { readTextFromClipboard().then(() => {
@ -79,7 +81,7 @@ function HomePage() {
}).catch(err => { }).catch(err => {
alert('Failed to paste from fetch!'); alert('Failed to paste from fetch!');
}); });
} };
useEffect(() => { useEffect(() => {
if (isVisible && bottomRef.current) { if (isVisible && bottomRef.current) {
@ -87,6 +89,43 @@ function HomePage() {
} }
}, [message, isVisible]); }, [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');
}
})
.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 ( return (
<> <>
<div className="flex flex-col w-full overflow-y-auto p-4 min-h-full"> <div className="flex flex-col w-full overflow-y-auto p-4 min-h-full">
@ -140,6 +179,23 @@ function HomePage() {
onChange={(e) => setData(e.target.value)} 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"> <div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
<button <button
type="button" type="button"

View File

@ -0,0 +1,146 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function MyAccount() {
const [wvList, setWvList] = useState([]);
const [prList, setPrList] = useState([]);
const [uploading, setUploading] = useState(false);
const [username, setUsername] = useState('');
// Fetch user info
const fetchUserInfo = async () => {
try {
const response = await axios.post('/userinfo');
setWvList(response.data.Widevine_Devices || []);
setPrList(response.data.Playready_Devices || []);
setUsername(response.data.Username || '');
} catch (err) {
console.error('Failed to fetch user info', err);
}
};
useEffect(() => {
fetchUserInfo();
}, []);
// Handle file upload
const handleUpload = async (event, cdmType) => {
const file = event.target.files[0];
if (!file) return;
const extension = file.name.split('.').pop();
if ((cdmType === 'PR' && extension !== 'prd') || (cdmType === 'WV' && extension !== 'wvd')) {
alert(`Please upload a .${cdmType === 'PR' ? 'prd' : 'wvd'} file.`);
return;
}
const formData = new FormData();
formData.append('file', file);
setUploading(true);
try {
await axios.post(`/upload/${cdmType}`, formData);
await fetchUserInfo(); // Refresh list after upload
} catch (err) {
console.error('Upload failed', err);
alert('Upload failed');
} finally {
setUploading(false);
}
};
// Handle logout
const handleLogout = async () => {
try {
await axios.post('/logout');
window.location.reload();
} catch (error) {
console.error('Logout failed:', error);
alert('Logout failed!');
}
};
return (
<div id="myaccount" className="flex flex-row w-full min-h-full overflow-y-auto p-4">
<div className="flex flex-col w-full min-h-full lg:flex-row">
{/* Left Panel */}
<div className="border-2 border-yellow-500/50 lg:h-full lg:w-96 w-full rounded-2xl p-4 flex flex-col items-center overflow-y-auto">
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 w-full text-center mb-2">
{username ? `${username}` : 'My Account'}
</h1>
<button
onClick={handleLogout}
className="mt-auto w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
>
Log out
</button>
</div>
{/* Right Panel */}
<div className="flex flex-col grow lg:ml-2 mt-2 lg:mt-0">
{/* Widevine Section */}
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl lg:p-4 p-2 overflow-y-auto">
<h1 className="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 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">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 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>
);
}
export default MyAccount;

View File

@ -0,0 +1,95 @@
import React, { useState } from 'react';
function Register() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [status, setStatus] = useState('');
const handleRegister = async () => {
try {
const response = await fetch('/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.message) {
setStatus(data.message);
} else if (data.error) {
setStatus(data.error);
}
} catch (err) {
setStatus('An error occurred while registering.');
}
};
const handleLogin = async () => {
try {
const response = await fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include', // Important to send cookies
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.message) {
// Successful login - reload the page to trigger Account check
window.location.reload();
} else if (data.error) {
setStatus(data.error);
}
} catch (err) {
setStatus('An error occurred while logging in.');
}
};
return (
<div className="flex flex-col w-full h-full items-center justify-center p-4">
<div className="flex flex-col w-full h-full lg:w-1/2 lg:h-96 border-2 border-yellow-500/50 rounded-2xl p-4 overflow-x-auto justify-center items-center">
<div className="flex flex-col w-full">
<label htmlFor="username" className="text-lg font-bold mb-2 text-white">Username:</label>
<input
type="text"
value={username}
onChange={e => setUsername(e.target.value)}
placeholder="Username"
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
/>
<label htmlFor="password" className="text-lg font-bold mb-2 text-white">Password:</label>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Password"
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
/>
</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;

View File

@ -1,16 +1,30 @@
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 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({
discord: '#',
telegram: '#',
gitea: '#',
});
useEffect(() => {
fetch('/api/links')
.then((res) => res.json())
.then((data) => setExternalLinks(data))
.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'
@ -18,6 +32,7 @@ function SideMenu({ isMenuOpen, setIsMenuOpen }) {
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 */}
<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">
@ -33,13 +48,15 @@ function SideMenu({ isMenuOpen, setIsMenuOpen }) {
</div> </div>
</div> </div>
<div className="overflow-y-auto flex flex-col p-5 w-full space-y-2 flex-grow"> {/* 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 <NavLink
to="/" to="/"
className={({ isActive }) => className={({ isActive }) =>
`flex flex-row items-center gap-3 p-3 border-l-4 ${ `flex flex-row items-center gap-3 p-3 border-l-4 ${
isActive isActive
? 'border-l-4 border-l-sky-500/50 bg-black/50 text-white' ? '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' : 'border-transparent hover:border-l-sky-500/50 hover:bg-white/5 text-white/80'
}` }`
} }
@ -95,9 +112,29 @@ function SideMenu({ isMenuOpen, setIsMenuOpen }) {
</NavLink> </NavLink>
</div> </div>
<div className="h-16 self-end w-full flex flex-row bg-black/5"> {/* 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 <a
href="https://discord.cdrm-project.com/" href={externalLinks.discord}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="w-1/3 h-full flex items-center justify-center hover:bg-blue-950 group" className="w-1/3 h-full flex items-center justify-center hover:bg-blue-950 group"
@ -109,7 +146,7 @@ function SideMenu({ isMenuOpen, setIsMenuOpen }) {
/> />
</a> </a>
<a <a
href="https://telegram.cdrm-project.com" href={externalLinks.telegram}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="w-1/3 h-full flex items-center justify-center hover:bg-blue-400 group" className="w-1/3 h-full flex items-center justify-center hover:bg-blue-400 group"
@ -121,7 +158,7 @@ function SideMenu({ isMenuOpen, setIsMenuOpen }) {
/> />
</a> </a>
<a <a
href="https://cdm-project.com/tpd94/cdrm-project" href={externalLinks.gitea}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="w-1/3 h-full flex items-center justify-center hover:bg-green-700 group" className="w-1/3 h-full flex items-center justify-center hover:bg-green-700 group"
@ -135,7 +172,6 @@ function SideMenu({ isMenuOpen, setIsMenuOpen }) {
</div> </div>
</div> </div>
</div> </div>
</>
); );
} }

View File

@ -1,5 +1,5 @@
data = { data = {
'discord': '#', 'discord': 'https://discord.cdrm-project.com/',
'telegram': '#', 'telegram': 'https://telegram.cdrm-project.com/',
'gitea': 'https://github.com/tpd94/cdrm-project-2.0' 'gitea': 'https://cdm-project.com/tpd94/cdm-project'
} }

View File

@ -4,8 +4,8 @@ tags = {
'keywords': 'CDRM, Widevine, PlayReady, DRM, Decrypt, CDM, CDM-Project, CDRM-Project, TPD94, Decryption', 'keywords': 'CDRM, Widevine, PlayReady, DRM, Decrypt, CDM, CDM-Project, CDRM-Project, TPD94, Decryption',
'opengraph_title': 'CDRM-Project', 'opengraph_title': 'CDRM-Project',
'opengraph_description': 'Self Hosted web application written in Python/JavaScript utilizing the Flask/Tailwind Framework and ReactJS library to decrypt Widevine & Playready content', 'opengraph_description': 'Self Hosted web application written in Python/JavaScript utilizing the Flask/Tailwind Framework and ReactJS library to decrypt Widevine & Playready content',
'opengraph_image': '', 'opengraph_image': 'https://cdrm-project.com/og-home.jpg',
'opengraph_url': '', 'opengraph_url': 'https://cdm-project.com/tpd94/cdrm-project',
'tab_title': 'CDRM-Project', 'tab_title': 'CDRM-Project',
}, },
'cache': { 'cache': {
@ -13,8 +13,8 @@ tags = {
'keywords': 'Cache, Vault, Widevine, PlayReady, DRM, Decryption, CDM, CDRM-Project, CDRM-Project, TPD94, Decryption', 'keywords': 'Cache, Vault, Widevine, PlayReady, DRM, Decryption, CDM, CDRM-Project, CDRM-Project, TPD94, Decryption',
'opengraph_title': 'Search the Cache', 'opengraph_title': 'Search the Cache',
'opengraph_description': 'Search the cache by KID or PSSH for decryption keys', 'opengraph_description': 'Search the cache by KID or PSSH for decryption keys',
'opengraph_image': '', 'opengraph_image': 'https://cdrm-project.com/og-cache.jpg',
'opengraph_url': '', 'opengraph_url': 'https://cdrm-project.com/cache',
'tab_title': 'Cache', 'tab_title': 'Cache',
}, },
'testplayer': { 'testplayer': {
@ -22,8 +22,8 @@ tags = {
'keywords': 'Shaka, Player, DRM, CDRM, CDM, CDRM-Project, TPD94, Decryption, CDM-Project, KID, KEY', 'keywords': 'Shaka, Player, DRM, CDRM, CDM, CDRM-Project, TPD94, Decryption, CDM-Project, KID, KEY',
'opengraph_title': 'Test Player', 'opengraph_title': 'Test Player',
'opengraph_description': 'Shaka Player for testing decryption keys', 'opengraph_description': 'Shaka Player for testing decryption keys',
'opengraph_image': '', 'opengraph_image': 'https://cdrm-project.com/og-testplayer.jpg',
'opengraph_url': '', 'opengraph_url': 'https://cdrm-project.com/testplayer',
'tab_title': 'Test Player', 'tab_title': 'Test Player',
}, },
'api': { 'api': {
@ -31,8 +31,17 @@ tags = {
'keywords': 'API, python, requests, send, remotecdm, remote, cdm, CDM-Project, CDRM-Project, TPD94, Decryption, DRM, Web, Vault', 'keywords': 'API, python, requests, send, remotecdm, remote, cdm, CDM-Project, CDRM-Project, TPD94, Decryption, DRM, Web, Vault',
'opengraph_title': 'API', 'opengraph_title': 'API',
'opengraph_description': 'Documentation for the program "CDRM-Project"', 'opengraph_description': 'Documentation for the program "CDRM-Project"',
'opengraph_image': '', 'opengraph_image': 'https://cdrm-project.com/og-api.jpg',
'opengraph_url': '', 'opengraph_url': 'https://cdrm-project.com/api',
'tab_title': 'API', 'tab_title': 'API',
},
'account': {
'description': 'Account for CDRM-Project',
'keywords': 'Login, CDRM, CDM, CDRM-Project, register, account',
'opengraph_title': 'My account',
'opengraph_description': 'Account for CDRM-Project',
'opengraph_image': 'https://cdrm-project.com/og-home.jpg',
'opengraph_url': 'https://cdrm-project.com/account',
'tab_title': 'My account',
} }
} }

View File

@ -0,0 +1,45 @@
import sqlite3
import os
import bcrypt
def create_user_database():
os.makedirs(f'{os.getcwd()}/databases/sql', exist_ok=True)
with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_info (
Username TEXT PRIMARY KEY,
Password TEXT
)
''')
def add_user(username, password):
hashed_pw = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
cursor = conn.cursor()
try:
cursor.execute('INSERT INTO user_info (Username, Password) VALUES (?, ?)', (username, hashed_pw))
conn.commit()
return True
except sqlite3.IntegrityError:
return False
def verify_user(username, password):
with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
cursor = conn.cursor()
cursor.execute('SELECT Password FROM user_info WHERE Username = ?', (username,))
result = cursor.fetchone()
if result:
stored_hash = result[0]
# Ensure stored_hash is bytes; decode if it's still a string (SQLite may store as TEXT)
if isinstance(stored_hash, str):
stored_hash = stored_hash.encode('utf-8')
return bcrypt.checkpw(password.encode('utf-8'), stored_hash)
else:
return False

View File

@ -84,7 +84,8 @@ def is_url_and_split(input_str):
else: else:
return False, None, None return False, None, None
def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, headers: str = None, cookies: str = None, json_data: str = None): def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, headers: str = None, cookies: str = None, json_data: str = None, device: str = 'public', username: str = None):
print(f'Using device {device} for user {username}')
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file: with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
config = yaml.safe_load(file) config = yaml.safe_load(file)
if config['database_type'].lower() == 'sqlite': if config['database_type'].lower() == 'sqlite':
@ -106,6 +107,7 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
'message': f'An error occurred processing PSSH\n\n{error}' 'message': f'An error occurred processing PSSH\n\n{error}'
} }
try: try:
if device == 'public':
base_name = config["default_pr_cdm"] base_name = config["default_pr_cdm"]
if not base_name.endswith(".prd"): if not base_name.endswith(".prd"):
base_name += ".prd" base_name += ".prd"
@ -119,6 +121,20 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
'status': 'error', 'status': 'error',
'message': 'No default .prd file found' 'message': 'No default .prd file found'
} }
else:
base_name = device
if not base_name.endswith(".prd"):
base_name += ".prd"
prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}')
else:
prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}')
if prd_files:
pr_device = playreadyDevice.load(prd_files[0])
else:
return {
'status': 'error',
'message': f'{base_name} does not exist'
}
except Exception as error: except Exception as error:
return { return {
'status': 'error', 'status': 'error',
@ -266,6 +282,7 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
'message': f'An error occurred processing PSSH\n\n{error}' 'message': f'An error occurred processing PSSH\n\n{error}'
} }
try: try:
if device == 'public':
base_name = config["default_wv_cdm"] base_name = config["default_wv_cdm"]
if not base_name.endswith(".wvd"): if not base_name.endswith(".wvd"):
base_name += ".wvd" base_name += ".wvd"
@ -279,6 +296,20 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
'status': 'error', 'status': 'error',
'message': 'No default .wvd file found' 'message': 'No default .wvd file found'
} }
else:
base_name = device
if not base_name.endswith(".wvd"):
base_name += ".wvd"
wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}')
else:
wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}')
if wvd_files:
wv_device = widevineDevice.load(wvd_files[0])
else:
return {
'status': 'error',
'message': f'{base_name} does not exist'
}
except Exception as error: except Exception as error:
return { return {
'status': 'error', 'status': 'error',

View File

@ -8,7 +8,22 @@ def check_for_wvd_cdm():
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file: with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
config = yaml.safe_load(file) config = yaml.safe_load(file)
if config['default_wv_cdm'] == '': if config['default_wv_cdm'] == '':
exit(f"Please put the name of your Widevine CDM inside of {os.getcwd()}/configs/config.yaml") answer = ' '
while answer[0].upper() != 'Y' and answer[0].upper() != 'N':
answer = input('No default Widevine CDM specified, would you like to download one from The CDM Project? (Y)es/(N)o: ')
if answer[0].upper() == 'Y':
response = requests.get(url='https://cdm-project.com/CDRM-Team/CDMs/raw/branch/main/Widevine/L3/public.wvd')
if response.status_code == 200:
with open(f'{os.getcwd()}/configs/CDMs/WV/public.wvd', 'wb') as file:
file.write(response.content)
config['default_wv_cdm'] = 'public'
with open(f'{os.getcwd()}/configs/config.yaml', 'w') as file:
yaml.dump(config, file)
print("Successfully downloaded Widevine CDM")
else:
exit(f"Download failed, please try again or place a .wvd file in {os.getcwd()}/configs/CDMs/WV and specify the name in {os.getcwd()}/configs/config.yaml")
if answer[0].upper() == 'N':
exit(f"Place a .wvd file in {os.getcwd()}/configs/CDMs/WV and specify the name in {os.getcwd()}/configs/config.yaml")
else: else:
base_name = config["default_wv_cdm"] base_name = config["default_wv_cdm"]
if not base_name.endswith(".wvd"): if not base_name.endswith(".wvd"):
@ -22,7 +37,22 @@ def check_for_prd_cdm():
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file: with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
config = yaml.safe_load(file) config = yaml.safe_load(file)
if config['default_pr_cdm'] == '': if config['default_pr_cdm'] == '':
exit(f"Please put the name of your PlayReady CDM inside of {os.getcwd()}/configs/config.yaml") answer = ' '
while answer[0].upper() != 'Y' and answer[0].upper() != 'N':
answer = input('No default PlayReady CDM specified, would you like to download one from The CDM Project? (Y)es/(N)o: ')
if answer[0].upper() == 'Y':
response = requests.get(url='https://cdm-project.com/CDRM-Team/CDMs/raw/branch/main/Playready/SL2000/public.prd')
if response.status_code == 200:
with open(f'{os.getcwd()}/configs/CDMs/PR/public.prd', 'wb') as file:
file.write(response.content)
config['default_pr_cdm'] = 'public'
with open(f'{os.getcwd()}/configs/config.yaml', 'w') as file:
yaml.dump(config, file)
print("Successfully downloaded PlayReady CDM")
else:
exit(f"Download failed, please try again or place a .prd file in {os.getcwd()}/configs/CDMs/PR and specify the name in {os.getcwd()}/configs/config.yaml")
if answer[0].upper() == 'N':
exit(f"Place a .prd file in {os.getcwd()}/configs/CDMs/PR and specify the name in {os.getcwd()}/configs/config.yaml")
else: else:
base_name = config["default_pr_cdm"] base_name = config["default_pr_cdm"]
if not base_name.endswith(".prd"): if not base_name.endswith(".prd"):

View File

@ -7,6 +7,7 @@ def check_for_config_file():
default_config = """\ default_config = """\
default_wv_cdm: '' default_wv_cdm: ''
default_pr_cdm: '' default_pr_cdm: ''
secret_key_flask: 'secretkey'
# change the type to mariadb to use mariadb below # change the type to mariadb to use mariadb below
database_type: 'sqlite' database_type: 'sqlite'
fqdn: '' fqdn: ''

View File

@ -14,6 +14,13 @@ def check_for_sqlite_database():
else: else:
return return
def check_for_user_database():
if os.path.exists(f'{os.getcwd()}/databases/users.db'):
return
else:
from custom_functions.database.user_db import create_user_database
create_user_database()
def check_for_mariadb_database(): def check_for_mariadb_database():
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file: with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
config = yaml.safe_load(file) config = yaml.safe_load(file)
@ -27,3 +34,4 @@ def check_for_mariadb_database():
def check_for_sql_database(): def check_for_sql_database():
check_for_sqlite_database() check_for_sqlite_database()
check_for_mariadb_database() check_for_mariadb_database()
check_for_user_database()

View File

@ -0,0 +1,17 @@
import os
import glob
def user_allowed_to_use_device(device, username):
base_path = os.path.join(os.getcwd(), 'configs', 'CDMs', username)
# Get filenames with extensions
pr_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'PR', '*.prd'))]
wv_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'WV', '*.wvd'))]
# Combine all filenames
all_files = pr_files + wv_files
# Check if filename matches directly or by adding extensions
possible_names = {device, f"{device}.prd", f"{device}.wvd"}
return any(name in all_files for name in possible_names)

14
main.py
View File

@ -8,14 +8,26 @@ from routes.react import react_bp
from routes.api import api_bp from routes.api import api_bp
from routes.remote_device_wv import remotecdm_wv_bp from routes.remote_device_wv import remotecdm_wv_bp
from routes.remote_device_pr import remotecdm_pr_bp from routes.remote_device_pr import remotecdm_pr_bp
from routes.upload import upload_bp
from routes.user_info import user_info_bp
from routes.register import register_bp
from routes.login import login_bp
import os
import yaml
app = Flask(__name__) app = Flask(__name__)
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
config = yaml.safe_load(file)
app.secret_key = config['secret_key_flask']
CORS(app) CORS(app)
# Register the blueprint # Register the blueprint
app.register_blueprint(react_bp) app.register_blueprint(react_bp)
app.register_blueprint(api_bp) app.register_blueprint(api_bp)
app.register_blueprint(register_bp)
app.register_blueprint(login_bp)
app.register_blueprint(user_info_bp)
app.register_blueprint(upload_bp)
app.register_blueprint(remotecdm_wv_bp) app.register_blueprint(remotecdm_wv_bp)
app.register_blueprint(remotecdm_pr_bp) app.register_blueprint(remotecdm_pr_bp)

View File

@ -6,3 +6,4 @@ requests~=2.32.3
protobuf~=4.25.6 protobuf~=4.25.6
PyYAML~=6.0.2 PyYAML~=6.0.2
mysql-connector-python mysql-connector-python
bcrypt

View File

@ -1,8 +1,9 @@
import os import os
import sqlite3 import sqlite3
from flask import Blueprint, jsonify, request, send_file from flask import Blueprint, jsonify, request, send_file, session
import json import json
from custom_functions.decrypt.api_decrypt import api_decrypt from custom_functions.decrypt.api_decrypt import api_decrypt
from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
import shutil import shutil
import math import math
import yaml import yaml
@ -219,12 +220,27 @@ def decrypt_data():
api_request_cookies = None api_request_cookies = None
if 'data' in api_request_data: if 'data' in api_request_data:
if api_request_data['data'] == '': if api_request_data['data'] == '':
api_request_data = None api_request_data_func = None
else: else:
api_request_data = api_request_data['data'] api_request_data_func = api_request_data['data']
else: api_request_data_func = None
if 'device' in api_request_data:
if api_request_data['device'] == 'default' or api_request_data['device'] == 'CDRM-Project Public Widevine CDM' or api_request_data['device'] == 'CDRM-Project Public PlayReady CDM':
api_request_device = 'public'
else: else:
api_request_data = None api_request_device = api_request_data['device']
result = api_decrypt(pssh=api_request_pssh, proxy=api_request_proxy, license_url=api_request_licurl, headers=api_request_headers, cookies=api_request_cookies, json_data=api_request_data) else:
api_request_device = 'public'
username = None
if api_request_device != 'public':
username = session.get('username')
if not username:
return jsonify({'message': 'Not logged in, not allowed'}), 400
if user_allowed_to_use_device(device=api_request_device, username=username):
api_request_device = api_request_device
else:
return jsonify({'message': f'Not authorized / Not found'}), 403
result = api_decrypt(pssh=api_request_pssh, proxy=api_request_proxy, license_url=api_request_licurl, headers=api_request_headers, cookies=api_request_cookies, json_data=api_request_data_func, device=api_request_device, username=username)
if result['status'] == 'success': if result['status'] == 'success':
return jsonify({ return jsonify({
'status': 'success', 'status': 'success',

37
routes/login.py Normal file
View File

@ -0,0 +1,37 @@
from flask import Blueprint, request, jsonify, session
from custom_functions.database.user_db import verify_user
login_bp = Blueprint(
'login_bp',
__name__,
)
@login_bp.route('/login', methods=['POST'])
def login():
if request.method == 'POST':
data = request.get_json()
for required_field in ['username', 'password']:
if required_field not in data:
return jsonify({'error': f'Missing required field: {required_field}'}), 400
if verify_user(data['username'], data['password']):
session['username'] = data['username'] # Stored securely in a signed cookie
return jsonify({'message': 'Successfully logged in!'})
else:
return jsonify({'error': 'Invalid username or password!'}), 401
@login_bp.route('/login/status', methods=['POST'])
def login_status():
try:
username = session.get('username')
if username:
return jsonify({'message': 'True'})
else:
return jsonify({'message': 'False'})
except:
return jsonify({'message': 'False'})
@login_bp.route('/logout', methods=['POST'])
def logout():
session.pop('username', None)
return jsonify({'message': 'Successfully logged out!'})

View File

@ -26,7 +26,7 @@ def index(path=''):
file_path = os.path.join(react_bp.static_folder, path) file_path = os.path.join(react_bp.static_folder, path)
if path != "" and os.path.exists(file_path): if path != "" and os.path.exists(file_path):
return send_from_directory(react_bp.static_folder, path) return send_from_directory(react_bp.static_folder, path)
elif path.lower() in ['', 'cache', 'api', 'testplayer']: elif path.lower() in ['', 'cache', 'api', 'testplayer', 'account']:
data = index_tags.tags.get(path.lower(), index_tags.tags['index']) data = index_tags.tags.get(path.lower(), index_tags.tags['index'])
return render_template('index.html', data=data) return render_template('index.html', data=data)
else: else:

29
routes/register.py Normal file
View File

@ -0,0 +1,29 @@
from flask import Blueprint, request, jsonify
from custom_functions.database.user_db import add_user
register_bp = Blueprint(
'register_bp',
__name__,
)
@register_bp.route('/register', methods=['POST'])
def register():
if request.method == 'POST':
data = request.get_json()
for required_field in ['username', 'password']:
if required_field not in data:
return jsonify({
'error': f'Missing required field: {required_field}'
})
if add_user(data['username'], data['password']):
return jsonify({
'message': 'User successfully registered!'
})
else:
return jsonify({
'error': 'User already exists!'
})
else:
return jsonify({
'error': 'Method not supported'
})

42
routes/upload.py Normal file
View File

@ -0,0 +1,42 @@
from flask import Blueprint, request, jsonify, session
import os
import logging
upload_bp = Blueprint('upload_bp', __name__)
@upload_bp.route('/upload/<cdmtype>', methods=['POST'])
def upload(cdmtype):
try:
username = session.get('username')
if not username:
return jsonify({'message': 'False', 'error': 'No username in session'}), 400
# Validate CDM type
if cdmtype not in ['PR', 'WV']:
return jsonify({'message': 'False', 'error': 'Invalid CDM type'}), 400
# Set up user directory paths
base_path = os.path.join(os.getcwd(), 'configs', 'CDMs', username)
pr_path = os.path.join(base_path, 'PR')
wv_path = os.path.join(base_path, 'WV')
# Create necessary directories if they don't exist
os.makedirs(pr_path, exist_ok=True)
os.makedirs(wv_path, exist_ok=True)
# Get uploaded file
uploaded_file = request.files.get('file')
if not uploaded_file:
return jsonify({'message': 'False', 'error': 'No file provided'}), 400
# Determine correct save path based on cdmtype
filename = uploaded_file.filename
save_path = os.path.join(pr_path if cdmtype == 'PR' else wv_path, filename)
uploaded_file.save(save_path)
return jsonify({'message': 'Success', 'file_saved_to': save_path})
except Exception as e:
logging.exception("Upload failed")
return jsonify({'message': 'False', 'error': 'Server error'}), 500

26
routes/user_info.py Normal file
View File

@ -0,0 +1,26 @@
from flask import Blueprint, request, jsonify, session
import os
import glob
import logging
user_info_bp = Blueprint('user_info_bp', __name__)
@user_info_bp.route('/userinfo', methods=['POST'])
def user_info():
username = session.get('username')
if not username:
return jsonify({'message': 'False'}), 400
try:
base_path = os.path.join(os.getcwd(), 'configs', 'CDMs', username)
pr_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'PR', '*.prd'))]
wv_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'WV', '*.wvd'))]
return jsonify({
'Username': username,
'Widevine_Devices': wv_files,
'Playready_Devices': pr_files
})
except Exception as e:
logging.exception("Error retrieving device files")
return jsonify({'message': 'False'}), 500