new file: Dockerfile new file: README.md new file: app.py new file: chat-logs/chat-index.json new file: chat-logs/crea-1-10.08.2020-merged.txt new file: chat-logs/crea-1-11.08.2020-merged.txt new file: chat-logs/crea-1-12.08.2020-merged.txt new file: chat-logs/crea-1-13.08.2020-merged.txt new file: chat-logs/crea-1-14.08.2020-merged.txt new file: chat-logs/crea-1-15.08.2020-merged.txt new file: chat-logs/crea-1-18.08.2020-merged.txt new file: chat-logs/crea-1-20.08.2020-merged.txt new file: chat-logs/crea-1-2020-07-27-1-filtered.txt new file: chat-logs/crea-1-2020-07-28-1-filtered.txt new file: chat-logs/crea-1-2020-07-29-1-filtered.txt new file: chat-logs/crea-1-2020-07-30-1-filtered.txt new file: chat-logs/crea-1-2020-08-03-1-filtered.txt new file: chat-logs/crea-1-2020-08-04-1-filtered.txt new file: chat-logs/crea-1-2020-08-08-1-filtered.txt new file: chat-logs/crea-1-2020-08-09-1-filtered.txt new file: chat-logs/crea-1-2020-08-10-1-filtered.txt new file: chat-logs/crea-1-2020-08-11-1-filtered.txt new file: chat-logs/crea-1-2020-08-13-1-filtered.txt new file: chat-logs/crea-1-2020-08-16-1-filtered.txt new file: chat-logs/crea-1-2020-08-17-1-filtered.txt new file: chat-logs/crea-1-2020-08-18-1-filtered.txt new file: chat-logs/crea-1-2020-08-20-1-filtered.txt new file: chat-logs/crea-1-2020-08-24-1-filtered.txt new file: chat-logs/crea-1-2020-08-29-1-filtered.txt new file: chat-logs/crea-1-2020-08-30-1-filtered.txt new file: chat-logs/crea-1-21.08.2020-merged.txt new file: chat-logs/crea-1-22.08.2020-merged.txt new file: chat-logs/crea-1-23.08.2020-merged.txt new file: chat-logs/crea-1-24.07.2020-merged.txt new file: chat-logs/crea-1-25.07.2020-merged.txt new file: chat-logs/crea-1-25.08.2020-merged.txt new file: chat-logs/crea-1-26.07.2020-merged.txt new file: chat-logs/crea-1-26.08.2020-merged.txt new file: chat-logs/crea-1-27.08.2020-merged.txt new file: chat-logs/crea-1-28.08.2020-merged.txt new file: chat-logs/crea-1-crea-1-10.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-11.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-12.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-14.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-15.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-18.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-20.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-21.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-22.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-23.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-24.07.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-25.07.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-25.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-26.07.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-26.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-27.08.2020-merged-filtered.txt new file: chat-logs/crea-1-crea-1-28.08.2020-merged-filtered.txt new file: chat-logs/survival-1-15.08.2020-merged.txt new file: chat-logs/survival-1-2020-07-27-1-filtered.txt new file: chat-logs/survival-1-2020-07-28-1-filtered.txt new file: chat-logs/survival-1-2020-08-07-1-filtered.txt new file: chat-logs/survival-1-2020-08-08-1-filtered.txt new file: chat-logs/survival-1-2020-08-11-1-filtered.txt new file: chat-logs/survival-1-2020-08-13-1-filtered.txt new file: chat-logs/survival-1-2020-08-14-1-filtered.txt new file: chat-logs/survival-1-2020-08-17-1-filtered.txt new file: chat-logs/survival-1-2020-08-18-1-filtered.txt new file: chat-logs/survival-1-2020-08-19-1-filtered.txt new file: chat-logs/survival-1-25.07.2020-merged.txt new file: chat-logs/survival-1-survival-1-15.08.2020-merged-filtered.txt new file: chat-logs/survival-1-survival-1-25.07.2020-merged-filtered.txt new file: chat-logs/thesur-1-2020-08-17-1-filtered.txt new file: chat-logs/thesur-1-2020-08-31-1-filtered.txt new file: count_all_sessions.py new file: count_sessions.py new file: index.html new file: local-chat-analyzer.js new file: merge_daily_logs.py new file: process_thesur_logs.py new file: quick_add.py new file: requirements.txt new file: script.js new file: server.py new file: statistics-integration.js new file: statistics.css new file: statistics.js new file: style.css
390 lines
14 KiB
Python
390 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Quick Add Script für neue Minecraft Chat Logs
|
|
Filtert rohe Server-Logs und fügt sie automatisch zur chat-index.json hinzu
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import re
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
# Set UTF-8 encoding for console output
|
|
if sys.platform == "win32":
|
|
import codecs
|
|
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
|
|
sys.stderr = codecs.getwriter("utf-8")(sys.stderr.detach())
|
|
|
|
def detect_gamemode_from_path(filepath):
|
|
"""Erkennt den Gamemode basierend auf dem Ordnernamen oder Dateinamen"""
|
|
# Zuerst schauen wir in den Ordnernamen
|
|
path_parts = filepath.replace('\\', '/').split('/')
|
|
|
|
for part in reversed(path_parts): # Von hinten nach vorne durch die Pfad-Teile
|
|
part_lower = part.lower()
|
|
|
|
# Direkte Gamemode-Erkennung aus Ordnernamen
|
|
if part_lower in ['crea-1', 'creative-1', 'creative']:
|
|
return 'crea-1'
|
|
elif part_lower in ['survival-1', 'surv-1', 'survival']:
|
|
return 'survival-1'
|
|
elif part_lower in ['event', 'events', 'special']:
|
|
return 'event'
|
|
elif part_lower in ['test', 'testing', 'dev']:
|
|
return 'test'
|
|
|
|
# Pattern-basierte Erkennung
|
|
if 'crea' in part_lower or 'creative' in part_lower:
|
|
return 'crea-1'
|
|
elif 'survival' in part_lower or 'surv' in part_lower:
|
|
return 'survival-1'
|
|
elif 'event' in part_lower or 'special' in part_lower:
|
|
return 'event'
|
|
elif 'test' in part_lower:
|
|
return 'test'
|
|
|
|
# Fallback: Dateiname analysieren
|
|
filename = os.path.basename(filepath).lower()
|
|
if 'crea' in filename or 'creative' in filename:
|
|
return 'crea-1'
|
|
elif 'survival' in filename or 'surv' in filename:
|
|
return 'survival-1'
|
|
elif 'event' in filename or 'special' in filename:
|
|
return 'event'
|
|
elif 'test' in filename:
|
|
return 'test'
|
|
|
|
return 'crea-1' # Default
|
|
|
|
def detect_gamemode_from_filename(filename):
|
|
"""Legacy function - verwendet jetzt detect_gamemode_from_path"""
|
|
return detect_gamemode_from_path(filename)
|
|
|
|
def extract_date_from_filename(filename):
|
|
"""Extrahiert das Datum aus dem Dateinamen"""
|
|
# Pattern: DD.MM.YYYY oder DD-MM-YYYY oder YYYY-MM-DD
|
|
date_patterns = [
|
|
r'(\d{2})\.(\d{2})\.(\d{4})', # DD.MM.YYYY
|
|
r'(\d{2})-(\d{2})-(\d{4})', # DD-MM-YYYY
|
|
r'(\d{4})-(\d{2})-(\d{2})', # YYYY-MM-DD
|
|
]
|
|
|
|
for pattern in date_patterns:
|
|
match = re.search(pattern, filename)
|
|
if match:
|
|
if pattern == date_patterns[2]: # YYYY-MM-DD
|
|
year, month, day = match.groups()
|
|
else: # DD.MM.YYYY or DD-MM-YYYY
|
|
day, month, year = match.groups()
|
|
|
|
try:
|
|
date_obj = datetime(int(year), int(month), int(day))
|
|
return date_obj.strftime('%Y-%m-%d')
|
|
except ValueError:
|
|
continue
|
|
|
|
return datetime.now().strftime('%Y-%m-%d')
|
|
|
|
def analyze_filtered_log(filepath):
|
|
"""Analysiert eine gefilterte Log-Datei"""
|
|
participants = set()
|
|
message_count = 0
|
|
|
|
try:
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
|
|
message_count += 1
|
|
|
|
# Extrahiere Spielernamen aus verschiedenen Formaten
|
|
# Chat: [Async Chat Thread - #0/INFO]: Admin ▎ SimolZimol : message
|
|
chat_match = re.search(r': (?:Admin|Mod|Member) ▎ ([^:]+) :', line)
|
|
if chat_match:
|
|
participants.add(chat_match.group(1).strip())
|
|
|
|
# Join/Leave: Admin ▎ SimolZimol joined the game
|
|
join_match = re.search(r': (?:Admin|Mod|Member) ▎ ([^j]+) joined', line)
|
|
if join_match:
|
|
participants.add(join_match.group(1).strip())
|
|
|
|
# Einfache Join/Leave: SimolZimol joined the game
|
|
simple_join = re.search(r': ([^j]+) joined the game', line)
|
|
if simple_join and '▎' not in line:
|
|
participants.add(simple_join.group(1).strip())
|
|
|
|
except Exception as e:
|
|
try:
|
|
print(f"❌ Fehler beim Analysieren der Datei: {e}")
|
|
except UnicodeEncodeError:
|
|
print(f">> Fehler beim Analysieren der Datei: {e}")
|
|
return [], 0
|
|
|
|
return list(participants), message_count
|
|
|
|
def add_to_chat_index(chat_entry):
|
|
"""Fügt einen neuen Chat-Eintrag zur chat-index.json hinzu"""
|
|
index_path = 'chat-logs/chat-index.json'
|
|
|
|
try:
|
|
with open(index_path, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
# Handle both old and new format
|
|
if isinstance(data, list):
|
|
# Old format - convert to new
|
|
chats = data
|
|
categories = {
|
|
"crea-1": {
|
|
"name": "🏰 Creative Mode",
|
|
"description": "Creative building sessions on the main server",
|
|
"color": "#4CAF50"
|
|
},
|
|
"survival-1": {
|
|
"name": "⛏️ Survival Mode",
|
|
"description": "Survival adventures and base building",
|
|
"color": "#FF9800"
|
|
},
|
|
"event": {
|
|
"name": "🎉 Special Events",
|
|
"description": "Special events and community gatherings",
|
|
"color": "#9C27B0"
|
|
},
|
|
"test": {
|
|
"name": "🔧 Testing & Setup",
|
|
"description": "Server testing and configuration sessions",
|
|
"color": "#607D8B"
|
|
}
|
|
}
|
|
data = {
|
|
"categories": categories,
|
|
"chats": chats
|
|
}
|
|
|
|
# Add new chat
|
|
data["chats"].append(chat_entry)
|
|
|
|
# Save back
|
|
with open(index_path, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
|
|
|
try:
|
|
print(f"✅ Chat erfolgreich zur Index-Datei hinzugefügt!")
|
|
except UnicodeEncodeError:
|
|
print(f">> Chat erfolgreich zur Index-Datei hinzugefügt!")
|
|
return True
|
|
|
|
except Exception as e:
|
|
try:
|
|
print(f"❌ Fehler beim Aktualisieren der Index-Datei: {e}")
|
|
except UnicodeEncodeError:
|
|
print(f">> Fehler beim Aktualisieren der Index-Datei: {e}")
|
|
return False
|
|
|
|
def is_already_filtered(filepath):
|
|
"""Prüft ob eine Datei bereits gefiltert ist (wenige Server-Logs)"""
|
|
try:
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
lines = f.readlines()
|
|
|
|
if len(lines) == 0:
|
|
return False
|
|
|
|
# Zähle Server-Log-Zeilen vs Chat-Zeilen
|
|
server_log_count = 0
|
|
chat_count = 0
|
|
|
|
for line in lines[:100]: # Schaue nur erste 100 Zeilen an
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
|
|
# Server-spezifische Patterns
|
|
if any(pattern in line for pattern in [
|
|
'Server thread/INFO', 'main/INFO', 'ThreadedAnvilChunkStorage',
|
|
'Reloading ResourceManager', 'Starting minecraft server'
|
|
]):
|
|
server_log_count += 1
|
|
elif '[Async Chat Thread' in line or '] : ' in line:
|
|
chat_count += 1
|
|
|
|
# Wenn mehr Chat als Server-Logs, ist es wahrscheinlich schon gefiltert
|
|
return chat_count > server_log_count
|
|
|
|
except Exception:
|
|
return False
|
|
|
|
def needs_filtering(filepath):
|
|
"""Bestimmt ob eine Datei gefiltert werden muss"""
|
|
# Bereits gefilterte Dateien
|
|
if '-filtered' in filepath or 'filtered' in filepath.lower():
|
|
return False
|
|
|
|
return not is_already_filtered(filepath)
|
|
|
|
def main():
|
|
try:
|
|
print(">> Quick Add - Minecraft Chat Log")
|
|
except UnicodeEncodeError:
|
|
print(">> Quick Add - Minecraft Chat Log")
|
|
print("=" * 40)
|
|
|
|
# Check for auto mode
|
|
auto_mode = '--auto' in sys.argv
|
|
if auto_mode:
|
|
sys.argv.remove('--auto')
|
|
|
|
if len(sys.argv) != 2:
|
|
print("Usage: python quick_add.py <log_file> [--auto]")
|
|
print("Beispiel: python quick_add.py raw-logs/crea-1/2020-07-25-1.log")
|
|
print(" python quick_add.py chat-logs/bereits-gefiltert.txt")
|
|
print(" python quick_add.py raw-logs/crea-1/2020-07-25-1.log --auto")
|
|
return
|
|
|
|
input_file = sys.argv[1]
|
|
|
|
if not os.path.exists(input_file):
|
|
try:
|
|
print(f"❌ Datei '{input_file}' existiert nicht!")
|
|
except UnicodeEncodeError:
|
|
print(f">> Datei '{input_file}' existiert nicht!")
|
|
return
|
|
|
|
# Erkenne Gamemode aus dem vollständigen Pfad
|
|
gamemode = detect_gamemode_from_path(input_file)
|
|
try:
|
|
print(f"🎮 Erkannter Gamemode: {gamemode}")
|
|
except UnicodeEncodeError:
|
|
print(f">> Erkannter Gamemode: {gamemode}")
|
|
|
|
# Bestimme ob Filterung nötig ist
|
|
needs_filter = needs_filtering(input_file)
|
|
|
|
if needs_filter:
|
|
try:
|
|
print("🔄 Raw Log erkannt - wird gefiltert...")
|
|
except UnicodeEncodeError:
|
|
print(">> Raw Log erkannt - wird gefiltert...")
|
|
|
|
# Generate filtered filename
|
|
base_name = os.path.splitext(os.path.basename(input_file))[0]
|
|
filtered_file = f"chat-logs/{gamemode}-{base_name}-filtered.txt"
|
|
|
|
print(f"📥 Input: {input_file}")
|
|
print(f"📤 Output: {filtered_file}")
|
|
|
|
# Filter the raw log
|
|
filter_result = os.system(f'python filter_raw_logs.py "{input_file}" "{filtered_file}"')
|
|
|
|
if filter_result != 0 or not os.path.exists(filtered_file):
|
|
try:
|
|
print("❌ Filterung fehlgeschlagen!")
|
|
except UnicodeEncodeError:
|
|
print(">> Filterung fehlgeschlagen!")
|
|
return
|
|
|
|
process_file = filtered_file
|
|
else:
|
|
try:
|
|
print("✅ Bereits gefilterte Datei erkannt - überspringe Filterung")
|
|
except UnicodeEncodeError:
|
|
print(">> Bereits gefilterte Datei erkannt - überspringe Filterung")
|
|
process_file = input_file
|
|
|
|
# Analyze the filtered log
|
|
try:
|
|
print("\n🔍 Analysiere Chat-Datei...")
|
|
except UnicodeEncodeError:
|
|
print("\n>> Analysiere Chat-Datei...")
|
|
participants, message_count = analyze_filtered_log(process_file)
|
|
|
|
# Extract metadata
|
|
date = extract_date_from_filename(os.path.basename(process_file))
|
|
|
|
# Generate chat entry
|
|
gamemode_titles = {
|
|
'crea-1': 'Creative Session',
|
|
'survival-1': 'Survival Session',
|
|
'event': 'Special Event',
|
|
'test': 'Test Session'
|
|
}
|
|
|
|
# Verbesserte ID-Generierung
|
|
date_parts = date.split('-')
|
|
date_short = f"{date_parts[2]}.{date_parts[1]}.{date_parts[0]}" # DD.MM.YYYY
|
|
chat_id = f"{gamemode}-{date_short}"
|
|
|
|
title = f"{gamemode_titles.get(gamemode, '📝 Session')} - {datetime.strptime(date, '%Y-%m-%d').strftime('%B %d, %Y')}"
|
|
|
|
chat_entry = {
|
|
"id": chat_id,
|
|
"title": title,
|
|
"description": f"Session with {len(participants)} players and {message_count} messages",
|
|
"date": date,
|
|
"category": gamemode,
|
|
"participants": participants,
|
|
"messages": message_count,
|
|
"filename": os.path.basename(process_file),
|
|
"mapDownload": {
|
|
"available": False
|
|
}
|
|
}
|
|
|
|
try:
|
|
print(f"\n📊 Session Details:")
|
|
print(f" 🆔 ID: {chat_id}")
|
|
print(f" 📅 Datum: {date}")
|
|
print(f" 🎮 Kategorie: {gamemode}")
|
|
print(f" 💬 Nachrichten: {message_count}")
|
|
print(f" 👥 Spieler: {', '.join(participants)}")
|
|
except UnicodeEncodeError:
|
|
print(f"\n>> Session Details:")
|
|
print(f" ID: {chat_id}")
|
|
print(f" Datum: {date}")
|
|
print(f" Kategorie: {gamemode}")
|
|
print(f" Nachrichten: {message_count}")
|
|
print(f" Spieler: {', '.join(participants)}")
|
|
|
|
# Ask for confirmation
|
|
if auto_mode:
|
|
try:
|
|
print(f"\n🤖 Automatischer Modus - füge zur chat-index.json hinzu...")
|
|
except UnicodeEncodeError:
|
|
print(f"\n>> Automatischer Modus - füge zur chat-index.json hinzu...")
|
|
response = 'y'
|
|
else:
|
|
try:
|
|
response = input(f"\n❓ Zur chat-index.json hinzufügen? (y/N): ")
|
|
except UnicodeEncodeError:
|
|
response = input(f"\n>> Zur chat-index.json hinzufügen? (y/N): ")
|
|
|
|
if response.lower() in ['y', 'yes', 'j', 'ja']:
|
|
if add_to_chat_index(chat_entry):
|
|
try:
|
|
print(f"\n🎉 Erfolgreich hinzugefügt! Die Webseite zeigt jetzt {len(participants)} Spieler und {message_count} Nachrichten an.")
|
|
except UnicodeEncodeError:
|
|
print(f"\n>> Erfolgreich hinzugefügt! Die Webseite zeigt jetzt {len(participants)} Spieler und {message_count} Nachrichten an.")
|
|
else:
|
|
try:
|
|
print(f"\n❌ Fehler beim Hinzufügen!")
|
|
except UnicodeEncodeError:
|
|
print(f"\n>> Fehler beim Hinzufügen!")
|
|
else:
|
|
if needs_filter:
|
|
try:
|
|
print(f"\n📝 Abgebrochen. Die gefilterte Datei '{process_file}' wurde erstellt, aber nicht zur Index hinzugefügt.")
|
|
except UnicodeEncodeError:
|
|
print(f"\n>> Abgebrochen. Die gefilterte Datei '{process_file}' wurde erstellt, aber nicht zur Index hinzugefügt.")
|
|
else:
|
|
try:
|
|
print(f"\n📝 Abgebrochen. Keine Änderungen vorgenommen.")
|
|
except UnicodeEncodeError:
|
|
print(f"\n>> Abgebrochen. Keine Änderungen vorgenommen.")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|