diff --git a/src/db.py b/src/db.py index 8c00935..49cd070 100644 --- a/src/db.py +++ b/src/db.py @@ -46,10 +46,50 @@ class DuckDB: channel = channel.strip() if not channel: return '__unknown__' + # Preserve internal buckets used by the bot/database. + # This allows explicit references like '__global__' without being remapped to '__pm__'. + if channel.startswith('__') and channel.endswith('__'): + return channel if channel.startswith('#') or channel.startswith('&'): return channel.lower() return '__pm__' + def is_ignored(self, nick: str, channel: str) -> bool: + """Return True if nick is ignored for this channel or globally.""" + try: + if not isinstance(nick, str) or not nick.strip(): + return False + nick_clean = sanitize_user_input( + nick, + max_length=50, + allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-[]{}^`|\\' + ) + nick_lower = nick_clean.lower().strip() + if not nick_lower: + return False + + # Channel-scoped ignore + player = self.get_player_if_exists(nick_lower, channel) + if isinstance(player, dict) and bool(player.get('ignored', False)): + return True + + # Global ignore bucket + global_player = self.get_player_if_exists(nick_lower, '__global__') + return bool(global_player and global_player.get('ignored', False)) + except Exception: + return False + + def set_global_ignored(self, nick: str, ignored: bool) -> bool: + """Set global ignored flag for nick (persisted).""" + try: + player = self.get_player(nick, '__global__') + if not isinstance(player, dict): + return False + player['ignored'] = bool(ignored) + return True + except Exception: + return False + @property def players(self): """Backward-compatible flattened view of all players across channels.""" diff --git a/src/duckhuntbot.py b/src/duckhuntbot.py index 5e5fb61..5866ab2 100644 --- a/src/duckhuntbot.py +++ b/src/duckhuntbot.py @@ -627,7 +627,7 @@ class DuckHuntBot: self.logger.warning(f"Error updating player activity for {nick}: {e}") try: - if player.get('ignored', False) and not self.is_admin(user): + if self.db.is_ignored(nick, safe_channel) and not self.is_admin(user): return except Exception as e: self.logger.error(f"Error checking admin/ignore status: {e}") @@ -1519,8 +1519,8 @@ class DuckHuntBot: target = args[0].lower() player = self.db.get_player(target, channel) - - action_func(player) + + action_func(player, target) if is_private_msg: action_name = "Ignored" if message_key == 'admin_ignore' else "Unignored" @@ -1538,7 +1538,7 @@ class DuckHuntBot: usage_command='usage_ignore', private_usage='!ignore ', message_key='admin_ignore', - action_func=lambda player: player.update({'ignored': True}) + action_func=lambda player, target: (player.update({'ignored': True}), self.db.set_global_ignored(target, True)) ) async def handle_unignore(self, nick, channel, args): @@ -1548,7 +1548,7 @@ class DuckHuntBot: usage_command='usage_unignore', private_usage='!unignore ', message_key='admin_unignore', - action_func=lambda player: player.update({'ignored': False}) + action_func=lambda player, target: (player.update({'ignored': False}), self.db.set_global_ignored(target, False)) ) async def handle_ducklaunch(self, nick, channel, args):