Added User support
- Use your own Widevine / Playready CDMs!
This commit is contained in:
parent
4382ff2e5f
commit
a74ba64696
1
cdrm-frontend/dist/assets/index-COb8XlA9.css
vendored
1
cdrm-frontend/dist/assets/index-COb8XlA9.css
vendored
File diff suppressed because one or more lines are too long
155
cdrm-frontend/dist/assets/index-DN7XJ__W.js
vendored
155
cdrm-frontend/dist/assets/index-DN7XJ__W.js
vendored
File diff suppressed because one or more lines are too long
4
cdrm-frontend/dist/index.html
vendored
4
cdrm-frontend/dist/index.html
vendored
@ -12,8 +12,8 @@
|
||||
<meta property='og:url' content="{{ data.opengraph_url }}" />
|
||||
<meta property='og:locale' content='en_US' />
|
||||
<title>{{ data.tab_title }}</title>
|
||||
<script type="module" crossorigin src="/assets/index-DN7XJ__W.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-COb8XlA9.css">
|
||||
<script type="module" crossorigin src="/assets/index-CjVpgi8Q.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DKsxfXVF.css">
|
||||
</head>
|
||||
<body class="w-full h-full">
|
||||
<div id="root" class="w-full h-full"></div>
|
||||
|
279
cdrm-frontend/package-lock.json
generated
279
cdrm-frontend/package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"axios": "^1.9.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
@ -1669,6 +1670,23 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"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_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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@ -1788,6 +1819,18 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -1858,6 +1901,15 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
@ -1867,6 +1919,20 @@
|
||||
"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": {
|
||||
"version": "1.5.143",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.143.tgz",
|
||||
@ -1893,6 +1959,51 @@
|
||||
"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": {
|
||||
"version": "0.25.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
|
||||
@ -2220,6 +2331,41 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "2.3.3",
|
||||
"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_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": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
@ -2244,6 +2399,43 @@
|
||||
"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": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@ -2270,6 +2462,18 @@
|
||||
"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": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@ -2286,6 +2490,45 @@
|
||||
"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": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@ -2725,6 +2968,36 @@
|
||||
"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": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -2936,6 +3209,12 @@
|
||||
"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": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"axios": "^1.9.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
|
@ -6,6 +6,7 @@ import TestPlayer from "./components/Pages/TestPlayer";
|
||||
import NavBar from "./components/NavBar";
|
||||
import NavBarMain from "./components/NavBarMain";
|
||||
import SideMenu from "./components/SideMenu"; // Add this import
|
||||
import Account from "./components/Pages/Account";
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
|
||||
function App() {
|
||||
@ -31,6 +32,7 @@ function App() {
|
||||
<Route path="/cache" element={<Cache />} />
|
||||
<Route path="/api" element={<API />} />
|
||||
<Route path="/testplayer" element={<TestPlayer />} />
|
||||
<Route path="/account" element={<Account />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
13
cdrm-frontend/src/assets/icons/account.svg
Normal file
13
cdrm-frontend/src/assets/icons/account.svg
Normal 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 |
@ -4,6 +4,7 @@ import homeIcon from '../assets/icons/home.svg';
|
||||
import cacheIcon from '../assets/icons/cache.svg';
|
||||
import apiIcon from '../assets/icons/api.svg';
|
||||
import testPlayerIcon from '../assets/icons/testplayer.svg';
|
||||
import accountIcon from '../assets/icons/account.svg';
|
||||
import discordIcon from '../assets/icons/discord.svg';
|
||||
import telegramIcon from '../assets/icons/telegram.svg';
|
||||
import giteaIcon from '../assets/icons/gitea.svg';
|
||||
@ -24,81 +25,135 @@ function NavBar() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full bg-white/1">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<p className='text-white text-2xl font-bold p-3 text-center mb-5'>
|
||||
<a href='/'>CDRM-Project</a>
|
||||
<p className="text-white text-2xl font-bold p-3 text-center mb-5">
|
||||
<a href="/">CDRM-Project</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='overflow-y-auto grow'>
|
||||
{/* Static routes */}
|
||||
{[{
|
||||
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 }) => (
|
||||
{/* Scrollable navigation area */}
|
||||
<div className="overflow-y-auto grow flex flex-col">
|
||||
{/* Main NavLinks */}
|
||||
<NavLink
|
||||
to="/"
|
||||
className={({ isActive }) =>
|
||||
`flex flex-row p-3 border-l-3 ${
|
||||
isActive
|
||||
? 'border-l-sky-500/50 bg-black/50'
|
||||
: 'hover:border-l-sky-500/50 hover:bg-white/5'
|
||||
}`
|
||||
}
|
||||
>
|
||||
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
||||
<img src={homeIcon} alt="Home" className="w-1/2 cursor-pointer" />
|
||||
</button>
|
||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
||||
Home
|
||||
</p>
|
||||
</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
|
||||
key={label}
|
||||
to={to}
|
||||
to="/account"
|
||||
className={({ isActive }) =>
|
||||
`flex flex-row p-3 border-l-3 ${
|
||||
isActive
|
||||
? `border-l-${color}-500/50 bg-black/50`
|
||||
: `hover:border-l-${color}-500/50 hover:bg-white/5`
|
||||
? '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={icon} alt={label} className='w-1/2 cursor-pointer' />
|
||||
<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'>
|
||||
{label}
|
||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
||||
My Account
|
||||
</p>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* External links */}
|
||||
<div className='flex flex-row w-full h-16 self-end bg-black/25'>
|
||||
{/* External links at very bottom */}
|
||||
<div className="flex flex-row w-full h-16 bg-black/25">
|
||||
<a
|
||||
href={externalLinks.discord}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-950 group'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-950 group"
|
||||
>
|
||||
<img src={discordIcon} alt="Discord" className='w-1/2 group-hover:animate-bounce' />
|
||||
<img src={discordIcon} alt="Discord" className="w-1/2 group-hover:animate-bounce" />
|
||||
</a>
|
||||
<a
|
||||
href={externalLinks.telegram}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-400 group'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-400 group"
|
||||
>
|
||||
<img src={telegramIcon} alt="Telegram" className='w-1/2 group-hover:animate-bounce' />
|
||||
<img src={telegramIcon} alt="Telegram" className="w-1/2 group-hover:animate-bounce" />
|
||||
</a>
|
||||
<a
|
||||
href={externalLinks.gitea}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-green-700 group'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
38
cdrm-frontend/src/components/Pages/Account.jsx
Normal file
38
cdrm-frontend/src/components/Pages/Account.jsx
Normal 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;
|
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { readTextFromClipboard } from '../Functions/ParseChallenge'
|
||||
import { readTextFromClipboard } from '../Functions/ParseChallenge';
|
||||
import { Helmet } from 'react-helmet'; // Import Helmet
|
||||
|
||||
function HomePage() {
|
||||
@ -11,6 +11,8 @@ function HomePage() {
|
||||
const [data, setData] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [devices, setDevices] = useState([]);
|
||||
const [selectedDevice, setSelectedDevice] = useState('default');
|
||||
|
||||
const bottomRef = useRef(null);
|
||||
const messageRef = useRef(null); // Reference to result container
|
||||
@ -41,7 +43,8 @@ function HomePage() {
|
||||
proxy: proxy,
|
||||
headers: headers,
|
||||
cookies: cookies,
|
||||
data: data
|
||||
data: data,
|
||||
device: selectedDevice, // Include selected device in the request
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
@ -67,7 +70,6 @@ function HomePage() {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleFetchPaste = () => {
|
||||
event.preventDefault();
|
||||
@ -79,7 +81,7 @@ function HomePage() {
|
||||
}).catch(err => {
|
||||
alert('Failed to paste from fetch!');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible && bottomRef.current) {
|
||||
@ -87,6 +89,43 @@ function HomePage() {
|
||||
}
|
||||
}, [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 (
|
||||
<>
|
||||
<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)}
|
||||
/>
|
||||
|
||||
{/* 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"
|
||||
|
129
cdrm-frontend/src/components/Pages/MyAccount.jsx
Normal file
129
cdrm-frontend/src/components/Pages/MyAccount.jsx
Normal file
@ -0,0 +1,129 @@
|
||||
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(''); // <-- Added state for username
|
||||
|
||||
// Fetch user CDMs
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const response = await axios.post('/userinfo');
|
||||
setWvList(response.data.Widevine_Devices || []);
|
||||
setPrList(response.data.Playready_Devices || []);
|
||||
setUsername(response.data.Username || ''); // <-- Set username here
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
|
||||
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">
|
||||
{username ? `${username}` : 'My Account'}
|
||||
</h1>
|
||||
</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 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 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;
|
95
cdrm-frontend/src/components/Pages/Register.jsx
Normal file
95
cdrm-frontend/src/components/Pages/Register.jsx
Normal 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;
|
@ -1,45 +1,62 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import closeIcon from '../assets/icons/close.svg';
|
||||
import homeIcon from '../assets/icons/home.svg';
|
||||
import cacheIcon from '../assets/icons/cache.svg';
|
||||
import apiIcon from '../assets/icons/api.svg';
|
||||
import testPlayerIcon from '../assets/icons/testplayer.svg';
|
||||
import testPlayerIcon from '../assets/icons/testplayer.svg';
|
||||
import accountIcon from '../assets/icons/account.svg';
|
||||
import discordIcon from '../assets/icons/discord.svg';
|
||||
import telegramIcon from '../assets/icons/telegram.svg';
|
||||
import giteaIcon from '../assets/icons/gitea.svg';
|
||||
|
||||
function SideMenu({ isMenuOpen, setIsMenuOpen }) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col fixed top-0 left-0 w-full h-full bg-black transition-transform transform ${
|
||||
isMenuOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
} z-50`}
|
||||
style={{ transitionDuration: '0.3s' }}
|
||||
>
|
||||
<div className="flex flex-col bg-gray-950/55 h-full">
|
||||
<div className="h-16 w-full border-b-2 border-white/5 flex flex-row">
|
||||
<div className="w-1/4 h-full"></div>
|
||||
<p className="grow text-white md:text-2xl font-bold text-center flex items-center justify-center p-4">
|
||||
CDRM-Project
|
||||
</p>
|
||||
<div className="w-1/4 h-full">
|
||||
<button
|
||||
className="w-full h-full flex items-center justify-center"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<img src={closeIcon} alt="Close" className="w-1/2 h-1/2 cursor-pointer" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
const [externalLinks, setExternalLinks] = useState({
|
||||
discord: '#',
|
||||
telegram: '#',
|
||||
gitea: '#',
|
||||
});
|
||||
|
||||
<div className="overflow-y-auto flex flex-col p-5 w-full space-y-2 flex-grow">
|
||||
useEffect(() => {
|
||||
fetch('/api/links')
|
||||
.then((res) => res.json())
|
||||
.then((data) => setExternalLinks(data))
|
||||
.catch((err) => console.error('Failed to fetch links:', err));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col fixed top-0 left-0 w-full h-full bg-black transition-transform transform ${
|
||||
isMenuOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
} z-50`}
|
||||
style={{ transitionDuration: '0.3s' }}
|
||||
>
|
||||
<div className="flex flex-col bg-gray-950/55 h-full">
|
||||
{/* Header */}
|
||||
<div className="h-16 w-full border-b-2 border-white/5 flex flex-row">
|
||||
<div className="w-1/4 h-full"></div>
|
||||
<p className="grow text-white md:text-2xl font-bold text-center flex items-center justify-center p-4">
|
||||
CDRM-Project
|
||||
</p>
|
||||
<div className="w-1/4 h-full">
|
||||
<button
|
||||
className="w-full h-full flex items-center justify-center"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
<img src={closeIcon} alt="Close" className="w-1/2 h-1/2 cursor-pointer" />
|
||||
</button>
|
||||
</div>
|
||||
</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-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'
|
||||
}`
|
||||
}
|
||||
@ -95,47 +112,66 @@ function SideMenu({ isMenuOpen, setIsMenuOpen }) {
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div className="h-16 self-end w-full flex flex-row bg-black/5">
|
||||
<a
|
||||
href="https://discord.cdrm-project.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-1/3 h-full flex items-center justify-center hover:bg-blue-950 group"
|
||||
{/* 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={discordIcon}
|
||||
alt="Discord"
|
||||
className="w-full h-2/3 p-1 cursor-pointer group-hover:animate-bounce"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://telegram.cdrm-project.com"
|
||||
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="https://cdm-project.com/tpd94/cdrm-project"
|
||||
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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
|
45
custom_functions/database/user_db.py
Normal file
45
custom_functions/database/user_db.py
Normal 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
|
@ -84,7 +84,8 @@ def is_url_and_split(input_str):
|
||||
else:
|
||||
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:
|
||||
config = yaml.safe_load(file)
|
||||
if config['database_type'].lower() == 'sqlite':
|
||||
@ -106,19 +107,34 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
|
||||
'message': f'An error occurred processing PSSH\n\n{error}'
|
||||
}
|
||||
try:
|
||||
base_name = config["default_pr_cdm"]
|
||||
if not base_name.endswith(".prd"):
|
||||
base_name += ".prd"
|
||||
prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/PR/{base_name}')
|
||||
if device == 'public':
|
||||
base_name = config["default_pr_cdm"]
|
||||
if not base_name.endswith(".prd"):
|
||||
base_name += ".prd"
|
||||
prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/PR/{base_name}')
|
||||
else:
|
||||
prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/PR/{base_name}')
|
||||
if prd_files:
|
||||
pr_device = playreadyDevice.load(prd_files[0])
|
||||
else:
|
||||
return {
|
||||
'status': 'error',
|
||||
'message': 'No default .prd file found'
|
||||
}
|
||||
else:
|
||||
prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/PR/{base_name}')
|
||||
if prd_files:
|
||||
pr_device = playreadyDevice.load(prd_files[0])
|
||||
else:
|
||||
return {
|
||||
'status': 'error',
|
||||
'message': 'No default .prd file found'
|
||||
}
|
||||
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:
|
||||
return {
|
||||
'status': 'error',
|
||||
@ -266,19 +282,34 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
|
||||
'message': f'An error occurred processing PSSH\n\n{error}'
|
||||
}
|
||||
try:
|
||||
base_name = config["default_wv_cdm"]
|
||||
if not base_name.endswith(".wvd"):
|
||||
base_name += ".wvd"
|
||||
wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/WV/{base_name}')
|
||||
if device == 'public':
|
||||
base_name = config["default_wv_cdm"]
|
||||
if not base_name.endswith(".wvd"):
|
||||
base_name += ".wvd"
|
||||
wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/WV/{base_name}')
|
||||
else:
|
||||
wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/WV/{base_name}')
|
||||
if wvd_files:
|
||||
wv_device = widevineDevice.load(wvd_files[0])
|
||||
else:
|
||||
return {
|
||||
'status': 'error',
|
||||
'message': 'No default .wvd file found'
|
||||
}
|
||||
else:
|
||||
wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/WV/{base_name}')
|
||||
if wvd_files:
|
||||
wv_device = widevineDevice.load(wvd_files[0])
|
||||
else:
|
||||
return {
|
||||
'status': 'error',
|
||||
'message': 'No default .wvd file found'
|
||||
}
|
||||
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:
|
||||
return {
|
||||
'status': 'error',
|
||||
|
@ -14,6 +14,13 @@ def check_for_sqlite_database():
|
||||
else:
|
||||
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():
|
||||
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
|
||||
config = yaml.safe_load(file)
|
||||
@ -26,4 +33,5 @@ def check_for_mariadb_database():
|
||||
|
||||
def check_for_sql_database():
|
||||
check_for_sqlite_database()
|
||||
check_for_mariadb_database()
|
||||
check_for_mariadb_database()
|
||||
check_for_user_database()
|
17
custom_functions/user_checks/device_allowed.py
Normal file
17
custom_functions/user_checks/device_allowed.py
Normal 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)
|
9
main.py
9
main.py
@ -8,14 +8,23 @@ from routes.react import react_bp
|
||||
from routes.api import api_bp
|
||||
from routes.remote_device_wv import remotecdm_wv_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
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'TT'
|
||||
|
||||
CORS(app)
|
||||
|
||||
# Register the blueprint
|
||||
app.register_blueprint(react_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_pr_bp)
|
||||
|
||||
|
@ -5,4 +5,5 @@ pyplayready~=0.6.0
|
||||
requests~=2.32.3
|
||||
protobuf~=4.25.6
|
||||
PyYAML~=6.0.2
|
||||
mysql-connector-python
|
||||
mysql-connector-python
|
||||
bcrypt
|
@ -1,8 +1,9 @@
|
||||
import os
|
||||
import sqlite3
|
||||
from flask import Blueprint, jsonify, request, send_file
|
||||
from flask import Blueprint, jsonify, request, send_file, session
|
||||
import json
|
||||
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 math
|
||||
import yaml
|
||||
@ -219,12 +220,27 @@ def decrypt_data():
|
||||
api_request_cookies = None
|
||||
if 'data' in api_request_data:
|
||||
if api_request_data['data'] == '':
|
||||
api_request_data = None
|
||||
api_request_data_func = None
|
||||
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:
|
||||
api_request_device = api_request_data['device']
|
||||
else:
|
||||
api_request_data = None
|
||||
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)
|
||||
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':
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
|
32
routes/login.py
Normal file
32
routes/login.py
Normal file
@ -0,0 +1,32 @@
|
||||
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'})
|
29
routes/register.py
Normal file
29
routes/register.py
Normal 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
42
routes/upload.py
Normal 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
26
routes/user_info.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user