#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ C&C Match Poller - Spiel-Logs auslesen Liest echte Match-Daten aus C&C Server Logs """ import os import re import time import logging from datetime import datetime from typing import Dict, List import mysql.connector from mysql.connector import Error logging.basicConfig(level=logging.INFO) class CNCMatchLogPoller: def __init__(self, db_config: Dict): self.db_config = db_config # Log-DateiPfade (anpassen an dein System) self.log_paths = [ '/var/log/cnc/', # Standard Linux Logs './logs/', # Relative Logs '../cnc-logs/', # Parent Logs '/tmp/cnc-logs/', # Temp Logs ] def find_log_files(self) -> List[str]: """Sucht nach C&C Log-Dateien""" log_files = [] for path in self.log_paths: if os.path.exists(path): for file in os.listdir(path): if file.endswith(('.log', '.txt')) and 'match' in file.lower(): log_files.append(os.path.join(path, file)) logging.info(f"Log-Datei gefunden: {file}") return log_files def parse_log_file(self, file_path: str) -> List[Dict]: """Parst eine Log-Datei und extrahiert Match-Daten""" matches = [] try: with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Regex-Muster für Match-Informationen (anpassen!) match_patterns = [ # Standard C&C Log-Format r'MATCH_START:.*?Map:(?P[^|]+).*?Player1:(?P[^|]+).*?Player2:(?P[^|]+).*?Duration:(?P\d+)', # Alternative Formate r'Game started.*?map=(?P[^\s]+).*?players=(?P[^,]+),(?P[^,]+)', # CNCNet Format r'GAME.*?map:"(?P[^"]+)".*?players:\[(?P[^,]+),(?P[^\]]+)\]', ] for pattern in match_patterns: found_matches = re.finditer(pattern, content, re.IGNORECASE | re.DOTALL) for match in found_matches: match_data = { 'map_name': match.group('map').strip(), 'player1_name': match.group('p1').strip(), 'player2_name': match.group('p2').strip(), 'duration': int(match.group('duration')) if match.group('duration') else 1800, 'timestamp': datetime.now() # Später aus Log extrahieren } matches.append(match_data) logging.info(f"{len(matches)} Matches aus {os.path.basename(file_path)} extrahiert") except Exception as e: logging.error(f"Fehler beim Lesen von {file_path}: {e}") return matches def get_real_players(self) -> Dict[str, int]: """Holt echte Spieler aus der NexusControl DB""" 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)} echte Spieler aus DB geladen") return players except Error as e: logging.error(f"Fehler beim Laden der Spieler: {e}") return {} finally: cursor.close() conn.close() def connect_db(self): """Stellt DB-Verbindung her""" try: return mysql.connector.connect(**self.db_config) except Error as e: logging.error(f"DB-Verbindung fehlgeschlagen: {e}") return None def save_match(self, match_data: Dict, players: Dict[str, int]) -> bool: """Speichert Match in DB""" conn = self.connect_db() if not conn: return False try: cursor = conn.cursor() # Spieler-Namen in IDs umwandeln p1_id = players.get(match_data['player1_name']) p2_id = players.get(match_data['player2_name']) if not p1_id or not p2_id: logging.warning(f"Spieler nicht gefunden: {match_data['player1_name']} oder {match_data['player2_name']}") return False # Match-ID generieren match_id = f"log_{int(time.time())}_{hash(match_data['player1_name']) % 1000}" # Match einfügen cursor.execute(""" INSERT INTO matches (match_id, map_name, duration_seconds, started_at, match_type, api_source, created_at) VALUES (%s, %s, %s, %s, 'ranked', 'game_logs', NOW()) ON DUPLICATE KEY UPDATE updated_at = NOW() """, (match_id, match_data['map_name'], match_data['duration'], match_data['timestamp'])) # Spieler einfügen/aktualisieren for player_name, player_id in [(match_data['player1_name'], p1_id), (match_data['player2_name'], p2_id)]: 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() """, (str(player_id), player_name)) # Match-Zuordnung cursor.execute(""" INSERT INTO match_players (match_id, player_id, faction, result, created_at) VALUES (%s, %s, %s, %s, NOW()) """, (match_id, str(p1_id), 'Allies', 'win')) # Später echtes Ergebnis aus Log cursor.execute(""" INSERT INTO match_players (match_id, player_id, faction, result, created_at) VALUES (%s, %s, %s, %s, NOW()) """, (match_id, str(p2_id), 'Soviet', 'loss')) conn.commit() logging.info(f"Log-Match gespeichert: {match_id}") return True except Error as e: conn.rollback() logging.error(f"Fehler beim Speichern: {e}") return False finally: cursor.close() conn.close() def run_polling_cycle(self): """Führt Polling-Zyklus durch""" logging.info("Starte Log-Polling-Zyklus...") # Log-Dateien finden log_files = self.find_log_files() if not log_files: logging.warning("Keine Log-Dateien gefunden!") return # Echte Spieler laden players = self.get_real_players() if not players: logging.error("Keine Spieler in DB gefunden!") return # Logs auslesen und speichern total_saved = 0 for log_file in log_files: matches = self.parse_log_file(log_file) for match in matches: if self.save_match(match, players): total_saved += 1 logging.info(f"Log-Polling abgeschlossen: {total_saved} Matches gespeichert") def main(): db_config = { 'host': 'localhost', 'database': 'NexusControl', 'user': 'root', 'password': 'HOk~k5$!0q', 'charset': 'utf8mb4' } poller = CNCMatchLogPoller(db_config) poller.run_polling_cycle() if __name__ == "__main__": main()