300 lines
9.2 KiB
Python
Raw Normal View History

"""Module to handle the API routes."""
2025-04-24 17:06:14 -04:00
import os
import sqlite3
import json
import shutil
import math
from io import StringIO
import tempfile
import time
from flask import Blueprint, jsonify, request, send_file, session, after_this_request
import yaml
import mysql.connector
from custom_functions.decrypt.api_decrypt import api_decrypt
from custom_functions.user_checks.device_allowed import user_allowed_to_use_device
from custom_functions.database.unified_db_ops import (
search_by_pssh_or_kid,
cache_to_db,
get_key_by_kid_and_service,
get_unique_services,
get_kid_key_dict,
key_count,
)
from configs.icon_links import data as icon_data
2025-04-24 17:06:14 -04:00
api_bp = Blueprint("api", __name__)
with open(os.path.join(os.getcwd(), "configs", "config.yaml"), "r", encoding="utf-8") as file:
2025-04-24 17:06:14 -04:00
config = yaml.safe_load(file)
2025-04-24 17:06:14 -04:00
def get_db_config():
"""Get the MariaDB database configuration."""
with open(
os.path.join(os.getcwd(), "configs", "config.yaml"), "r", encoding="utf-8"
) as file_mariadb:
config_mariadb = yaml.safe_load(file_mariadb)
2025-04-24 17:06:14 -04:00
db_config = {
"host": f'{config_mariadb["mariadb"]["host"]}',
"user": f'{config_mariadb["mariadb"]["user"]}',
"password": f'{config_mariadb["mariadb"]["password"]}',
"database": f'{config_mariadb["mariadb"]["database"]}',
2025-04-24 17:06:14 -04:00
}
return db_config
@api_bp.route("/api/cache/search", methods=["POST"])
2025-04-24 17:06:14 -04:00
def get_data():
"""Get the data from the database."""
search_argument = json.loads(request.data)["input"]
2025-04-24 17:06:14 -04:00
results = search_by_pssh_or_kid(search_filter=search_argument)
return jsonify(results)
@api_bp.route("/api/cache/<service>/<kid>", methods=["GET"])
2025-04-24 17:06:14 -04:00
def get_single_key_service(service, kid):
"""Get the single key from the database."""
2025-04-24 17:06:14 -04:00
result = get_key_by_kid_and_service(kid=kid, service=service)
return jsonify(
{
"code": 0,
"content_key": result,
}
)
2025-04-24 17:06:14 -04:00
@api_bp.route("/api/cache/<service>", methods=["GET"])
2025-04-24 17:06:14 -04:00
def get_multiple_key_service(service):
"""Get the multiple keys from the database."""
2025-04-24 17:06:14 -04:00
result = get_kid_key_dict(service_name=service)
pages = math.ceil(len(result) / 10)
return jsonify({"code": 0, "content_keys": result, "pages": pages})
2025-04-24 17:06:14 -04:00
@api_bp.route("/api/cache/<service>/<kid>", methods=["POST"])
2025-04-24 17:06:14 -04:00
def add_single_key_service(service, kid):
"""Add the single key to the database."""
2025-04-24 17:06:14 -04:00
body = request.get_json()
content_key = body["content_key"]
2025-04-24 17:06:14 -04:00
result = cache_to_db(service=service, kid=kid, key=content_key)
if result:
return jsonify(
{
"code": 0,
"updated": True,
}
)
return jsonify(
{
"code": 0,
"updated": True,
}
)
2025-04-24 17:06:14 -04:00
@api_bp.route("/api/cache/<service>", methods=["POST"])
2025-04-24 17:06:14 -04:00
def add_multiple_key_service(service):
"""Add the multiple keys to the database."""
2025-04-24 17:06:14 -04:00
body = request.get_json()
keys_added = 0
keys_updated = 0
for kid, key in body["content_keys"].items():
2025-04-24 17:06:14 -04:00
result = cache_to_db(service=service, kid=kid, key=key)
if result is True:
keys_updated += 1
else:
2025-04-24 17:06:14 -04:00
keys_added += 1
return jsonify(
{
"code": 0,
"added": str(keys_added),
"updated": str(keys_updated),
}
)
2025-04-24 17:06:14 -04:00
@api_bp.route("/api/cache", methods=["POST"])
2025-04-24 17:06:14 -04:00
def unique_service():
"""Get the unique services from the database."""
2025-04-24 17:06:14 -04:00
services = get_unique_services()
return jsonify(
{
"code": 0,
"service_list": services,
}
)
2025-04-24 17:06:14 -04:00
@api_bp.route("/api/cache/download", methods=["GET"])
2025-04-24 17:06:14 -04:00
def download_database():
"""Download the database."""
if config["database_type"].lower() != "mariadb":
original_database_path = os.path.join(os.getcwd(), "databases", "sql", "key_cache.db")
2025-04-24 17:06:14 -04:00
# Make a copy of the original database (without locking the original)
modified_database_path = os.path.join(os.getcwd(), "databases", "sql", "key_cache_modified.db")
2025-04-24 17:06:14 -04:00
# Using shutil.copy2 to preserve metadata (timestamps, etc.)
shutil.copy2(original_database_path, modified_database_path)
# Open the copied database for modification using 'with' statement to avoid locks
with sqlite3.connect(modified_database_path) as conn:
cursor = conn.cursor()
# Update all rows to remove Headers and Cookies (set them to NULL or empty strings)
cursor.execute(
"""
2025-04-24 17:06:14 -04:00
UPDATE licenses
SET Headers = NULL,
Cookies = NULL
"""
)
2025-04-24 17:06:14 -04:00
# 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
# Send the modified database as an attachment
return send_file(
modified_database_path, as_attachment=True, download_name="key_cache.db"
)
try:
conn = mysql.connector.connect(**get_db_config())
cursor = conn.cursor()
# Get column names
cursor.execute("SHOW COLUMNS FROM licenses")
columns = [row[0] for row in cursor.fetchall()]
# Build SELECT with Headers and Cookies as NULL
select_columns = []
for col in columns:
if col.lower() in ("headers", "cookies"):
select_columns.append("NULL AS " + col)
else:
select_columns.append(col)
select_query = f"SELECT {', '.join(select_columns)} FROM licenses"
cursor.execute(select_query)
rows = cursor.fetchall()
# Dump to SQL-like format
output = StringIO()
output.write("-- Dump of `licenses` table (Headers and Cookies are NULL)\n")
for row in rows:
values = ", ".join(
f"'{str(v).replace('\'', '\\\'')}'" if v is not None else "NULL"
for v in row
)
output.write(
f"INSERT INTO licenses ({', '.join(columns)}) VALUES ({values});\n"
)
# Write to a temp file for download
temp_dir = tempfile.gettempdir()
temp_path = os.path.join(temp_dir, "key_cache.sql")
with open(temp_path, "w", encoding="utf-8") as f:
f.write(output.getvalue())
@after_this_request
def remove_file(response):
try:
os.remove(temp_path)
except Exception:
pass
return response
return send_file(
temp_path, as_attachment=True, download_name="licenses_dump.sql"
)
except mysql.connector.Error as err:
return {"error": str(err)}, 500
2025-04-24 17:06:14 -04:00
_keycount_cache = {"count": None, "timestamp": 0}
@api_bp.route("/api/cache/keycount", methods=["GET"])
2025-04-24 17:06:14 -04:00
def get_count():
"""Get the count of the keys in the database."""
2025-04-24 17:06:14 -04:00
now = time.time()
if now - _keycount_cache["timestamp"] > 10 or _keycount_cache["count"] is None:
_keycount_cache["count"] = key_count()
_keycount_cache["timestamp"] = now
return jsonify({"count": _keycount_cache["count"]})
@api_bp.route("/api/decrypt", methods=["POST"])
2025-04-24 17:06:14 -04:00
def decrypt_data():
"""Decrypt the data."""
api_request_data = request.get_json(force=True)
# Helper to get fields or None if missing/empty
def get_field(key, default=""):
value = api_request_data.get(key, default)
return value if value != "" else default
api_request_pssh = get_field("pssh")
api_request_licurl = get_field("licurl")
api_request_proxy = get_field("proxy")
api_request_headers = get_field("headers")
api_request_cookies = get_field("cookies")
api_request_data_func = get_field("data")
# Device logic
device = get_field("device", "public")
if device in [
"default",
"CDRM-Project Public Widevine CDM",
"CDRM-Project Public PlayReady CDM",
"",
None,
]:
api_request_device = "public"
else:
api_request_device = device
username = ""
if api_request_device != "public":
username = session.get("username")
if not username:
return jsonify({"message": "Not logged in, not allowed"}), 400
if not user_allowed_to_use_device(device=api_request_device, username=username):
return jsonify({"message": "Not authorized / Not found"}), 403
result = api_decrypt(
pssh=api_request_pssh,
proxy=api_request_proxy,
license_url=api_request_licurl,
headers=api_request_headers,
cookies=api_request_cookies,
json_data=api_request_data_func,
device=api_request_device,
username=username,
)
if result["status"] == "success":
return jsonify({"status": "success", "message": result["message"]})
return jsonify({"status": "fail", "message": result["message"]})
@api_bp.route("/api/links", methods=["GET"])
def get_links():
"""Get the links."""
return jsonify(
{
"discord": icon_data["discord"],
"telegram": icon_data["telegram"],
"gitea": icon_data["gitea"],
}
)
2025-06-01 12:19:19 -04:00
@api_bp.route("/api/extension", methods=["POST"])
2025-06-01 12:19:19 -04:00
def verify_extension():
"""Verify the extension."""
return jsonify(
{
"status": True,
}
)