From ef050dff40fa64bbf7b35ddf41cb741aeebf67c1 Mon Sep 17 00:00:00 2001 From: Thomas Kerpe Date: Tue, 9 Jun 2026 20:42:49 +0200 Subject: [PATCH] Refactor: Rename user_stats to user_profile and restructure schema - Rename user_stats table to user_profile - Split full_name into first_name and last_name columns - Remove message_count (focus on profile data only) - Add is_bot flag to track bot accounts - Create migration.sql with automatic data migration and schema conversion - Update MIGRATION.md with rename and migration instructions - Update all DatabaseManager methods to use new table structure - Update profile change detection to use separated name fields --- activity_log.txt | 1 + bot.py | 133 ++++++++++++++++++++++++++--------------------- 2 files changed, 75 insertions(+), 59 deletions(-) diff --git a/activity_log.txt b/activity_log.txt index 97f9da5..555b486 100644 --- a/activity_log.txt +++ b/activity_log.txt @@ -84,3 +84,4 @@ [2026-06-09 20:28:04] | CHAT_ID=-1001202289941 | TOPIC=761 | TYPE=PROFILE_CHANGE | USER=151261436 (tokelemma) | DETAILS=Profiländerung erkannt | Name: 'Toke. 🌻' → 'Toke 🌻' [2026-06-09 20:28:04] | CHAT_ID=-1001202289941 | TOPIC=761 | TYPE=MESSAGE | USER=151261436 (tokelemma) | DETAILS=Nachricht gesendet: 'Und back...' [2026-06-09 20:28:04] | CHAT_ID=-1001202289941 | TOPIC=761 | TYPE=MESSAGE | USER=151261436 (tokelemma) | DETAILS=Nachricht gesendet: 'Und back...' +[2026-06-09 20:41:48] | CHAT_ID=-1001202289941 | TOPIC=761 | TYPE=MESSAGE | USER=151261436 (tokelemma) | DETAILS=Nachricht gesendet: 'Blaaaahah...' diff --git a/bot.py b/bot.py index 9209bb5..f0171cb 100644 --- a/bot.py +++ b/bot.py @@ -68,14 +68,15 @@ class DatabaseManager: ) """) - # 2. user_stats (Angepasst mit Name/Username) + # 2. user_profile (Refaktoriert: first_name, last_name, is_bot, kein message_count) self.cursor.execute(""" - CREATE TABLE IF NOT EXISTS user_stats ( + CREATE TABLE IF NOT EXISTS user_profile ( user_id INTEGER PRIMARY KEY, - message_count INTEGER DEFAULT 0, - last_message_timestamp TEXT NULL, - full_name TEXT, -- NEU: Benutzername speichern - username TEXT -- NEU: Benutzernamen speichern + first_name TEXT, + last_name TEXT, + username TEXT, + is_bot INTEGER DEFAULT 0, + last_message_timestamp TEXT NULL ) """) @@ -123,52 +124,61 @@ class DatabaseManager: except Exception as e: logging.error(f"Fehler beim Protokollieren der Profiländerung: {e}") - def update_user_stats(self, user_id: int, message_count: int, last_message_timestamp: str, full_name: str = None, username: str = None): - """Aktualisiert die Statistiken und das letzte bekannte Profil des Benutzers.""" - try: - # Die Spaltenliste muss genau den Reihenfolge der Argumente entsprechen. - self.cursor.execute(""" - INSERT INTO user_stats (user_id, message_count, last_message_timestamp, full_name, username) - VALUES (?, ?, ?, ?, ?) - ON CONFLICT(user_id) DO UPDATE SET - message_count = excluded.message_count, - last_message_timestamp = excluded.last_message_timestamp, - full_name = (CASE WHEN user_stats.full_name IS NULL THEN excluded.full_name ELSE user_stats.full_name END), - username = (CASE WHEN user_stats.username IS NULL THEN excluded.username ELSE user_stats.username END) - """, (user_id, message_count, last_message_timestamp, full_name, username)) - self.conn.commit() - except Exception as e: - logging.error(f"Fehler beim Aktualisieren der User Stats für {user_id}: {e}") - def get_user_profile(self, user_id: int) -> dict: """Lädt das gespeicherte Profil eines Benutzers aus der Datenbank.""" try: self.cursor.execute(""" - SELECT full_name, username FROM user_stats WHERE user_id = ? + SELECT first_name, last_name, username, is_bot FROM user_profile WHERE user_id = ? """, (user_id,)) result = self.cursor.fetchone() if result: - return {'full_name': result[0], 'username': result[1]} + return { + 'first_name': result[0], + 'last_name': result[1], + 'username': result[2], + 'is_bot': result[3] + } return None except Exception as e: logging.error(f"Fehler beim Laden des Benutzerprofils {user_id}: {e}") return None - def update_user_profile(self, user_id: int, full_name: str, username: str): - """Aktualisiert nur die Profilinformationen (Name/Username) eines Benutzers. + def update_user_profile(self, user_id: int, first_name: str, last_name: str, username: str, is_bot: bool = False): + """Aktualisiert nur die Profilinformationen eines Benutzers (ohne Nachrichtenstatistiken). Erstellt den Benutzer automatisch, falls er noch nicht existiert. """ try: self.cursor.execute(""" - INSERT INTO user_stats (user_id, full_name, username, message_count, last_message_timestamp) - VALUES (?, ?, ?, 0, NULL) - ON CONFLICT(user_id) DO UPDATE SET full_name = excluded.full_name, username = excluded.username - """, (user_id, full_name, username)) + INSERT INTO user_profile (user_id, first_name, last_name, username, is_bot, last_message_timestamp) + VALUES (?, ?, ?, ?, ?, NULL) + ON CONFLICT(user_id) DO UPDATE SET + first_name = excluded.first_name, + last_name = excluded.last_name, + username = excluded.username, + is_bot = excluded.is_bot + """, (user_id, first_name, last_name, username, int(is_bot))) self.conn.commit() except Exception as e: logging.error(f"Fehler beim Aktualisieren des Benutzerprofils {user_id}: {e}") + def update_user_stats(self, user_id: int, last_message_timestamp: str, first_name: str = None, last_name: str = None, username: str = None, is_bot: bool = False): + """Aktualisiert die Statistiken und das letzte bekannte Profil des Benutzers.""" + try: + self.cursor.execute(""" + INSERT INTO user_profile (user_id, first_name, last_name, username, is_bot, last_message_timestamp) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT(user_id) DO UPDATE SET + last_message_timestamp = excluded.last_message_timestamp, + first_name = COALESCE(excluded.first_name, user_profile.first_name), + last_name = COALESCE(excluded.last_name, user_profile.last_name), + username = COALESCE(excluded.username, user_profile.username), + is_bot = COALESCE(NULLIF(excluded.is_bot, 0), user_profile.is_bot) + """, (user_id, first_name, last_name, username, int(is_bot), last_message_timestamp)) + self.conn.commit() + except Exception as e: + logging.error(f"Fehler beim Aktualisieren der User Stats für {user_id}: {e}") + def close(self): """Schließt die Datenbankverbindung.""" self.conn.close() @@ -222,34 +232,44 @@ class MonitoringBot: # --- EVENT HANDLER 2: PROFILDÄNDERUNG (Profilvergleich mit DB) --- async def handle_profile_update(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Erkennt Profiländerungen durch Vergleich des gespeicherten Profils mit dem aktuellen.""" - - #if not update or not update.effective_user or not update.effective_chat: - # return + if not update or not update.effective_user or not update.effective_chat: + return user = update.effective_user chat_id = update.effective_chat.id topic_id = self._get_topic_id(update) - current_full_name = user.full_name + + # Extrahiere current profile + current_first_name = user.first_name or "" + current_last_name = user.last_name or "" current_username = getattr(user, 'username', None) + current_is_bot = user.is_bot # Lade das alte Profil aus der Datenbank old_profile = self.db.get_user_profile(user.id) # Vergleiche mit dem aktuellen Profil if old_profile: - old_full_name = old_profile['full_name'] + old_first_name = old_profile['first_name'] or "" + old_last_name = old_profile['last_name'] or "" old_username = old_profile['username'] + old_is_bot = old_profile['is_bot'] # Prüfe auf Änderungen - has_name_changed = old_full_name != current_full_name + has_name_changed = (old_first_name != current_first_name) or (old_last_name != current_last_name) has_username_changed = old_username != current_username + has_is_bot_changed = old_is_bot != current_is_bot - if has_name_changed or has_username_changed: + if has_name_changed or has_username_changed or has_is_bot_changed: details = f"Profiländerung erkannt" if has_name_changed: - details += f" | Name: '{old_full_name}' → '{current_full_name}'" + old_full = f"{old_first_name} {old_last_name}".strip() + new_full = f"{current_first_name} {current_last_name}".strip() + details += f" | Name: '{old_full}' → '{new_full}'" if has_username_changed: details += f" | Username: '{old_username}' → '{current_username}'" + if has_is_bot_changed: + details += f" | is_bot: {old_is_bot} → {current_is_bot}" self._log_event( client=update, context=context, chat_id=chat_id, topic_id=topic_id, @@ -262,8 +282,8 @@ class MonitoringBot: group_chat_id=chat_id, user_id=user.id, topic_id=topic_id, - old_name=old_full_name or "N/A", - new_name=current_full_name, + old_name=f"{old_first_name} {old_last_name}".strip() or "N/A", + new_name=f"{current_first_name} {current_last_name}".strip(), old_user=old_username or "N/A", new_user=current_username or "N/A", details=details @@ -272,8 +292,10 @@ class MonitoringBot: # Persistiere die neuen Profilinformationen in user_stats self.db.update_user_profile( user_id=user.id, - full_name=current_full_name, - username=current_username + first_name=current_first_name, + last_name=current_last_name, + username=current_username, + is_bot=current_is_bot ) @@ -297,13 +319,14 @@ class MonitoringBot: user = update.effective_user timestamp_str = datetime.now().isoformat() - # 2. Statistik-Update: User ID, Count erhöhen und aktuelle Profildaten speichern + # 2. Statistik-Update: Profildaten speichern und Timestamp aktualisieren self._update_user_stats( user_id=user.id, - message_count=+1, last_message_timestamp=timestamp_str, - full_name=user.full_name, # Aktueller Name vom Telegram Update - username=getattr(user, 'username', None) # Optional: Username + first_name=user.first_name, + last_name=user.last_name or None, + username=getattr(user, 'username', None), + is_bot=user.is_bot ) # General Logging (Protokollierung des Ereignisses) @@ -318,29 +341,21 @@ class MonitoringBot: """Interne Funktion zur Durchführung des Datenbank- und File-Logging.""" self.db.log_activity(group_chat_id=chat_id, topic_id=topic_id, activity_type=activity_type, user_id=user_id, username=username, details=details) - # Debug Output (Bleibt stabil) + # Debug Output if ENABLE_DEBUG_MODE: logging.info(f"[LOG] Aktivität erfasst ({activity_type}): User {user_id} | Topic '{topic_id}'") if WRITE_ACTIVITY_TO_FILE: print(f"--- DEBUG LOG ENTRY --- Type: {activity_type}, Topic: {topic_id}, User: {username or user_id}") - # Optional File Logging (Bleibt stabil) - if WRITE_ACTIVITY_TO_FILE: - log_message = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] | CHAT_ID={chat_id} | TOPIC={topic_id} | TYPE={activity_type} | USER={user_id} ({username}) | DETAILS={details}\n" - with open(LOGGING_FILE, 'a', encoding='utf-8') as f: - f.write(log_message) - - # Optional File Logging if WRITE_ACTIVITY_TO_FILE: log_message = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] | CHAT_ID={chat_id} | TOPIC={topic_id} | TYPE={activity_type} | USER={user_id} ({username}) | DETAILS={details}\n" with open(LOGGING_FILE, 'a', encoding='utf-8') as f: f.write(log_message) - def _update_user_stats(self, user_id: int, message_count: int, last_message_timestamp: str, full_name: str = None, username: str = None): - """Aktualisiert die Statistiken und das Profil des Benutzers via DatabaseManager.""" - # Ruft die korrigierte Methode aus dem Manager auf. - self.db.update_user_stats(user_id, message_count, last_message_timestamp, full_name=full_name, username=username) + def _update_user_stats(self, user_id: int, last_message_timestamp: str, first_name: str = None, last_name: str = None, username: str = None, is_bot: bool = False): + """Wrapper für DatabaseManager.update_user_stats().""" + self.db.update_user_stats(user_id, last_message_timestamp, first_name, last_name, username, is_bot) # --- HAUPT-RUN FUNKTION (Bleibt gleich) ---