#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ C&C Match Poller - ECHTE TDRA API Verwendet die echte TDRA API: https://cncapi.kanedaspring.de """ import requests import time import json import base64 import logging from datetime import datetime, timedelta from typing import Dict, List import mysql.connector from mysql.connector import Error logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('match_poller.log'), logging.StreamHandler() ] ) class TDRRealMatchPoller: def __init__(self, db_config: Dict): self.db_config = db_config self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) TDRA-Poller/1.0', 'Accept': 'application/json', 'Referer': 'https://cnc.kanedaspring.de/' }) # Echte TDRA API URLs - DIE FUNKTIONIERENDE! self.tdra_api = { 'base': 'https://tdra.org/api', 'matches': 'https://tdra.org/api/matches', 'players': 'https://tdra.org/api/players', 'ladder': 'https://tdra.org/api/ladder', 'results': 'https://tdra.org/api/results', 'v2': 'https://tdra.org/api/v2' } # TDRA Auth Token aus app.js self.encoded_token = 'S0VOTlkuS0FORe+/vQ==' try: self.auth_token = base64.b64decode(self.encoded_token).decode('utf-8') logging.info(f"TDRA Auth Token: {self.auth_token}") except: self.auth_token = 'KENNY.KANE' logging.warning("Konnte Auth Token nicht dekodieren, nutze Fallback") def connect_db(self): """Stellt Verbindung zur Datenbank her""" try: conn = mysql.connector.connect(**self.db_config) return conn except Error as e: logging.error(f"Datenbankverbindung fehlgeschlagen: {e}") return None def test_tdra_connection(self) -> bool: """Testet Verbindung zur TDRA API""" try: logging.info("Teste TDRA API Verbindung...") # Basis-Endpoint testen response = self.session.get(self.tdra_api['base'], timeout=10) response.raise_for_status() logging.info(f"✅ TDRA API erreichbar: {response.status_code}") return True except requests.exceptions.RequestException as e: logging.error(f"❌ TDRA API nicht erreichbar: {e}") return False def get_tdra_matches(self, limit: int = 50) -> List[Dict]: """Holt echte Matches von TDRA API""" try: logging.info(f"Hole {limit} Matches von TDRA API...") # TDRA Matches API (aus app.js Funktionen) endpoints = [ f"{self.tdra_api['matches']}", f"{self.tdra_api['v2']}/matches", f"{self.tdra_api['results']}", f"{self.tdra_api['base']}/results" ] matches = [] for endpoint in endpoints: try: params = { 'limit': limit, 'period': 'recent' } logging.info(f"Versuche Endpoint: {endpoint}") response = self.session.get(endpoint, params=params, timeout=15) response.raise_for_status() data = response.json() logging.info(f"✅ {len(data) if isinstance(data, list) else 1} Matches von {endpoint}") if isinstance(data, list): matches.extend(data) elif isinstance(data, dict) and 'data' in data: matches.extend(data['data']) else: matches.append(data) break # Erfolg, keine weiteren Endpunkte testen except Exception as e: logging.warning(f"Endpoint {endpoint} fehlgeschlagen: {e}") continue if not matches: logging.warning("Keine Matches von TDRA API erhalten") return [] logging.info(f"Gesamt: {len(matches)} TDRA Matches erhalten") return matches[:limit] # Limitieren except Exception as e: logging.error(f"Fehler bei TDRA Matches: {e}") return [] def get_tdra_players(self, limit: int = 100) -> List[Dict]: """Holt echte Spieler von TDRA API""" try: logging.info(f"Hole {limit} Spieler von TDRA API...") endpoints = [ f"{self.tdra_api['players']}", f"{self.tdra_api['v2']}/players", f"{self.tdra_api['ladder']}" ] players = [] for endpoint in endpoints: try: params = {'limit': limit} response = self.session.get(endpoint, params=params, timeout=15) response.raise_for_status() data = response.json() if isinstance(data, list): players.extend(data) elif isinstance(data, dict) and 'data' in data: players.extend(data['data']) break except Exception as e: logging.warning(f"Player Endpoint {endpoint} fehlgeschlagen: {e}") continue logging.info(f"{len(players)} TDRA Spieler erhalten") return players except Exception as e: logging.error(f"Fehler bei TDRA Spielern: {e}") return [] def convert_tdra_match(self, tdra_match: Dict) -> Dict: """Konvertiert TDRA Match zu unserem Format""" try: # TDRA Match-Struktur anpassen match_id = tdra_match.get('matchid', f"tdra_{int(time.time())}") # Spieler-Daten players = [] tdra_players = tdra_match.get('players', []) # Falls players nicht da, andere Struktur versuchen if not tdra_players and 'player1' in tdra_match: tdra_players = [tdra_match.get('player1', {}), tdra_match.get('player2', {})] for i, player_data in enumerate(tdra_players): player = { 'player_id': str(player_data.get('id', player_data.get('player_id', f'unknown_{i}'))), 'name': player_data.get('name', player_data.get('player_name', f'Player_{i+1}')), 'faction': player_data.get('faction', player_data.get('team', 'Unknown')), 'result': 'win' if player_data.get('won', player_data.get('winner', False)) else 'loss', 'final_score': player_data.get('score', player_data.get('points', 0)), 'tdra_elo': player_data.get('elo', player_data.get('rating', 1000)), 'tdra_skill': player_data.get('skill', 1000) } players.append(player) # Match-Daten match = { 'match_id': f"tdra_{match_id}", 'map_name': tdra_match.get('map', tdra_match.get('mapname', 'Unknown Map')), 'duration_seconds': tdra_match.get('duration', tdra_match.get('time', 0)), 'started_at': self._parse_tdra_timestamp(tdra_match.get('starttime', tdra_match.get('date'))), 'ended_at': self._parse_tdra_timestamp(tdra_match.get('endtime')), 'match_type': tdra_match.get('type', tdra_match.get('gametype', 'ranked')), 'has_replay': tdra_match.get('replay', False), 'replay_url': tdra_match.get('replay_url', ''), 'host': tdra_match.get('host', 'Unknown'), 'players': players, 'raw_tdra_data': json.dumps(tdra_match), 'api_source': 'tdra_real_api' } return match except Exception as e: logging.error(f"Fehler bei TDRA Match-Konvertierung: {e}") return {} def _parse_tdra_timestamp(self, timestamp) -> datetime: """Parst TDRA Timestamp""" try: if not timestamp: return datetime.now() if isinstance(timestamp, (int, float)): return datetime.fromtimestamp(timestamp) if isinstance(timestamp, str): # Unix Timestamp als String if timestamp.isdigit(): return datetime.fromtimestamp(int(timestamp)) # ISO Format formats = [ '%Y-%m-%dT%H:%M:%S.%fZ', '%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%d %H:%M:%S' ] for fmt in formats: try: return datetime.strptime(timestamp, fmt) except ValueError: continue return datetime.now() except Exception as e: logging.warning(f"TDRA Timestamp Fehler: {e}") return datetime.now() def save_tdra_match(self, conn, match: Dict) -> bool: """Speichert TDRA Match in Datenbank""" try: cursor = conn.cursor() match_id = match.get('match_id') if not match_id: return False # Prüfen ob Match schon existiert cursor.execute("SELECT match_id FROM matches WHERE match_id = %s", (match_id,)) if cursor.fetchone(): return False # Match einfügen cursor.execute(""" INSERT INTO matches ( match_id, map_name, duration_seconds, started_at, ended_at, match_type, has_replay, replay_url, api_source, raw_data, created_at ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW()) """, ( match_id, match.get('map_name'), match.get('duration_seconds'), match.get('started_at'), match.get('ended_at'), match.get('match_type'), match.get('has_replay'), match.get('replay_url'), match.get('api_source'), match.get('raw_tdra_data') )) # Spieler speichern for player in match.get('players', []): player_id = player.get('player_id') player_name = player.get('name') if not player_id or not player_name: continue # In players Tabelle speichern cursor.execute(""" INSERT INTO players (player_id, name, created_at, updated_at) VALUES (%s, %s, NOW(), NOW()) ON DUPLICATE KEY UPDATE name = VALUES(name), updated_at = NOW() """, (player_id, player_name)) # Match-Zuordnung speichern cursor.execute(""" INSERT INTO match_players (match_id, player_id, faction, result, created_at) VALUES (%s, %s, %s, %s, NOW()) """, ( match_id, player_id, player.get('faction', 'Unknown'), player.get('result', 'unknown') )) conn.commit() logging.info(f"✅ TDRA-Match gespeichert: {match_id}") return True except Error as e: conn.rollback() logging.error(f"Fehler beim Speichern: {e}") return False finally: cursor.close() def run_tdra_real_polling(self): """Führt echten TDRA Polling durch""" logging.info("=== ECHTER TDRA API POLLING ===") # Verbindung testen if not self.test_tdra_connection(): logging.error("TDRA API nicht erreichbar - breche ab") return # Matches abrufen tdra_matches = self.get_tdra_matches(50) if not tdra_matches: logging.warning("Keine TDRA Matches erhalten") return # Verbindung zur DB conn = self.connect_db() if not conn: return try: total_saved = 0 for tdra_match in tdra_matches: # Konvertieren match = self.convert_tdra_match(tdra_match) if not match: continue # Speichern if self.save_tdra_match(conn, match): total_saved += 1 # Kurze Pause time.sleep(0.5) logging.info(f"✅ TDRA Polling abgeschlossen: {total_saved} neue Matches") except Exception as e: logging.error(f"Fehler im TDRA Polling: {e}") finally: conn.close() def main(): """Hauptfunktion""" db_config = { 'host': 'localhost', 'database': 'NexusControl', 'user': 'root', 'password': 'HOk~k5$!0q', 'charset': 'utf8mb4' } poller = TDRRealMatchPoller(db_config) print("=== ECHTER TDRA API POLLER ===") print("✅ API: https://cncapi.kanedaspring.de") print("� Genau wie auf cnc.kanedaspring.de!") print() poller.run_tdra_real_polling() if __name__ == "__main__": main()