#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ C&C Match Poller - TDRA LAUNCHER API Nutzt die echte TDRA API wie auf cnc.kanedaspring.de """ import requests import time 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 TDRAMatchPoller: def __init__(self, db_config: Dict): self.db_config = db_config self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'NexusControl-TDRA-Poller/1.0', 'Accept': 'application/json' }) # TDRA API URLs (basierend auf cnc.kanedaspring.de) self.tdra_api = { 'base': 'https://cnc.kanedaspring.de/api', 'matches': 'https://cnc.kanedaspring.de/api/results', 'players': 'https://cnc.kanedaspring.de/api/players', 'ladder': 'https://cnc.kanedaspring.de/api/ladderboard', 'replays': 'https://cnc.kanedaspring.de/api/replays' } 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 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 Results API params = { 'limit': limit, 'period': 'recent', # Letzte Matches 'type': 'ranked' # Nur Ranked Matches } response = self.session.get(self.tdra_api['matches'], params=params, timeout=30) response.raise_for_status() matches_data = response.json() logging.info(f"{len(matches_data)} Matches von TDRA API erhalten") return matches_data except requests.exceptions.RequestException as e: logging.error(f"TDRA API Fehler: {e}") return [] except json.JSONDecodeError as e: logging.error(f"TDRA JSON Fehler: {e}") return [] except Exception as e: logging.error(f"Unerwarteter TDRA Fehler: {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 extrahieren players = [] tdra_players = tdra_match.get('players', []) for player_data in tdra_players: player = { 'player_id': str(player_data.get('id', 'unknown')), 'name': player_data.get('name', 'Unknown Player'), 'faction': player_data.get('faction', 'Unknown'), 'result': 'win' if player_data.get('won', False) else 'loss', 'final_score': player_data.get('score', 0), 'resources_collected': player_data.get('harvested', 0), 'tdra_skill': player_data.get('skill', 1000), 'tdra_elo': player_data.get('rating', 1000) } players.append(player) # Match-Daten match = { 'match_id': f"tdra_{match_id}", 'map_name': tdra_match.get('map', 'Unknown Map'), 'duration_seconds': tdra_match.get('duration', 0), 'started_at': self._parse_tdra_timestamp(tdra_match.get('starttime')), 'ended_at': self._parse_tdra_timestamp(tdra_match.get('endtime')), 'match_type': tdra_match.get('type', 'ranked'), 'has_replay': tdra_match.get('replay', False), 'replay_url': tdra_match.get('replay_url', ''), 'players': players, 'host': tdra_match.get('host', 'Unknown'), 'tdra_data': json.dumps(tdra_match), # Originaldaten speichern 'api_source': 'tdra_launcher' } return match except Exception as e: logging.error(f"Fehler bei TDRA Match-Konvertierung: {e}") return {} def _parse_tdra_timestamp(self, timestamp_str: str) -> datetime: """Parst TDRA Timestamp""" try: if not timestamp_str: return datetime.now() # TDRA verwendet wahrscheinlich Unix Timestamp oder ISO Format if timestamp_str.isdigit(): return datetime.fromtimestamp(int(timestamp_str)) else: # ISO Format versuchen 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_str, fmt) except ValueError: continue return datetime.now() except Exception as e: logging.warning(f"TDRA Timestamp Fehler: {e}") return datetime.now() def get_nexuscontrol_players(self) -> Dict[str, int]: """Holt NexusControl Spieler für Namensabgleich""" conn = self.connect_db() if not conn: return {} try: cursor = conn.cursor(dictionary=True) cursor.execute(""" SELECT SpielerID, Name FROM Spieler WHERE is_active = 1 AND Name IS NOT NULL AND Name != '' """) players = {} for row in cursor.fetchall(): players[row['Name']] = row['SpielerID'] logging.info(f"{len(players)} NexusControl Spieler für Abgleich geladen") return players except Error as e: logging.error(f"Fehler beim Laden der NexusControl Spieler: {e}") return {} finally: cursor.close() conn.close() 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('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 des TDRA-Matches: {e}") return False finally: cursor.close() def run_tdra_polling_cycle(self): """Führt TDRA Polling-Zyklus durch""" logging.info("Starte TDRA Launcher API Polling-Zyklus...") # TDRA Matches abrufen tdra_matches = self.get_tdra_matches(50) if not tdra_matches: logging.warning("Keine TDRA Matches erhalten!") return # NexusControl Spieler für Abgleich nexus_players = self.get_nexuscontrol_players() conn = self.connect_db() if not conn: return try: total_saved = 0 for tdra_match in tdra_matches: # TDRA Match 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-Zyklus: {e}") finally: conn.close() def main(): """Hauptfunktion""" db_config = { 'host': 'localhost', 'database': 'NexusControl', 'user': 'root', 'password': 'HOk~k5$!0q', 'charset': 'utf8mb4' } poller = TDRAMatchPoller(db_config) print("=== TDRA Launcher API Poller ===") print("✅ Echte TDRA Daten wie auf cnc.kanedaspring.de!") print() poller.run_tdra_polling_cycle() if __name__ == "__main__": main()