forked from tpd94/CDRM-Project
		
	Update project structure and formatting, add build code, remove /dist
This commit is contained in:
		
							parent
							
								
									84999654ed
								
							
						
					
					
						commit
						bafd3db4f4
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -9,3 +9,4 @@ main.spec
 | 
				
			|||||||
pyinstallericon.ico
 | 
					pyinstallericon.ico
 | 
				
			||||||
icon.ico
 | 
					icon.ico
 | 
				
			||||||
venv
 | 
					venv
 | 
				
			||||||
 | 
					frontend-dist
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								build.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								build.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					"""Main file to run the application."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def build_frontend():
 | 
				
			||||||
 | 
					    """Build the frontend."""
 | 
				
			||||||
 | 
					    frontend_dir = "cdrm-frontend"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Check and run npm commands if needed
 | 
				
			||||||
 | 
					    if not os.path.exists(f"{frontend_dir}/node_modules"):
 | 
				
			||||||
 | 
					        subprocess.run(["npm", "install"], cwd=frontend_dir, check=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not os.path.exists(f"{frontend_dir}/dist"):
 | 
				
			||||||
 | 
					        subprocess.run(["npm", "run", "build"], cwd=frontend_dir, check=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Move dist to frontend-dist
 | 
				
			||||||
 | 
					    if os.path.exists("frontend-dist"):
 | 
				
			||||||
 | 
					        shutil.rmtree("frontend-dist")
 | 
				
			||||||
 | 
					    shutil.copytree(f"{frontend_dir}/dist", "frontend-dist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("✅ Build complete. Run the application with 'python main.py'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    build_frontend()
 | 
				
			||||||
							
								
								
									
										193
									
								
								cdrm-frontend/dist/assets/index-1tbqhIbb.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										193
									
								
								cdrm-frontend/dist/assets/index-1tbqhIbb.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								cdrm-frontend/dist/assets/index-C8jLRdm9.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								cdrm-frontend/dist/assets/index-C8jLRdm9.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								cdrm-frontend/dist/favico.png
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cdrm-frontend/dist/favico.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 862 B  | 
							
								
								
									
										21
									
								
								cdrm-frontend/dist/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								cdrm-frontend/dist/index.html
									
									
									
									
										vendored
									
									
								
							@ -1,21 +0,0 @@
 | 
				
			|||||||
<!doctype html>
 | 
					 | 
				
			||||||
<html lang="en" class="w-full h-full">
 | 
					 | 
				
			||||||
    <head>
 | 
					 | 
				
			||||||
        <meta charset="UTF-8" />
 | 
					 | 
				
			||||||
        <link rel="icon" type="image/svg+xml" href="/favico.png" />
 | 
					 | 
				
			||||||
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					 | 
				
			||||||
        <meta name="description" content="{{ data.description }}" />
 | 
					 | 
				
			||||||
        <meta name="keywords" content="{{ data.keywords }}" />
 | 
					 | 
				
			||||||
        <meta property="og:title" content="{{ data.opengraph_title }}" />
 | 
					 | 
				
			||||||
        <meta property="og:description" content="{{ data.opengraph_description }}" />
 | 
					 | 
				
			||||||
        <meta property="og:image" content="{{ data.opengraph_image }}" />
 | 
					 | 
				
			||||||
        <meta property="og:url" content="{{ data.opengraph_url }}" />
 | 
					 | 
				
			||||||
        <meta property="og:locale" content="en_US" />
 | 
					 | 
				
			||||||
        <title>{{ data.tab_title }}</title>
 | 
					 | 
				
			||||||
      <script type="module" crossorigin src="/assets/index-1tbqhIbb.js"></script>
 | 
					 | 
				
			||||||
      <link rel="stylesheet" crossorigin href="/assets/index-C8jLRdm9.css">
 | 
					 | 
				
			||||||
    </head>
 | 
					 | 
				
			||||||
    <body class="w-full h-full">
 | 
					 | 
				
			||||||
        <div id="root" class="w-full h-full"></div>
 | 
					 | 
				
			||||||
    </body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-api.jpg
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-api.jpg
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 189 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-cache.jpg
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-cache.jpg
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 207 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-home.jpg
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-home.jpg
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 302 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-testplayer.jpg
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								cdrm-frontend/dist/og-testplayer.jpg
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 99 KiB  | 
@ -1,5 +1,5 @@
 | 
				
			|||||||
data = {
 | 
					data = {
 | 
				
			||||||
    'discord': 'https://discord.cdrm-project.com/',
 | 
					    "discord": "https://discord.cdrm-project.com/",
 | 
				
			||||||
    'telegram': 'https://telegram.cdrm-project.com/',
 | 
					    "telegram": "https://telegram.cdrm-project.com/",
 | 
				
			||||||
    'gitea': 'https://cdm-project.com/tpd94/cdrm-project'
 | 
					    "gitea": "https://cdm-project.com/tpd94/cdrm-project",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,47 +1,47 @@
 | 
				
			|||||||
tags = {
 | 
					tags = {
 | 
				
			||||||
    'index': {
 | 
					    "index": {
 | 
				
			||||||
        'description': 'Decrypt Widevine and PlayReady protected content',
 | 
					        "description": "Decrypt Widevine and PlayReady protected content",
 | 
				
			||||||
        'keywords': 'CDRM, Widevine, PlayReady, DRM, Decrypt, CDM, CDM-Project, CDRM-Project, TPD94, Decryption',
 | 
					        "keywords": "CDRM, Widevine, PlayReady, DRM, Decrypt, CDM, CDM-Project, CDRM-Project, TPD94, Decryption",
 | 
				
			||||||
        'opengraph_title': 'CDRM-Project',
 | 
					        "opengraph_title": "CDRM-Project",
 | 
				
			||||||
        'opengraph_description': 'Self Hosted web application written in Python/JavaScript utilizing the Flask/Tailwind Framework and ReactJS library to decrypt Widevine & Playready content',
 | 
					        "opengraph_description": "Self Hosted web application written in Python/JavaScript utilizing the Flask/Tailwind Framework and ReactJS library to decrypt Widevine & Playready content",
 | 
				
			||||||
        'opengraph_image': 'https://cdrm-project.com/og-home.jpg',
 | 
					        "opengraph_image": "https://cdrm-project.com/og-home.jpg",
 | 
				
			||||||
        'opengraph_url': 'https://cdm-project.com/tpd94/cdrm-project',
 | 
					        "opengraph_url": "https://cdm-project.com/tpd94/cdrm-project",
 | 
				
			||||||
        'tab_title': 'CDRM-Project',
 | 
					        "tab_title": "CDRM-Project",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    'cache': {
 | 
					    "cache": {
 | 
				
			||||||
        'description': 'Search the cache by KID or PSSH for decryption keys',
 | 
					        "description": "Search the cache by KID or PSSH for decryption keys",
 | 
				
			||||||
        'keywords': 'Cache, Vault, Widevine, PlayReady, DRM, Decryption, CDM, CDRM-Project, CDRM-Project, TPD94, Decryption',
 | 
					        "keywords": "Cache, Vault, Widevine, PlayReady, DRM, Decryption, CDM, CDRM-Project, CDRM-Project, TPD94, Decryption",
 | 
				
			||||||
        'opengraph_title': 'Search the Cache',
 | 
					        "opengraph_title": "Search the Cache",
 | 
				
			||||||
        'opengraph_description': 'Search the cache by KID or PSSH for decryption keys',
 | 
					        "opengraph_description": "Search the cache by KID or PSSH for decryption keys",
 | 
				
			||||||
        'opengraph_image': 'https://cdrm-project.com/og-cache.jpg',
 | 
					        "opengraph_image": "https://cdrm-project.com/og-cache.jpg",
 | 
				
			||||||
        'opengraph_url': 'https://cdrm-project.com/cache',
 | 
					        "opengraph_url": "https://cdrm-project.com/cache",
 | 
				
			||||||
        'tab_title': 'Cache',
 | 
					        "tab_title": "Cache",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    'testplayer': {
 | 
					    "testplayer": {
 | 
				
			||||||
        'description': 'Shaka Player for testing decryption keys',
 | 
					        "description": "Shaka Player for testing decryption keys",
 | 
				
			||||||
        'keywords': 'Shaka, Player, DRM, CDRM, CDM, CDRM-Project, TPD94, Decryption, CDM-Project, KID, KEY',
 | 
					        "keywords": "Shaka, Player, DRM, CDRM, CDM, CDRM-Project, TPD94, Decryption, CDM-Project, KID, KEY",
 | 
				
			||||||
        'opengraph_title': 'Test Player',
 | 
					        "opengraph_title": "Test Player",
 | 
				
			||||||
        'opengraph_description': 'Shaka Player for testing decryption keys',
 | 
					        "opengraph_description": "Shaka Player for testing decryption keys",
 | 
				
			||||||
        'opengraph_image': 'https://cdrm-project.com/og-testplayer.jpg',
 | 
					        "opengraph_image": "https://cdrm-project.com/og-testplayer.jpg",
 | 
				
			||||||
        'opengraph_url': 'https://cdrm-project.com/testplayer',
 | 
					        "opengraph_url": "https://cdrm-project.com/testplayer",
 | 
				
			||||||
        'tab_title': 'Test Player',
 | 
					        "tab_title": "Test Player",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    'api': {
 | 
					    "api": {
 | 
				
			||||||
        'description': 'API documentation for the program "CDRM-Project"',
 | 
					        "description": 'API documentation for the program "CDRM-Project"',
 | 
				
			||||||
        'keywords': 'API, python, requests, send, remotecdm, remote, cdm, CDM-Project, CDRM-Project, TPD94, Decryption, DRM, Web, Vault',
 | 
					        "keywords": "API, python, requests, send, remotecdm, remote, cdm, CDM-Project, CDRM-Project, TPD94, Decryption, DRM, Web, Vault",
 | 
				
			||||||
        'opengraph_title': 'API',
 | 
					        "opengraph_title": "API",
 | 
				
			||||||
        'opengraph_description': 'Documentation for the program "CDRM-Project"',
 | 
					        "opengraph_description": 'Documentation for the program "CDRM-Project"',
 | 
				
			||||||
        'opengraph_image': 'https://cdrm-project.com/og-api.jpg',
 | 
					        "opengraph_image": "https://cdrm-project.com/og-api.jpg",
 | 
				
			||||||
        'opengraph_url': 'https://cdrm-project.com/api',
 | 
					        "opengraph_url": "https://cdrm-project.com/api",
 | 
				
			||||||
        'tab_title': 'API',
 | 
					        "tab_title": "API",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    'account': {
 | 
					    "account": {
 | 
				
			||||||
        'description': 'Account for CDRM-Project',
 | 
					        "description": "Account for CDRM-Project",
 | 
				
			||||||
        'keywords': 'Login, CDRM, CDM, CDRM-Project, register, account',
 | 
					        "keywords": "Login, CDRM, CDM, CDRM-Project, register, account",
 | 
				
			||||||
        'opengraph_title': 'My account',
 | 
					        "opengraph_title": "My account",
 | 
				
			||||||
        'opengraph_description': 'Account for CDRM-Project',
 | 
					        "opengraph_description": "Account for CDRM-Project",
 | 
				
			||||||
        'opengraph_image': 'https://cdrm-project.com/og-home.jpg',
 | 
					        "opengraph_image": "https://cdrm-project.com/og-home.jpg",
 | 
				
			||||||
        'opengraph_url': 'https://cdrm-project.com/account',
 | 
					        "opengraph_url": "https://cdrm-project.com/account",
 | 
				
			||||||
        'tab_title': 'My account',
 | 
					        "tab_title": "My account",
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,16 +4,15 @@ import mysql.connector
 | 
				
			|||||||
from mysql.connector import Error
 | 
					from mysql.connector import Error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_db_config():
 | 
					def get_db_config():
 | 
				
			||||||
    # Configure your MariaDB connection
 | 
					    # Configure your MariaDB connection
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					    with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
        config = yaml.safe_load(file)
 | 
					        config = yaml.safe_load(file)
 | 
				
			||||||
    db_config = {
 | 
					    db_config = {
 | 
				
			||||||
        'host': f'{config["mariadb"]["host"]}',
 | 
					        "host": f'{config["mariadb"]["host"]}',
 | 
				
			||||||
        'user': f'{config["mariadb"]["user"]}',
 | 
					        "user": f'{config["mariadb"]["user"]}',
 | 
				
			||||||
        'password': f'{config["mariadb"]["password"]}',
 | 
					        "password": f'{config["mariadb"]["password"]}',
 | 
				
			||||||
        'database': f'{config["mariadb"]["database"]}'
 | 
					        "database": f'{config["mariadb"]["database"]}',
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return db_config
 | 
					    return db_config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -22,7 +21,8 @@ def create_database():
 | 
				
			|||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with mysql.connector.connect(**get_db_config()) as conn:
 | 
					        with mysql.connector.connect(**get_db_config()) as conn:
 | 
				
			||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
            cursor.execute('''
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
            CREATE TABLE IF NOT EXISTS licenses (
 | 
					            CREATE TABLE IF NOT EXISTS licenses (
 | 
				
			||||||
                SERVICE VARCHAR(255),
 | 
					                SERVICE VARCHAR(255),
 | 
				
			||||||
                PSSH TEXT,
 | 
					                PSSH TEXT,
 | 
				
			||||||
@ -33,20 +33,32 @@ def create_database():
 | 
				
			|||||||
                Cookies TEXT,
 | 
					                Cookies TEXT,
 | 
				
			||||||
                Data BLOB
 | 
					                Data BLOB
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            ''')
 | 
					            """
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            conn.commit()
 | 
					            conn.commit()
 | 
				
			||||||
    except Error as e:
 | 
					    except Error as e:
 | 
				
			||||||
        print(f"Error: {e}")
 | 
					        print(f"Error: {e}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_to_db(service=None, pssh=None, kid=None, key=None, license_url=None, headers=None, cookies=None, data=None):
 | 
					
 | 
				
			||||||
 | 
					def cache_to_db(
 | 
				
			||||||
 | 
					    service=None,
 | 
				
			||||||
 | 
					    pssh=None,
 | 
				
			||||||
 | 
					    kid=None,
 | 
				
			||||||
 | 
					    key=None,
 | 
				
			||||||
 | 
					    license_url=None,
 | 
				
			||||||
 | 
					    headers=None,
 | 
				
			||||||
 | 
					    cookies=None,
 | 
				
			||||||
 | 
					    data=None,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with mysql.connector.connect(**get_db_config()) as conn:
 | 
					        with mysql.connector.connect(**get_db_config()) as conn:
 | 
				
			||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            cursor.execute('SELECT 1 FROM licenses WHERE KID = %s', (kid,))
 | 
					            cursor.execute("SELECT 1 FROM licenses WHERE KID = %s", (kid,))
 | 
				
			||||||
            existing_record = cursor.fetchone()
 | 
					            existing_record = cursor.fetchone()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            cursor.execute('''
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
            INSERT INTO licenses (SERVICE, PSSH, KID, `Key`, License_URL, Headers, Cookies, Data)
 | 
					            INSERT INTO licenses (SERVICE, PSSH, KID, `Key`, License_URL, Headers, Cookies, Data)
 | 
				
			||||||
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
 | 
					            VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
 | 
				
			||||||
            ON DUPLICATE KEY UPDATE
 | 
					            ON DUPLICATE KEY UPDATE
 | 
				
			||||||
@ -57,7 +69,9 @@ def cache_to_db(service=None, pssh=None, kid=None, key=None, license_url=None, h
 | 
				
			|||||||
                Headers = VALUES(Headers),
 | 
					                Headers = VALUES(Headers),
 | 
				
			||||||
                Cookies = VALUES(Cookies),
 | 
					                Cookies = VALUES(Cookies),
 | 
				
			||||||
                Data = VALUES(Data)
 | 
					                Data = VALUES(Data)
 | 
				
			||||||
            ''', (service, pssh, kid, key, license_url, headers, cookies, data))
 | 
					            """,
 | 
				
			||||||
 | 
					                (service, pssh, kid, key, license_url, headers, cookies, data),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            conn.commit()
 | 
					            conn.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return True if existing_record else False
 | 
					            return True if existing_record else False
 | 
				
			||||||
@ -65,6 +79,7 @@ def cache_to_db(service=None, pssh=None, kid=None, key=None, license_url=None, h
 | 
				
			|||||||
        print(f"Error: {e}")
 | 
					        print(f"Error: {e}")
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def search_by_pssh_or_kid(search_filter):
 | 
					def search_by_pssh_or_kid(search_filter):
 | 
				
			||||||
    results = set()
 | 
					    results = set()
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
@ -72,54 +87,71 @@ def search_by_pssh_or_kid(search_filter):
 | 
				
			|||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
            like_filter = f"%{search_filter}%"
 | 
					            like_filter = f"%{search_filter}%"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            cursor.execute('SELECT PSSH, KID, `Key` FROM licenses WHERE PSSH LIKE %s', (like_filter,))
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                "SELECT PSSH, KID, `Key` FROM licenses WHERE PSSH LIKE %s",
 | 
				
			||||||
 | 
					                (like_filter,),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            results.update(cursor.fetchall())
 | 
					            results.update(cursor.fetchall())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            cursor.execute('SELECT PSSH, KID, `Key` FROM licenses WHERE KID LIKE %s', (like_filter,))
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                "SELECT PSSH, KID, `Key` FROM licenses WHERE KID LIKE %s",
 | 
				
			||||||
 | 
					                (like_filter,),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            results.update(cursor.fetchall())
 | 
					            results.update(cursor.fetchall())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final_results = [{'PSSH': row[0], 'KID': row[1], 'Key': row[2]} for row in results]
 | 
					        final_results = [
 | 
				
			||||||
 | 
					            {"PSSH": row[0], "KID": row[1], "Key": row[2]} for row in results
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
        return final_results[:20]
 | 
					        return final_results[:20]
 | 
				
			||||||
    except Error as e:
 | 
					    except Error as e:
 | 
				
			||||||
        print(f"Error: {e}")
 | 
					        print(f"Error: {e}")
 | 
				
			||||||
        return []
 | 
					        return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_key_by_kid_and_service(kid, service):
 | 
					def get_key_by_kid_and_service(kid, service):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with mysql.connector.connect(**get_db_config()) as conn:
 | 
					        with mysql.connector.connect(**get_db_config()) as conn:
 | 
				
			||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
            cursor.execute('SELECT `Key` FROM licenses WHERE KID = %s AND SERVICE = %s', (kid, service))
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                "SELECT `Key` FROM licenses WHERE KID = %s AND SERVICE = %s",
 | 
				
			||||||
 | 
					                (kid, service),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            result = cursor.fetchone()
 | 
					            result = cursor.fetchone()
 | 
				
			||||||
            return result[0] if result else None
 | 
					            return result[0] if result else None
 | 
				
			||||||
    except Error as e:
 | 
					    except Error as e:
 | 
				
			||||||
        print(f"Error: {e}")
 | 
					        print(f"Error: {e}")
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_kid_key_dict(service_name):
 | 
					def get_kid_key_dict(service_name):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with mysql.connector.connect(**get_db_config()) as conn:
 | 
					        with mysql.connector.connect(**get_db_config()) as conn:
 | 
				
			||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
            cursor.execute('SELECT KID, `Key` FROM licenses WHERE SERVICE = %s', (service_name,))
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                "SELECT KID, `Key` FROM licenses WHERE SERVICE = %s", (service_name,)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            return {row[0]: row[1] for row in cursor.fetchall()}
 | 
					            return {row[0]: row[1] for row in cursor.fetchall()}
 | 
				
			||||||
    except Error as e:
 | 
					    except Error as e:
 | 
				
			||||||
        print(f"Error: {e}")
 | 
					        print(f"Error: {e}")
 | 
				
			||||||
        return {}
 | 
					        return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_unique_services():
 | 
					def get_unique_services():
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with mysql.connector.connect(**get_db_config()) as conn:
 | 
					        with mysql.connector.connect(**get_db_config()) as conn:
 | 
				
			||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
            cursor.execute('SELECT DISTINCT SERVICE FROM licenses')
 | 
					            cursor.execute("SELECT DISTINCT SERVICE FROM licenses")
 | 
				
			||||||
            return [row[0] for row in cursor.fetchall()]
 | 
					            return [row[0] for row in cursor.fetchall()]
 | 
				
			||||||
    except Error as e:
 | 
					    except Error as e:
 | 
				
			||||||
        print(f"Error: {e}")
 | 
					        print(f"Error: {e}")
 | 
				
			||||||
        return []
 | 
					        return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def key_count():
 | 
					def key_count():
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with mysql.connector.connect(**get_db_config()) as conn:
 | 
					        with mysql.connector.connect(**get_db_config()) as conn:
 | 
				
			||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
            cursor.execute('SELECT COUNT(KID) FROM licenses')
 | 
					            cursor.execute("SELECT COUNT(KID) FROM licenses")
 | 
				
			||||||
            return cursor.fetchone()[0]
 | 
					            return cursor.fetchone()[0]
 | 
				
			||||||
    except Error as e:
 | 
					    except Error as e:
 | 
				
			||||||
        print(f"Error: {e}")
 | 
					        print(f"Error: {e}")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,13 @@
 | 
				
			|||||||
import sqlite3
 | 
					import sqlite3
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_database():
 | 
					def create_database():
 | 
				
			||||||
    # Using with statement to manage the connection and cursor
 | 
					    # Using with statement to manage the connection and cursor
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/key_cache.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/key_cache.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('''
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
        CREATE TABLE IF NOT EXISTS licenses (
 | 
					        CREATE TABLE IF NOT EXISTS licenses (
 | 
				
			||||||
            SERVICE TEXT,
 | 
					            SERVICE TEXT,
 | 
				
			||||||
            PSSH TEXT,
 | 
					            PSSH TEXT,
 | 
				
			||||||
@ -16,92 +18,127 @@ def create_database():
 | 
				
			|||||||
            Cookies TEXT,
 | 
					            Cookies TEXT,
 | 
				
			||||||
            Data TEXT
 | 
					            Data TEXT
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        ''')
 | 
					        """
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cache_to_db(service: str = None, pssh: str = None, kid: str = None, key: str = None, license_url: str = None, headers: str = None, cookies: str = None, data: str = None):
 | 
					
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/key_cache.db') as conn:
 | 
					def cache_to_db(
 | 
				
			||||||
 | 
					    service: str = None,
 | 
				
			||||||
 | 
					    pssh: str = None,
 | 
				
			||||||
 | 
					    kid: str = None,
 | 
				
			||||||
 | 
					    key: str = None,
 | 
				
			||||||
 | 
					    license_url: str = None,
 | 
				
			||||||
 | 
					    headers: str = None,
 | 
				
			||||||
 | 
					    cookies: str = None,
 | 
				
			||||||
 | 
					    data: str = None,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/key_cache.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check if the record with the given KID already exists
 | 
					        # Check if the record with the given KID already exists
 | 
				
			||||||
        cursor.execute('''SELECT 1 FROM licenses WHERE KID = ?''', (kid,))
 | 
					        cursor.execute("""SELECT 1 FROM licenses WHERE KID = ?""", (kid,))
 | 
				
			||||||
        existing_record = cursor.fetchone()
 | 
					        existing_record = cursor.fetchone()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Insert or replace the record
 | 
					        # Insert or replace the record
 | 
				
			||||||
        cursor.execute('''
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
        INSERT OR REPLACE INTO licenses (SERVICE, PSSH, KID, Key, License_URL, Headers, Cookies, Data)
 | 
					        INSERT OR REPLACE INTO licenses (SERVICE, PSSH, KID, Key, License_URL, Headers, Cookies, Data)
 | 
				
			||||||
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
 | 
					        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
 | 
				
			||||||
        ''', (service, pssh, kid, key, license_url, headers, cookies, data))
 | 
					        """,
 | 
				
			||||||
 | 
					            (service, pssh, kid, key, license_url, headers, cookies, data),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If the record was existing and updated, return True (updated), else return False (added)
 | 
					        # If the record was existing and updated, return True (updated), else return False (added)
 | 
				
			||||||
        return True if existing_record else False
 | 
					        return True if existing_record else False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def search_by_pssh_or_kid(search_filter):
 | 
					def search_by_pssh_or_kid(search_filter):
 | 
				
			||||||
    # Using with statement to automatically close the connection
 | 
					    # Using with statement to automatically close the connection
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/key_cache.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/key_cache.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Initialize a set to store unique matching records
 | 
					        # Initialize a set to store unique matching records
 | 
				
			||||||
        results = set()
 | 
					        results = set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Search for records where PSSH contains the search_filter
 | 
					        # Search for records where PSSH contains the search_filter
 | 
				
			||||||
        cursor.execute('''
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
        SELECT * FROM licenses WHERE PSSH LIKE ?
 | 
					        SELECT * FROM licenses WHERE PSSH LIKE ?
 | 
				
			||||||
        ''', ('%' + search_filter + '%',))
 | 
					        """,
 | 
				
			||||||
 | 
					            ("%" + search_filter + "%",),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        rows = cursor.fetchall()
 | 
					        rows = cursor.fetchall()
 | 
				
			||||||
        for row in rows:
 | 
					        for row in rows:
 | 
				
			||||||
            results.add((row[1], row[2], row[3]))  # (PSSH, KID, Key)
 | 
					            results.add((row[1], row[2], row[3]))  # (PSSH, KID, Key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Search for records where KID contains the search_filter
 | 
					        # Search for records where KID contains the search_filter
 | 
				
			||||||
        cursor.execute('''
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
        SELECT * FROM licenses WHERE KID LIKE ?
 | 
					        SELECT * FROM licenses WHERE KID LIKE ?
 | 
				
			||||||
        ''', ('%' + search_filter + '%',))
 | 
					        """,
 | 
				
			||||||
 | 
					            ("%" + search_filter + "%",),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        rows = cursor.fetchall()
 | 
					        rows = cursor.fetchall()
 | 
				
			||||||
        for row in rows:
 | 
					        for row in rows:
 | 
				
			||||||
            results.add((row[1], row[2], row[3]))  # (PSSH, KID, Key)
 | 
					            results.add((row[1], row[2], row[3]))  # (PSSH, KID, Key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Convert the set of results to a list of dictionaries for output
 | 
					        # Convert the set of results to a list of dictionaries for output
 | 
				
			||||||
        final_results = [{'PSSH': result[0], 'KID': result[1], 'Key': result[2]} for result in results]
 | 
					        final_results = [
 | 
				
			||||||
 | 
					            {"PSSH": result[0], "KID": result[1], "Key": result[2]}
 | 
				
			||||||
 | 
					            for result in results
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return final_results[:20]
 | 
					    return final_results[:20]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_key_by_kid_and_service(kid, service):
 | 
					def get_key_by_kid_and_service(kid, service):
 | 
				
			||||||
    # Using 'with' to automatically close the connection when done
 | 
					    # Using 'with' to automatically close the connection when done
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/key_cache.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/key_cache.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Query to search by KID and SERVICE
 | 
					        # Query to search by KID and SERVICE
 | 
				
			||||||
        cursor.execute('''
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
        SELECT Key FROM licenses WHERE KID = ? AND SERVICE = ?
 | 
					        SELECT Key FROM licenses WHERE KID = ? AND SERVICE = ?
 | 
				
			||||||
        ''', (kid, service))
 | 
					        """,
 | 
				
			||||||
 | 
					            (kid, service),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Fetch the result
 | 
					        # Fetch the result
 | 
				
			||||||
        result = cursor.fetchone()
 | 
					        result = cursor.fetchone()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Check if a result was found
 | 
					        # Check if a result was found
 | 
				
			||||||
        return result[0] if result else None  # The 'Key' is the first (and only) column returned in the result
 | 
					        return (
 | 
				
			||||||
 | 
					            result[0] if result else None
 | 
				
			||||||
 | 
					        )  # The 'Key' is the first (and only) column returned in the result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_kid_key_dict(service_name):
 | 
					def get_kid_key_dict(service_name):
 | 
				
			||||||
    # Using with statement to automatically manage the connection and cursor
 | 
					    # Using with statement to automatically manage the connection and cursor
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/key_cache.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/key_cache.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Query to fetch KID and Key for the selected service
 | 
					        # Query to fetch KID and Key for the selected service
 | 
				
			||||||
        cursor.execute('''
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
        SELECT KID, Key FROM licenses WHERE SERVICE = ?
 | 
					        SELECT KID, Key FROM licenses WHERE SERVICE = ?
 | 
				
			||||||
        ''', (service_name,))
 | 
					        """,
 | 
				
			||||||
 | 
					            (service_name,),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Fetch all results and create the dictionary
 | 
					        # Fetch all results and create the dictionary
 | 
				
			||||||
        kid_key_dict = {row[0]: row[1] for row in cursor.fetchall()}
 | 
					        kid_key_dict = {row[0]: row[1] for row in cursor.fetchall()}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return kid_key_dict
 | 
					    return kid_key_dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_unique_services():
 | 
					def get_unique_services():
 | 
				
			||||||
    # Using with statement to automatically manage the connection and cursor
 | 
					    # Using with statement to automatically manage the connection and cursor
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/key_cache.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/key_cache.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Query to get distinct services from the 'licenses' table
 | 
					        # Query to get distinct services from the 'licenses' table
 | 
				
			||||||
        cursor.execute('SELECT DISTINCT SERVICE FROM licenses')
 | 
					        cursor.execute("SELECT DISTINCT SERVICE FROM licenses")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Fetch all results and extract the unique services
 | 
					        # Fetch all results and extract the unique services
 | 
				
			||||||
        services = cursor.fetchall()
 | 
					        services = cursor.fetchall()
 | 
				
			||||||
@ -111,13 +148,14 @@ def get_unique_services():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return unique_services
 | 
					    return unique_services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def key_count():
 | 
					def key_count():
 | 
				
			||||||
    # Using with statement to automatically manage the connection and cursor
 | 
					    # Using with statement to automatically manage the connection and cursor
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/key_cache.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/key_cache.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Count the number of KID entries in the licenses table
 | 
					        # Count the number of KID entries in the licenses table
 | 
				
			||||||
        cursor.execute('SELECT COUNT(KID) FROM licenses')
 | 
					        cursor.execute("SELECT COUNT(KID) FROM licenses")
 | 
				
			||||||
        count = cursor.fetchone()[0]  # Fetch the result and get the count
 | 
					        count = cursor.fetchone()[0]  # Fetch the result and get the count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return count
 | 
					    return count
 | 
				
			||||||
 | 
				
			|||||||
@ -4,27 +4,32 @@ import bcrypt
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_user_database():
 | 
					def create_user_database():
 | 
				
			||||||
    os.makedirs(f'{os.getcwd()}/databases/sql', exist_ok=True)
 | 
					    os.makedirs(f"{os.getcwd()}/databases/sql", exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('''
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
        CREATE TABLE IF NOT EXISTS user_info (
 | 
					        CREATE TABLE IF NOT EXISTS user_info (
 | 
				
			||||||
            Username TEXT PRIMARY KEY,
 | 
					            Username TEXT PRIMARY KEY,
 | 
				
			||||||
            Password TEXT,
 | 
					            Password TEXT,
 | 
				
			||||||
            Styled_Username TEXT,
 | 
					            Styled_Username TEXT,
 | 
				
			||||||
            API_Key TEXT
 | 
					            API_Key TEXT
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        ''')
 | 
					        """
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def add_user(username, password, api_key):
 | 
					def add_user(username, password, api_key):
 | 
				
			||||||
    hashed_pw = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
 | 
					    hashed_pw = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            cursor.execute('INSERT INTO user_info (Username, Password, Styled_Username, API_Key) VALUES (?, ?, ?, ?)', (username.lower(), hashed_pw, username, api_key))
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                "INSERT INTO user_info (Username, Password, Styled_Username, API_Key) VALUES (?, ?, ?, ?)",
 | 
				
			||||||
 | 
					                (username.lower(), hashed_pw, username, api_key),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            conn.commit()
 | 
					            conn.commit()
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
        except sqlite3.IntegrityError:
 | 
					        except sqlite3.IntegrityError:
 | 
				
			||||||
@ -32,24 +37,29 @@ def add_user(username, password, api_key):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def verify_user(username, password):
 | 
					def verify_user(username, password):
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('SELECT Password FROM user_info WHERE Username = ?', (username.lower(),))
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            "SELECT Password FROM user_info WHERE Username = ?", (username.lower(),)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        result = cursor.fetchone()
 | 
					        result = cursor.fetchone()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if result:
 | 
					        if result:
 | 
				
			||||||
            stored_hash = result[0]
 | 
					            stored_hash = result[0]
 | 
				
			||||||
            # Ensure stored_hash is bytes; decode if it's still a string (SQLite may store as TEXT)
 | 
					            # Ensure stored_hash is bytes; decode if it's still a string (SQLite may store as TEXT)
 | 
				
			||||||
            if isinstance(stored_hash, str):
 | 
					            if isinstance(stored_hash, str):
 | 
				
			||||||
                stored_hash = stored_hash.encode('utf-8')
 | 
					                stored_hash = stored_hash.encode("utf-8")
 | 
				
			||||||
            return bcrypt.checkpw(password.encode('utf-8'), stored_hash)
 | 
					            return bcrypt.checkpw(password.encode("utf-8"), stored_hash)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def fetch_api_key(username):
 | 
					def fetch_api_key(username):
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('SELECT API_Key FROM user_info WHERE Username = ?', (username.lower(),))
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            "SELECT API_Key FROM user_info WHERE Username = ?", (username.lower(),)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        result = cursor.fetchone()
 | 
					        result = cursor.fetchone()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if result:
 | 
					        if result:
 | 
				
			||||||
@ -57,30 +67,42 @@ def fetch_api_key(username):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def change_password(username, new_password):
 | 
					def change_password(username, new_password):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Hash the new password
 | 
					    # Hash the new password
 | 
				
			||||||
    new_hashed_pw = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt())
 | 
					    new_hashed_pw = bcrypt.hashpw(new_password.encode("utf-8"), bcrypt.gensalt())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Update the password in the database
 | 
					    # Update the password in the database
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('UPDATE user_info SET Password = ? WHERE Username = ?', (new_hashed_pw, username.lower()))
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            "UPDATE user_info SET Password = ? WHERE Username = ?",
 | 
				
			||||||
 | 
					            (new_hashed_pw, username.lower()),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        conn.commit()
 | 
					        conn.commit()
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def change_api_key(username, new_api_key):
 | 
					def change_api_key(username, new_api_key):
 | 
				
			||||||
    # Update the API key in the database
 | 
					    # Update the API key in the database
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('UPDATE user_info SET API_Key = ? WHERE Username = ?', (new_api_key, username.lower()))
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            "UPDATE user_info SET API_Key = ? WHERE Username = ?",
 | 
				
			||||||
 | 
					            (new_api_key, username.lower()),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        conn.commit()
 | 
					        conn.commit()
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def fetch_styled_username(username):
 | 
					def fetch_styled_username(username):
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('SELECT Styled_Username FROM user_info WHERE Username = ?', (username.lower(),))
 | 
					        cursor.execute(
 | 
				
			||||||
 | 
					            "SELECT Styled_Username FROM user_info WHERE Username = ?",
 | 
				
			||||||
 | 
					            (username.lower(),),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        result = cursor.fetchone()
 | 
					        result = cursor.fetchone()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if result:
 | 
					        if result:
 | 
				
			||||||
@ -88,13 +110,14 @@ def fetch_styled_username(username):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def fetch_username_by_api_key(api_key):
 | 
					def fetch_username_by_api_key(api_key):
 | 
				
			||||||
    with sqlite3.connect(f'{os.getcwd()}/databases/sql/users.db') as conn:
 | 
					    with sqlite3.connect(f"{os.getcwd()}/databases/sql/users.db") as conn:
 | 
				
			||||||
        cursor = conn.cursor()
 | 
					        cursor = conn.cursor()
 | 
				
			||||||
        cursor.execute('SELECT Username FROM user_info WHERE API_Key = ?', (api_key,))
 | 
					        cursor.execute("SELECT Username FROM user_info WHERE API_Key = ?", (api_key,))
 | 
				
			||||||
        result = cursor.fetchone()
 | 
					        result = cursor.fetchone()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if result:
 | 
					        if result:
 | 
				
			||||||
            return result[0]  # Return the username
 | 
					            return result[0]  # Return the username
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return None  # If no user is found for the API key
 | 
					            return None  # If no user is found for the API key
 | 
				
			||||||
 | 
				
			|||||||
@ -13,19 +13,23 @@ import yaml
 | 
				
			|||||||
from urllib.parse import urlparse
 | 
					from urllib.parse import urlparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def find_license_key(data, keywords=None):
 | 
					def find_license_key(data, keywords=None):
 | 
				
			||||||
    if keywords is None:
 | 
					    if keywords is None:
 | 
				
			||||||
        keywords = ["license", "licenseData", "widevine2License"]  # Default list of keywords to search for
 | 
					        keywords = [
 | 
				
			||||||
 | 
					            "license",
 | 
				
			||||||
 | 
					            "licenseData",
 | 
				
			||||||
 | 
					            "widevine2License",
 | 
				
			||||||
 | 
					        ]  # Default list of keywords to search for
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # If the data is a dictionary, check each key
 | 
					    # If the data is a dictionary, check each key
 | 
				
			||||||
    if isinstance(data, dict):
 | 
					    if isinstance(data, dict):
 | 
				
			||||||
        for key, value in data.items():
 | 
					        for key, value in data.items():
 | 
				
			||||||
            if any(keyword in key.lower() for keyword in
 | 
					            if any(
 | 
				
			||||||
                   keywords):  # Check if any keyword is in the key (case-insensitive)
 | 
					                keyword in key.lower() for keyword in keywords
 | 
				
			||||||
                return value.replace("-", "+").replace("_", "/")  # Return the value immediately when found
 | 
					            ):  # Check if any keyword is in the key (case-insensitive)
 | 
				
			||||||
 | 
					                return value.replace("-", "+").replace(
 | 
				
			||||||
 | 
					                    "_", "/"
 | 
				
			||||||
 | 
					                )  # Return the value immediately when found
 | 
				
			||||||
            # Recursively check if the value is a dictionary or list
 | 
					            # Recursively check if the value is a dictionary or list
 | 
				
			||||||
            if isinstance(value, (dict, list)):
 | 
					            if isinstance(value, (dict, list)):
 | 
				
			||||||
                result = find_license_key(value, keywords)  # Recursively search
 | 
					                result = find_license_key(value, keywords)  # Recursively search
 | 
				
			||||||
@ -44,21 +48,32 @@ def find_license_key(data, keywords=None):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
def find_license_challenge(data, keywords=None, new_value=None):
 | 
					def find_license_challenge(data, keywords=None, new_value=None):
 | 
				
			||||||
    if keywords is None:
 | 
					    if keywords is None:
 | 
				
			||||||
        keywords = ["license", "licenseData", "widevine2License", "licenseRequest"]  # Default list of keywords to search for
 | 
					        keywords = [
 | 
				
			||||||
 | 
					            "license",
 | 
				
			||||||
 | 
					            "licenseData",
 | 
				
			||||||
 | 
					            "widevine2License",
 | 
				
			||||||
 | 
					            "licenseRequest",
 | 
				
			||||||
 | 
					        ]  # Default list of keywords to search for
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # If the data is a dictionary, check each key
 | 
					    # If the data is a dictionary, check each key
 | 
				
			||||||
    if isinstance(data, dict):
 | 
					    if isinstance(data, dict):
 | 
				
			||||||
        for key, value in data.items():
 | 
					        for key, value in data.items():
 | 
				
			||||||
            if any(keyword in key.lower() for keyword in keywords):  # Check if any keyword is in the key (case-insensitive)
 | 
					            if any(
 | 
				
			||||||
 | 
					                keyword in key.lower() for keyword in keywords
 | 
				
			||||||
 | 
					            ):  # Check if any keyword is in the key (case-insensitive)
 | 
				
			||||||
                data[key] = new_value  # Modify the value in-place
 | 
					                data[key] = new_value  # Modify the value in-place
 | 
				
			||||||
            # Recursively check if the value is a dictionary or list
 | 
					            # Recursively check if the value is a dictionary or list
 | 
				
			||||||
            elif isinstance(value, (dict, list)):
 | 
					            elif isinstance(value, (dict, list)):
 | 
				
			||||||
                find_license_challenge(value, keywords, new_value)  # Recursively modify in place
 | 
					                find_license_challenge(
 | 
				
			||||||
 | 
					                    value, keywords, new_value
 | 
				
			||||||
 | 
					                )  # Recursively modify in place
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # If the data is a list, iterate through each item
 | 
					    # If the data is a list, iterate through each item
 | 
				
			||||||
    elif isinstance(data, list):
 | 
					    elif isinstance(data, list):
 | 
				
			||||||
        for i, item in enumerate(data):
 | 
					        for i, item in enumerate(data):
 | 
				
			||||||
            result = find_license_challenge(item, keywords, new_value)  # Recursively modify in place
 | 
					            result = find_license_challenge(
 | 
				
			||||||
 | 
					                item, keywords, new_value
 | 
				
			||||||
 | 
					            )  # Recursively modify in place
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return data  # Return the modified original data (no new structure is created)
 | 
					    return data  # Return the modified original data (no new structure is created)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,11 +83,12 @@ def is_base64(string):
 | 
				
			|||||||
        # Try decoding the string
 | 
					        # Try decoding the string
 | 
				
			||||||
        decoded_data = base64.b64decode(string)
 | 
					        decoded_data = base64.b64decode(string)
 | 
				
			||||||
        # Check if the decoded data, when re-encoded, matches the original string
 | 
					        # Check if the decoded data, when re-encoded, matches the original string
 | 
				
			||||||
        return base64.b64encode(decoded_data).decode('utf-8') == string
 | 
					        return base64.b64encode(decoded_data).decode("utf-8") == string
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        # If decoding or encoding fails, it's not Base64
 | 
					        # If decoding or encoding fails, it's not Base64
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def is_url_and_split(input_str):
 | 
					def is_url_and_split(input_str):
 | 
				
			||||||
    parsed = urlparse(input_str)
 | 
					    parsed = urlparse(input_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,82 +100,99 @@ def is_url_and_split(input_str):
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return False, None, None
 | 
					        return False, None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, headers: str = None, cookies: str = None, json_data: str = None, device: str = 'public', username: str = None):
 | 
					
 | 
				
			||||||
    print(f'Using device {device} for user {username}')
 | 
					def api_decrypt(
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					    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)
 | 
					        config = yaml.safe_load(file)
 | 
				
			||||||
    if config['database_type'].lower() == 'sqlite':
 | 
					    if config["database_type"].lower() == "sqlite":
 | 
				
			||||||
        from custom_functions.database.cache_to_db_sqlite import cache_to_db
 | 
					        from custom_functions.database.cache_to_db_sqlite import cache_to_db
 | 
				
			||||||
    elif config['database_type'].lower() == 'mariadb':
 | 
					    elif config["database_type"].lower() == "mariadb":
 | 
				
			||||||
        from custom_functions.database.cache_to_db_mariadb import cache_to_db
 | 
					        from custom_functions.database.cache_to_db_mariadb import cache_to_db
 | 
				
			||||||
    if pssh is None:
 | 
					    if pssh is None:
 | 
				
			||||||
        return {
 | 
					        return {"status": "error", "message": "No PSSH provided"}
 | 
				
			||||||
            'status': 'error',
 | 
					 | 
				
			||||||
            'message': 'No PSSH provided'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        if "</WRMHEADER>".encode("utf-16-le") in base64.b64decode(pssh):  # PR
 | 
					        if "</WRMHEADER>".encode("utf-16-le") in base64.b64decode(pssh):  # PR
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                pr_pssh = playreadyPSSH(pssh)
 | 
					                pr_pssh = playreadyPSSH(pssh)
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred processing PSSH\n\n{error}'
 | 
					                    "message": f"An error occurred processing PSSH\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                if device == 'public':
 | 
					                if device == "public":
 | 
				
			||||||
                    base_name = config["default_pr_cdm"]
 | 
					                    base_name = config["default_pr_cdm"]
 | 
				
			||||||
                    if not base_name.endswith(".prd"):
 | 
					                    if not base_name.endswith(".prd"):
 | 
				
			||||||
                        base_name += ".prd"
 | 
					                        base_name += ".prd"
 | 
				
			||||||
                        prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/PR/{base_name}')
 | 
					                        prd_files = glob.glob(
 | 
				
			||||||
 | 
					                            f"{os.getcwd()}/configs/CDMs/PR/{base_name}"
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/PR/{base_name}')
 | 
					                        prd_files = glob.glob(
 | 
				
			||||||
 | 
					                            f"{os.getcwd()}/configs/CDMs/PR/{base_name}"
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                    if prd_files:
 | 
					                    if prd_files:
 | 
				
			||||||
                        pr_device = playreadyDevice.load(prd_files[0])
 | 
					                        pr_device = playreadyDevice.load(prd_files[0])
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        return {
 | 
					                        return {
 | 
				
			||||||
                            'status': 'error',
 | 
					                            "status": "error",
 | 
				
			||||||
                            'message': 'No default .prd file found'
 | 
					                            "message": "No default .prd file found",
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    base_name = device
 | 
					                    base_name = device
 | 
				
			||||||
                    if not base_name.endswith(".prd"):
 | 
					                    if not base_name.endswith(".prd"):
 | 
				
			||||||
                        base_name += ".prd"
 | 
					                        base_name += ".prd"
 | 
				
			||||||
                        prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}')
 | 
					                        prd_files = glob.glob(
 | 
				
			||||||
 | 
					                            f"{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}"
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        prd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}')
 | 
					                        prd_files = glob.glob(
 | 
				
			||||||
 | 
					                            f"{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}"
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                    if prd_files:
 | 
					                    if prd_files:
 | 
				
			||||||
                        pr_device = playreadyDevice.load(prd_files[0])
 | 
					                        pr_device = playreadyDevice.load(prd_files[0])
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        return {
 | 
					                        return {
 | 
				
			||||||
                            'status': 'error',
 | 
					                            "status": "error",
 | 
				
			||||||
                            'message': f'{base_name} does not exist'
 | 
					                            "message": f"{base_name} does not exist",
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred location PlayReady CDM file\n\n{error}'
 | 
					                    "message": f"An error occurred location PlayReady CDM file\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                pr_cdm = playreadyCdm.from_device(pr_device)
 | 
					                pr_cdm = playreadyCdm.from_device(pr_device)
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred loading PlayReady CDM\n\n{error}'
 | 
					                    "message": f"An error occurred loading PlayReady CDM\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                pr_session_id = pr_cdm.open()
 | 
					                pr_session_id = pr_cdm.open()
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred opening a CDM session\n\n{error}'
 | 
					                    "message": f"An error occurred opening a CDM session\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                pr_challenge = pr_cdm.get_license_challenge(pr_session_id, pr_pssh.wrm_headers[0])
 | 
					                pr_challenge = pr_cdm.get_license_challenge(
 | 
				
			||||||
 | 
					                    pr_session_id, pr_pssh.wrm_headers[0]
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred getting license challenge\n\n{error}'
 | 
					                    "message": f"An error occurred getting license challenge\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                if headers:
 | 
					                if headers:
 | 
				
			||||||
@ -168,8 +201,8 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                    format_headers = None
 | 
					                    format_headers = None
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred getting headers\n\n{error}'
 | 
					                    "message": f"An error occurred getting headers\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                if cookies:
 | 
					                if cookies:
 | 
				
			||||||
@ -178,8 +211,8 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                    format_cookies = None
 | 
					                    format_cookies = None
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred getting cookies\n\n{error}'
 | 
					                    "message": f"An error occurred getting cookies\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                if json_data and not is_base64(json_data):
 | 
					                if json_data and not is_base64(json_data):
 | 
				
			||||||
@ -188,19 +221,19 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                    format_json_data = None
 | 
					                    format_json_data = None
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred getting json_data\n\n{error}'
 | 
					                    "message": f"An error occurred getting json_data\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            licence = None
 | 
					            licence = None
 | 
				
			||||||
            proxies = None
 | 
					            proxies = None
 | 
				
			||||||
            if proxy is not None:
 | 
					            if proxy is not None:
 | 
				
			||||||
                is_url, protocol, fqdn = is_url_and_split(proxy)
 | 
					                is_url, protocol, fqdn = is_url_and_split(proxy)
 | 
				
			||||||
                if is_url:
 | 
					                if is_url:
 | 
				
			||||||
                    proxies = {'http': proxy, 'https': proxy}
 | 
					                    proxies = {"http": proxy, "https": proxy}
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    return {
 | 
					                    return {
 | 
				
			||||||
                        'status': 'error',
 | 
					                        "status": "error",
 | 
				
			||||||
                        'message': f'Your proxy is invalid, please put it in the format of http(s)://fqdn.tld:port'
 | 
					                        "message": f"Your proxy is invalid, please put it in the format of http(s)://fqdn.tld:port",
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                licence = requests.post(
 | 
					                licence = requests.post(
 | 
				
			||||||
@ -209,132 +242,133 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                    proxies=proxies,
 | 
					                    proxies=proxies,
 | 
				
			||||||
                    cookies=format_cookies,
 | 
					                    cookies=format_cookies,
 | 
				
			||||||
                    json=format_json_data if format_json_data is not None else None,
 | 
					                    json=format_json_data if format_json_data is not None else None,
 | 
				
			||||||
                    data=pr_challenge if format_json_data is None else None
 | 
					                    data=pr_challenge if format_json_data is None else None,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            except requests.exceptions.ConnectionError as error:
 | 
					            except requests.exceptions.ConnectionError as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred sending license challenge through your proxy\n\n{error}'
 | 
					                    "message": f"An error occurred sending license challenge through your proxy\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred sending license reqeust\n\n{error}\n\n{licence.content}'
 | 
					                    "message": f"An error occurred sending license reqeust\n\n{error}\n\n{licence.content}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                pr_cdm.parse_license(pr_session_id, licence.text)
 | 
					                pr_cdm.parse_license(pr_session_id, licence.text)
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred parsing license content\n\n{error}\n\n{licence.content}'
 | 
					                    "message": f"An error occurred parsing license content\n\n{error}\n\n{licence.content}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            returned_keys = ""
 | 
					            returned_keys = ""
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                keys = list(pr_cdm.get_keys(pr_session_id))
 | 
					                keys = list(pr_cdm.get_keys(pr_session_id))
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred getting keys\n\n{error}'
 | 
					                    "message": f"An error occurred getting keys\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                for index, key in enumerate(keys):
 | 
					                for index, key in enumerate(keys):
 | 
				
			||||||
                    if key.key_type != 'SIGNING':
 | 
					                    if key.key_type != "SIGNING":
 | 
				
			||||||
                        cache_to_db(pssh=pssh, license_url=license_url, headers=headers, cookies=cookies,
 | 
					                        cache_to_db(
 | 
				
			||||||
                                    data=pr_challenge if json_data is None else json_data, kid=key.key_id.hex,
 | 
					                            pssh=pssh,
 | 
				
			||||||
                                    key=key.key.hex())
 | 
					                            license_url=license_url,
 | 
				
			||||||
 | 
					                            headers=headers,
 | 
				
			||||||
 | 
					                            cookies=cookies,
 | 
				
			||||||
 | 
					                            data=pr_challenge if json_data is None else json_data,
 | 
				
			||||||
 | 
					                            kid=key.key_id.hex,
 | 
				
			||||||
 | 
					                            key=key.key.hex(),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                        if index != len(keys) - 1:
 | 
					                        if index != len(keys) - 1:
 | 
				
			||||||
                            returned_keys += f"{key.key_id.hex}:{key.key.hex()}\n"
 | 
					                            returned_keys += f"{key.key_id.hex}:{key.key.hex()}\n"
 | 
				
			||||||
                        else:
 | 
					                        else:
 | 
				
			||||||
                            returned_keys += f"{key.key_id.hex}:{key.key.hex()}"
 | 
					                            returned_keys += f"{key.key_id.hex}:{key.key.hex()}"
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred formatting keys\n\n{error}'
 | 
					                    "message": f"An error occurred formatting keys\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                pr_cdm.close(pr_session_id)
 | 
					                pr_cdm.close(pr_session_id)
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred closing session\n\n{error}'
 | 
					                    "message": f"An error occurred closing session\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                return {
 | 
					                return {"status": "success", "message": returned_keys}
 | 
				
			||||||
                    'status': 'success',
 | 
					 | 
				
			||||||
                    'message': returned_keys
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred getting returned_keys\n\n{error}'
 | 
					                    "message": f"An error occurred getting returned_keys\n\n{error}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
    except Exception as error:
 | 
					    except Exception as error:
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'status': 'error',
 | 
					            "status": "error",
 | 
				
			||||||
            'message': f'An error occurred processing PSSH\n\n{error}'
 | 
					            "message": f"An error occurred processing PSSH\n\n{error}",
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            wv_pssh = widevinePSSH(pssh)
 | 
					            wv_pssh = widevinePSSH(pssh)
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred processing PSSH\n\n{error}'
 | 
					                "message": f"An error occurred processing PSSH\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if device == 'public':
 | 
					            if device == "public":
 | 
				
			||||||
                base_name = config["default_wv_cdm"]
 | 
					                base_name = config["default_wv_cdm"]
 | 
				
			||||||
                if not base_name.endswith(".wvd"):
 | 
					                if not base_name.endswith(".wvd"):
 | 
				
			||||||
                    base_name += ".wvd"
 | 
					                    base_name += ".wvd"
 | 
				
			||||||
                    wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/WV/{base_name}')
 | 
					                    wvd_files = glob.glob(f"{os.getcwd()}/configs/CDMs/WV/{base_name}")
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/WV/{base_name}')
 | 
					                    wvd_files = glob.glob(f"{os.getcwd()}/configs/CDMs/WV/{base_name}")
 | 
				
			||||||
                if wvd_files:
 | 
					                if wvd_files:
 | 
				
			||||||
                    wv_device = widevineDevice.load(wvd_files[0])
 | 
					                    wv_device = widevineDevice.load(wvd_files[0])
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    return {
 | 
					                    return {"status": "error", "message": "No default .wvd file found"}
 | 
				
			||||||
                        'status': 'error',
 | 
					 | 
				
			||||||
                        'message': 'No default .wvd file found'
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                base_name = device
 | 
					                base_name = device
 | 
				
			||||||
                if not base_name.endswith(".wvd"):
 | 
					                if not base_name.endswith(".wvd"):
 | 
				
			||||||
                    base_name += ".wvd"
 | 
					                    base_name += ".wvd"
 | 
				
			||||||
                    wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}')
 | 
					                    wvd_files = glob.glob(
 | 
				
			||||||
 | 
					                        f"{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    wvd_files = glob.glob(f'{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}')
 | 
					                    wvd_files = glob.glob(
 | 
				
			||||||
 | 
					                        f"{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                if wvd_files:
 | 
					                if wvd_files:
 | 
				
			||||||
                    wv_device = widevineDevice.load(wvd_files[0])
 | 
					                    wv_device = widevineDevice.load(wvd_files[0])
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    return {
 | 
					                    return {"status": "error", "message": f"{base_name} does not exist"}
 | 
				
			||||||
                        'status': 'error',
 | 
					 | 
				
			||||||
                        'message': f'{base_name} does not exist'
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred location Widevine CDM file\n\n{error}'
 | 
					                "message": f"An error occurred location Widevine CDM file\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            wv_cdm = widevineCdm.from_device(wv_device)
 | 
					            wv_cdm = widevineCdm.from_device(wv_device)
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred loading Widevine CDM\n\n{error}'
 | 
					                "message": f"An error occurred loading Widevine CDM\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            wv_session_id = wv_cdm.open()
 | 
					            wv_session_id = wv_cdm.open()
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred opening a CDM session\n\n{error}'
 | 
					                "message": f"An error occurred opening a CDM session\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            wv_challenge = wv_cdm.get_license_challenge(wv_session_id, wv_pssh)
 | 
					            wv_challenge = wv_cdm.get_license_challenge(wv_session_id, wv_pssh)
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred getting license challenge\n\n{error}'
 | 
					                "message": f"An error occurred getting license challenge\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if headers:
 | 
					            if headers:
 | 
				
			||||||
@ -343,8 +377,8 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                format_headers = None
 | 
					                format_headers = None
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred getting headers\n\n{error}'
 | 
					                "message": f"An error occurred getting headers\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if cookies:
 | 
					            if cookies:
 | 
				
			||||||
@ -353,26 +387,29 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                format_cookies = None
 | 
					                format_cookies = None
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred getting cookies\n\n{error}'
 | 
					                "message": f"An error occurred getting cookies\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if json_data and not is_base64(json_data):
 | 
					            if json_data and not is_base64(json_data):
 | 
				
			||||||
                format_json_data = ast.literal_eval(json_data)
 | 
					                format_json_data = ast.literal_eval(json_data)
 | 
				
			||||||
                format_json_data = find_license_challenge(data=format_json_data, new_value=base64.b64encode(wv_challenge).decode())
 | 
					                format_json_data = find_license_challenge(
 | 
				
			||||||
 | 
					                    data=format_json_data,
 | 
				
			||||||
 | 
					                    new_value=base64.b64encode(wv_challenge).decode(),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                format_json_data = None
 | 
					                format_json_data = None
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred getting json_data\n\n{error}'
 | 
					                "message": f"An error occurred getting json_data\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        licence = None
 | 
					        licence = None
 | 
				
			||||||
        proxies = None
 | 
					        proxies = None
 | 
				
			||||||
        if proxy is not None:
 | 
					        if proxy is not None:
 | 
				
			||||||
            is_url, protocol, fqdn = is_url_and_split(proxy)
 | 
					            is_url, protocol, fqdn = is_url_and_split(proxy)
 | 
				
			||||||
            if is_url:
 | 
					            if is_url:
 | 
				
			||||||
                proxies = {'http': proxy, 'https': proxy}
 | 
					                proxies = {"http": proxy, "https": proxy}
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            licence = requests.post(
 | 
					            licence = requests.post(
 | 
				
			||||||
                url=license_url,
 | 
					                url=license_url,
 | 
				
			||||||
@ -380,17 +417,17 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                proxies=proxies,
 | 
					                proxies=proxies,
 | 
				
			||||||
                cookies=format_cookies,
 | 
					                cookies=format_cookies,
 | 
				
			||||||
                json=format_json_data if format_json_data is not None else None,
 | 
					                json=format_json_data if format_json_data is not None else None,
 | 
				
			||||||
                data=wv_challenge if format_json_data is None else None
 | 
					                data=wv_challenge if format_json_data is None else None,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        except requests.exceptions.ConnectionError as error:
 | 
					        except requests.exceptions.ConnectionError as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred sending license challenge through your proxy\n\n{error}'
 | 
					                "message": f"An error occurred sending license challenge through your proxy\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred sending license reqeust\n\n{error}\n\n{licence.content}'
 | 
					                "message": f"An error occurred sending license reqeust\n\n{error}\n\n{licence.content}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            wv_cdm.parse_license(wv_session_id, licence.content)
 | 
					            wv_cdm.parse_license(wv_session_id, licence.content)
 | 
				
			||||||
@ -401,44 +438,49 @@ def api_decrypt(pssh:str = None, license_url: str = None, proxy: str = None, hea
 | 
				
			|||||||
                wv_cdm.parse_license(wv_session_id, license_value)
 | 
					                wv_cdm.parse_license(wv_session_id, license_value)
 | 
				
			||||||
            except Exception as error:
 | 
					            except Exception as error:
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
                    'status': 'error',
 | 
					                    "status": "error",
 | 
				
			||||||
                    'message': f'An error occurred parsing license content\n\n{error}\n\n{licence.content}'
 | 
					                    "message": f"An error occurred parsing license content\n\n{error}\n\n{licence.content}",
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
        returned_keys = ""
 | 
					        returned_keys = ""
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            keys = list(wv_cdm.get_keys(wv_session_id))
 | 
					            keys = list(wv_cdm.get_keys(wv_session_id))
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred getting keys\n\n{error}'
 | 
					                "message": f"An error occurred getting keys\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            for index, key in enumerate(keys):
 | 
					            for index, key in enumerate(keys):
 | 
				
			||||||
                if key.type != 'SIGNING':
 | 
					                if key.type != "SIGNING":
 | 
				
			||||||
                    cache_to_db(pssh=pssh, license_url=license_url, headers=headers, cookies=cookies, data=wv_challenge if json_data is None else json_data, kid=key.kid.hex, key=key.key.hex())
 | 
					                    cache_to_db(
 | 
				
			||||||
 | 
					                        pssh=pssh,
 | 
				
			||||||
 | 
					                        license_url=license_url,
 | 
				
			||||||
 | 
					                        headers=headers,
 | 
				
			||||||
 | 
					                        cookies=cookies,
 | 
				
			||||||
 | 
					                        data=wv_challenge if json_data is None else json_data,
 | 
				
			||||||
 | 
					                        kid=key.kid.hex,
 | 
				
			||||||
 | 
					                        key=key.key.hex(),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    if index != len(keys) - 1:
 | 
					                    if index != len(keys) - 1:
 | 
				
			||||||
                        returned_keys += f"{key.kid.hex}:{key.key.hex()}\n"
 | 
					                        returned_keys += f"{key.kid.hex}:{key.key.hex()}\n"
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        returned_keys += f"{key.kid.hex}:{key.key.hex()}"
 | 
					                        returned_keys += f"{key.kid.hex}:{key.key.hex()}"
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred formatting keys\n\n{error}'
 | 
					                "message": f"An error occurred formatting keys\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
           wv_cdm.close(wv_session_id)
 | 
					            wv_cdm.close(wv_session_id)
 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred closing session\n\n{error}'
 | 
					                "message": f"An error occurred closing session\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return {
 | 
					            return {"status": "success", "message": returned_keys}
 | 
				
			||||||
                'status': 'success',
 | 
					 | 
				
			||||||
                'message': returned_keys
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        except Exception as error:
 | 
					        except Exception as error:
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                'status': 'error',
 | 
					                "status": "error",
 | 
				
			||||||
                'message': f'An error occurred getting returned_keys\n\n{error}'
 | 
					                "message": f"An error occurred getting returned_keys\n\n{error}",
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -3,66 +3,86 @@ import yaml
 | 
				
			|||||||
import requests
 | 
					import requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
def check_for_wvd_cdm():
 | 
					def check_for_wvd_cdm():
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					    with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
        config = yaml.safe_load(file)
 | 
					        config = yaml.safe_load(file)
 | 
				
			||||||
    if config['default_wv_cdm'] == '':
 | 
					    if config["default_wv_cdm"] == "":
 | 
				
			||||||
        answer = ' '
 | 
					        answer = " "
 | 
				
			||||||
        while answer[0].upper() != 'Y' and answer[0].upper() != 'N':
 | 
					        while answer[0].upper() != "Y" and answer[0].upper() != "N":
 | 
				
			||||||
            answer = input('No default Widevine CDM specified, would you like to download one from The CDM Project? (Y)es/(N)o: ')
 | 
					            answer = input(
 | 
				
			||||||
        if answer[0].upper() == 'Y':
 | 
					                "No default Widevine CDM specified, would you like to download one from The CDM Project? (Y)es/(N)o: "
 | 
				
			||||||
            response = requests.get(url='https://cdm-project.com/CDRM-Team/CDMs/raw/branch/main/Widevine/L3/public.wvd')
 | 
					            )
 | 
				
			||||||
 | 
					        if answer[0].upper() == "Y":
 | 
				
			||||||
 | 
					            response = requests.get(
 | 
				
			||||||
 | 
					                url="https://cdm-project.com/CDRM-Team/CDMs/raw/branch/main/Widevine/L3/public.wvd"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            if response.status_code == 200:
 | 
					            if response.status_code == 200:
 | 
				
			||||||
                with open(f'{os.getcwd()}/configs/CDMs/WV/public.wvd', 'wb') as file:
 | 
					                with open(f"{os.getcwd()}/configs/CDMs/WV/public.wvd", "wb") as file:
 | 
				
			||||||
                    file.write(response.content)
 | 
					                    file.write(response.content)
 | 
				
			||||||
                config['default_wv_cdm'] = 'public'
 | 
					                config["default_wv_cdm"] = "public"
 | 
				
			||||||
                with open(f'{os.getcwd()}/configs/config.yaml', 'w') as file:
 | 
					                with open(f"{os.getcwd()}/configs/config.yaml", "w") as file:
 | 
				
			||||||
                    yaml.dump(config, file)
 | 
					                    yaml.dump(config, file)
 | 
				
			||||||
                print("Successfully downloaded Widevine CDM")
 | 
					                print("Successfully downloaded Widevine CDM")
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                exit(f"Download failed, please try again or place a .wvd file in {os.getcwd()}/configs/CDMs/WV and specify the name in {os.getcwd()}/configs/config.yaml")
 | 
					                exit(
 | 
				
			||||||
        if answer[0].upper() == 'N':
 | 
					                    f"Download failed, please try again or place a .wvd file in {os.getcwd()}/configs/CDMs/WV and specify the name in {os.getcwd()}/configs/config.yaml"
 | 
				
			||||||
            exit(f"Place a .wvd file in {os.getcwd()}/configs/CDMs/WV and specify the name in {os.getcwd()}/configs/config.yaml")
 | 
					                )
 | 
				
			||||||
 | 
					        if answer[0].upper() == "N":
 | 
				
			||||||
 | 
					            exit(
 | 
				
			||||||
 | 
					                f"Place a .wvd file in {os.getcwd()}/configs/CDMs/WV and specify the name in {os.getcwd()}/configs/config.yaml"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        base_name = config["default_wv_cdm"]
 | 
					        base_name = config["default_wv_cdm"]
 | 
				
			||||||
        if not base_name.endswith(".wvd"):
 | 
					        if not base_name.endswith(".wvd"):
 | 
				
			||||||
            base_name += ".wvd"
 | 
					            base_name += ".wvd"
 | 
				
			||||||
        if os.path.exists(f'{os.getcwd()}/configs/CDMs/WV/{base_name}'):
 | 
					        if os.path.exists(f"{os.getcwd()}/configs/CDMs/WV/{base_name}"):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            exit(f"Widevine CDM {base_name} does not exist in {os.getcwd()}/configs/CDMs/WV")
 | 
					            exit(
 | 
				
			||||||
 | 
					                f"Widevine CDM {base_name} does not exist in {os.getcwd()}/configs/CDMs/WV"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_prd_cdm():
 | 
					def check_for_prd_cdm():
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					    with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
        config = yaml.safe_load(file)
 | 
					        config = yaml.safe_load(file)
 | 
				
			||||||
    if config['default_pr_cdm'] == '':
 | 
					    if config["default_pr_cdm"] == "":
 | 
				
			||||||
        answer = ' '
 | 
					        answer = " "
 | 
				
			||||||
        while answer[0].upper() != 'Y' and answer[0].upper() != 'N':
 | 
					        while answer[0].upper() != "Y" and answer[0].upper() != "N":
 | 
				
			||||||
            answer = input('No default PlayReady CDM specified, would you like to download one from The CDM Project? (Y)es/(N)o: ')
 | 
					            answer = input(
 | 
				
			||||||
        if answer[0].upper() == 'Y':
 | 
					                "No default PlayReady CDM specified, would you like to download one from The CDM Project? (Y)es/(N)o: "
 | 
				
			||||||
            response = requests.get(url='https://cdm-project.com/CDRM-Team/CDMs/raw/branch/main/Playready/SL2000/public.prd')
 | 
					            )
 | 
				
			||||||
 | 
					        if answer[0].upper() == "Y":
 | 
				
			||||||
 | 
					            response = requests.get(
 | 
				
			||||||
 | 
					                url="https://cdm-project.com/CDRM-Team/CDMs/raw/branch/main/Playready/SL2000/public.prd"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            if response.status_code == 200:
 | 
					            if response.status_code == 200:
 | 
				
			||||||
                with open(f'{os.getcwd()}/configs/CDMs/PR/public.prd', 'wb') as file:
 | 
					                with open(f"{os.getcwd()}/configs/CDMs/PR/public.prd", "wb") as file:
 | 
				
			||||||
                    file.write(response.content)
 | 
					                    file.write(response.content)
 | 
				
			||||||
                config['default_pr_cdm'] = 'public'
 | 
					                config["default_pr_cdm"] = "public"
 | 
				
			||||||
                with open(f'{os.getcwd()}/configs/config.yaml', 'w') as file:
 | 
					                with open(f"{os.getcwd()}/configs/config.yaml", "w") as file:
 | 
				
			||||||
                    yaml.dump(config, file)
 | 
					                    yaml.dump(config, file)
 | 
				
			||||||
                print("Successfully downloaded PlayReady CDM")
 | 
					                print("Successfully downloaded PlayReady CDM")
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                exit(f"Download failed, please try again or place a .prd file in {os.getcwd()}/configs/CDMs/PR and specify the name in {os.getcwd()}/configs/config.yaml")
 | 
					                exit(
 | 
				
			||||||
        if answer[0].upper() == 'N':
 | 
					                    f"Download failed, please try again or place a .prd file in {os.getcwd()}/configs/CDMs/PR and specify the name in {os.getcwd()}/configs/config.yaml"
 | 
				
			||||||
            exit(f"Place a .prd file in {os.getcwd()}/configs/CDMs/PR and specify the name in {os.getcwd()}/configs/config.yaml")
 | 
					                )
 | 
				
			||||||
 | 
					        if answer[0].upper() == "N":
 | 
				
			||||||
 | 
					            exit(
 | 
				
			||||||
 | 
					                f"Place a .prd file in {os.getcwd()}/configs/CDMs/PR and specify the name in {os.getcwd()}/configs/config.yaml"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        base_name = config["default_pr_cdm"]
 | 
					        base_name = config["default_pr_cdm"]
 | 
				
			||||||
        if not base_name.endswith(".prd"):
 | 
					        if not base_name.endswith(".prd"):
 | 
				
			||||||
            base_name += ".prd"
 | 
					            base_name += ".prd"
 | 
				
			||||||
        if os.path.exists(f'{os.getcwd()}/configs/CDMs/PR/{base_name}'):
 | 
					        if os.path.exists(f"{os.getcwd()}/configs/CDMs/PR/{base_name}"):
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            exit(f"PlayReady CDM {base_name} does not exist in {os.getcwd()}/configs/CDMs/WV")
 | 
					            exit(
 | 
				
			||||||
 | 
					                f"PlayReady CDM {base_name} does not exist in {os.getcwd()}/configs/CDMs/WV"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_cdms():
 | 
					def check_for_cdms():
 | 
				
			||||||
    check_for_wvd_cdm()
 | 
					    check_for_wvd_cdm()
 | 
				
			||||||
    check_for_prd_cdm()
 | 
					    check_for_prd_cdm()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,8 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_config_file():
 | 
					def check_for_config_file():
 | 
				
			||||||
    if os.path.exists(f'{os.getcwd()}/configs/config.yaml'):
 | 
					    if os.path.exists(f"{os.getcwd()}/configs/config.yaml"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        default_config = """\
 | 
					        default_config = """\
 | 
				
			||||||
@ -21,6 +22,6 @@ remote_cdm_secret: ''
 | 
				
			|||||||
#  port: ''
 | 
					#  port: ''
 | 
				
			||||||
#  database: ''
 | 
					#  database: ''
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'w') as f:
 | 
					    with open(f"{os.getcwd()}/configs/config.yaml", "w") as f:
 | 
				
			||||||
        f.write(default_config)
 | 
					        f.write(default_config)
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
				
			|||||||
@ -1,37 +1,44 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import yaml
 | 
					import yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_sqlite_database():
 | 
					def check_for_sqlite_database():
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					    with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
        config = yaml.safe_load(file)
 | 
					        config = yaml.safe_load(file)
 | 
				
			||||||
    if os.path.exists(f'{os.getcwd()}/databases/key_cache.db'):
 | 
					    if os.path.exists(f"{os.getcwd()}/databases/key_cache.db"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        if config['database_type'].lower() != 'mariadb':
 | 
					        if config["database_type"].lower() != "mariadb":
 | 
				
			||||||
            from custom_functions.database.cache_to_db_sqlite import create_database
 | 
					            from custom_functions.database.cache_to_db_sqlite import create_database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            create_database()
 | 
					            create_database()
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_user_database():
 | 
					def check_for_user_database():
 | 
				
			||||||
    if os.path.exists(f'{os.getcwd()}/databases/users.db'):
 | 
					    if os.path.exists(f"{os.getcwd()}/databases/users.db"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        from custom_functions.database.user_db import create_user_database
 | 
					        from custom_functions.database.user_db import create_user_database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        create_user_database()
 | 
					        create_user_database()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_mariadb_database():
 | 
					def check_for_mariadb_database():
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					    with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
        config = yaml.safe_load(file)
 | 
					        config = yaml.safe_load(file)
 | 
				
			||||||
    if config['database_type'].lower() == 'mariadb':
 | 
					    if config["database_type"].lower() == "mariadb":
 | 
				
			||||||
        from custom_functions.database.cache_to_db_mariadb import create_database
 | 
					        from custom_functions.database.cache_to_db_mariadb import create_database
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        create_database()
 | 
					        create_database()
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_sql_database():
 | 
					def check_for_sql_database():
 | 
				
			||||||
    check_for_sqlite_database()
 | 
					    check_for_sqlite_database()
 | 
				
			||||||
    check_for_mariadb_database()
 | 
					    check_for_mariadb_database()
 | 
				
			||||||
    check_for_user_database()
 | 
					    check_for_user_database()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,44 +1,50 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_config_folder():
 | 
					def check_for_config_folder():
 | 
				
			||||||
    if os.path.isdir(f'{os.getcwd()}/configs'):
 | 
					    if os.path.isdir(f"{os.getcwd()}/configs"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        os.mkdir(f'{os.getcwd()}/configs')
 | 
					        os.mkdir(f"{os.getcwd()}/configs")
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_database_folder():
 | 
					def check_for_database_folder():
 | 
				
			||||||
    if os.path.isdir(f'{os.getcwd()}/databases'):
 | 
					    if os.path.isdir(f"{os.getcwd()}/databases"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        os.mkdir(f'{os.getcwd()}/databases')
 | 
					        os.mkdir(f"{os.getcwd()}/databases")
 | 
				
			||||||
        os.mkdir(f'{os.getcwd()}/databases/sql')
 | 
					        os.mkdir(f"{os.getcwd()}/databases/sql")
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_cdm_folder():
 | 
					def check_for_cdm_folder():
 | 
				
			||||||
    if os.path.isdir(f'{os.getcwd()}/configs/CDMs'):
 | 
					    if os.path.isdir(f"{os.getcwd()}/configs/CDMs"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        os.mkdir(f'{os.getcwd()}/configs/CDMs')
 | 
					        os.mkdir(f"{os.getcwd()}/configs/CDMs")
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_wv_cdm_folder():
 | 
					def check_for_wv_cdm_folder():
 | 
				
			||||||
    if os.path.isdir(f'{os.getcwd()}/configs/CDMs/WV'):
 | 
					    if os.path.isdir(f"{os.getcwd()}/configs/CDMs/WV"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        os.mkdir(f'{os.getcwd()}/configs/CDMs/WV')
 | 
					        os.mkdir(f"{os.getcwd()}/configs/CDMs/WV")
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_for_cdm_pr_folder():
 | 
					def check_for_cdm_pr_folder():
 | 
				
			||||||
    if os.path.isdir(f'{os.getcwd()}/configs/CDMs/PR'):
 | 
					    if os.path.isdir(f"{os.getcwd()}/configs/CDMs/PR"):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        os.mkdir(f'{os.getcwd()}/configs/CDMs/PR')
 | 
					        os.mkdir(f"{os.getcwd()}/configs/CDMs/PR")
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def folder_checks():
 | 
					def folder_checks():
 | 
				
			||||||
    check_for_config_folder()
 | 
					    check_for_config_folder()
 | 
				
			||||||
    check_for_database_folder()
 | 
					    check_for_database_folder()
 | 
				
			||||||
    check_for_cdm_folder()
 | 
					    check_for_cdm_folder()
 | 
				
			||||||
    check_for_wv_cdm_folder()
 | 
					    check_for_wv_cdm_folder()
 | 
				
			||||||
    check_for_cdm_pr_folder()
 | 
					    check_for_cdm_pr_folder()
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,10 @@ from custom_functions.prechecks.config_file_checks import check_for_config_file
 | 
				
			|||||||
from custom_functions.prechecks.database_checks import check_for_sql_database
 | 
					from custom_functions.prechecks.database_checks import check_for_sql_database
 | 
				
			||||||
from custom_functions.prechecks.cdm_checks import check_for_cdms
 | 
					from custom_functions.prechecks.cdm_checks import check_for_cdms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run_precheck():
 | 
					def run_precheck():
 | 
				
			||||||
    folder_checks()
 | 
					    folder_checks()
 | 
				
			||||||
    check_for_config_file()
 | 
					    check_for_config_file()
 | 
				
			||||||
    check_for_cdms()
 | 
					    check_for_cdms()
 | 
				
			||||||
    check_for_sql_database()
 | 
					    check_for_sql_database()
 | 
				
			||||||
    return
 | 
					    return
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ import os
 | 
				
			|||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
import venv
 | 
					import venv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def version_check():
 | 
					def version_check():
 | 
				
			||||||
    major_version = sys.version_info.major
 | 
					    major_version = sys.version_info.major
 | 
				
			||||||
    minor_version = sys.version_info.minor
 | 
					    minor_version = sys.version_info.minor
 | 
				
			||||||
@ -15,20 +16,29 @@ def version_check():
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        exit("Python 2 detected, Python version 3.12 or higher is required")
 | 
					        exit("Python 2 detected, Python version 3.12 or higher is required")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def pip_check():
 | 
					def pip_check():
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        import pip
 | 
					        import pip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    except ImportError:
 | 
					    except ImportError:
 | 
				
			||||||
        exit("Pip is not installed")
 | 
					        exit("Pip is not installed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def venv_check():
 | 
					def venv_check():
 | 
				
			||||||
    # Check if we're already inside a virtual environment
 | 
					    # Check if we're already inside a virtual environment
 | 
				
			||||||
    if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
 | 
					    if hasattr(sys, "real_prefix") or (
 | 
				
			||||||
 | 
					        hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    venv_path = os.path.join(os.getcwd(), 'cdrm-venv')
 | 
					    venv_path = os.path.join(os.getcwd(), "cdrm-venv")
 | 
				
			||||||
    venv_python = os.path.join(venv_path, 'bin', 'python') if os.name != 'nt' else os.path.join(venv_path, 'Scripts', 'python.exe')
 | 
					    venv_python = (
 | 
				
			||||||
 | 
					        os.path.join(venv_path, "bin", "python")
 | 
				
			||||||
 | 
					        if os.name != "nt"
 | 
				
			||||||
 | 
					        else os.path.join(venv_path, "Scripts", "python.exe")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # If venv already exists, restart script using its Python
 | 
					    # If venv already exists, restart script using its Python
 | 
				
			||||||
    if os.path.exists(venv_path):
 | 
					    if os.path.exists(venv_path):
 | 
				
			||||||
@ -36,14 +46,14 @@ def venv_check():
 | 
				
			|||||||
        sys.exit()
 | 
					        sys.exit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Ask user for permission to create a virtual environment
 | 
					    # Ask user for permission to create a virtual environment
 | 
				
			||||||
    answer = ''
 | 
					    answer = ""
 | 
				
			||||||
    while not answer or answer[0].upper() not in {'Y', 'N'}:
 | 
					    while not answer or answer[0].upper() not in {"Y", "N"}:
 | 
				
			||||||
        answer = input(
 | 
					        answer = input(
 | 
				
			||||||
            'Program is not running from a venv. To maintain compatibility and dependencies, this program must be run from one.\n'
 | 
					            "Program is not running from a venv. To maintain compatibility and dependencies, this program must be run from one.\n"
 | 
				
			||||||
            'Would you like me to create one for you? (Y/N): '
 | 
					            "Would you like me to create one for you? (Y/N): "
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if answer[0].upper() == 'Y':
 | 
					    if answer[0].upper() == "Y":
 | 
				
			||||||
        print("Creating virtual environment...")
 | 
					        print("Creating virtual environment...")
 | 
				
			||||||
        venv.create(venv_path, with_pip=True)
 | 
					        venv.create(venv_path, with_pip=True)
 | 
				
			||||||
        subprocess.call([venv_python] + sys.argv)
 | 
					        subprocess.call([venv_python] + sys.argv)
 | 
				
			||||||
@ -61,25 +71,33 @@ def requirements_check():
 | 
				
			|||||||
        import flask_cors
 | 
					        import flask_cors
 | 
				
			||||||
        import yaml
 | 
					        import yaml
 | 
				
			||||||
        import mysql.connector
 | 
					        import mysql.connector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    except ImportError:
 | 
					    except ImportError:
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            user_input = input("Missing packages. Do you want to install them? (Y/N): ").strip().upper()
 | 
					            user_input = (
 | 
				
			||||||
            if user_input == 'Y':
 | 
					                input("Missing packages. Do you want to install them? (Y/N): ")
 | 
				
			||||||
 | 
					                .strip()
 | 
				
			||||||
 | 
					                .upper()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if user_input == "Y":
 | 
				
			||||||
                print("Installing packages from requirements.txt...")
 | 
					                print("Installing packages from requirements.txt...")
 | 
				
			||||||
                subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
 | 
					                subprocess.check_call(
 | 
				
			||||||
 | 
					                    [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"]
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                print("Installation complete.")
 | 
					                print("Installation complete.")
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
            elif user_input == 'N':
 | 
					            elif user_input == "N":
 | 
				
			||||||
                print("Dependencies required, please install them and run again.")
 | 
					                print("Dependencies required, please install them and run again.")
 | 
				
			||||||
                sys.exit()
 | 
					                sys.exit()
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                print("Invalid input. Please enter 'Y' to install or 'N' to exit.")
 | 
					                print("Invalid input. Please enter 'Y' to install or 'N' to exit.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run_python_checks():
 | 
					def run_python_checks():
 | 
				
			||||||
    if getattr(sys, 'frozen', False):  # Check if running from PyInstaller
 | 
					    if getattr(sys, "frozen", False):  # Check if running from PyInstaller
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
    version_check()
 | 
					    version_check()
 | 
				
			||||||
    pip_check()
 | 
					    pip_check()
 | 
				
			||||||
    venv_check()
 | 
					    venv_check()
 | 
				
			||||||
    requirements_check()
 | 
					    requirements_check()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,17 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import glob
 | 
					import glob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def user_allowed_to_use_device(device, username):
 | 
					def user_allowed_to_use_device(device, username):
 | 
				
			||||||
    base_path = os.path.join(os.getcwd(), 'configs', 'CDMs', username)
 | 
					    base_path = os.path.join(os.getcwd(), "configs", "CDMs", username)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Get filenames with extensions
 | 
					    # Get filenames with extensions
 | 
				
			||||||
    pr_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'PR', '*.prd'))]
 | 
					    pr_files = [
 | 
				
			||||||
    wv_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'WV', '*.wvd'))]
 | 
					        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
 | 
					    # Combine all filenames
 | 
				
			||||||
    all_files = pr_files + wv_files
 | 
					    all_files = pr_files + wv_files
 | 
				
			||||||
@ -14,4 +19,4 @@ def user_allowed_to_use_device(device, username):
 | 
				
			|||||||
    # Check if filename matches directly or by adding extensions
 | 
					    # Check if filename matches directly or by adding extensions
 | 
				
			||||||
    possible_names = {device, f"{device}.prd", f"{device}.wvd"}
 | 
					    possible_names = {device, f"{device}.prd", f"{device}.wvd"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return any(name in all_files for name in possible_names)
 | 
					    return any(name in all_files for name in possible_names)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								main.py
									
									
									
									
									
								
							@ -1,6 +1,8 @@
 | 
				
			|||||||
from custom_functions.prechecks.python_checks import run_python_checks
 | 
					from custom_functions.prechecks.python_checks import run_python_checks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_python_checks()
 | 
					run_python_checks()
 | 
				
			||||||
from custom_functions.prechecks.precheck import run_precheck
 | 
					from custom_functions.prechecks.precheck import run_precheck
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run_precheck()
 | 
					run_precheck()
 | 
				
			||||||
from flask import Flask
 | 
					from flask import Flask
 | 
				
			||||||
from flask_cors import CORS
 | 
					from flask_cors import CORS
 | 
				
			||||||
@ -15,10 +17,11 @@ from routes.login import login_bp
 | 
				
			|||||||
from routes.user_changes import user_change_bp
 | 
					from routes.user_changes import user_change_bp
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import yaml
 | 
					import yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app = Flask(__name__)
 | 
					app = Flask(__name__)
 | 
				
			||||||
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
    config = yaml.safe_load(file)
 | 
					    config = yaml.safe_load(file)
 | 
				
			||||||
app.secret_key = config['secret_key_flask']
 | 
					app.secret_key = config["secret_key_flask"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CORS(app)
 | 
					CORS(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -33,5 +36,5 @@ app.register_blueprint(remotecdm_wv_bp)
 | 
				
			|||||||
app.register_blueprint(remotecdm_pr_bp)
 | 
					app.register_blueprint(remotecdm_pr_bp)
 | 
				
			||||||
app.register_blueprint(user_change_bp)
 | 
					app.register_blueprint(user_change_bp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    app.run(debug=True, host='0.0.0.0')
 | 
					    app.run(debug=True, host="0.0.0.0")
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					[tool.black]
 | 
				
			||||||
 | 
					line-length = 88
 | 
				
			||||||
 | 
					target-version = ['py38']
 | 
				
			||||||
 | 
					include = '\.pyi?$'
 | 
				
			||||||
 | 
					exclude = '''
 | 
				
			||||||
 | 
					/(
 | 
				
			||||||
 | 
					    \.eggs
 | 
				
			||||||
 | 
					  | \.git
 | 
				
			||||||
 | 
					  | \.hg
 | 
				
			||||||
 | 
					  | \.mypy_cache
 | 
				
			||||||
 | 
					  | \.tox
 | 
				
			||||||
 | 
					  | \.venv
 | 
				
			||||||
 | 
					  | _build
 | 
				
			||||||
 | 
					  | buck-out
 | 
				
			||||||
 | 
					  | build
 | 
				
			||||||
 | 
					  | dist
 | 
				
			||||||
 | 
					  | cdrm-frontend
 | 
				
			||||||
 | 
					)/
 | 
				
			||||||
 | 
					'''
 | 
				
			||||||
@ -7,3 +7,4 @@ protobuf~=4.25.6
 | 
				
			|||||||
PyYAML
 | 
					PyYAML
 | 
				
			||||||
mysql-connector-python
 | 
					mysql-connector-python
 | 
				
			||||||
bcrypt
 | 
					bcrypt
 | 
				
			||||||
 | 
					black
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										300
									
								
								routes/api.py
									
									
									
									
									
								
							
							
						
						
									
										300
									
								
								routes/api.py
									
									
									
									
									
								
							@ -13,101 +13,126 @@ import tempfile
 | 
				
			|||||||
import time
 | 
					import time
 | 
				
			||||||
from configs.icon_links import data as icon_data
 | 
					from configs.icon_links import data as icon_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api_bp = Blueprint('api', __name__)
 | 
					api_bp = Blueprint("api", __name__)
 | 
				
			||||||
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
    config = yaml.safe_load(file)
 | 
					    config = yaml.safe_load(file)
 | 
				
			||||||
if config['database_type'].lower() != 'mariadb':
 | 
					if config["database_type"].lower() != "mariadb":
 | 
				
			||||||
    from custom_functions.database.cache_to_db_sqlite import search_by_pssh_or_kid, cache_to_db, \
 | 
					    from custom_functions.database.cache_to_db_sqlite import (
 | 
				
			||||||
        get_key_by_kid_and_service, get_unique_services, get_kid_key_dict, key_count
 | 
					        search_by_pssh_or_kid,
 | 
				
			||||||
elif config['database_type'].lower() == 'mariadb':
 | 
					        cache_to_db,
 | 
				
			||||||
    from custom_functions.database.cache_to_db_mariadb import search_by_pssh_or_kid, cache_to_db, \
 | 
					        get_key_by_kid_and_service,
 | 
				
			||||||
        get_key_by_kid_and_service, get_unique_services, get_kid_key_dict, key_count
 | 
					        get_unique_services,
 | 
				
			||||||
 | 
					        get_kid_key_dict,
 | 
				
			||||||
 | 
					        key_count,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					elif config["database_type"].lower() == "mariadb":
 | 
				
			||||||
 | 
					    from custom_functions.database.cache_to_db_mariadb import (
 | 
				
			||||||
 | 
					        search_by_pssh_or_kid,
 | 
				
			||||||
 | 
					        cache_to_db,
 | 
				
			||||||
 | 
					        get_key_by_kid_and_service,
 | 
				
			||||||
 | 
					        get_unique_services,
 | 
				
			||||||
 | 
					        get_kid_key_dict,
 | 
				
			||||||
 | 
					        key_count,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_db_config():
 | 
					def get_db_config():
 | 
				
			||||||
    # Configure your MariaDB connection
 | 
					    # Configure your MariaDB connection
 | 
				
			||||||
    with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					    with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
        config = yaml.safe_load(file)
 | 
					        config = yaml.safe_load(file)
 | 
				
			||||||
    db_config = {
 | 
					    db_config = {
 | 
				
			||||||
        'host': f'{config["mariadb"]["host"]}',
 | 
					        "host": f'{config["mariadb"]["host"]}',
 | 
				
			||||||
        'user': f'{config["mariadb"]["user"]}',
 | 
					        "user": f'{config["mariadb"]["user"]}',
 | 
				
			||||||
        'password': f'{config["mariadb"]["password"]}',
 | 
					        "password": f'{config["mariadb"]["password"]}',
 | 
				
			||||||
        'database': f'{config["mariadb"]["database"]}'
 | 
					        "database": f'{config["mariadb"]["database"]}',
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return db_config
 | 
					    return db_config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache/search', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/cache/search", methods=["POST"])
 | 
				
			||||||
def get_data():
 | 
					def get_data():
 | 
				
			||||||
    search_argument = json.loads(request.data)['input']
 | 
					    search_argument = json.loads(request.data)["input"]
 | 
				
			||||||
    results = search_by_pssh_or_kid(search_filter=search_argument)
 | 
					    results = search_by_pssh_or_kid(search_filter=search_argument)
 | 
				
			||||||
    return jsonify(results)
 | 
					    return jsonify(results)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache/<service>/<kid>', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/cache/<service>/<kid>", methods=["GET"])
 | 
				
			||||||
def get_single_key_service(service, kid):
 | 
					def get_single_key_service(service, kid):
 | 
				
			||||||
    result = get_key_by_kid_and_service(kid=kid, service=service)
 | 
					    result = get_key_by_kid_and_service(kid=kid, service=service)
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify(
 | 
				
			||||||
        'code': 0,
 | 
					        {
 | 
				
			||||||
        'content_key': result,
 | 
					            "code": 0,
 | 
				
			||||||
    })
 | 
					            "content_key": result,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache/<service>', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/cache/<service>", methods=["GET"])
 | 
				
			||||||
def get_multiple_key_service(service):
 | 
					def get_multiple_key_service(service):
 | 
				
			||||||
    result = get_kid_key_dict(service_name=service)
 | 
					    result = get_kid_key_dict(service_name=service)
 | 
				
			||||||
    pages = math.ceil(len(result) / 10)
 | 
					    pages = math.ceil(len(result) / 10)
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify({"code": 0, "content_keys": result, "pages": pages})
 | 
				
			||||||
        'code': 0,
 | 
					 | 
				
			||||||
        'content_keys': result,
 | 
					 | 
				
			||||||
        'pages': pages
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache/<service>/<kid>', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/cache/<service>/<kid>", methods=["POST"])
 | 
				
			||||||
def add_single_key_service(service, kid):
 | 
					def add_single_key_service(service, kid):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    content_key = body['content_key']
 | 
					    content_key = body["content_key"]
 | 
				
			||||||
    result = cache_to_db(service=service, kid=kid, key=content_key)
 | 
					    result = cache_to_db(service=service, kid=kid, key=content_key)
 | 
				
			||||||
    if result:
 | 
					    if result:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'code': 0,
 | 
					            {
 | 
				
			||||||
            'updated': True,
 | 
					                "code": 0,
 | 
				
			||||||
        })
 | 
					                "updated": True,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    elif result is False:
 | 
					    elif result is False:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'code': 0,
 | 
					            {
 | 
				
			||||||
            'updated': True,
 | 
					                "code": 0,
 | 
				
			||||||
        })
 | 
					                "updated": True,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache/<service>', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/cache/<service>", methods=["POST"])
 | 
				
			||||||
def add_multiple_key_service(service):
 | 
					def add_multiple_key_service(service):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    keys_added = 0
 | 
					    keys_added = 0
 | 
				
			||||||
    keys_updated = 0
 | 
					    keys_updated = 0
 | 
				
			||||||
    for kid, key in body['content_keys'].items():
 | 
					    for kid, key in body["content_keys"].items():
 | 
				
			||||||
        result = cache_to_db(service=service, kid=kid, key=key)
 | 
					        result = cache_to_db(service=service, kid=kid, key=key)
 | 
				
			||||||
        if result is True:
 | 
					        if result is True:
 | 
				
			||||||
            keys_updated += 1
 | 
					            keys_updated += 1
 | 
				
			||||||
        elif result is False:
 | 
					        elif result is False:
 | 
				
			||||||
            keys_added += 1
 | 
					            keys_added += 1
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify(
 | 
				
			||||||
        'code': 0,
 | 
					        {
 | 
				
			||||||
        'added': str(keys_added),
 | 
					            "code": 0,
 | 
				
			||||||
        'updated': str(keys_updated),
 | 
					            "added": str(keys_added),
 | 
				
			||||||
    })
 | 
					            "updated": str(keys_updated),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/cache", methods=["POST"])
 | 
				
			||||||
def unique_service():
 | 
					def unique_service():
 | 
				
			||||||
    services = get_unique_services()
 | 
					    services = get_unique_services()
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify(
 | 
				
			||||||
        'code': 0,
 | 
					        {
 | 
				
			||||||
        'service_list': services,
 | 
					            "code": 0,
 | 
				
			||||||
    })
 | 
					            "service_list": services,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache/download', methods=['GET'])
 | 
					@api_bp.route("/api/cache/download", methods=["GET"])
 | 
				
			||||||
def download_database():
 | 
					def download_database():
 | 
				
			||||||
    if config['database_type'].lower() != 'mariadb':
 | 
					    if config["database_type"].lower() != "mariadb":
 | 
				
			||||||
        original_database_path = f'{os.getcwd()}/databases/sql/key_cache.db'
 | 
					        original_database_path = f"{os.getcwd()}/databases/sql/key_cache.db"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Make a copy of the original database (without locking the original)
 | 
					        # Make a copy of the original database (without locking the original)
 | 
				
			||||||
        modified_database_path = f'{os.getcwd()}/databases/sql/key_cache_modified.db'
 | 
					        modified_database_path = f"{os.getcwd()}/databases/sql/key_cache_modified.db"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Using shutil.copy2 to preserve metadata (timestamps, etc.)
 | 
					        # Using shutil.copy2 to preserve metadata (timestamps, etc.)
 | 
				
			||||||
        shutil.copy2(original_database_path, modified_database_path)
 | 
					        shutil.copy2(original_database_path, modified_database_path)
 | 
				
			||||||
@ -117,34 +142,40 @@ def download_database():
 | 
				
			|||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Update all rows to remove Headers and Cookies (set them to NULL or empty strings)
 | 
					            # Update all rows to remove Headers and Cookies (set them to NULL or empty strings)
 | 
				
			||||||
            cursor.execute('''
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
            UPDATE licenses
 | 
					            UPDATE licenses
 | 
				
			||||||
            SET Headers = NULL,
 | 
					            SET Headers = NULL,
 | 
				
			||||||
                Cookies = NULL
 | 
					                Cookies = NULL
 | 
				
			||||||
            ''')
 | 
					            """
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # No need for explicit commit, it's done automatically with the 'with' block
 | 
					            # No need for explicit commit, it's done automatically with the 'with' block
 | 
				
			||||||
            # The connection will automatically be committed and closed when the block ends
 | 
					            # The connection will automatically be committed and closed when the block ends
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Send the modified database as an attachment
 | 
					        # Send the modified database as an attachment
 | 
				
			||||||
        return send_file(modified_database_path, as_attachment=True, download_name='key_cache.db')
 | 
					        return send_file(
 | 
				
			||||||
    if config['database_type'].lower() == 'mariadb':
 | 
					            modified_database_path, as_attachment=True, download_name="key_cache.db"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    if config["database_type"].lower() == "mariadb":
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            # Connect to MariaDB
 | 
					            # Connect to MariaDB
 | 
				
			||||||
            conn = mysql.connector.connect(**get_db_config())
 | 
					            conn = mysql.connector.connect(**get_db_config())
 | 
				
			||||||
            cursor = conn.cursor()
 | 
					            cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Update sensitive data (this updates the live DB, you may want to duplicate rows instead)
 | 
					            # Update sensitive data (this updates the live DB, you may want to duplicate rows instead)
 | 
				
			||||||
            cursor.execute('''
 | 
					            cursor.execute(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
            UPDATE licenses
 | 
					            UPDATE licenses
 | 
				
			||||||
            SET Headers = NULL,
 | 
					            SET Headers = NULL,
 | 
				
			||||||
                Cookies = NULL
 | 
					                Cookies = NULL
 | 
				
			||||||
            ''')
 | 
					            """
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            conn.commit()
 | 
					            conn.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Now export the table
 | 
					            # Now export the table
 | 
				
			||||||
            cursor.execute('SELECT * FROM licenses')
 | 
					            cursor.execute("SELECT * FROM licenses")
 | 
				
			||||||
            rows = cursor.fetchall()
 | 
					            rows = cursor.fetchall()
 | 
				
			||||||
            column_names = [desc[0] for desc in cursor.description]
 | 
					            column_names = [desc[0] for desc in cursor.description]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -152,116 +183,135 @@ def download_database():
 | 
				
			|||||||
            output = StringIO()
 | 
					            output = StringIO()
 | 
				
			||||||
            output.write(f"-- Dump of `licenses` table\n")
 | 
					            output.write(f"-- Dump of `licenses` table\n")
 | 
				
			||||||
            for row in rows:
 | 
					            for row in rows:
 | 
				
			||||||
                values = ', '.join(f"'{str(v).replace('\'', '\\\'')}'" if v is not None else 'NULL' for v in row)
 | 
					                values = ", ".join(
 | 
				
			||||||
                output.write(f"INSERT INTO licenses ({', '.join(column_names)}) VALUES ({values});\n")
 | 
					                    f"'{str(v).replace('\'', '\\\'')}'" if v is not None else "NULL"
 | 
				
			||||||
 | 
					                    for v in row
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                output.write(
 | 
				
			||||||
 | 
					                    f"INSERT INTO licenses ({', '.join(column_names)}) VALUES ({values});\n"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Write to a temp file for download
 | 
					            # Write to a temp file for download
 | 
				
			||||||
            temp_dir = tempfile.gettempdir()
 | 
					            temp_dir = tempfile.gettempdir()
 | 
				
			||||||
            temp_path = os.path.join(temp_dir, 'key_cache.sql')
 | 
					            temp_path = os.path.join(temp_dir, "key_cache.sql")
 | 
				
			||||||
            with open(temp_path, 'w', encoding='utf-8') as f:
 | 
					            with open(temp_path, "w", encoding="utf-8") as f:
 | 
				
			||||||
                f.write(output.getvalue())
 | 
					                f.write(output.getvalue())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return send_file(temp_path, as_attachment=True, download_name='licenses_dump.sql')
 | 
					            return send_file(
 | 
				
			||||||
 | 
					                temp_path, as_attachment=True, download_name="licenses_dump.sql"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        except mysql.connector.Error as err:
 | 
					        except mysql.connector.Error as err:
 | 
				
			||||||
            return {"error": str(err)}, 500
 | 
					            return {"error": str(err)}, 500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_keycount_cache = {
 | 
					 | 
				
			||||||
    'count': None,
 | 
					 | 
				
			||||||
    'timestamp': 0
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/cache/keycount', methods=['GET'])
 | 
					_keycount_cache = {"count": None, "timestamp": 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/cache/keycount", methods=["GET"])
 | 
				
			||||||
def get_count():
 | 
					def get_count():
 | 
				
			||||||
    now = time.time()
 | 
					    now = time.time()
 | 
				
			||||||
    if now - _keycount_cache['timestamp'] > 10 or _keycount_cache['count'] is None:
 | 
					    if now - _keycount_cache["timestamp"] > 10 or _keycount_cache["count"] is None:
 | 
				
			||||||
        _keycount_cache['count'] = key_count()
 | 
					        _keycount_cache["count"] = key_count()
 | 
				
			||||||
        _keycount_cache['timestamp'] = now
 | 
					        _keycount_cache["timestamp"] = now
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify({"count": _keycount_cache["count"]})
 | 
				
			||||||
        'count': _keycount_cache['count']
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/decrypt', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/decrypt", methods=["POST"])
 | 
				
			||||||
def decrypt_data():
 | 
					def decrypt_data():
 | 
				
			||||||
    api_request_data = json.loads(request.data)
 | 
					    api_request_data = json.loads(request.data)
 | 
				
			||||||
    if 'pssh' in api_request_data:
 | 
					    if "pssh" in api_request_data:
 | 
				
			||||||
        if api_request_data['pssh'] == '':
 | 
					        if api_request_data["pssh"] == "":
 | 
				
			||||||
            api_request_pssh = None
 | 
					            api_request_pssh = None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            api_request_pssh = api_request_data['pssh']
 | 
					            api_request_pssh = api_request_data["pssh"]
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        api_request_pssh = None
 | 
					        api_request_pssh = None
 | 
				
			||||||
    if 'licurl' in api_request_data:
 | 
					    if "licurl" in api_request_data:
 | 
				
			||||||
        if api_request_data['licurl'] == '':
 | 
					        if api_request_data["licurl"] == "":
 | 
				
			||||||
            api_request_licurl = None
 | 
					            api_request_licurl = None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            api_request_licurl = api_request_data['licurl']
 | 
					            api_request_licurl = api_request_data["licurl"]
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        api_request_licurl = None
 | 
					        api_request_licurl = None
 | 
				
			||||||
    if 'proxy' in api_request_data:
 | 
					    if "proxy" in api_request_data:
 | 
				
			||||||
        if api_request_data['proxy'] == '':
 | 
					        if api_request_data["proxy"] == "":
 | 
				
			||||||
            api_request_proxy = None
 | 
					            api_request_proxy = None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            api_request_proxy = api_request_data['proxy']
 | 
					            api_request_proxy = api_request_data["proxy"]
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        api_request_proxy = None
 | 
					        api_request_proxy = None
 | 
				
			||||||
    if 'headers' in api_request_data:
 | 
					    if "headers" in api_request_data:
 | 
				
			||||||
        if api_request_data['headers'] == '':
 | 
					        if api_request_data["headers"] == "":
 | 
				
			||||||
            api_request_headers = None
 | 
					            api_request_headers = None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            api_request_headers = api_request_data['headers']
 | 
					            api_request_headers = api_request_data["headers"]
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        api_request_headers = None
 | 
					        api_request_headers = None
 | 
				
			||||||
    if 'cookies' in api_request_data:
 | 
					    if "cookies" in api_request_data:
 | 
				
			||||||
        if api_request_data['cookies'] == '':
 | 
					        if api_request_data["cookies"] == "":
 | 
				
			||||||
            api_request_cookies = None
 | 
					            api_request_cookies = None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            api_request_cookies = api_request_data['cookies']
 | 
					            api_request_cookies = api_request_data["cookies"]
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        api_request_cookies = None
 | 
					        api_request_cookies = None
 | 
				
			||||||
    if 'data' in api_request_data:
 | 
					    if "data" in api_request_data:
 | 
				
			||||||
        if api_request_data['data'] == '':
 | 
					        if api_request_data["data"] == "":
 | 
				
			||||||
            api_request_data_func = None
 | 
					            api_request_data_func = None
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            api_request_data_func = 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:
 | 
					    else:
 | 
				
			||||||
        api_request_device = 'public'
 | 
					        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_device = "public"
 | 
				
			||||||
    username = None
 | 
					    username = None
 | 
				
			||||||
    if api_request_device != 'public':
 | 
					    if api_request_device != "public":
 | 
				
			||||||
        username = session.get('username')
 | 
					        username = session.get("username")
 | 
				
			||||||
        if not username:
 | 
					        if not username:
 | 
				
			||||||
            return jsonify({'message': 'Not logged in, not allowed'}), 400
 | 
					            return jsonify({"message": "Not logged in, not allowed"}), 400
 | 
				
			||||||
        if user_allowed_to_use_device(device=api_request_device, username=username):
 | 
					        if user_allowed_to_use_device(device=api_request_device, username=username):
 | 
				
			||||||
            api_request_device = api_request_device
 | 
					            api_request_device = api_request_device
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return jsonify({'message': f'Not authorized / Not found'}), 403
 | 
					            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)
 | 
					    result = api_decrypt(
 | 
				
			||||||
    if result['status'] == 'success':
 | 
					        pssh=api_request_pssh,
 | 
				
			||||||
        return jsonify({
 | 
					        proxy=api_request_proxy,
 | 
				
			||||||
            'status': 'success',
 | 
					        license_url=api_request_licurl,
 | 
				
			||||||
            'message': result['message']
 | 
					        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", "message": result["message"]})
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"status": "fail", "message": result["message"]})
 | 
				
			||||||
            'status': 'fail',
 | 
					 | 
				
			||||||
            'message': result['message']
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/links', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/links", methods=["GET"])
 | 
				
			||||||
def get_links():
 | 
					def get_links():
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify(
 | 
				
			||||||
        'discord': icon_data['discord'],
 | 
					        {
 | 
				
			||||||
        'telegram': icon_data['telegram'],
 | 
					            "discord": icon_data["discord"],
 | 
				
			||||||
        'gitea': icon_data['gitea'],
 | 
					            "telegram": icon_data["telegram"],
 | 
				
			||||||
    })
 | 
					            "gitea": icon_data["gitea"],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@api_bp.route('/api/extension', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@api_bp.route("/api/extension", methods=["POST"])
 | 
				
			||||||
def verify_extension():
 | 
					def verify_extension():
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify(
 | 
				
			||||||
        'status': True,
 | 
					        {
 | 
				
			||||||
    })
 | 
					            "status": True,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
				
			|||||||
@ -2,36 +2,44 @@ from flask import Blueprint, request, jsonify, session
 | 
				
			|||||||
from custom_functions.database.user_db import verify_user
 | 
					from custom_functions.database.user_db import verify_user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
login_bp = Blueprint(
 | 
					login_bp = Blueprint(
 | 
				
			||||||
    'login_bp',
 | 
					    "login_bp",
 | 
				
			||||||
    __name__,
 | 
					    __name__,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_bp.route('/login', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@login_bp.route("/login", methods=["POST"])
 | 
				
			||||||
def login():
 | 
					def login():
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == "POST":
 | 
				
			||||||
        data = request.get_json()
 | 
					        data = request.get_json()
 | 
				
			||||||
        for required_field in ['username', 'password']:
 | 
					        for required_field in ["username", "password"]:
 | 
				
			||||||
            if required_field not in data:
 | 
					            if required_field not in data:
 | 
				
			||||||
                return jsonify({'error': f'Missing required field: {required_field}'}), 400
 | 
					                return (
 | 
				
			||||||
 | 
					                    jsonify({"error": f"Missing required field: {required_field}"}),
 | 
				
			||||||
 | 
					                    400,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if verify_user(data['username'], data['password']):
 | 
					        if verify_user(data["username"], data["password"]):
 | 
				
			||||||
            session['username'] = data['username'].lower()  # Stored securely in a signed cookie
 | 
					            session["username"] = data[
 | 
				
			||||||
            return jsonify({'message': 'Successfully logged in!'})
 | 
					                "username"
 | 
				
			||||||
 | 
					            ].lower()  # Stored securely in a signed cookie
 | 
				
			||||||
 | 
					            return jsonify({"message": "Successfully logged in!"})
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return jsonify({'error': 'Invalid username or password!'}), 401
 | 
					            return jsonify({"error": "Invalid username or password!"}), 401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_bp.route('/login/status', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@login_bp.route("/login/status", methods=["POST"])
 | 
				
			||||||
def login_status():
 | 
					def login_status():
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        username = session.get('username')
 | 
					        username = session.get("username")
 | 
				
			||||||
        if username:
 | 
					        if username:
 | 
				
			||||||
            return jsonify({'message': 'True'})
 | 
					            return jsonify({"message": "True"})
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return jsonify({'message': 'False'})
 | 
					            return jsonify({"message": "False"})
 | 
				
			||||||
    except:
 | 
					    except:
 | 
				
			||||||
        return jsonify({'message': 'False'})
 | 
					        return jsonify({"message": "False"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_bp.route('/logout', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@login_bp.route("/logout", methods=["POST"])
 | 
				
			||||||
def logout():
 | 
					def logout():
 | 
				
			||||||
    session.pop('username', None)
 | 
					    session.pop("username", None)
 | 
				
			||||||
    return jsonify({'message': 'Successfully logged out!'})
 | 
					    return jsonify({"message": "Successfully logged out!"})
 | 
				
			||||||
 | 
				
			|||||||
@ -3,31 +3,32 @@ import os
 | 
				
			|||||||
from flask import Blueprint, send_from_directory, request, render_template
 | 
					from flask import Blueprint, send_from_directory, request, render_template
 | 
				
			||||||
from configs import index_tags
 | 
					from configs import index_tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if getattr(sys, 'frozen', False):  # Running as a bundled app
 | 
					if getattr(sys, "frozen", False):  # Running as a bundled app
 | 
				
			||||||
    base_path = sys._MEIPASS
 | 
					    base_path = sys._MEIPASS
 | 
				
			||||||
else:  # Running in a normal Python environment
 | 
					else:  # Running in a normal Python environment
 | 
				
			||||||
    base_path = os.path.abspath(".")
 | 
					    base_path = os.path.abspath(".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static_folder = os.path.join(base_path, 'cdrm-frontend', 'dist')
 | 
					static_folder = os.path.join(base_path, "frontend-dist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
react_bp = Blueprint(
 | 
					react_bp = Blueprint(
 | 
				
			||||||
    'react_bp',
 | 
					    "react_bp",
 | 
				
			||||||
    __name__,
 | 
					    __name__,
 | 
				
			||||||
    static_folder=static_folder,
 | 
					    static_folder=static_folder,
 | 
				
			||||||
    static_url_path='/',
 | 
					    static_url_path="/",
 | 
				
			||||||
    template_folder=static_folder
 | 
					    template_folder=static_folder,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@react_bp.route('/', methods=['GET'])
 | 
					
 | 
				
			||||||
@react_bp.route('/<path:path>', methods=["GET"])
 | 
					@react_bp.route("/", methods=["GET"])
 | 
				
			||||||
@react_bp.route('/<path>', methods=["GET"])
 | 
					@react_bp.route("/<path:path>", methods=["GET"])
 | 
				
			||||||
def index(path=''):
 | 
					@react_bp.route("/<path>", methods=["GET"])
 | 
				
			||||||
    if request.method == 'GET':
 | 
					def index(path=""):
 | 
				
			||||||
 | 
					    if request.method == "GET":
 | 
				
			||||||
        file_path = os.path.join(react_bp.static_folder, path)
 | 
					        file_path = os.path.join(react_bp.static_folder, path)
 | 
				
			||||||
        if path != "" and os.path.exists(file_path):
 | 
					        if path != "" and os.path.exists(file_path):
 | 
				
			||||||
            return send_from_directory(react_bp.static_folder, path)
 | 
					            return send_from_directory(react_bp.static_folder, path)
 | 
				
			||||||
        elif path.lower() in ['', 'cache', 'api', 'testplayer', 'account']:
 | 
					        elif path.lower() in ["", "cache", "api", "testplayer", "account"]:
 | 
				
			||||||
            data = index_tags.tags.get(path.lower(), index_tags.tags['index'])
 | 
					            data = index_tags.tags.get(path.lower(), index_tags.tags["index"])
 | 
				
			||||||
            return render_template('index.html', data=data)
 | 
					            return render_template("index.html", data=data)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return send_from_directory(react_bp.static_folder, 'index.html')
 | 
					            return send_from_directory(react_bp.static_folder, "index.html")
 | 
				
			||||||
 | 
				
			|||||||
@ -3,40 +3,44 @@ from flask import Blueprint, request, jsonify
 | 
				
			|||||||
from custom_functions.database.user_db import add_user
 | 
					from custom_functions.database.user_db import add_user
 | 
				
			||||||
import uuid
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
register_bp = Blueprint('register_bp', __name__)
 | 
					register_bp = Blueprint("register_bp", __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
USERNAME_REGEX = re.compile(r'^[A-Za-z0-9_-]+$')
 | 
					USERNAME_REGEX = re.compile(r"^[A-Za-z0-9_-]+$")
 | 
				
			||||||
PASSWORD_REGEX = re.compile(r'^\S+$')
 | 
					PASSWORD_REGEX = re.compile(r"^\S+$")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_bp.route('/register', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@register_bp.route("/register", methods=["POST"])
 | 
				
			||||||
def register():
 | 
					def register():
 | 
				
			||||||
    if request.method != 'POST':
 | 
					    if request.method != "POST":
 | 
				
			||||||
        return jsonify({'error': 'Method not supported'}), 405
 | 
					        return jsonify({"error": "Method not supported"}), 405
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = request.get_json()
 | 
					    data = request.get_json()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Check required fields
 | 
					    # Check required fields
 | 
				
			||||||
    for required_field in ['username', 'password']:
 | 
					    for required_field in ["username", "password"]:
 | 
				
			||||||
        if required_field not in data:
 | 
					        if required_field not in data:
 | 
				
			||||||
            return jsonify({'error': f'Missing required field: {required_field}'}), 400
 | 
					            return jsonify({"error": f"Missing required field: {required_field}"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    username = data['username']
 | 
					    username = data["username"]
 | 
				
			||||||
    password = data['password']
 | 
					    password = data["password"]
 | 
				
			||||||
    api_key = str(uuid.uuid4())
 | 
					    api_key = str(uuid.uuid4())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Validate username and password
 | 
					    # Validate username and password
 | 
				
			||||||
    if not USERNAME_REGEX.fullmatch(username):
 | 
					    if not USERNAME_REGEX.fullmatch(username):
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'error': 'Invalid username. Only letters, numbers, hyphens, and underscores are allowed.'
 | 
					            jsonify(
 | 
				
			||||||
        }), 400
 | 
					                {
 | 
				
			||||||
 | 
					                    "error": "Invalid username. Only letters, numbers, hyphens, and underscores are allowed."
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not PASSWORD_REGEX.fullmatch(password):
 | 
					    if not PASSWORD_REGEX.fullmatch(password):
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"error": "Invalid password. Spaces are not allowed."}), 400
 | 
				
			||||||
            'error': 'Invalid password. Spaces are not allowed.'
 | 
					 | 
				
			||||||
        }), 400
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Attempt to add user
 | 
					    # Attempt to add user
 | 
				
			||||||
    if add_user(username, password, api_key):
 | 
					    if add_user(username, password, api_key):
 | 
				
			||||||
        return jsonify({'message': 'User successfully registered!'}), 201
 | 
					        return jsonify({"message": "User successfully registered!"}), 201
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return jsonify({'error': 'User already exists!'}), 409
 | 
					        return jsonify({"error": "User already exists!"}), 409
 | 
				
			||||||
 | 
				
			|||||||
@ -6,136 +6,195 @@ import yaml
 | 
				
			|||||||
from pyplayready.device import Device as PlayReadyDevice
 | 
					from pyplayready.device import Device as PlayReadyDevice
 | 
				
			||||||
from pyplayready.cdm import Cdm as PlayReadyCDM
 | 
					from pyplayready.cdm import Cdm as PlayReadyCDM
 | 
				
			||||||
from pyplayready import PSSH as PlayReadyPSSH
 | 
					from pyplayready import PSSH as PlayReadyPSSH
 | 
				
			||||||
from pyplayready.exceptions import (InvalidSession, TooManySessions, InvalidLicense, InvalidPssh)
 | 
					from pyplayready.exceptions import (
 | 
				
			||||||
 | 
					    InvalidSession,
 | 
				
			||||||
 | 
					    TooManySessions,
 | 
				
			||||||
 | 
					    InvalidLicense,
 | 
				
			||||||
 | 
					    InvalidPssh,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from custom_functions.database.user_db import fetch_username_by_api_key
 | 
					from custom_functions.database.user_db import fetch_username_by_api_key
 | 
				
			||||||
from custom_functions.decrypt.api_decrypt import is_base64
 | 
					from custom_functions.decrypt.api_decrypt import is_base64
 | 
				
			||||||
from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
 | 
					from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					remotecdm_pr_bp = Blueprint("remotecdm_pr", __name__)
 | 
				
			||||||
 | 
					with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
remotecdm_pr_bp = Blueprint('remotecdm_pr', __name__)
 | 
					 | 
				
			||||||
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					 | 
				
			||||||
    config = yaml.safe_load(file)
 | 
					    config = yaml.safe_load(file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready', methods=['GET', 'HEAD'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_pr_bp.route("/remotecdm/playready", methods=["GET", "HEAD"])
 | 
				
			||||||
def remote_cdm_playready():
 | 
					def remote_cdm_playready():
 | 
				
			||||||
    if request.method == 'GET':
 | 
					    if request.method == "GET":
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"message": "OK"})
 | 
				
			||||||
            'message': 'OK'
 | 
					    if request.method == "HEAD":
 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    if request.method == 'HEAD':
 | 
					 | 
				
			||||||
        response = Response(status=200)
 | 
					        response = Response(status=200)
 | 
				
			||||||
        response.headers['Server'] = 'playready serve'
 | 
					        response.headers["Server"] = "playready serve"
 | 
				
			||||||
        return response
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready/deviceinfo', methods=['GET'])
 | 
					@remotecdm_pr_bp.route("/remotecdm/playready/deviceinfo", methods=["GET"])
 | 
				
			||||||
def remote_cdm_playready_deviceinfo():
 | 
					def remote_cdm_playready_deviceinfo():
 | 
				
			||||||
    base_name = config["default_pr_cdm"]
 | 
					    base_name = config["default_pr_cdm"]
 | 
				
			||||||
    if not base_name.endswith(".prd"):
 | 
					    if not base_name.endswith(".prd"):
 | 
				
			||||||
        full_file_name = (base_name + ".prd")
 | 
					        full_file_name = base_name + ".prd"
 | 
				
			||||||
    device = PlayReadyDevice.load(f'{os.getcwd()}/configs/CDMs/PR/{full_file_name}')
 | 
					    device = PlayReadyDevice.load(f"{os.getcwd()}/configs/CDMs/PR/{full_file_name}")
 | 
				
			||||||
    cdm = PlayReadyCDM.from_device(device)
 | 
					    cdm = PlayReadyCDM.from_device(device)
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify(
 | 
				
			||||||
        'security_level': cdm.security_level,
 | 
					        {
 | 
				
			||||||
        'host': f'{config["fqdn"]}/remotecdm/playready',
 | 
					            "security_level": cdm.security_level,
 | 
				
			||||||
        'secret': f'{config["remote_cdm_secret"]}',
 | 
					            "host": f'{config["fqdn"]}/remotecdm/playready',
 | 
				
			||||||
        'device_name': Path(base_name).stem
 | 
					            "secret": f'{config["remote_cdm_secret"]}',
 | 
				
			||||||
    })
 | 
					            "device_name": Path(base_name).stem,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready/deviceinfo/<device>', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_pr_bp.route("/remotecdm/playready/deviceinfo/<device>", methods=["GET"])
 | 
				
			||||||
def remote_cdm_playready_deviceinfo_specific(device):
 | 
					def remote_cdm_playready_deviceinfo_specific(device):
 | 
				
			||||||
    if request.method == 'GET':
 | 
					    if request.method == "GET":
 | 
				
			||||||
        base_name = Path(device).with_suffix('.prd').name
 | 
					        base_name = Path(device).with_suffix(".prd").name
 | 
				
			||||||
        api_key = request.headers['X-Secret-Key']
 | 
					        api_key = request.headers["X-Secret-Key"]
 | 
				
			||||||
        username = fetch_username_by_api_key(api_key)
 | 
					        username = fetch_username_by_api_key(api_key)
 | 
				
			||||||
        device = PlayReadyDevice.load(f'{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}')
 | 
					        device = PlayReadyDevice.load(
 | 
				
			||||||
 | 
					            f"{os.getcwd()}/configs/CDMs/{username}/PR/{base_name}"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        cdm = PlayReadyCDM.from_device(device)
 | 
					        cdm = PlayReadyCDM.from_device(device)
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'security_level': cdm.security_level,
 | 
					            {
 | 
				
			||||||
            'host': f'{config["fqdn"]}/remotecdm/widevine',
 | 
					                "security_level": cdm.security_level,
 | 
				
			||||||
            'secret': f'{api_key}',
 | 
					                "host": f'{config["fqdn"]}/remotecdm/widevine',
 | 
				
			||||||
            'device_name': Path(base_name).stem
 | 
					                "secret": f"{api_key}",
 | 
				
			||||||
        })
 | 
					                "device_name": Path(base_name).stem,
 | 
				
			||||||
 | 
					 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready/<device>/open', methods=['GET'])
 | 
					 | 
				
			||||||
def remote_cdm_playready_open(device):
 | 
					 | 
				
			||||||
    if str(device).lower() == config['default_pr_cdm'].lower():
 | 
					 | 
				
			||||||
        pr_device = PlayReadyDevice.load(f'{os.getcwd()}/configs/CDMs/PR/{config["default_pr_cdm"]}.prd')
 | 
					 | 
				
			||||||
        cdm = current_app.config['CDM'] = PlayReadyCDM.from_device(pr_device)
 | 
					 | 
				
			||||||
        session_id = cdm.open()
 | 
					 | 
				
			||||||
        return jsonify({
 | 
					 | 
				
			||||||
            'message': 'Success',
 | 
					 | 
				
			||||||
            'data': {
 | 
					 | 
				
			||||||
                'session_id': session_id.hex(),
 | 
					 | 
				
			||||||
                'device': {
 | 
					 | 
				
			||||||
                    'security_level': cdm.security_level
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        )
 | 
				
			||||||
    if request.headers['X-Secret-Key'] and str(device).lower() != config['default_pr_cdm'].lower():
 | 
					
 | 
				
			||||||
        api_key = request.headers['X-Secret-Key']
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_pr_bp.route("/remotecdm/playready/<device>/open", methods=["GET"])
 | 
				
			||||||
 | 
					def remote_cdm_playready_open(device):
 | 
				
			||||||
 | 
					    if str(device).lower() == config["default_pr_cdm"].lower():
 | 
				
			||||||
 | 
					        pr_device = PlayReadyDevice.load(
 | 
				
			||||||
 | 
					            f'{os.getcwd()}/configs/CDMs/PR/{config["default_pr_cdm"]}.prd'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        cdm = current_app.config["CDM"] = PlayReadyCDM.from_device(pr_device)
 | 
				
			||||||
 | 
					        session_id = cdm.open()
 | 
				
			||||||
 | 
					        return jsonify(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "message": "Success",
 | 
				
			||||||
 | 
					                "data": {
 | 
				
			||||||
 | 
					                    "session_id": session_id.hex(),
 | 
				
			||||||
 | 
					                    "device": {"security_level": cdm.security_level},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        request.headers["X-Secret-Key"]
 | 
				
			||||||
 | 
					        and str(device).lower() != config["default_pr_cdm"].lower()
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        api_key = request.headers["X-Secret-Key"]
 | 
				
			||||||
        user = fetch_username_by_api_key(api_key=api_key)
 | 
					        user = fetch_username_by_api_key(api_key=api_key)
 | 
				
			||||||
        if user:
 | 
					        if user:
 | 
				
			||||||
            if user_allowed_to_use_device(device=device, username=user):
 | 
					            if user_allowed_to_use_device(device=device, username=user):
 | 
				
			||||||
                pr_device = PlayReadyDevice.load(f'{os.getcwd()}/configs/CDMs/{user}/PR/{device}.prd')
 | 
					                pr_device = PlayReadyDevice.load(
 | 
				
			||||||
                cdm = current_app.config['CDM'] = PlayReadyCDM.from_device(pr_device)
 | 
					                    f"{os.getcwd()}/configs/CDMs/{user}/PR/{device}.prd"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                cdm = current_app.config["CDM"] = PlayReadyCDM.from_device(pr_device)
 | 
				
			||||||
                session_id = cdm.open()
 | 
					                session_id = cdm.open()
 | 
				
			||||||
                return jsonify({
 | 
					                return jsonify(
 | 
				
			||||||
                    'message': 'Success',
 | 
					                    {
 | 
				
			||||||
                    'data': {
 | 
					                        "message": "Success",
 | 
				
			||||||
                        'session_id': session_id.hex(),
 | 
					                        "data": {
 | 
				
			||||||
                        'device': {
 | 
					                            "session_id": session_id.hex(),
 | 
				
			||||||
                            'security_level': cdm.security_level
 | 
					                            "device": {"security_level": cdm.security_level},
 | 
				
			||||||
                        }
 | 
					                        },
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                })
 | 
					                )
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return jsonify({
 | 
					                return (
 | 
				
			||||||
                    'message': f"Device '{device}' is not found or you are not authorized to use it.",
 | 
					                    jsonify(
 | 
				
			||||||
                }), 403
 | 
					                        {
 | 
				
			||||||
 | 
					                            "message": f"Device '{device}' is not found or you are not authorized to use it.",
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    403,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'message': f"Device '{device}' is not found or you are not authorized to use it.",
 | 
					                jsonify(
 | 
				
			||||||
            }), 403
 | 
					                    {
 | 
				
			||||||
 | 
					                        "message": f"Device '{device}' is not found or you are not authorized to use it.",
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                403,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'message': f"Device '{device}' is not found or you are not authorized to use it.",
 | 
					            jsonify(
 | 
				
			||||||
        }), 403
 | 
					                {
 | 
				
			||||||
 | 
					                    "message": f"Device '{device}' is not found or you are not authorized to use it.",
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            403,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready/<device>/close/<session_id>', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_pr_bp.route(
 | 
				
			||||||
 | 
					    "/remotecdm/playready/<device>/close/<session_id>", methods=["GET"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
def remote_cdm_playready_close(device, session_id):
 | 
					def remote_cdm_playready_close(device, session_id):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        session_id = bytes.fromhex(session_id)
 | 
					        session_id = bytes.fromhex(session_id)
 | 
				
			||||||
        cdm = current_app.config["CDM"]
 | 
					        cdm = current_app.config["CDM"]
 | 
				
			||||||
        if not cdm:
 | 
					        if not cdm:
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'message': f'No CDM for "{device}" has been opened yet. No session to close'
 | 
					                jsonify(
 | 
				
			||||||
            }), 400
 | 
					                    {
 | 
				
			||||||
 | 
					                        "message": f'No CDM for "{device}" has been opened yet. No session to close'
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            cdm.close(session_id)
 | 
					            cdm.close(session_id)
 | 
				
			||||||
        except InvalidSession:
 | 
					        except InvalidSession:
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'message': f'Invalid session ID "{session_id.hex()}", it may have expired'
 | 
					                jsonify(
 | 
				
			||||||
            }), 400
 | 
					                    {
 | 
				
			||||||
        return jsonify({
 | 
					                        "message": f'Invalid session ID "{session_id.hex()}", it may have expired'
 | 
				
			||||||
            'message': f'Successfully closed Session "{session_id.hex()}".',
 | 
					                    }
 | 
				
			||||||
        }), 200
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            jsonify(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "message": f'Successfully closed Session "{session_id.hex()}".',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            200,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'message': f'Failed to close Session "{session_id.hex()}".'
 | 
					            jsonify({"message": f'Failed to close Session "{session_id.hex()}".'}),
 | 
				
			||||||
        }), 400
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready/<device>/get_license_challenge', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_pr_bp.route(
 | 
				
			||||||
 | 
					    "/remotecdm/playready/<device>/get_license_challenge", methods=["POST"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
def remote_cdm_playready_get_license_challenge(device):
 | 
					def remote_cdm_playready_get_license_challenge(device):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("session_id", "init_data"):
 | 
					    for required_field in ("session_id", "init_data"):
 | 
				
			||||||
        if not body.get(required_field):
 | 
					        if not body.get(required_field):
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                jsonify(
 | 
				
			||||||
            }), 400
 | 
					                    {
 | 
				
			||||||
 | 
					                        "message": f'Missing required field "{required_field}" in JSON body'
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
    init_data = body["init_data"]
 | 
					    init_data = body["init_data"]
 | 
				
			||||||
@ -145,42 +204,37 @@ def remote_cdm_playready_get_license_challenge(device):
 | 
				
			|||||||
            if pssh.wrm_headers:
 | 
					            if pssh.wrm_headers:
 | 
				
			||||||
                init_data = pssh.wrm_headers[0]
 | 
					                init_data = pssh.wrm_headers[0]
 | 
				
			||||||
        except InvalidPssh as e:
 | 
					        except InvalidPssh as e:
 | 
				
			||||||
            return jsonify({
 | 
					            return jsonify({"message": f"Unable to parse base64 PSSH, {e}"})
 | 
				
			||||||
                'message': f'Unable to parse base64 PSSH, {e}'
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        license_request = cdm.get_license_challenge(
 | 
					        license_request = cdm.get_license_challenge(
 | 
				
			||||||
            session_id=session_id,
 | 
					            session_id=session_id, wrm_header=init_data
 | 
				
			||||||
            wrm_header=init_data
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
 | 
					            {
 | 
				
			||||||
        })
 | 
					                "message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"message": f"Error, {e}"})
 | 
				
			||||||
            'message': f'Error, {e}'
 | 
					    return jsonify({"message": "success", "data": {"challenge": license_request}})
 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    return jsonify({
 | 
					 | 
				
			||||||
        'message': 'success',
 | 
					 | 
				
			||||||
        'data': {
 | 
					 | 
				
			||||||
            'challenge': license_request
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready/<device>/parse_license', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_pr_bp.route("/remotecdm/playready/<device>/parse_license", methods=["POST"])
 | 
				
			||||||
def remote_cdm_playready_parse_license(device):
 | 
					def remote_cdm_playready_parse_license(device):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("license_message", "session_id"):
 | 
					    for required_field in ("license_message", "session_id"):
 | 
				
			||||||
        if not body.get(required_field):
 | 
					        if not body.get(required_field):
 | 
				
			||||||
            return jsonify({
 | 
					            return jsonify(
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                {"message": f'Missing required field "{required_field}" in JSON body'}
 | 
				
			||||||
            })
 | 
					            )
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
    if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'message': f"No Cdm session for {device} has been opened yet. No session to use."
 | 
					            {
 | 
				
			||||||
        })
 | 
					                "message": f"No Cdm session for {device} has been opened yet. No session to use."
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
    license_message = body["license_message"]
 | 
					    license_message = body["license_message"]
 | 
				
			||||||
    if is_base64(license_message):
 | 
					    if is_base64(license_message):
 | 
				
			||||||
@ -188,45 +242,44 @@ def remote_cdm_playready_parse_license(device):
 | 
				
			|||||||
    try:
 | 
					    try:
 | 
				
			||||||
        cdm.parse_license(session_id, license_message)
 | 
					        cdm.parse_license(session_id, license_message)
 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
 | 
					            {
 | 
				
			||||||
        })
 | 
					                "message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except InvalidLicense as e:
 | 
					    except InvalidLicense as e:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"message": f"Invalid License, {e}"})
 | 
				
			||||||
            'message': f"Invalid License, {e}"
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"message": f"Error, {e}"})
 | 
				
			||||||
            'message': f"Error, {e}"
 | 
					    return jsonify(
 | 
				
			||||||
        })
 | 
					        {"message": "Successfully parsed and loaded the Keys from the License message"}
 | 
				
			||||||
    return jsonify({
 | 
					    )
 | 
				
			||||||
        'message': 'Successfully parsed and loaded the Keys from the License message'
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_pr_bp.route('/remotecdm/playready/<device>/get_keys', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_pr_bp.route("/remotecdm/playready/<device>/get_keys", methods=["POST"])
 | 
				
			||||||
def remote_cdm_playready_get_keys(device):
 | 
					def remote_cdm_playready_get_keys(device):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("session_id",):
 | 
					    for required_field in ("session_id",):
 | 
				
			||||||
        if not body.get(required_field):
 | 
					        if not body.get(required_field):
 | 
				
			||||||
            return jsonify({
 | 
					            return jsonify(
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                {"message": f'Missing required field "{required_field}" in JSON body'}
 | 
				
			||||||
            })
 | 
					            )
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
    if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'message': f"Missing required field '{required_field}' in JSON body."
 | 
					            {"message": f"Missing required field '{required_field}' in JSON body."}
 | 
				
			||||||
        })
 | 
					        )
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        keys = cdm.get_keys(session_id)
 | 
					        keys = cdm.get_keys(session_id)
 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'message': f"Invalid Session ID '{session_id.hex()}', it may have expired."
 | 
					            {
 | 
				
			||||||
        })
 | 
					                "message": f"Invalid Session ID '{session_id.hex()}', it may have expired."
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"message": f"Error, {e}"})
 | 
				
			||||||
            'message': f"Error, {e}"
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    keys_json = [
 | 
					    keys_json = [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "key_id": key.key_id.hex,
 | 
					            "key_id": key.key_id.hex,
 | 
				
			||||||
@ -237,9 +290,4 @@ def remote_cdm_playready_get_keys(device):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        for key in keys
 | 
					        for key in keys
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    return jsonify({
 | 
					    return jsonify({"message": "success", "data": {"keys": keys_json}})
 | 
				
			||||||
        'message': 'success',
 | 
					 | 
				
			||||||
        'data': {
 | 
					 | 
				
			||||||
            'keys': keys_json
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -7,381 +7,575 @@ from pywidevine.pssh import PSSH as widevinePSSH
 | 
				
			|||||||
from pywidevine import __version__
 | 
					from pywidevine import __version__
 | 
				
			||||||
from pywidevine.cdm import Cdm as widevineCDM
 | 
					from pywidevine.cdm import Cdm as widevineCDM
 | 
				
			||||||
from pywidevine.device import Device as widevineDevice
 | 
					from pywidevine.device import Device as widevineDevice
 | 
				
			||||||
from pywidevine.exceptions import (InvalidContext, InvalidInitData, InvalidLicenseMessage, InvalidLicenseType,
 | 
					from pywidevine.exceptions import (
 | 
				
			||||||
                                   InvalidSession, SignatureMismatch, TooManySessions)
 | 
					    InvalidContext,
 | 
				
			||||||
 | 
					    InvalidInitData,
 | 
				
			||||||
 | 
					    InvalidLicenseMessage,
 | 
				
			||||||
 | 
					    InvalidLicenseType,
 | 
				
			||||||
 | 
					    InvalidSession,
 | 
				
			||||||
 | 
					    SignatureMismatch,
 | 
				
			||||||
 | 
					    TooManySessions,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import yaml
 | 
					import yaml
 | 
				
			||||||
from custom_functions.database.user_db import fetch_api_key, fetch_username_by_api_key
 | 
					from custom_functions.database.user_db import fetch_api_key, fetch_username_by_api_key
 | 
				
			||||||
from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
 | 
					from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
remotecdm_wv_bp = Blueprint('remotecdm_wv', __name__)
 | 
					remotecdm_wv_bp = Blueprint("remotecdm_wv", __name__)
 | 
				
			||||||
with open(f'{os.getcwd()}/configs/config.yaml', 'r') as file:
 | 
					with open(f"{os.getcwd()}/configs/config.yaml", "r") as file:
 | 
				
			||||||
    config = yaml.safe_load(file)
 | 
					    config = yaml.safe_load(file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine', methods=['GET', 'HEAD'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route("/remotecdm/widevine", methods=["GET", "HEAD"])
 | 
				
			||||||
def remote_cdm_widevine():
 | 
					def remote_cdm_widevine():
 | 
				
			||||||
    if request.method == 'GET':
 | 
					    if request.method == "GET":
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'status': 200,
 | 
					            {"status": 200, "message": f"{config['fqdn'].upper()} Remote Widevine CDM."}
 | 
				
			||||||
            'message': f"{config['fqdn'].upper()} Remote Widevine CDM."
 | 
					        )
 | 
				
			||||||
        })
 | 
					    if request.method == "HEAD":
 | 
				
			||||||
    if request.method == 'HEAD':
 | 
					 | 
				
			||||||
        response = Response(status=200)
 | 
					        response = Response(status=200)
 | 
				
			||||||
        response.headers['Server'] = f'https://github.com/devine-dl/pywidevine serve v{__version__}'
 | 
					        response.headers["Server"] = (
 | 
				
			||||||
 | 
					            f"https://github.com/devine-dl/pywidevine serve v{__version__}"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        return response
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/deviceinfo', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route("/remotecdm/widevine/deviceinfo", methods=["GET"])
 | 
				
			||||||
def remote_cdm_widevine_deviceinfo():
 | 
					def remote_cdm_widevine_deviceinfo():
 | 
				
			||||||
    if request.method == 'GET':
 | 
					    if request.method == "GET":
 | 
				
			||||||
        base_name = config["default_wv_cdm"]
 | 
					        base_name = config["default_wv_cdm"]
 | 
				
			||||||
        if not base_name.endswith(".wvd"):
 | 
					        if not base_name.endswith(".wvd"):
 | 
				
			||||||
            base_name = (base_name + ".wvd")
 | 
					            base_name = base_name + ".wvd"
 | 
				
			||||||
        device = widevineDevice.load(f'{os.getcwd()}/configs/CDMs/WV/{base_name}')
 | 
					        device = widevineDevice.load(f"{os.getcwd()}/configs/CDMs/WV/{base_name}")
 | 
				
			||||||
        cdm = widevineCDM.from_device(device)
 | 
					        cdm = widevineCDM.from_device(device)
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'device_type': cdm.device_type.name,
 | 
					            {
 | 
				
			||||||
            'system_id': cdm.system_id,
 | 
					                "device_type": cdm.device_type.name,
 | 
				
			||||||
            'security_level': cdm.security_level,
 | 
					                "system_id": cdm.system_id,
 | 
				
			||||||
            'host': f'{config["fqdn"]}/remotecdm/widevine',
 | 
					                "security_level": cdm.security_level,
 | 
				
			||||||
            'secret': f'{config["remote_cdm_secret"]}',
 | 
					                "host": f'{config["fqdn"]}/remotecdm/widevine',
 | 
				
			||||||
            'device_name': Path(base_name).stem
 | 
					                "secret": f'{config["remote_cdm_secret"]}',
 | 
				
			||||||
        })
 | 
					                "device_name": Path(base_name).stem,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/deviceinfo/<device>', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route("/remotecdm/widevine/deviceinfo/<device>", methods=["GET"])
 | 
				
			||||||
def remote_cdm_widevine_deviceinfo_specific(device):
 | 
					def remote_cdm_widevine_deviceinfo_specific(device):
 | 
				
			||||||
    if request.method == 'GET':
 | 
					    if request.method == "GET":
 | 
				
			||||||
        base_name = Path(device).with_suffix('.wvd').name
 | 
					        base_name = Path(device).with_suffix(".wvd").name
 | 
				
			||||||
        api_key = request.headers['X-Secret-Key']
 | 
					        api_key = request.headers["X-Secret-Key"]
 | 
				
			||||||
        username = fetch_username_by_api_key(api_key)
 | 
					        username = fetch_username_by_api_key(api_key)
 | 
				
			||||||
        device = widevineDevice.load(f'{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}')
 | 
					        device = widevineDevice.load(
 | 
				
			||||||
 | 
					            f"{os.getcwd()}/configs/CDMs/{username}/WV/{base_name}"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        cdm = widevineCDM.from_device(device)
 | 
					        cdm = widevineCDM.from_device(device)
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify(
 | 
				
			||||||
            'device_type': cdm.device_type.name,
 | 
					            {
 | 
				
			||||||
            'system_id': cdm.system_id,
 | 
					                "device_type": cdm.device_type.name,
 | 
				
			||||||
            'security_level': cdm.security_level,
 | 
					                "system_id": cdm.system_id,
 | 
				
			||||||
            'host': f'{config["fqdn"]}/remotecdm/widevine',
 | 
					                "security_level": cdm.security_level,
 | 
				
			||||||
            'secret': f'{api_key}',
 | 
					                "host": f'{config["fqdn"]}/remotecdm/widevine',
 | 
				
			||||||
            'device_name': Path(base_name).stem
 | 
					                "secret": f"{api_key}",
 | 
				
			||||||
        })
 | 
					                "device_name": Path(base_name).stem,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/<device>/open', methods=['GET'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route("/remotecdm/widevine/<device>/open", methods=["GET"])
 | 
				
			||||||
def remote_cdm_widevine_open(device):
 | 
					def remote_cdm_widevine_open(device):
 | 
				
			||||||
    if str(device).lower() == config['default_wv_cdm'].lower():
 | 
					    if str(device).lower() == config["default_wv_cdm"].lower():
 | 
				
			||||||
        wv_device = widevineDevice.load(f'{os.getcwd()}/configs/CDMs/WV/{config["default_wv_cdm"]}.wvd')
 | 
					        wv_device = widevineDevice.load(
 | 
				
			||||||
 | 
					            f'{os.getcwd()}/configs/CDMs/WV/{config["default_wv_cdm"]}.wvd'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        cdm = current_app.config["CDM"] = widevineCDM.from_device(wv_device)
 | 
					        cdm = current_app.config["CDM"] = widevineCDM.from_device(wv_device)
 | 
				
			||||||
        session_id = cdm.open()
 | 
					        session_id = cdm.open()
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 200,
 | 
					            jsonify(
 | 
				
			||||||
            'message': 'Success',
 | 
					                {
 | 
				
			||||||
            'data': {
 | 
					                    "status": 200,
 | 
				
			||||||
                'session_id': session_id.hex(),
 | 
					                    "message": "Success",
 | 
				
			||||||
                'device': {
 | 
					                    "data": {
 | 
				
			||||||
                    'system_id': cdm.system_id,
 | 
					                        "session_id": session_id.hex(),
 | 
				
			||||||
                    'security_level': cdm.security_level,
 | 
					                        "device": {
 | 
				
			||||||
 | 
					                            "system_id": cdm.system_id,
 | 
				
			||||||
 | 
					                            "security_level": cdm.security_level,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            ),
 | 
				
			||||||
        }), 200
 | 
					            200,
 | 
				
			||||||
    if request.headers['X-Secret-Key'] and str(device).lower() != config['default_wv_cdm'].lower():
 | 
					        )
 | 
				
			||||||
        api_key = request.headers['X-Secret-Key']
 | 
					    if (
 | 
				
			||||||
 | 
					        request.headers["X-Secret-Key"]
 | 
				
			||||||
 | 
					        and str(device).lower() != config["default_wv_cdm"].lower()
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        api_key = request.headers["X-Secret-Key"]
 | 
				
			||||||
        user = fetch_username_by_api_key(api_key=api_key)
 | 
					        user = fetch_username_by_api_key(api_key=api_key)
 | 
				
			||||||
        if user:
 | 
					        if user:
 | 
				
			||||||
            if user_allowed_to_use_device(device=device, username=user):
 | 
					            if user_allowed_to_use_device(device=device, username=user):
 | 
				
			||||||
                wv_device = widevineDevice.load(f'{os.getcwd()}/configs/CDMs/{user}/WV/{device}.wvd')
 | 
					                wv_device = widevineDevice.load(
 | 
				
			||||||
 | 
					                    f"{os.getcwd()}/configs/CDMs/{user}/WV/{device}.wvd"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
                cdm = current_app.config["CDM"] = widevineCDM.from_device(wv_device)
 | 
					                cdm = current_app.config["CDM"] = widevineCDM.from_device(wv_device)
 | 
				
			||||||
                session_id = cdm.open()
 | 
					                session_id = cdm.open()
 | 
				
			||||||
                return jsonify({
 | 
					                return (
 | 
				
			||||||
                    'status': 200,
 | 
					                    jsonify(
 | 
				
			||||||
                    'message': 'Success',
 | 
					                        {
 | 
				
			||||||
                    'data': {
 | 
					                            "status": 200,
 | 
				
			||||||
                        'session_id': session_id.hex(),
 | 
					                            "message": "Success",
 | 
				
			||||||
                        'device': {
 | 
					                            "data": {
 | 
				
			||||||
                            'system_id': cdm.system_id,
 | 
					                                "session_id": session_id.hex(),
 | 
				
			||||||
                            'security_level': cdm.security_level,
 | 
					                                "device": {
 | 
				
			||||||
 | 
					                                    "system_id": cdm.system_id,
 | 
				
			||||||
 | 
					                                    "security_level": cdm.security_level,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    ),
 | 
				
			||||||
                }), 200
 | 
					                    200,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return jsonify({
 | 
					                return (
 | 
				
			||||||
                    'message': f"Device '{device}' is not found or you are not authorized to use it.",
 | 
					                    jsonify(
 | 
				
			||||||
                    'status': 403
 | 
					                        {
 | 
				
			||||||
                }), 403
 | 
					                            "message": f"Device '{device}' is not found or you are not authorized to use it.",
 | 
				
			||||||
 | 
					                            "status": 403,
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    403,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'message': f"Device '{device}' is not found or you are not authorized to use it.",
 | 
					                jsonify(
 | 
				
			||||||
                'status': 403
 | 
					                    {
 | 
				
			||||||
            }), 403
 | 
					                        "message": f"Device '{device}' is not found or you are not authorized to use it.",
 | 
				
			||||||
 | 
					                        "status": 403,
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                403,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'message': f"Device '{device}' is not found or you are not authorized to use it.",
 | 
					            jsonify(
 | 
				
			||||||
            'status': 403
 | 
					                {
 | 
				
			||||||
        }), 403
 | 
					                    "message": f"Device '{device}' is not found or you are not authorized to use it.",
 | 
				
			||||||
 | 
					                    "status": 403,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            403,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/<device>/close/<session_id>', methods=['GET'])
 | 
					@remotecdm_wv_bp.route(
 | 
				
			||||||
 | 
					    "/remotecdm/widevine/<device>/close/<session_id>", methods=["GET"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
def remote_cdm_widevine_close(device, session_id):
 | 
					def remote_cdm_widevine_close(device, session_id):
 | 
				
			||||||
        session_id = bytes.fromhex(session_id)
 | 
					    session_id = bytes.fromhex(session_id)
 | 
				
			||||||
        cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
        if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
            return jsonify({
 | 
					        return (
 | 
				
			||||||
                'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
                'message': f'No CDM for "{device}" has been opened yet. No session to close'
 | 
					                {
 | 
				
			||||||
            }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
        try:
 | 
					                    "message": f'No CDM for "{device}" has been opened yet. No session to close',
 | 
				
			||||||
            cdm.close(session_id)
 | 
					                }
 | 
				
			||||||
        except InvalidSession:
 | 
					            ),
 | 
				
			||||||
            return jsonify({
 | 
					            400,
 | 
				
			||||||
                'status': 400,
 | 
					        )
 | 
				
			||||||
                'message': f'Invalid session ID "{session_id.hex()}", it may have expired'
 | 
					    try:
 | 
				
			||||||
            }), 400
 | 
					        cdm.close(session_id)
 | 
				
			||||||
        return jsonify({
 | 
					    except InvalidSession:
 | 
				
			||||||
            'status': 200,
 | 
					        return (
 | 
				
			||||||
            'message': f'Successfully closed Session "{session_id.hex()}".',
 | 
					            jsonify(
 | 
				
			||||||
        }), 200
 | 
					                {
 | 
				
			||||||
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'Invalid session ID "{session_id.hex()}", it may have expired',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        jsonify(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "status": 200,
 | 
				
			||||||
 | 
					                "message": f'Successfully closed Session "{session_id.hex()}".',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        200,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/<device>/set_service_certificate', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route(
 | 
				
			||||||
 | 
					    "/remotecdm/widevine/<device>/set_service_certificate", methods=["POST"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
def remote_cdm_widevine_set_service_certificate(device):
 | 
					def remote_cdm_widevine_set_service_certificate(device):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("session_id", "certificate"):
 | 
					    for required_field in ("session_id", "certificate"):
 | 
				
			||||||
        if required_field == "certificate":
 | 
					        if required_field == "certificate":
 | 
				
			||||||
            has_field = required_field in body  # it needs the key, but can be empty/null
 | 
					            has_field = (
 | 
				
			||||||
 | 
					                required_field in body
 | 
				
			||||||
 | 
					            )  # it needs the key, but can be empty/null
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            has_field = body.get(required_field)
 | 
					            has_field = body.get(required_field)
 | 
				
			||||||
        if not has_field:
 | 
					        if not has_field:
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'status': 400,
 | 
					                jsonify(
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                    {
 | 
				
			||||||
            }), 400
 | 
					                        "status": 400,
 | 
				
			||||||
 | 
					                        "message": f'Missing required field "{required_field}" in JSON body',
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
    if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'No CDM session for "{device}" has been opened yet. No session to use'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'No CDM session for "{device}" has been opened yet. No session to use',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    certificate = body["certificate"]
 | 
					    certificate = body["certificate"]
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        provider_id = cdm.set_service_certificate(session_id, certificate)
 | 
					        provider_id = cdm.set_service_certificate(session_id, certificate)
 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'Invalid session id: "{session_id.hex()}", it may have expired'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'Invalid session id: "{session_id.hex()}", it may have expired',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except DecodeError as error:
 | 
					    except DecodeError as error:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'Invalid Service Certificate, {error}'
 | 
					                {"status": 400, "message": f"Invalid Service Certificate, {error}"}
 | 
				
			||||||
        }), 400
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except SignatureMismatch:
 | 
					    except SignatureMismatch:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': 'Signature Validation failed on the Service Certificate, rejecting'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
    return jsonify({
 | 
					                    "message": "Signature Validation failed on the Service Certificate, rejecting",
 | 
				
			||||||
        'status': 200,
 | 
					                }
 | 
				
			||||||
        'message': f"Successfully {['set', 'unset'][not certificate]} the Service Certificate.",
 | 
					            ),
 | 
				
			||||||
        'data': {
 | 
					            400,
 | 
				
			||||||
            'provider_id': provider_id,
 | 
					        )
 | 
				
			||||||
        }
 | 
					    return (
 | 
				
			||||||
    }), 200
 | 
					        jsonify(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "status": 200,
 | 
				
			||||||
 | 
					                "message": f"Successfully {['set', 'unset'][not certificate]} the Service Certificate.",
 | 
				
			||||||
 | 
					                "data": {
 | 
				
			||||||
 | 
					                    "provider_id": provider_id,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        200,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/<device>/get_service_certificate', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route(
 | 
				
			||||||
 | 
					    "/remotecdm/widevine/<device>/get_service_certificate", methods=["POST"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
def remote_cdm_widevine_get_service_certificate(device):
 | 
					def remote_cdm_widevine_get_service_certificate(device):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("session_id",):
 | 
					    for required_field in ("session_id",):
 | 
				
			||||||
        if not body.get(required_field):
 | 
					        if not body.get(required_field):
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'status': 400,
 | 
					                jsonify(
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                    {
 | 
				
			||||||
            }), 400
 | 
					                        "status": 400,
 | 
				
			||||||
 | 
					                        "message": f'Missing required field "{required_field}" in JSON body',
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'No CDM session for "{device}" has been opened yet. No session to use'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'No CDM session for "{device}" has been opened yet. No session to use',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        service_certificate = cdm.get_service_certificate(session_id)
 | 
					        service_certificate = cdm.get_service_certificate(session_id)
 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'Invalid Session ID "{session_id.hex()}", it may have expired',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    if service_certificate:
 | 
					    if service_certificate:
 | 
				
			||||||
        service_certificate_b64 = base64.b64encode(service_certificate.SerializeToString()).decode()
 | 
					        service_certificate_b64 = base64.b64encode(
 | 
				
			||||||
 | 
					            service_certificate.SerializeToString()
 | 
				
			||||||
 | 
					        ).decode()
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        service_certificate_b64 = None
 | 
					        service_certificate_b64 = None
 | 
				
			||||||
    return jsonify({
 | 
					    return (
 | 
				
			||||||
        'status': 200,
 | 
					        jsonify(
 | 
				
			||||||
        'message': 'Successfully got the Service Certificate',
 | 
					            {
 | 
				
			||||||
        'data': {
 | 
					                "status": 200,
 | 
				
			||||||
            'service_certificate': service_certificate_b64,
 | 
					                "message": "Successfully got the Service Certificate",
 | 
				
			||||||
        }
 | 
					                "data": {
 | 
				
			||||||
    }), 200
 | 
					                    "service_certificate": service_certificate_b64,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        200,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/<device>/get_license_challenge/<license_type>', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route(
 | 
				
			||||||
 | 
					    "/remotecdm/widevine/<device>/get_license_challenge/<license_type>",
 | 
				
			||||||
 | 
					    methods=["POST"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
def remote_cdm_widevine_get_license_challenge(device, license_type):
 | 
					def remote_cdm_widevine_get_license_challenge(device, license_type):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("session_id", "init_data"):
 | 
					    for required_field in ("session_id", "init_data"):
 | 
				
			||||||
        if not body.get(required_field):
 | 
					        if not body.get(required_field):
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'status': 400,
 | 
					                jsonify(
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                    {
 | 
				
			||||||
            }), 400
 | 
					                        "status": 400,
 | 
				
			||||||
 | 
					                        "message": f'Missing required field "{required_field}" in JSON body',
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
    privacy_mode = body.get("privacy_mode", True)
 | 
					    privacy_mode = body.get("privacy_mode", True)
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
    if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'No CDM session for "{device}" has been opened yet. No session to use'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'No CDM session for "{device}" has been opened yet. No session to use',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    if current_app.config.get("force_privacy_mode"):
 | 
					    if current_app.config.get("force_privacy_mode"):
 | 
				
			||||||
        privacy_mode = True
 | 
					        privacy_mode = True
 | 
				
			||||||
        if not cdm.get_service_certificate(session_id):
 | 
					        if not cdm.get_service_certificate(session_id):
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'status': 403,
 | 
					                jsonify(
 | 
				
			||||||
                'message': 'No Service Certificate set but Privacy Mode is Enforced.'
 | 
					                    {
 | 
				
			||||||
            }), 403
 | 
					                        "status": 403,
 | 
				
			||||||
 | 
					                        "message": "No Service Certificate set but Privacy Mode is Enforced.",
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                403,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    current_app.config['pssh'] = body['init_data']
 | 
					    current_app.config["pssh"] = body["init_data"]
 | 
				
			||||||
    init_data = widevinePSSH(body['init_data'])
 | 
					    init_data = widevinePSSH(body["init_data"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        license_request = cdm.get_license_challenge(
 | 
					        license_request = cdm.get_license_challenge(
 | 
				
			||||||
            session_id=session_id,
 | 
					            session_id=session_id,
 | 
				
			||||||
            pssh=init_data,
 | 
					            pssh=init_data,
 | 
				
			||||||
            license_type=license_type,
 | 
					            license_type=license_type,
 | 
				
			||||||
            privacy_mode=privacy_mode
 | 
					            privacy_mode=privacy_mode,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'Invalid Session ID "{session_id.hex()}", it may have expired',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except InvalidInitData as error:
 | 
					    except InvalidInitData as error:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"status": 400, "message": f"Invalid Init Data, {error}"}), 400
 | 
				
			||||||
            'status': 400,
 | 
					 | 
				
			||||||
            'message': f'Invalid Init Data, {error}'
 | 
					 | 
				
			||||||
        }), 400
 | 
					 | 
				
			||||||
    except InvalidLicenseType:
 | 
					    except InvalidLicenseType:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify({"status": 400, "message": f"Invalid License Type {license_type}"}),
 | 
				
			||||||
            'message': f'Invalid License Type {license_type}'
 | 
					            400,
 | 
				
			||||||
        }), 400
 | 
					        )
 | 
				
			||||||
    return jsonify({
 | 
					    return (
 | 
				
			||||||
        'status': 200,
 | 
					        jsonify(
 | 
				
			||||||
        'message': 'Success',
 | 
					            {
 | 
				
			||||||
        'data': {
 | 
					                "status": 200,
 | 
				
			||||||
            'challenge_b64': base64.b64encode(license_request).decode()
 | 
					                "message": "Success",
 | 
				
			||||||
        }
 | 
					                "data": {"challenge_b64": base64.b64encode(license_request).decode()},
 | 
				
			||||||
    }), 200
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        200,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/<device>/parse_license', methods=['POST'])
 | 
					@remotecdm_wv_bp.route("/remotecdm/widevine/<device>/parse_license", methods=["POST"])
 | 
				
			||||||
def remote_cdm_widevine_parse_license(device):
 | 
					def remote_cdm_widevine_parse_license(device):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("session_id", "license_message"):
 | 
					    for required_field in ("session_id", "license_message"):
 | 
				
			||||||
        if not body.get(required_field):
 | 
					        if not body.get(required_field):
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'status': 400,
 | 
					                jsonify(
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                    {
 | 
				
			||||||
            }), 400
 | 
					                        "status": 400,
 | 
				
			||||||
 | 
					                        "message": f'Missing required field "{required_field}" in JSON body',
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
    if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'No CDM session for "{device}" has been opened yet. No session to use'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'No CDM session for "{device}" has been opened yet. No session to use',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        cdm.parse_license(session_id, body['license_message'])
 | 
					        cdm.parse_license(session_id, body["license_message"])
 | 
				
			||||||
    except InvalidLicenseMessage as error:
 | 
					    except InvalidLicenseMessage as error:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify({"status": 400, "message": f"Invalid License Message, {error}"}),
 | 
				
			||||||
            'message': f'Invalid License Message, {error}'
 | 
					            400,
 | 
				
			||||||
        }), 400
 | 
					        )
 | 
				
			||||||
    except InvalidContext as error:
 | 
					    except InvalidContext as error:
 | 
				
			||||||
        return jsonify({
 | 
					        return jsonify({"status": 400, "message": f"Invalid Context, {error}"}), 400
 | 
				
			||||||
            'status': 400,
 | 
					 | 
				
			||||||
            'message': f'Invalid Context, {error}'
 | 
					 | 
				
			||||||
        }), 400
 | 
					 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'Invalid Session ID "{session_id.hex()}", it may have expired',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except SignatureMismatch:
 | 
					    except SignatureMismatch:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'Signature Validation failed on the License Message, rejecting.'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
    return jsonify({
 | 
					                    "message": f"Signature Validation failed on the License Message, rejecting.",
 | 
				
			||||||
        'status': 200,
 | 
					                }
 | 
				
			||||||
        'message': 'Successfully parsed and loaded the Keys from the License message.',
 | 
					            ),
 | 
				
			||||||
    }), 200
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        jsonify(
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                "status": 200,
 | 
				
			||||||
 | 
					                "message": "Successfully parsed and loaded the Keys from the License message.",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        200,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@remotecdm_wv_bp.route('/remotecdm/widevine/<device>/get_keys/<key_type>', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@remotecdm_wv_bp.route(
 | 
				
			||||||
 | 
					    "/remotecdm/widevine/<device>/get_keys/<key_type>", methods=["POST"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
def remote_cdm_widevine_get_keys(device, key_type):
 | 
					def remote_cdm_widevine_get_keys(device, key_type):
 | 
				
			||||||
    body = request.get_json()
 | 
					    body = request.get_json()
 | 
				
			||||||
    for required_field in ("session_id",):
 | 
					    for required_field in ("session_id",):
 | 
				
			||||||
        if not body.get(required_field):
 | 
					        if not body.get(required_field):
 | 
				
			||||||
            return jsonify({
 | 
					            return (
 | 
				
			||||||
                'status': 400,
 | 
					                jsonify(
 | 
				
			||||||
                'message': f'Missing required field "{required_field}" in JSON body'
 | 
					                    {
 | 
				
			||||||
            }), 400
 | 
					                        "status": 400,
 | 
				
			||||||
 | 
					                        "message": f'Missing required field "{required_field}" in JSON body',
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                400,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    session_id = bytes.fromhex(body["session_id"])
 | 
					    session_id = bytes.fromhex(body["session_id"])
 | 
				
			||||||
    key_type: Optional[str] = key_type
 | 
					    key_type: Optional[str] = key_type
 | 
				
			||||||
    if key_type == 'ALL':
 | 
					    if key_type == "ALL":
 | 
				
			||||||
        key_type = None
 | 
					        key_type = None
 | 
				
			||||||
    cdm = current_app.config["CDM"]
 | 
					    cdm = current_app.config["CDM"]
 | 
				
			||||||
    if not cdm:
 | 
					    if not cdm:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'No CDM session for "{device}" has been opened yet. No session to use'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'No CDM session for "{device}" has been opened yet. No session to use',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        keys = cdm.get_keys(session_id, key_type)
 | 
					        keys = cdm.get_keys(session_id, key_type)
 | 
				
			||||||
    except InvalidSession:
 | 
					    except InvalidSession:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'Invalid Session ID "{session_id.hex()}", it may have expired'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'Invalid Session ID "{session_id.hex()}", it may have expired',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except ValueError as error:
 | 
					    except ValueError as error:
 | 
				
			||||||
        return jsonify({
 | 
					        return (
 | 
				
			||||||
            'status': 400,
 | 
					            jsonify(
 | 
				
			||||||
            'message': f'The Key Type value "{key_type}" is invalid, {error}'
 | 
					                {
 | 
				
			||||||
        }), 400
 | 
					                    "status": 400,
 | 
				
			||||||
 | 
					                    "message": f'The Key Type value "{key_type}" is invalid, {error}',
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    keys_json = [
 | 
					    keys_json = [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "key_id": key.kid.hex,
 | 
					            "key_id": key.kid.hex,
 | 
				
			||||||
            "key": key.key.hex(),
 | 
					            "key": key.key.hex(),
 | 
				
			||||||
            "type": key.type,
 | 
					            "type": key.type,
 | 
				
			||||||
            "permissions": key.permissions
 | 
					            "permissions": key.permissions,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for key in keys
 | 
					        for key in keys
 | 
				
			||||||
        if not key_type or key.type == key_type
 | 
					        if not key_type or key.type == key_type
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    for entry in keys_json:
 | 
					    for entry in keys_json:
 | 
				
			||||||
        if config['database_type'].lower() != 'mariadb':
 | 
					        if config["database_type"].lower() != "mariadb":
 | 
				
			||||||
            from custom_functions.database.cache_to_db_sqlite import cache_to_db
 | 
					            from custom_functions.database.cache_to_db_sqlite import cache_to_db
 | 
				
			||||||
        elif config['database_type'].lower() == 'mariadb':
 | 
					        elif config["database_type"].lower() == "mariadb":
 | 
				
			||||||
            from custom_functions.database.cache_to_db_mariadb import cache_to_db
 | 
					            from custom_functions.database.cache_to_db_mariadb import cache_to_db
 | 
				
			||||||
        if entry['type'] != 'SIGNING':
 | 
					        if entry["type"] != "SIGNING":
 | 
				
			||||||
            cache_to_db(pssh=str(current_app.config['pssh']), kid=entry['key_id'], key=entry['key'])
 | 
					            cache_to_db(
 | 
				
			||||||
 | 
					                pssh=str(current_app.config["pssh"]),
 | 
				
			||||||
 | 
					                kid=entry["key_id"],
 | 
				
			||||||
 | 
					                key=entry["key"],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return jsonify({
 | 
					    return (
 | 
				
			||||||
        'status': 200,
 | 
					        jsonify({"status": 200, "message": "Success", "data": {"keys": keys_json}}),
 | 
				
			||||||
        'message': 'Success',
 | 
					        200,
 | 
				
			||||||
        'data': {
 | 
					    )
 | 
				
			||||||
            'keys': keys_json
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }), 200
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -2,41 +2,41 @@ from flask import Blueprint, request, jsonify, session
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
upload_bp = Blueprint('upload_bp', __name__)
 | 
					upload_bp = Blueprint("upload_bp", __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@upload_bp.route('/upload/<cdmtype>', methods=['POST'])
 | 
					@upload_bp.route("/upload/<cdmtype>", methods=["POST"])
 | 
				
			||||||
def upload(cdmtype):
 | 
					def upload(cdmtype):
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        username = session.get('username')
 | 
					        username = session.get("username")
 | 
				
			||||||
        if not username:
 | 
					        if not username:
 | 
				
			||||||
            return jsonify({'message': 'False', 'error': 'No username in session'}), 400
 | 
					            return jsonify({"message": "False", "error": "No username in session"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Validate CDM type
 | 
					        # Validate CDM type
 | 
				
			||||||
        if cdmtype not in ['PR', 'WV']:
 | 
					        if cdmtype not in ["PR", "WV"]:
 | 
				
			||||||
            return jsonify({'message': 'False', 'error': 'Invalid CDM type'}), 400
 | 
					            return jsonify({"message": "False", "error": "Invalid CDM type"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Set up user directory paths
 | 
					        # Set up user directory paths
 | 
				
			||||||
        base_path = os.path.join(os.getcwd(), 'configs', 'CDMs', username)
 | 
					        base_path = os.path.join(os.getcwd(), "configs", "CDMs", username)
 | 
				
			||||||
        pr_path = os.path.join(base_path, 'PR')
 | 
					        pr_path = os.path.join(base_path, "PR")
 | 
				
			||||||
        wv_path = os.path.join(base_path, 'WV')
 | 
					        wv_path = os.path.join(base_path, "WV")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create necessary directories if they don't exist
 | 
					        # Create necessary directories if they don't exist
 | 
				
			||||||
        os.makedirs(pr_path, exist_ok=True)
 | 
					        os.makedirs(pr_path, exist_ok=True)
 | 
				
			||||||
        os.makedirs(wv_path, exist_ok=True)
 | 
					        os.makedirs(wv_path, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Get uploaded file
 | 
					        # Get uploaded file
 | 
				
			||||||
        uploaded_file = request.files.get('file')
 | 
					        uploaded_file = request.files.get("file")
 | 
				
			||||||
        if not uploaded_file:
 | 
					        if not uploaded_file:
 | 
				
			||||||
            return jsonify({'message': 'False', 'error': 'No file provided'}), 400
 | 
					            return jsonify({"message": "False", "error": "No file provided"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Determine correct save path based on cdmtype
 | 
					        # Determine correct save path based on cdmtype
 | 
				
			||||||
        filename = uploaded_file.filename
 | 
					        filename = uploaded_file.filename
 | 
				
			||||||
        save_path = os.path.join(pr_path if cdmtype == 'PR' else wv_path, filename)
 | 
					        save_path = os.path.join(pr_path if cdmtype == "PR" else wv_path, filename)
 | 
				
			||||||
        uploaded_file.save(save_path)
 | 
					        uploaded_file.save(save_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return jsonify({'message': 'Success', 'file_saved_to': save_path})
 | 
					        return jsonify({"message": "Success", "file_saved_to": save_path})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        logging.exception("Upload failed")
 | 
					        logging.exception("Upload failed")
 | 
				
			||||||
        return jsonify({'message': 'False', 'error': 'Server error'}), 500
 | 
					        return jsonify({"message": "False", "error": "Server error"}), 500
 | 
				
			||||||
 | 
				
			|||||||
@ -2,53 +2,60 @@ import re
 | 
				
			|||||||
from flask import Blueprint, request, jsonify, session
 | 
					from flask import Blueprint, request, jsonify, session
 | 
				
			||||||
from custom_functions.database.user_db import change_password, change_api_key
 | 
					from custom_functions.database.user_db import change_password, change_api_key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
user_change_bp = Blueprint('user_change_bp', __name__)
 | 
					user_change_bp = Blueprint("user_change_bp", __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Define allowed characters regex (no spaces allowed)
 | 
					# Define allowed characters regex (no spaces allowed)
 | 
				
			||||||
PASSWORD_REGEX = re.compile(r'^[A-Za-z0-9!@#$%^&*()_+\-=\[\]{};\'":\\|,.<>\/?`~]+$')
 | 
					PASSWORD_REGEX = re.compile(r'^[A-Za-z0-9!@#$%^&*()_+\-=\[\]{};\'":\\|,.<>\/?`~]+$')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@user_change_bp.route('/user/change_password', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@user_change_bp.route("/user/change_password", methods=["POST"])
 | 
				
			||||||
def change_password_route():
 | 
					def change_password_route():
 | 
				
			||||||
    username = session.get('username')
 | 
					    username = session.get("username")
 | 
				
			||||||
    if not username:
 | 
					    if not username:
 | 
				
			||||||
        return jsonify({'message': 'False'}), 400
 | 
					        return jsonify({"message": "False"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        data = request.get_json()
 | 
					        data = request.get_json()
 | 
				
			||||||
        new_password = data.get('new_password', '')
 | 
					        new_password = data.get("new_password", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not PASSWORD_REGEX.match(new_password):
 | 
					        if not PASSWORD_REGEX.match(new_password):
 | 
				
			||||||
            return jsonify({'message': 'Invalid password format'}), 400
 | 
					            return jsonify({"message": "Invalid password format"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        change_password(username=username, new_password=new_password)
 | 
					        change_password(username=username, new_password=new_password)
 | 
				
			||||||
        return jsonify({'message': 'True'}), 200
 | 
					        return jsonify({"message": "True"}), 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        return jsonify({'message': 'False'}), 400
 | 
					        return jsonify({"message": "False"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@user_change_bp.route('/user/change_api_key', methods=['POST'])
 | 
					@user_change_bp.route("/user/change_api_key", methods=["POST"])
 | 
				
			||||||
def change_api_key_route():
 | 
					def change_api_key_route():
 | 
				
			||||||
    # Ensure the user is logged in by checking session for 'username'
 | 
					    # Ensure the user is logged in by checking session for 'username'
 | 
				
			||||||
    username = session.get('username')
 | 
					    username = session.get("username")
 | 
				
			||||||
    if not username:
 | 
					    if not username:
 | 
				
			||||||
        return jsonify({'message': 'False', 'error': 'User not logged in'}), 400
 | 
					        return jsonify({"message": "False", "error": "User not logged in"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Get the new API key from the request body
 | 
					    # Get the new API key from the request body
 | 
				
			||||||
    new_api_key = request.json.get('new_api_key')
 | 
					    new_api_key = request.json.get("new_api_key")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not new_api_key:
 | 
					    if not new_api_key:
 | 
				
			||||||
        return jsonify({'message': 'False', 'error': 'New API key not provided'}), 400
 | 
					        return jsonify({"message": "False", "error": "New API key not provided"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        # Call the function to update the API key in the database
 | 
					        # Call the function to update the API key in the database
 | 
				
			||||||
        success = change_api_key(username=username, new_api_key=new_api_key)
 | 
					        success = change_api_key(username=username, new_api_key=new_api_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if success:
 | 
					        if success:
 | 
				
			||||||
            return jsonify({'message': 'True', 'success': 'API key changed successfully'}), 200
 | 
					            return (
 | 
				
			||||||
 | 
					                jsonify({"message": "True", "success": "API key changed successfully"}),
 | 
				
			||||||
 | 
					                200,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            return jsonify({'message': 'False', 'error': 'Failed to change API key'}), 500
 | 
					            return (
 | 
				
			||||||
 | 
					                jsonify({"message": "False", "error": "Failed to change API key"}),
 | 
				
			||||||
 | 
					                500,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        # Catch any unexpected errors and return a response
 | 
					        # Catch any unexpected errors and return a response
 | 
				
			||||||
        return jsonify({'message': 'False', 'error': str(e)}), 500
 | 
					        return jsonify({"message": "False", "error": str(e)}), 500
 | 
				
			||||||
 | 
				
			|||||||
@ -2,33 +2,46 @@ from flask import Blueprint, request, jsonify, session
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import glob
 | 
					import glob
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
from custom_functions.database.user_db import fetch_api_key, fetch_styled_username, fetch_username_by_api_key
 | 
					from custom_functions.database.user_db import (
 | 
				
			||||||
 | 
					    fetch_api_key,
 | 
				
			||||||
 | 
					    fetch_styled_username,
 | 
				
			||||||
 | 
					    fetch_username_by_api_key,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
user_info_bp = Blueprint('user_info_bp', __name__)
 | 
					user_info_bp = Blueprint("user_info_bp", __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@user_info_bp.route('/userinfo', methods=['POST'])
 | 
					
 | 
				
			||||||
 | 
					@user_info_bp.route("/userinfo", methods=["POST"])
 | 
				
			||||||
def user_info():
 | 
					def user_info():
 | 
				
			||||||
    username = session.get('username')
 | 
					    username = session.get("username")
 | 
				
			||||||
    if not username:
 | 
					    if not username:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            headers = request.headers
 | 
					            headers = request.headers
 | 
				
			||||||
            api_key = headers['Api-Key']
 | 
					            api_key = headers["Api-Key"]
 | 
				
			||||||
            username = fetch_username_by_api_key(api_key)
 | 
					            username = fetch_username_by_api_key(api_key)
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            return jsonify({'message': 'False'}), 400
 | 
					            return jsonify({"message": "False"}), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        base_path = os.path.join(os.getcwd(), 'configs', 'CDMs', username.lower())
 | 
					        base_path = os.path.join(os.getcwd(), "configs", "CDMs", username.lower())
 | 
				
			||||||
        pr_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'PR', '*.prd'))]
 | 
					        pr_files = [
 | 
				
			||||||
        wv_files = [os.path.basename(f) for f in glob.glob(os.path.join(base_path, 'WV', '*.wvd'))]
 | 
					            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({
 | 
					        return jsonify(
 | 
				
			||||||
            'Username': username,
 | 
					            {
 | 
				
			||||||
            'Widevine_Devices': wv_files,
 | 
					                "Username": username,
 | 
				
			||||||
            'Playready_Devices': pr_files,
 | 
					                "Widevine_Devices": wv_files,
 | 
				
			||||||
            'API_Key': fetch_api_key(username),
 | 
					                "Playready_Devices": pr_files,
 | 
				
			||||||
            'Styled_Username': fetch_styled_username(username)
 | 
					                "API_Key": fetch_api_key(username),
 | 
				
			||||||
        })
 | 
					                "Styled_Username": fetch_styled_username(username),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        logging.exception("Error retrieving device files")
 | 
					        logging.exception("Error retrieving device files")
 | 
				
			||||||
        return jsonify({'message': 'False'}), 500
 | 
					        return jsonify({"message": "False"}), 500
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user