From d63f72be9545523a446f31762851de77ffab18ea 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 ++++++++++++++++++++++++++-------------------- group_monitor.old | Bin 0 -> 32768 bytes 3 files changed, 75 insertions(+), 59 deletions(-) create mode 100644 group_monitor.old 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) --- diff --git a/group_monitor.old b/group_monitor.old new file mode 100644 index 0000000000000000000000000000000000000000..9dd2c2879bf2fe45a2193b2f3dfecc39ddde2764 GIT binary patch literal 32768 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 V>5*zoN(~T%k6IBd3>ci3{|}x9C@=s3 literal 0 HcmV?d00001