From 813e4f7e6f8941316af53a8e2fe4c1ed247d637c Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Fri, 19 Jun 2026 11:49:06 -0700 Subject: python migration --- .python-version | 1 + __pycache__/app.cpython-313.pyc | Bin 0 -> 15762 bytes __pycache__/config.cpython-313.pyc | Bin 0 -> 1494 bytes app.py | 365 ++++++++++++++++++++++++++++++++++ banner.jpg | Bin 0 -> 382245 bytes cache.json | 1 + config.php | 73 ------- config.py | 74 +++++++ crash.jpg | Bin 0 -> 6481 bytes debug.php | 231 ---------------------- game.jpeg | Bin 0 -> 2795333 bytes general.jpg | Bin 0 -> 73705 bytes illit.jpg | Bin 0 -> 29759 bytes index.css | 395 +++++++++++++++++++++++++++++++++++++ index.php | 363 ---------------------------------- info.png | Bin 0 -> 1270596 bytes pyproject.toml | 11 ++ requirements.txt | 3 + uv.lock | 264 +++++++++++++++++++++++++ 19 files changed, 1114 insertions(+), 667 deletions(-) create mode 100644 .python-version create mode 100644 __pycache__/app.cpython-313.pyc create mode 100644 __pycache__/config.cpython-313.pyc create mode 100644 app.py create mode 100644 banner.jpg create mode 100644 cache.json delete mode 100644 config.php create mode 100644 config.py create mode 100644 crash.jpg delete mode 100644 debug.php create mode 100644 game.jpeg create mode 100644 general.jpg create mode 100644 illit.jpg create mode 100644 index.css delete mode 100644 index.php create mode 100644 info.png create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 uv.lock diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000..c01cd98 Binary files /dev/null and b/__pycache__/app.cpython-313.pyc differ diff --git a/__pycache__/config.cpython-313.pyc b/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000..eda5147 Binary files /dev/null and b/__pycache__/config.cpython-313.pyc differ diff --git a/app.py b/app.py new file mode 100644 index 0000000..5395f2e --- /dev/null +++ b/app.py @@ -0,0 +1,365 @@ +import json +import random +import time +from pathlib import Path +from urllib.parse import quote + +import requests +from flask import Flask, Response, render_template_string, request, send_from_directory +from flask_cors import CORS + +from config import BASE_DIR, CONFIG + +app = Flask(__name__) +CORS(app) + +ALLOWED_STATIC_EXTENSIONS = { + ".css", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".bmp", + ".svg", + ".ico", +} + + +def safe_url(filename): + return quote(str(filename or ""), safe="/") + + +def generate_stars(count, opacity): + shadows = [] + for _ in range(count): + x = random.randint(0, 2000) + y = random.randint(0, 2000) + shadows.append(f"{x}px {y}px rgba(255, 255, 255, {opacity})") + return ", ".join(shadows) + + +def fetch_teamspeak_data(cfg): + cache_file = Path(cfg["cache_file"]) + if cache_file.exists() and (time.time() - cache_file.stat().st_mtime) < cfg["cache_time"]: + try: + return json.loads(cache_file.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + pass + + base_url = f"https://{cfg['host']}:{cfg['port']}/{cfg['server_id']}" + headers = {"x-api-key": cfg["api_key"]} + + try: + channels_res = requests.get(f"{base_url}/channellist", headers=headers, timeout=2) + clients_res = requests.get(f"{base_url}/clientlist?-groups", headers=headers, timeout=2) + channels_json = channels_res.json() + clients_json = clients_res.json() + except requests.RequestException: + return {"channels": [], "clients": []} + except ValueError: + return {"channels": [], "clients": []} + + data = { + "channels": channels_json.get("body") or [], + "clients": clients_json.get("body") or [], + } + + try: + cache_file.write_text(json.dumps(data), encoding="utf-8") + except OSError: + pass + + return data + + +def same_id(left, right): + return str(left or 0) == str(right or 0) + + +def sort_ts_channels(channels, pid=0): + ordered = [] + by_order = {} + + for channel in channels: + if same_id(channel.get("pid", 0), pid): + by_order[str(channel.get("channel_order", 0))] = channel + + current_order = "0" + max_iterations = len(channels) + 1 + iterations = 0 + + while current_order in by_order and iterations < max_iterations: + channel = by_order[current_order] + ordered.append(channel) + ordered.extend(sort_ts_channels(channels, channel.get("cid"))) + current_order = str(channel.get("cid")) + iterations += 1 + + return ordered + + +def build_context(): + start_time = time.perf_counter() + ts_data = fetch_teamspeak_data(CONFIG["server"]) + api_ping = round((time.perf_counter() - start_time) * 1000) + + raw_channels = ts_data.get("channels") or [] + for channel in raw_channels: + channel["channel_name"] = channel.get("channel_name").replace("[Cspacer]", "") + raw_clients = ts_data.get("clients") or [] + is_online = bool(raw_channels) + + channels = sort_ts_channels(raw_channels, 0) + + clients_by_channel = {} + total_real_clients = 0 + + if is_online: + for client in raw_clients: + if int(client.get("client_type") or 0) == 1: + continue + clients_by_channel.setdefault(str(client.get("cid")), []).append(client) + total_real_clients += 1 + + return { + "config": CONFIG, + "api_ping": api_ping, + "channels": channels, + "clients_by_channel": clients_by_channel, + "is_online": is_online, + "safe_url": safe_url, + "small_stars": generate_stars(150, 0.4), + "medium_stars": generate_stars(50, 0.6), + "large_stars": generate_stars(15, 0.8), + "total_real_clients": total_real_clients, + } + + +FRAGMENT_TEMPLATE = """ +{% if is_online %} +
+ Users Online + {{ total_real_clients }} +
+
+ {% for channel in channels %} + {% set c_name = channel.get('channel_name', '') %} + {% set cid = channel.get('cid')|string %} + {% set channel_clients = clients_by_channel.get(cid, []) %} + {% set bg_img = config.channel_banners.get(c_name, config.generic_images.get('background', '')) %} + {% set icon_img = config.channel_icons.get(c_name, config.generic_images.get('icon', '')) %} + {% set max_clients = channel.get('channel_maxclients') %} + + {% if bg_img %} + {% set bg_style = "background-image: linear-gradient(90deg, rgba(9, 18, 32, 0.96) 0%, rgba(9, 18, 32, 0.78) 45%, rgba(9, 18, 32, 0.94) 100%), url('" ~ safe_url(bg_img) ~ "');" %} + {% else %} + {% set bg_style = "" %} + {% endif %} + +
+
+
+ {% if icon_img %} + + {% endif %} +

{{ c_name }}

+
+ {# Only show client count for non-spacer channels #} + {% if '[spacer' not in c_name %} + + {{ channel_clients|length }} / {{ '∞' if max_clients == -1 or max_clients == '-1' else max_clients }} + + {% endif %} +
+ + {# Only show client list for non-spacer channels #} + {% if '[spacer' not in c_name|lower and '[cspacer' not in c_name|lower and channel_clients %} +
    + {% for client in channel_clients %} +
  • + {% set role_icon = namespace(path='') %} + {% for group_id in (client.get('client_servergroups', '')|string).split(',') %} + {% if not role_icon.path and group_id in config.role_icons %} + {% set role_icon.path = config.role_icons[group_id] %} + {% endif %} + {% endfor %} + + {% if role_icon.path %} + Role + {% else %} + + {% endif %} + {{ client.get('client_nickname', '') }} +
  • + {% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+{% else %} +
Unable to reach the TeamSpeak server.
+{% endif %} +""" + +PAGE_TEMPLATE = """ + + + + + + + {{ config.ui.title }} + + + + + + +{% if config.ui.enable_stars %} + +{% endif %} + +
+ {% if is_online %}
{% endif %} + + {% if config.ui.banner_url %} + + {% endif %} + +
+
+ TeamSpeak Server Status +

{{ config.ui.title }}

+

{{ config.ui.subtitle }}

+ {% if is_online %} +
SERVER ONLINE
+ {% else %} +
SERVER OFFLINE
+ {% endif %} +
+ + {{ config.buttons.join }} +

{{ config.buttons.download_text }} {{ config.buttons.download_link }}

+ +
+ {{ html_fragment|safe }} +
+ + {% if config.external_links %} + + {% endif %} + + +
+
+ + + + +""" + + +def render_fragment(context): + return render_template_string(FRAGMENT_TEMPLATE, **context) + + +@app.route("/") +def index(): + context = build_context() + html_fragment = render_fragment(context) + + if request.args.get("ajax") == "1": + return Response(html_fragment, mimetype="text/html; charset=utf-8") + + return render_template_string(PAGE_TEMPLATE, html_fragment=html_fragment, **context) + + +@app.route("/") +def static_asset(filename): + path = (BASE_DIR / filename).resolve() + if ( + path.is_file() + and BASE_DIR in path.parents + and path.suffix.lower() in ALLOWED_STATIC_EXTENSIONS + ): + return send_from_directory(BASE_DIR, filename) + return Response("Not found", status=404) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) diff --git a/banner.jpg b/banner.jpg new file mode 100644 index 0000000..31b5611 Binary files /dev/null and b/banner.jpg differ diff --git a/cache.json b/cache.json new file mode 100644 index 0000000..c2336bd --- /dev/null +++ b/cache.json @@ -0,0 +1 @@ +{"channels": [{"channel_name": "[Cspacer]INFORMATION", "channel_needed_subscribe_power": "0", "channel_order": "0", "cid": "13", "pid": "0", "total_clients": "0"}, {"channel_name": "[Cspacer]GAME SERVERS", "channel_needed_subscribe_power": "0", "channel_order": "13", "cid": "17", "pid": "0", "total_clients": "0"}, {"channel_name": "[Cspacer]\ud83d\udd07 AFK NPCs \ud83d\udd07", "channel_needed_subscribe_power": "0", "channel_order": "17", "cid": "4", "pid": "0", "total_clients": "1"}, {"channel_name": "General", "channel_needed_subscribe_power": "0", "channel_order": "4", "cid": "1", "pid": "0", "total_clients": "0"}, {"channel_name": "CRASHING OUT", "channel_needed_subscribe_power": "0", "channel_order": "1", "cid": "9", "pid": "0", "total_clients": "0"}, {"channel_name": "Wonhee HQ", "channel_needed_subscribe_power": "0", "channel_order": "9", "cid": "10", "pid": "0", "total_clients": "1"}], "clients": [{"cid": "4", "clid": "71", "client_channel_group_id": "8", "client_channel_group_inherited_channel_id": "4", "client_database_id": "1", "client_nickname": "serveradmin", "client_servergroups": "2", "client_type": "1"}, {"cid": "10", "clid": "1", "client_channel_group_id": "8", "client_channel_group_inherited_channel_id": "10", "client_database_id": "29", "client_nickname": "The Real Wonhee", "client_servergroups": "8", "client_type": "0"}]} \ No newline at end of file diff --git a/config.php b/config.php deleted file mode 100644 index 6a40406..0000000 --- a/config.php +++ /dev/null @@ -1,73 +0,0 @@ - [ - 'host' => '127.0.0.1', // IP du serveur (ex: 192.168.1.50) - 'port' => '10080', // Port HTTP WebQuery (TS6) - 'api_key' => 'VOTRE_CLE_API_ICI', - 'server_id' => '1', - 'ts_domain' => 'ts.votre-domaine.com', // Utilisé pour le bouton de connexion - 'cache_time' => 30, // Actualisation en secondes (anti-spam) - 'cache_file' => __DIR__ . '/cache.json' - ], - - // Design et Textes principaux - 'ui' => [ - 'titre' => 'Mon Serveur TeamSpeak', - 'sous_titre' => 'Bienvenue sur notre espace de discussion.', - 'couleur_fond' => '#070a10', // Couleur de fond principale - 'couleur_panneau' => '#131823', // Couleur des blocs de canaux - 'couleur_accent' => '#3b82f6', // Couleur des boutons et de la barre de temps - 'couleur_texte' => '#e2e8f0', - 'banniere_url' => '', // Laissez vide ('') pour ne pas afficher d'en-tête - 'activer_etoiles' => false, // Passez à true pour activer une animation spatiale en arrière-plan - ], - - // Textes et liens de l'interface - 'boutons' => [ - 'rejoindre' => 'Se connecter au serveur', - 'telecharger_texte' => 'Le logiciel n\'est pas installé sur votre PC ?', - 'telecharger_lien' => 'Télécharger TeamSpeak', - 'telecharger_url' => 'https://www.teamspeak.com/fr/downloads/' // Lien officiel au cas où il change - ], - - // Liens utiles (Ajoutez ou retirez autant de lignes que vous le souhaitez) - 'liens_externes' => [ - 'Notre Site Web' => 'https://www.votre-site.com', - 'Discord / Forum'=> 'https://discord.gg/votre-lien' - ], - - // Pied de page - 'footer' => [ - 'texte' => 'Hébergé par', - 'auteur' => 'VotrePseudo', - 'lien_auteur' => 'https://github.com/votre-pseudo' - ], - - // Personnalisation des canaux (Nom exact du canal => nom du fichier image) - 'bannieres_canaux' => [ - // 'Accueil' => 'accueil_fond.png', - // 'Général' => 'general_fond.png', - ], - - 'icones_canaux' => [ - // 'Accueil' => 'accueil_icone.png', - // 'Général' => 'general_icone.png', - ], - - 'images_generiques' => [ - 'fond' => '', // Image appliquée par défaut aux canaux inconnus - 'icone' => '' // Icône appliquée par défaut aux canaux inconnus - ], - - // Groupes de serveur (Rôles) : 'ID_DU_GROUPE' => 'Image' - 'icones_roles' => [ - // '6' => 'admin.png', - // '9' => 'vip.png', - ] -]; \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..936b159 --- /dev/null +++ b/config.py @@ -0,0 +1,74 @@ +"""TeamSpeak Web Explorer configuration.""" + +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent + +CONFIG = { + # Server connection settings + "server": { + "host": "chat.moekyun.me", # Server IP or hostname, e.g. 192.168.1.50 + "port": "8443", # HTTP WebQuery port (TS6) + "api_key": "", + "server_id": "1", + "ts_domain": "chat.moekyun.me", # Used by the connect button + "cache_time": 30, # Refresh interval in seconds (anti-spam) + "cache_file": BASE_DIR / "cache.json", + }, + + # Main design and copy + "ui": { + "title": "Overwatch Diddly Server", + "subtitle": "Jump into voice, organize channels, and see who is online.", + "background_color": "#07111f", # Main background color + "panel_color": "#101b2d", # Main panel and channel block color + "accent_color": "#2589ff", # Buttons, highlights, and refresh timer + "text_color": "#e8f2ff", + "banner_url": "banner.jpg", # Leave empty ('') to hide the header banner + "enable_stars": True, # Set to True to enable a subtle animated space background + }, + + # Interface buttons and links + "buttons": { + "join": "Connect to TeamSpeak", + "download_text": "Teamspeak is not installed yet?", + "download_link": "Download Teamspeak", + "download_url": "https://www.teamspeak.com/en/downloads/", # Official download link + }, + + # Useful links. Add or remove as many rows as you want. + "external_links": { + }, + + # Footer + "footer": { + "text": "another moekyun service", + "author": "", + "author_url": "", + }, + + # Channel customization. Exact channel name => image filename. + "channel_banners": { + "INFORMATION": "info.png", + "GAME SERVERS": "game.jpeg", + "General": "general.jpg", + "CRASHING OUT": "crash.png", + "Wonhee HQ": "illit.jpg", + }, + + "channel_icons": { + # "Lobby": "lobby_icon.png", + # "General": "general_icon.png", + }, + + "generic_images": { + "background": "", # Default image applied to unknown channels + "icon": "", # Default icon applied to unknown channels + }, + + # Server groups / roles: 'GROUP_ID' => 'Image' + "role_icons": { + # "6": "admin.png", + # "9": "vip.png", + }, +} diff --git a/crash.jpg b/crash.jpg new file mode 100644 index 0000000..bdf4a00 Binary files /dev/null and b/crash.jpg differ diff --git a/debug.php b/debug.php deleted file mode 100644 index 5f8e39c..0000000 --- a/debug.php +++ /dev/null @@ -1,231 +0,0 @@ - version_compare(PHP_VERSION, '7.4.0', '>='), - 'value' => PHP_VERSION, - 'msg' => 'PHP 7.4 ou supérieur recommandé.' - ]; - $reqs['allow_url_fopen'] = [ - 'status' => ini_get('allow_url_fopen'), - 'value' => ini_get('allow_url_fopen') ? 'Activé' : 'Désactivé', - 'msg' => 'Requis pour communiquer avec l\'API distante.' - ]; - $reqs['Permissions Dossier'] = [ - 'status' => is_writable(__DIR__), - 'value' => is_writable(__DIR__) ? 'Écriture OK' : 'Lecture seule', - 'msg' => 'Requis pour créer le fichier cache.json.' - ]; - return $reqs; -} - -function test_tcp_connection($host, $port) { - $start = microtime(true); - $fp = @fsockopen($host, $port, $errno, $errstr, 2); - $time = round((microtime(true) - $start) * 1000); - - if (!$fp) { - return ['status' => false, 'msg' => "Connexion refusée ({$errstr})", 'time' => '-']; - } else { - fclose($fp); - return ['status' => true, 'msg' => 'Port ouvert et accessible', 'time' => $time . ' ms']; - } -} - -function test_endpoint($url, $api_key, $endpoint_name) { - $options = [ - 'http' => [ - 'header' => "x-api-key: {$api_key}\r\n", - 'method' => 'GET', - 'timeout' => 3, - 'ignore_errors' => true - ] - ]; - $context = stream_context_create($options); - - $start_time = microtime(true); - $response = @file_get_contents($url, false, $context); - $ping = round((microtime(true) - $start_time) * 1000); - - $http_code = "Inconnu"; - if (isset($http_response_header) && is_array($http_response_header)) { - if (preg_match('#HTTP/[0-9\.]+\s+([0-9]+)#', $http_response_header[0], $matches)) { - $http_code = intval($matches[1]); - } - } - - $html = "
"; - $html .= "

➔ /{$endpoint_name}

"; - - if ($response === false) { - $html .= "Échec HTTP (Timeout)
"; - $html .= "

Impossible de récupérer les données HTTP.

"; - } else { - $data = json_decode($response, true); - $ts_code = isset($data['status']['code']) ? $data['status']['code'] : 'N/A'; - - if ($http_code == 200 && $ts_code === 0) { - $html .= "HTTP 200 TS Code: 0 (OK)"; - } elseif ($ts_code === 5120) { - $html .= "HTTP {$http_code} TS Code: 5120 (Out of Scope)"; - } elseif ($http_code == 401 || $ts_code === 2568) { - $html .= "HTTP {$http_code} TS Code: {$ts_code} (Clé Invalide)"; - } else { - $html .= "HTTP {$http_code} TS Code: {$ts_code}"; - } - $html .= "{$ping} ms
"; - - if ($ts_code === 5120 && $endpoint_name == 'serverinfo') { - $html .= "
ℹ️ Note : Erreur 5120 normale si votre clé API est configurée en lecture seule (scope=read).
"; - } - - $pretty_json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); - $html .= "
" . htmlspecialchars($pretty_json) . "
"; - } - $html .= ""; - return $html; -} -?> - - - - - - Diagnostic API TS6 - - - - -
-
- ⚠️ DANGER : NE LAISSEZ JAMAIS CE FICHIER SUR VOTRE SERVEUR WEB PUBLIC.
- Supprimez 'debug.php' de votre hébergement après utilisation. -
- - -
-

❌ Fichier config.php introuvable

-

Veuillez créer votre fichier config.php avant de lancer le diagnostic.

-
- - -

🔍 Diagnostic de l'Environnement

- -
-
-

⚙️ Configuration Détectée

-
Hôte API
-
Port API
-
ID Serveur
-
Clé API
-
- -
-

💻 Serveur Web (PHP)

- $req): ?> -
- - - - -
- -
- -
-

📡 Réseau & Cache

- -
- Ping TCP () - - - -
-
- Fichier Cache - - - -
- -
- Âge du Cache - -
-
- Taille du Cache - -
- -
-
- -

🔌 Tests de l'API TeamSpeak

- - - - -
- - - \ No newline at end of file diff --git a/game.jpeg b/game.jpeg new file mode 100644 index 0000000..d651107 Binary files /dev/null and b/game.jpeg differ diff --git a/general.jpg b/general.jpg new file mode 100644 index 0000000..f2f5bc0 Binary files /dev/null and b/general.jpg differ diff --git a/illit.jpg b/illit.jpg new file mode 100644 index 0000000..60f3655 Binary files /dev/null and b/illit.jpg differ diff --git a/index.css b/index.css new file mode 100644 index 0000000..26229e2 --- /dev/null +++ b/index.css @@ -0,0 +1,395 @@ +:root { + --cl-black: hsl(140, 1%, 6%); + --cl-gray-0: hsl(140, 2%, 8%); + --cl-gray-1: hsl(140, 2%, 12%); + --cl-gray-2: hsl(140, 4%, 16%); + --cl-gray-3: hsl(140, 4%, 24%); + --cl-gray-4: hsl(140, 4%, 36%); + --cl-gray-5: hsl(140, 4%, 44%); + --cl-gray-6: hsl(80, 8%, 52%); + --cl-gray-7: hsl(70, 8%, 58%); + --cl-gray-8: hsl(60, 16%, 66%); + --cl-gray-9: hsl(40, 32%, 78%); + --cl-gray-10: hsl(30, 32%, 84%); + --cl-white: hsl(26, 64%, 88%); + --cl-red-6: hsl(4, 83%, 67%); + --cl-red-7: hsl(4, 75%, 75%); + --cl-orange-6: hsl(26, 84%, 62%); + --cl-orange-7: hsl(26, 84%, 74%); + --cl-yellow: hsl(37, 80%, 69%); + --cl-green-6: hsl(120, 41%, 64%); + --cl-green-7: hsl(120, 42%, 75%); + --cl-cyan-6: hsl(160, 41%, 64%); + --cl-cyan-7: hsl(160, 32%, 75%); + --cl-blue-6: hsl(200, 55%, 64%); + --cl-blue-8: hsl(201, 55%, 80%); + --cl-magenta-7: hsl(320, 59%, 72%); + --cl-magenta-8: hsl(320, 61%, 80%); +} + +* { + box-sizing: border-box; + border-radius: 0px !important; +} + +body { + min-height: 100vh; + margin: 5vh 10vw; + display: flex; + justify-content: center; + font-family: "Iosevka", "Roboto Mono", monospace; + background-color: var(--cl-black); + color: var(--cl-white); +} + +/* Neutralize the old space background elements */ +body::before, +#space-bg, +.star-layer, +.stars-s, +.stars-m, +.stars-l { + display: none; +} + +.app-container { + position: relative; + width: 100%; + max-width: 760px; + height: fit-content; + background-color: var(--cl-gray-0); + border: 2px solid var(--cl-gray-2); +} + +.app-container::before { + display: none; +} + +.banner { + width: 100%; + height: auto; + display: block; + border-bottom: 2px solid var(--cl-gray-2); +} + +.content-wrapper { + position: relative; + padding: 2rem; +} + +.progress-bar { + position: absolute; + top: 0; + left: 0; + z-index: 10; + width: 100%; + height: 4px; + background-color: var(--cl-orange-6); +} + +.header { + margin-bottom: 1.75rem; + text-align: left; +} + +.eyebrow { + display: block; + margin-bottom: 0.65rem; + color: var(--cl-gray-6); + font-size: 0.85rem; + font-weight: bold; + text-transform: uppercase; +} + +.eyebrow::before, +.eyebrow::after { + display: none; +} + +.header h1 { + margin: 0 0 0.5rem; + color: var(--cl-white); + font-size: 2.2rem; + font-weight: bold; +} + +.header p { + max-width: 100%; + margin: 0; + color: var(--cl-gray-7); + font-size: 1rem; + line-height: 1.6; +} + +.status-badge { + display: inline-flex; + align-items: center; + margin-top: 1rem; + padding: 4px 12px; + font-size: 0.85rem; + font-weight: bold; + text-transform: uppercase; + border: 2px solid; +} + +.status-online { + color: var(--cl-green-6); + border-color: var(--cl-green-6); + background-color: transparent; +} + +.status-offline { + color: var(--cl-red-6); + border-color: var(--cl-red-6); + background-color: transparent; +} + +.pulse-dot { + width: 10px; + height: 10px; + margin-right: 0.75rem; + background-color: currentColor; +} + +.server-stats { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; + padding: 1rem; + border: 2px solid var(--cl-gray-2); + background-color: var(--cl-gray-1); +} + +.stat-item { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-bottom: 10px; + padding: 0.75rem; + border: 2px solid var(--cl-gray-2); + background-color: var(--cl-gray-0); +} + +.stat-label { + margin-bottom: 0.5rem; + color: var(--cl-gray-6); + font-size: 0.75rem; + font-weight: bold; + text-transform: uppercase; +} + +.stat-value { + color: var(--cl-white); + font-size: 1.25rem; + font-weight: bold; +} + +.btn-primary { + display: block; + width: 100%; + margin-bottom: 1rem; + padding: 12px; + color: var(--cl-orange-6); + text-align: center; + text-decoration: none; + text-transform: uppercase; + font-size: 1rem; + font-weight: bold; + border: 2px solid var(--cl-orange-6); + background-color: transparent; +} + +.btn-primary::before { + content: "[ "; +} + +.btn-primary::after { + content: " ]"; +} + +.btn-primary:hover { + color: var(--cl-black); + background-color: var(--cl-magenta-7); + border-color: transparent; +} + +.help-text { + margin: 1rem 0 2rem; + color: var(--cl-gray-6); + text-align: left; + font-size: 0.9rem; +} + +.help-text a { + color: var(--cl-orange-6); + text-decoration: none; + border-bottom: 2px solid var(--cl-orange-6); +} + +.help-text a:hover { + color: var(--cl-black); + background-color: var(--cl-magenta-7); + border-color: transparent; +} + +.channel-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.channel { + border: 2px solid var(--cl-gray-2); + background-color: var(--cl-gray-1); +} + +.channel-header { + padding: 0.75rem 1rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + color: var(--cl-white); + border-bottom: 2px solid var(--cl-gray-2); + background-color: var(--cl-gray-2); +} + +.channel-title-group { + display: flex; + align-items: center; + margin: 0; +} + +.channel-title-group::before { + content: ">"; + margin-right: 0.75rem; + color: var(--cl-orange-6); + font-weight: bold; +} + +.channel-title-group h2 { + margin: 0; + font-size: 1rem; + font-weight: bold; +} + +.channel-icon { + display: none; +} + +.channel-meta { + padding: 4px 8px; + color: var(--cl-cyan-6); + font-size: 0.85rem; + font-weight: bold; + border: 2px solid var(--cl-cyan-6); +} + +.client-list { + margin: 0; + padding: 0.5rem; + list-style: none; +} + +.client { + padding: 0.5rem 1rem; + display: flex; + align-items: center; + color: var(--cl-gray-8); + font-size: 0.95rem; + border: 2px solid transparent; +} + +.client::before { + content: "-"; + margin-right: 1rem; + color: var(--cl-gray-5); +} + +.client:hover { + color: var(--cl-white); + background-color: var(--cl-gray-2); + border-color: var(--cl-gray-4); +} + +.role-icon, +.client-no-role-icon { + display: none; +} + +.empty-server { + padding: 2rem 1rem; + color: var(--cl-gray-6); + text-align: center; + font-size: 1rem; + border: 2px dashed var(--cl-gray-4); + background-color: var(--cl-gray-1); +} + +.footer-links { + margin-top: 2.5rem; + padding-top: 1.5rem; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + gap: 1rem; + border-top: 2px solid var(--cl-gray-2); +} + +.footer-links a { + color: var(--cl-orange-6); + font-size: 0.9rem; + font-weight: bold; + text-decoration: none; + border: 2px solid transparent; + padding: 2px 4px; +} + +.footer-links a:hover { + color: var(--cl-black); + background-color: var(--cl-magenta-7); +} + +.credits { + margin-top: 1.5rem; + color: var(--cl-gray-6); + text-align: left; + font-size: 0.85rem; +} + +.credits a { + color: var(--cl-orange-6); + text-decoration: none; + border-bottom: 2px solid var(--cl-orange-6); +} + +.credits a:hover { + color: var(--cl-black); + background-color: var(--cl-magenta-7); + border-color: transparent; +} + +#ajax-container { + transition: none; +} + +@media (max-width: 640px) { + body { + margin: 2vh 4vw; + } + + .content-wrapper { + padding: 1.25rem; + } + + .server-stats { + grid-template-columns: 1fr; + } + + .channel-header { + align-items: flex-start; + flex-direction: column; + gap: 0.75rem; + } +} diff --git a/index.php b/index.php deleted file mode 100644 index 60d3239..0000000 --- a/index.php +++ /dev/null @@ -1,363 +0,0 @@ - ['header' => "x-api-key: {$cfg['api_key']}\r\n", 'method' => 'GET', 'timeout' => 2, 'ignore_errors' => true]]; - $context = stream_context_create($options); - - $base_url = "http://{$cfg['host']}:{$cfg['port']}/{$cfg['server_id']}"; - $channels_res = @file_get_contents("$base_url/channellist", false, $context); - $clients_res = @file_get_contents("$base_url/clientlist?-groups", false, $context); - - if ($channels_res !== false && $clients_res !== false) { - $data = [ - 'channels' => json_decode($channels_res, true)['body'] ?? [], - 'clients' => json_decode($clients_res, true)['body'] ?? [] - ]; - file_put_contents($cache_file, json_encode($data)); - return $data; - } - return ['channels' => [], 'clients' => []]; -} - -// Tri hiérarchique -function sort_ts_channels(array $channels, $pid = 0) { - $ordered = []; $by_order = []; - foreach ($channels as $c) { if (($c['pid'] ?? 0) == $pid) $by_order[$c['channel_order'] ?? 0] = $c; } - $current_order = 0; $max_iterations = count($channels) + 1; $i = 0; - while (isset($by_order[$current_order]) && $i < $max_iterations) { - $channel = $by_order[$current_order]; $ordered[] = $channel; - $sub_channels = sort_ts_channels($channels, $channel['cid']); - foreach ($sub_channels as $sc) $ordered[] = $sc; - $current_order = $channel['cid']; $i++; - } - return $ordered; -} - -// Traitement des données -$start_time = microtime(true); -$ts_data = fetch_teamspeak_data($config['server']); -$api_ping = round((microtime(true) - $start_time) * 1000); // Calcule le temps de réponse de l'API locale - -$raw_channels = $ts_data['channels']; -$raw_clients = $ts_data['clients']; -$is_online = !empty($raw_channels); - -$channels = sort_ts_channels($raw_channels, 0); - -$clients_by_channel = []; -$total_real_clients = 0; - -if ($is_online) { - foreach ($raw_clients as $client) { - if (($client['client_type'] ?? 0) == 1) continue; // On ignore les bots/ServerQuery - $clients_by_channel[$client['cid']][] = $client; - $total_real_clients++; - } -} - -// Rendu du fragment AJAX -ob_start(); -if ($is_online): ?> - -
-
- En Ligne - -
-
- Salons - -
-
- Latence API - ms -
-
- -
- -
-
-
- - - -

-
- - / - -
- - -
    - -
  • - '; - if (isset($client['client_servergroups'])) { - $groups = explode(',', $client['client_servergroups']); - foreach ($groups as $g_id) { - if (isset($config['icones_roles'][$g_id])) { - $role_icon = 'Rôle'; - break; - } - } - } - echo $role_icon; - ?> - -
  • - -
- -
- -
- -
Impossible d'établir le contact avec le serveur central.
- - - - - - - - <?php echo e($config['ui']['titre']); ?> - - - - - - - - - -
-
- - - - - -
-
-

-

- -
TRANSMISSION ACTIVE
- -
SIGNAL PERDU
- -
- - -

- -
- -
- - - - - -
- — Scan radar : s -
-
-
- - - - \ No newline at end of file diff --git a/info.png b/info.png new file mode 100644 index 0000000..fd27228 Binary files /dev/null and b/info.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ffeca58 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "ts6-web-explorer" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "flask>=3.1.3", + "flask-cors>=6.0.5", + "requests>=2.34.2", +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0062c2f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask>=3.0,<4 +flask-cors>=4.0,<5 +requests>=2.31,<3 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..91d187d --- /dev/null +++ b/uv.lock @@ -0,0 +1,264 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + +[[package]] +name = "flask-cors" +version = "6.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/03/4e464a50860f9adf08b5c1d3479cb8ea1f12af2aa69535c7042c6e628135/flask_cors-6.0.5.tar.gz", hash = "sha256:30c5031552cd59f620ac0c8211dac45b345d3b2df310e7721879e4f46ef9c601", size = 101386, upload-time = "2026-06-08T20:20:17.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/55/5bb1a2d918e9f02f131e47a59032bae70e48050e986e941511fd737a935c/flask_cors-6.0.5-py3-none-any.whl", hash = "sha256:68fcf75693e961f3af26683b23c4b9a8fb6b64de17d20d0c37b95e8de7ab2ed8", size = 16692, upload-time = "2026-06-08T20:20:16.247Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "ts6-web-explorer" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "flask" }, + { name = "flask-cors" }, + { name = "requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "flask", specifier = ">=3.1.3" }, + { name = "flask-cors", specifier = ">=6.0.5" }, + { name = "requests", specifier = ">=2.34.2" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] -- cgit v1.2.3