forked from tpd94/CDRM-Project
		
	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())
 | 
			
		||||
@ -68,7 +71,6 @@ function HomePage() {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const handleFetchPaste = () => {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    readTextFromClipboard().then(() => {
 | 
			
		||||
@ -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 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)
 | 
			
		||||
@ -27,3 +34,4 @@ def check_for_mariadb_database():
 | 
			
		||||
def check_for_sql_database():
 | 
			
		||||
    check_for_sqlite_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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,3 +6,4 @@ requests~=2.32.3
 | 
			
		||||
protobuf~=4.25.6
 | 
			
		||||
PyYAML~=6.0.2
 | 
			
		||||
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