diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-06-19 11:49:06 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-06-19 11:49:06 -0700 |
| commit | 813e4f7e6f8941316af53a8e2fe4c1ed247d637c (patch) | |
| tree | 5a930b836fa5bfff15b31bb87a1c6cc06b26df75 | |
| parent | f86d15fdd9f5b022c1b1a23b2219952d13641863 (diff) | |
python migration
| -rw-r--r-- | .python-version | 1 | ||||
| -rw-r--r-- | __pycache__/app.cpython-313.pyc | bin | 0 -> 15762 bytes | |||
| -rw-r--r-- | __pycache__/config.cpython-313.pyc | bin | 0 -> 1494 bytes | |||
| -rw-r--r-- | app.py | 365 | ||||
| -rw-r--r-- | banner.jpg | bin | 0 -> 382245 bytes | |||
| -rw-r--r-- | cache.json | 1 | ||||
| -rw-r--r-- | config.php | 73 | ||||
| -rw-r--r-- | config.py | 74 | ||||
| -rw-r--r-- | crash.jpg | bin | 0 -> 6481 bytes | |||
| -rw-r--r-- | debug.php | 231 | ||||
| -rw-r--r-- | game.jpeg | bin | 0 -> 2795333 bytes | |||
| -rw-r--r-- | general.jpg | bin | 0 -> 73705 bytes | |||
| -rw-r--r-- | illit.jpg | bin | 0 -> 29759 bytes | |||
| -rw-r--r-- | index.css | 395 | ||||
| -rw-r--r-- | index.php | 363 | ||||
| -rw-r--r-- | info.png | bin | 0 -> 1270596 bytes | |||
| -rw-r--r-- | pyproject.toml | 11 | ||||
| -rw-r--r-- | requirements.txt | 3 | ||||
| -rw-r--r-- | uv.lock | 264 |
19 files changed, 1114 insertions, 667 deletions
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 Binary files differnew file mode 100644 index 0000000..c01cd98 --- /dev/null +++ b/__pycache__/app.cpython-313.pyc diff --git a/__pycache__/config.cpython-313.pyc b/__pycache__/config.cpython-313.pyc Binary files differnew file mode 100644 index 0000000..eda5147 --- /dev/null +++ b/__pycache__/config.cpython-313.pyc @@ -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 %} + <div class="stat-item"> + <span class="stat-label">Users Online</span> + <span class="stat-value">{{ total_real_clients }}</span> + </div> + <div class="channel-list"> + {% 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 %} + + <article class="channel"> + <header class="channel-header" style="{{ bg_style }}"> + <div class="channel-title-group"> + {% if icon_img %} + <img src="{{ safe_url(icon_img) }}" class="channel-icon" alt="" aria-hidden="true"> + {% endif %} + <h2>{{ c_name }}</h2> + </div> + {# Only show client count for non-spacer channels #} + {% if '[spacer' not in c_name %} + <span class="channel-meta"> + {{ channel_clients|length }} / {{ '∞' if max_clients == -1 or max_clients == '-1' else max_clients }} + </span> + {% endif %} + </header> + + {# 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 %} + <ul class="client-list"> + {% for client in channel_clients %} + <li class="client"> + {% 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 %} + <img src="{{ safe_url(role_icon.path) }}" class="role-icon" alt="Role"> + {% else %} + <span class="client-no-role-icon"></span> + {% endif %} + <span>{{ client.get('client_nickname', '') }}</span> + </li> + {% endfor %} + </ul> + {% endif %} + </article> + {% endfor %} + </div> +{% else %} + <div class="empty-server">Unable to reach the TeamSpeak server.</div> +{% endif %} +""" + +PAGE_TEMPLATE = """ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{ config.ui.subtitle }}"> + <title>{{ config.ui.title }}</title> + <link rel="icon" type="image/png" href="logo.png"> + <link rel="stylesheet" href="index.css"> + <style> + :root { + --bg-color: {{ config.ui.background_color }}; + --panel-bg: {{ config.ui.panel_color }}; + --accent: {{ config.ui.accent_color }}; + --text-main: {{ config.ui.text_color }}; + } + + {% if config.ui.enable_stars %} + .stars-s { box-shadow: {{ small_stars }}; } + .stars-m { box-shadow: {{ medium_stars }}; } + .stars-l { box-shadow: {{ large_stars }}; } + {% endif %} + </style> +</head> +<body> + +{% if config.ui.enable_stars %} +<div id="space-bg" aria-hidden="true"> + <div class="star-layer stars-s"></div> + <div class="star-layer stars-m"></div> + <div class="star-layer stars-l"></div> +</div> +{% endif %} + +<main class="app-container"> + {% if is_online %}<div class="progress-bar" id="refresh-bar"></div>{% endif %} + + {% if config.ui.banner_url %} + <img src="{{ safe_url(config.ui.banner_url) }}" alt="Header banner" class="banner"> + {% endif %} + + <div class="content-wrapper"> + <header class="header"> + <span class="eyebrow">TeamSpeak Server Status</span> + <h1>{{ config.ui.title }}</h1> + <p>{{ config.ui.subtitle }}</p> + {% if is_online %} + <div class="status-badge status-online"><span class="pulse-dot"></span>SERVER ONLINE</div> + {% else %} + <div class="status-badge status-offline"><span class="pulse-dot"></span> SERVER OFFLINE</div> + {% endif %} + </header> + + <a href="ts3server://{{ config.server.ts_domain }}" class="btn-primary">{{ config.buttons.join }}</a> + <p class="help-text">{{ config.buttons.download_text }} <a href="{{ config.buttons.download_url }}" target="_blank" rel="noopener">{{ config.buttons.download_link }}</a></p> + + <section id="ajax-container" aria-live="polite"> + {{ html_fragment|safe }} + </section> + + {% if config.external_links %} + <nav class="footer-links"> + {% for name, url in config.external_links.items() %} + <a href="{{ url }}" target="_blank" rel="noopener">{{ name }}</a> + {% endfor %} + </nav> + {% endif %} + + <footer class="credits"> + {{ config.footer.text }} <a href="{{ config.footer.author_url }}" target="_blank" rel="noopener">{{ config.footer.author }}</a> — Refresh scan: <span id="timer-text">{{ config.server.cache_time }}</span>s + </footer> + </div> +</main> + +<script> + {% if is_online %} + document.addEventListener("DOMContentLoaded", () => { + let timeLeft = {{ config.server.cache_time|int }}; + const totalTime = {{ config.server.cache_time|int }}; + + const progressBar = document.getElementById('refresh-bar'); + const timerText = document.getElementById('timer-text'); + const container = document.getElementById('ajax-container'); + + function resetProgressBar() { + progressBar.style.transition = 'none'; + progressBar.style.width = '100%'; + void progressBar.offsetWidth; + progressBar.style.transition = `width ${totalTime}s linear`; + progressBar.style.width = '0%'; + } + + resetProgressBar(); + + setInterval(() => { + timeLeft--; + timerText.innerText = timeLeft; + + if (timeLeft <= 0) { + container.style.opacity = '0.5'; + fetch(window.location.href.split('?')[0] + '?ajax=1', { cache: 'no-store', headers: {'X-Requested-With': 'XMLHttpRequest'} }) + .then(r => { + if (!r.ok) throw new Error("HTTP error " + r.status); + return r.text(); + }) + .then(html => { + container.innerHTML = html; + container.style.opacity = '1'; + + timeLeft = totalTime; + timerText.innerText = timeLeft; + + resetProgressBar(); + }) + .catch(err => { + console.error("Refresh failed:", err); + container.style.opacity = '1'; + timeLeft = totalTime; + resetProgressBar(); + }); + } + }, 1000); + }); + {% endif %} +</script> +</body> +</html> +""" + + +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("/<path:filename>") +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 Binary files differnew file mode 100644 index 0000000..31b5611 --- /dev/null +++ b/banner.jpg 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 @@ -<?php
-/**
- * Configuration de l'Explorateur TeamSpeak
- * Renommez ce fichier en 'config.php' et remplissez vos informations.
- */
-
-return [
- // Paramètres de connexion au serveur
- 'server' => [
- '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 Binary files differnew file mode 100644 index 0000000..bdf4a00 --- /dev/null +++ b/crash.jpg diff --git a/debug.php b/debug.php deleted file mode 100644 index 5f8e39c..0000000 --- a/debug.php +++ /dev/null @@ -1,231 +0,0 @@ -<?php
-/**
- * Outil de Diagnostic Avancé - Explorateur Web TeamSpeak 6
- * Auteur : Guraz
- * ⚠️ ATTENTION : NE LAISSEZ PAS CE FICHIER SUR UN SERVEUR EN PRODUCTION ⚠️
- */
-
-// LA FONCTION MANQUANTE EST LÀ 👇
-function e($string) { return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8'); }
-
-$config_file = __DIR__ . '/config.php';
-$has_config = file_exists($config_file);
-
-if ($has_config) {
- $config = require $config_file;
- $cfg = $config['server'];
- $api_key_masked = empty($cfg['api_key']) ? "NON DÉFINIE" : substr($cfg['api_key'], 0, 6) . str_repeat('•', 20) . substr($cfg['api_key'], -4);
-}
-
-// Utilitaires de diagnostic
-function check_system_reqs() {
- $reqs = [];
- $reqs['PHP Version'] = [
- 'status' => 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 = "<div class='endpoint-card'><div class='endpoint-header'>";
- $html .= "<h2>➔ /{$endpoint_name}</h2><div class='endpoint-meta'>";
-
- if ($response === false) {
- $html .= "<span class='badge error'>Échec HTTP (Timeout)</span></div></div>";
- $html .= "<div class='endpoint-body'><p style='color: #f87171;'>Impossible de récupérer les données HTTP.</p></div>";
- } 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 .= "<span class='badge success'>HTTP 200</span> <span class='badge success'>TS Code: 0 (OK)</span>";
- } elseif ($ts_code === 5120) {
- $html .= "<span class='badge warning'>HTTP {$http_code}</span> <span class='badge warning'>TS Code: 5120 (Out of Scope)</span>";
- } elseif ($http_code == 401 || $ts_code === 2568) {
- $html .= "<span class='badge error'>HTTP {$http_code}</span> <span class='badge error'>TS Code: {$ts_code} (Clé Invalide)</span>";
- } else {
- $html .= "<span class='badge error'>HTTP {$http_code}</span> <span class='badge error'>TS Code: {$ts_code}</span>";
- }
- $html .= "<span class='badge info'>{$ping} ms</span></div></div>";
-
- if ($ts_code === 5120 && $endpoint_name == 'serverinfo') {
- $html .= "<div class='help-box'>ℹ️ <b>Note :</b> Erreur 5120 normale si votre clé API est configurée en lecture seule (scope=read).</div>";
- }
-
- $pretty_json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
- $html .= "<div class='endpoint-body'><pre><code>" . htmlspecialchars($pretty_json) . "</code></pre></div>";
- }
- $html .= "</div>";
- return $html;
-}
-?>
-<!DOCTYPE html>
-<html lang="fr">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Diagnostic API TS6</title>
- <style>
- :root { --bg: #0f172a; --panel: #1e293b; --text: #f8fafc; --text-muted: #94a3b8; --border: #334155; }
- body { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; background-color: var(--bg); color: var(--text); margin: 0; padding: 2rem; line-height: 1.5; }
- .container { max-width: 900px; margin: 0 auto; }
-
- .alert-danger { background-color: rgba(239, 68, 68, 0.2); border: 1px solid #ef4444; color: #fca5a5; padding: 1rem; border-radius: 8px; text-align: center; font-weight: bold; margin-bottom: 2rem; }
- .section-title { font-size: 1.5rem; color: #38bdf8; border-bottom: 2px solid var(--border); padding-bottom: 0.5rem; margin-top: 2rem; margin-bottom: 1rem; }
-
- .grid-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
- .card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; }
- .card h3 { margin-top: 0; font-size: 1.1rem; color: #e2e8f0; margin-bottom: 1rem; }
-
- .item-row { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255,255,255,0.05); padding: 0.5rem 0; }
- .item-row:last-child { border-bottom: none; }
- .item-label { color: var(--text-muted); font-size: 0.9rem; }
- .item-val { font-weight: bold; }
- .val-ok { color: #10b981; } .val-err { color: #ef4444; } .val-warn { color: #f59e0b; }
-
- .endpoint-card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 1.5rem; overflow: hidden; }
- .endpoint-header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; background: rgba(0,0,0,0.2); border-bottom: 1px solid var(--border); }
- .endpoint-header h2 { margin: 0; font-size: 1.1rem; color: #eab308; }
- .endpoint-meta { display: flex; gap: 0.5rem; }
-
- .badge { padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
- .badge.success { background: rgba(16, 185, 129, 0.2); color: #6ee7b7; border: 1px solid #10b981; }
- .badge.error { background: rgba(239, 68, 68, 0.2); color: #fca5a5; border: 1px solid #ef4444; }
- .badge.warning { background: rgba(245, 158, 11, 0.2); color: #fcd34d; border: 1px solid #f59e0b; }
- .badge.info { background: rgba(56, 189, 248, 0.2); color: #7dd3fc; border: 1px solid #38bdf8; }
-
- .help-box { margin: 1rem 1.5rem 0 1.5rem; padding: 0.75rem; background: rgba(56, 189, 248, 0.1); border-left: 4px solid #38bdf8; font-size: 0.9rem; color: #bae6fd; }
- .endpoint-body pre { margin: 0; padding: 1.5rem; overflow-x: auto; font-size: 0.85rem; color: #a7f3d0; max-height: 400px; }
- </style>
-</head>
-<body>
-
-<div class="container">
- <div class="alert-danger">
- ⚠️ DANGER : NE LAISSEZ JAMAIS CE FICHIER SUR VOTRE SERVEUR WEB PUBLIC.<br>
- Supprimez 'debug.php' de votre hébergement après utilisation.
- </div>
-
- <?php if (!$has_config): ?>
- <div class="card" style="border-color: #ef4444;">
- <h3 style="color: #fca5a5;">❌ Fichier config.php introuvable</h3>
- <p>Veuillez créer votre fichier <code>config.php</code> avant de lancer le diagnostic.</p>
- </div>
- <?php else: ?>
-
- <h2 class="section-title">🔍 Diagnostic de l'Environnement</h2>
-
- <div class="grid-cards">
- <div class="card">
- <h3>⚙️ Configuration Détectée</h3>
- <div class="item-row"><span class="item-label">Hôte API</span><span class="item-val"><?php echo e($cfg['host']); ?></span></div>
- <div class="item-row"><span class="item-label">Port API</span><span class="item-val"><?php echo e($cfg['port']); ?></span></div>
- <div class="item-row"><span class="item-label">ID Serveur</span><span class="item-val"><?php echo e($cfg['server_id']); ?></span></div>
- <div class="item-row"><span class="item-label">Clé API</span><span class="item-val" style="color: #fb923c;"><?php echo e($api_key_masked); ?></span></div>
- </div>
-
- <div class="card">
- <h3>💻 Serveur Web (PHP)</h3>
- <?php foreach (check_system_reqs() as $name => $req): ?>
- <div class="item-row">
- <span class="item-label" title="<?php echo e($req['msg']); ?>"><?php echo e($name); ?></span>
- <span class="item-val <?php echo $req['status'] ? 'val-ok' : 'val-err'; ?>">
- <?php echo $req['status'] ? '✓ ' : '✗ '; ?><?php echo e($req['value']); ?>
- </span>
- </div>
- <?php endforeach; ?>
- </div>
-
- <div class="card">
- <h3>📡 Réseau & Cache</h3>
- <?php
- $net = test_tcp_connection($cfg['host'], $cfg['port']);
- $cache_exists = file_exists($cfg['cache_file']);
- ?>
- <div class="item-row">
- <span class="item-label">Ping TCP (<?php echo e($cfg['port']); ?>)</span>
- <span class="item-val <?php echo $net['status'] ? 'val-ok' : 'val-err'; ?>" title="<?php echo e($net['msg']); ?>">
- <?php echo $net['status'] ? '✓ ' : '✗ '; ?><?php echo e($net['time']); ?>
- </span>
- </div>
- <div class="item-row">
- <span class="item-label">Fichier Cache</span>
- <span class="item-val <?php echo $cache_exists ? 'val-ok' : 'val-warn'; ?>">
- <?php echo $cache_exists ? '✓ Présent' : 'Non généré'; ?>
- </span>
- </div>
- <?php if ($cache_exists): ?>
- <div class="item-row">
- <span class="item-label">Âge du Cache</span>
- <span class="item-val"><?php echo (time() - filemtime($cfg['cache_file'])) . ' sec'; ?></span>
- </div>
- <div class="item-row">
- <span class="item-label">Taille du Cache</span>
- <span class="item-val"><?php echo round(filesize($cfg['cache_file']) / 1024, 1) . ' KB'; ?></span>
- </div>
- <?php endif; ?>
- </div>
- </div>
-
- <h2 class="section-title">🔌 Tests de l'API TeamSpeak</h2>
-
- <?php
- $base_url = "http://{$cfg['host']}:{$cfg['port']}/{$cfg['server_id']}";
-
- // Exécution des tests API
- echo test_endpoint("{$base_url}/channellist", $cfg['api_key'], "channellist");
- echo test_endpoint("{$base_url}/clientlist?-groups", $cfg['api_key'], "clientlist?-groups");
- echo test_endpoint("{$base_url}/serverinfo", $cfg['api_key'], "serverinfo");
- ?>
-
- <?php endif; ?>
-</div>
-
-</body>
-</html>
\ No newline at end of file diff --git a/game.jpeg b/game.jpeg Binary files differnew file mode 100644 index 0000000..d651107 --- /dev/null +++ b/game.jpeg diff --git a/general.jpg b/general.jpg Binary files differnew file mode 100644 index 0000000..f2f5bc0 --- /dev/null +++ b/general.jpg diff --git a/illit.jpg b/illit.jpg Binary files differnew file mode 100644 index 0000000..60f3655 --- /dev/null +++ b/illit.jpg 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 @@ -<?php
-/**
- * Explorateur Web - TeamSpeak 6
- */
-
-$config_file = __DIR__ . '/config.php';
-if (!file_exists($config_file)) {
- die("Erreur : Le fichier 'config.php' est introuvable. Veuillez le créer à partir de 'config.example.php'.");
-}
-$config = require $config_file;
-
-// Utilitaires
-function e($string) { return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8'); }
-function safeUrl($filename) { return str_replace('%2F', '/', rawurlencode($filename)); }
-
-function genererEtoiles($nombre, $opacite) {
- $ombres = [];
- for ($i = 0; $i < $nombre; $i++) {
- $x = rand(0, 2000); $y = rand(0, 2000);
- $ombres[] = "{$x}px {$y}px rgba(255, 255, 255, {$opacite})";
- }
- return implode(", ", $ombres);
-}
-
-// Récupération API (Strictement en LECTURE SEULE - scope=read)
-function fetch_teamspeak_data($cfg) {
- $cache_file = $cfg['cache_file'];
- if (file_exists($cache_file) && (time() - filemtime($cache_file)) < $cfg['cache_time']) {
- $cached_data = file_get_contents($cache_file);
- if ($cached_data) return json_decode($cached_data, true);
- }
-
- $options = ['http' => ['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): ?>
-
- <div class="server-stats">
- <div class="stat-item">
- <span class="stat-label">En Ligne</span>
- <span class="stat-value"><?php echo $total_real_clients; ?></span>
- </div>
- <div class="stat-item">
- <span class="stat-label">Salons</span>
- <span class="stat-value"><?php echo count($channels); ?></span>
- </div>
- <div class="stat-item">
- <span class="stat-label">Latence API</span>
- <span class="stat-value"><?php echo $api_ping; ?> ms</span>
- </div>
- </div>
-
- <div class="channel-list">
- <?php foreach ($channels as $channel):
- $c_name = $channel['channel_name'];
- if (strpos($c_name, '[spacer') !== false) continue;
-
- $cid = $channel['cid'];
- $channel_clients = $clients_by_channel[$cid] ?? [];
-
- $bg_img = $config['bannieres_canaux'][$c_name] ?? ($config['images_generiques']['fond'] ?? '');
- $icon_img = $config['icones_canaux'][$c_name] ?? ($config['images_generiques']['icone'] ?? '');
-
- $bg_style = "";
- if (!empty($bg_img)) {
- $bg_style = "background-image: linear-gradient(90deg, rgba(19, 24, 35, 0.95) 0%, rgba(19, 24, 35, 0.7) 40%, rgba(19, 24, 35, 0.9) 100%), url('" . safeUrl($bg_img) . "');";
- }
- ?>
- <article class="channel">
- <header class="channel-header" style="<?php echo $bg_style; ?>">
- <div class="channel-title-group">
- <?php if (!empty($icon_img)): ?>
- <img src="<?php echo safeUrl($icon_img); ?>" class="channel-icon" alt="" aria-hidden="true">
- <?php endif; ?>
- <h2><?php echo e($c_name); ?></h2>
- </div>
- <span class="channel-meta">
- <?php echo count($channel_clients); ?> / <?php echo ($channel['channel_maxclients'] == -1) ? '∞' : e($channel['channel_maxclients']); ?>
- </span>
- </header>
-
- <?php if (!empty($channel_clients)): ?>
- <ul class="client-list">
- <?php foreach ($channel_clients as $client): ?>
- <li class="client">
- <?php
- $role_icon = '<span class="client-no-role-icon"></span>';
- if (isset($client['client_servergroups'])) {
- $groups = explode(',', $client['client_servergroups']);
- foreach ($groups as $g_id) {
- if (isset($config['icones_roles'][$g_id])) {
- $role_icon = '<img src="' . safeUrl($config['icones_roles'][$g_id]) . '" class="role-icon" alt="Rôle">';
- break;
- }
- }
- }
- echo $role_icon;
- ?>
- <span><?php echo e($client['client_nickname']); ?></span>
- </li>
- <?php endforeach; ?>
- </ul>
- <?php endif; ?>
- </article>
- <?php endforeach; ?>
- </div>
-<?php else: ?>
- <div class="empty-server">Impossible d'établir le contact avec le serveur central.</div>
-<?php endif;
-$html_fragment = ob_get_clean();
-
-// Gestion de la réponse AJAX
-if (isset($_GET['ajax']) && $_GET['ajax'] === '1') {
- header('Content-Type: text/html; charset=utf-8');
- echo $html_fragment;
- exit;
-}
-?>
-<!DOCTYPE html>
-<html lang="fr">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="<?php echo e($config['ui']['sous_titre']); ?>">
- <title><?php echo e($config['ui']['titre']); ?></title>
- <link rel="icon" type="image/png" href="logo.png">
- <style>
- :root {
- --bg-color: <?php echo e($config['ui']['couleur_fond']); ?>;
- --panel-bg: <?php echo e($config['ui']['couleur_panneau']); ?>;
- --accent: <?php echo e($config['ui']['couleur_accent']); ?>;
- --text-main: <?php echo e($config['ui']['couleur_texte']); ?>;
- --text-muted: #64748b;
- --channel-bg: #1e2433;
- }
-
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: var(--bg-color); color: var(--text-main); margin: 0; padding: 2rem; display: flex; justify-content: center; min-height: 100vh; box-sizing: border-box; }
-
- <?php if ($config['ui']['activer_etoiles'] ?? false): ?>
- #space-bg { position: fixed; inset: 0; z-index: -1; background-color: var(--bg-color); overflow: hidden;}
- .star-layer { position: absolute; background: transparent; }
- .star-layer::after { content: ""; position: absolute; top: 2000px; background: inherit; box-shadow: inherit; }
- .stars-s { width: 1px; height: 1px; box-shadow: <?php echo genererEtoiles(150, 0.4); ?>; animation: animStar 250s linear infinite; }
- .stars-m { width: 2px; height: 2px; box-shadow: <?php echo genererEtoiles(50, 0.6); ?>; animation: animStar 180s linear infinite; }
- .stars-l { width: 2px; height: 2px; box-shadow: <?php echo genererEtoiles(15, 0.8); ?>; animation: animStar 100s linear infinite; }
- @keyframes animStar { from { transform: translateY(0); } to { transform: translateY(-2000px); } }
- <?php endif; ?>
-
- .app-container { background: var(--panel-bg); border-radius: 12px; width: 100%; max-width: 650px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7); border: 1px solid rgba(255, 255, 255, 0.04); position: relative; overflow: hidden; height: fit-content; }
-
- .banner { width: 100%; height: auto; display: block; border-bottom: 2px solid var(--accent); opacity: 0.95; }
- .content-wrapper { padding: 2rem; }
-
- /* Barre de progression fluide */
- .progress-bar { position: absolute; top: 0; left: 0; height: 2px; background-color: var(--accent); width: 100%; z-index: 10; }
-
- .header { text-align: center; margin-bottom: 2rem; }
- .header h1 { margin: 0 0 0.25rem 0; font-size: 1.5rem; color: #f8fafc; }
- .header p { color: var(--text-muted); margin: 0; font-size: 0.9rem; }
-
- .status-badge { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 4px; font-size: 0.7rem; font-weight: 700; margin-top: 15px; letter-spacing: 1px; }
- .status-online { color: #a7f3d0; background-color: rgba(6, 78, 59, 0.4); border: 1px solid rgba(16, 185, 129, 0.2); }
- .status-offline { color: #fca5a5; background-color: rgba(127, 29, 29, 0.4); border: 1px solid rgba(239, 68, 68, 0.2); }
- .pulse-dot { width: 6px; height: 6px; background-color: #10b981; border-radius: 50%; margin-right: 8px; animation: pulse 2s infinite; }
- .status-offline .pulse-dot { background-color: #ef4444; animation: none; }
- @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); } 70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); } 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); } }
-
- /* Stats du serveur adaptées à la lecture seule */
- .server-stats { display: flex; justify-content: space-around; background: rgba(0, 0, 0, 0.2); padding: 12px; border-radius: 8px; margin-bottom: 1.5rem; border: 1px solid rgba(255, 255, 255, 0.03); }
- .stat-item { display: flex; flex-direction: column; align-items: center; }
- .stat-label { font-size: 0.65rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; }
- .stat-value { font-size: 1rem; font-weight: 700; color: var(--text-main); font-variant-numeric: tabular-nums; }
-
- .btn-primary { display: block; padding: 12px; background-color: var(--accent); color: #000; text-align: center; text-decoration: none; border-radius: 6px; font-weight: 800; text-transform: uppercase; font-size: 0.85rem; transition: filter 0.2s; margin-bottom: 0.5rem; }
- .btn-primary:hover { filter: brightness(1.1); }
-
- .help-text { text-align: center; font-size: 0.8rem; color: var(--text-muted); margin: 0.5rem 0 2rem 0; }
- .help-text a { color: var(--text-main); text-decoration: none; border-bottom: 1px solid rgba(255,255,255,0.2); transition: border-color 0.2s; }
- .help-text a:hover { border-color: var(--accent); color: var(--accent); }
-
- .channel-list { display: flex; flex-direction: column; gap: 6px; }
- .channel { border-radius: 12px; overflow: hidden; border: 1px solid rgba(255, 255, 255, 0.03); background-color: var(--channel-bg); }
- .channel-header { padding: 8px 14px; display: flex; justify-content: space-between; align-items: center; color: #f1f5f9; background-size: cover; background-position: center; background-repeat: no-repeat; }
-
- .channel-title-group { display: flex; align-items: center; margin: 0; font-size: 0.9rem; font-weight: 600; }
- .channel-title-group h2 { font-size: 1rem; margin: 0; font-weight: 600; }
- .channel-icon { width: 20px; height: 20px; margin-right: 10px; border-radius: 4px; object-fit: contain; }
- .channel-meta { font-size: 0.8rem; color: rgba(255,255,255,0.7); }
-
- .client-list { list-style: none; padding: 4px 0 8px 0; margin: 0; }
- .client { padding: 4px 15px 4px 38px; font-size: 0.85rem; color: #cbd5e1; display: flex; align-items: center; font-weight: 500; }
- .role-icon { width: 16px; height: 16px; object-fit: contain; margin-right: 10px; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5)); }
- .client-no-role-icon { display: inline-block; width: 8px; height: 8px; border: 2px solid #475569; border-radius: 50%; margin-right: 10px; }
-
- .empty-server { text-align: center; color: var(--text-muted); padding: 3rem 0; font-size: 0.9rem; }
-
- .footer-links { text-align: center; padding-top: 1.5rem; margin-top: 2rem; border-top: 1px solid rgba(255, 255, 255, 0.05); }
- .footer-links a { color: var(--text-muted); text-decoration: none; font-size: 0.85rem; margin: 0 10px; transition: color 0.2s; }
- .footer-links a:hover { color: var(--text-main); }
-
- .credits { text-align: center; margin-top: 1rem; font-size: 0.75rem; color: #475569; }
- .credits a { color: var(--text-muted); font-weight: 600; text-decoration: none; transition: color 0.2s; }
- .credits a:hover { color: var(--accent); }
-
- #ajax-container { transition: opacity 0.3s ease-in-out; }
- </style>
-</head>
-<body>
-
-<?php if ($config['ui']['activer_etoiles'] ?? false): ?>
-<div id="space-bg" aria-hidden="true">
- <div class="star-layer stars-s"></div>
- <div class="star-layer stars-m"></div>
- <div class="star-layer stars-l"></div>
-</div>
-<?php endif; ?>
-
-<main class="app-container">
- <?php if ($is_online): ?><div class="progress-bar" id="refresh-bar"></div><?php endif; ?>
-
- <?php if (!empty($config['ui']['banniere_url'])): ?>
- <img src="<?php echo safeUrl($config['ui']['banniere_url']); ?>" alt="Bannière d'en-tête" class="banner">
- <?php endif; ?>
-
- <div class="content-wrapper">
- <header class="header">
- <h1><?php echo e($config['ui']['titre']); ?></h1>
- <p><?php echo e($config['ui']['sous_titre']); ?></p>
- <?php if ($is_online): ?>
- <div class="status-badge status-online"><span class="pulse-dot"></span> TRANSMISSION ACTIVE</div>
- <?php else: ?>
- <div class="status-badge status-offline"><span class="pulse-dot"></span> SIGNAL PERDU</div>
- <?php endif; ?>
- </header>
-
- <a href="ts3server://<?php echo e($config['server']['ts_domain']); ?>" class="btn-primary"><?php echo e($config['boutons']['rejoindre']); ?></a>
- <p class="help-text"><?php echo e($config['boutons']['telecharger_texte']); ?> <a href="<?php echo e($config['boutons']['telecharger_url']); ?>" target="_blank" rel="noopener"><?php echo e($config['boutons']['telecharger_lien']); ?></a></p>
-
- <section id="ajax-container" aria-live="polite">
- <?php echo $html_fragment; ?>
- </section>
-
- <?php if (!empty($config['liens_externes'])): ?>
- <nav class="footer-links">
- <?php foreach ($config['liens_externes'] as $nom => $url): ?>
- <a href="<?php echo e($url); ?>" target="_blank" rel="noopener"><?php echo e($nom); ?></a>
- <?php endforeach; ?>
- </nav>
- <?php endif; ?>
-
- <footer class="credits">
- <?php echo e($config['footer']['texte']); ?> <a href="<?php echo e($config['footer']['lien_auteur']); ?>" target="_blank" rel="noopener"><?php echo e($config['footer']['auteur']); ?></a> — Scan radar : <span id="timer-text"><?php echo $config['server']['cache_time']; ?></span>s
- </footer>
- </div>
-</main>
-
-<script>
- <?php if ($is_online): ?>
- document.addEventListener("DOMContentLoaded", () => {
- let timeLeft = <?php echo (int)$config['server']['cache_time']; ?>;
- const totalTime = <?php echo (int)$config['server']['cache_time']; ?>;
-
- const progressBar = document.getElementById('refresh-bar');
- const timerText = document.getElementById('timer-text');
- const container = document.getElementById('ajax-container');
-
- // Animation fluide de la barre de temps
- function resetProgressBar() {
- progressBar.style.transition = 'none';
- progressBar.style.width = '100%';
- void progressBar.offsetWidth;
- progressBar.style.transition = `width ${totalTime}s linear`;
- progressBar.style.width = '0%';
- }
-
- resetProgressBar();
-
- setInterval(() => {
- timeLeft--;
- timerText.innerText = timeLeft;
-
- if (timeLeft <= 0) {
- container.style.opacity = '0.5';
- fetch(window.location.href.split('?')[0] + '?ajax=1', { cache: 'no-store', headers: {'X-Requested-With': 'XMLHttpRequest'} })
- .then(r => {
- if (!r.ok) throw new Error("HTTP error " + r.status);
- return r.text();
- })
- .then(html => {
- container.innerHTML = html;
- container.style.opacity = '1';
-
- timeLeft = totalTime;
- timerText.innerText = timeLeft;
-
- resetProgressBar();
- })
- .catch(err => {
- console.error("Erreur de rafraîchissement :", err);
- container.style.opacity = '1';
- timeLeft = totalTime;
- resetProgressBar();
- });
- }
- }, 1000);
- });
- <?php endif; ?>
-</script>
-</body>
-</html>
\ No newline at end of file diff --git a/info.png b/info.png Binary files differnew file mode 100644 index 0000000..fd27228 --- /dev/null +++ b/info.png 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 @@ -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" }, +] |
