import discord import asyncio import re import time from config import BLACKLISTED_USERS, BUCKET_REACT_USERS, AUTO_DELETE_USERS, SPECIAL_RESPONSES from bot.commands.afk_commands import AfkCommands from bot.commands.utility_commands import UtilityCommands from bot.commands.fun_commands import FunCommands from bot.commands.admin_commands import AdminCommands from bot.commands.test_commands import TestCommands from utils.time_parser import parse_time class MessageHandler: def __init__(self, bot): self.bot = bot # Initialize command handlers self.afk_commands = AfkCommands(bot) self.utility_commands = UtilityCommands(bot) self.fun_commands = FunCommands(bot) self.admin_commands = AdminCommands(bot) self.test_commands = TestCommands(bot) # Attach command handlers to the bot for easier access from tests bot.afk_commands = self.afk_commands bot.utility_commands = self.utility_commands bot.fun_commands = self.fun_commands bot.admin_commands = self.admin_commands bot.test_commands = self.test_commands # Regex for detecting "in X time" patterns self.time_pattern = re.compile( r'in\s+((?:\d+\s*(?:seconds?|minutes?|hours?|days?|s|m|h|d)\s*)+)', re.IGNORECASE ) def parse_relative_time(self, time_str): """ Parse relative time strings like "2 hours", "30 minutes" Args: time_str: String in format like "2 hours", "30 minutes" Returns: Unix timestamp (seconds since epoch) for the future time or None if invalid """ # Convert to format our parse_time can handle time_part = time_str.replace('hours', 'h').replace('hour', 'h') time_part = time_part.replace('minutes', 'm').replace('minute', 'm') time_part = time_part.replace('seconds', 's').replace('second', 's') time_part = time_part.replace('days', 'd').replace('day', 'd') # Remove spaces to match expected format like "2h30m" time_part = re.sub(r'\s+', '', time_part) seconds = parse_time(time_part) if seconds: return int(time.time() + seconds) return None def create_discord_timestamp(self, unix_time, format_code='R'): """Create a Discord timestamp string""" return f"" def replace_time_patterns(self, content): """Replace "in X time" patterns with Discord timestamps""" def replace_match(match): time_str = match.group(1) unix_time = self.parse_relative_time(time_str) if unix_time: return self.create_discord_timestamp(unix_time) return match.group(0) # Return original text if parsing fails return self.time_pattern.sub(replace_match, content) async def handle_message(self, message): # Skip bot messages if message.author.bot: return # Look for and replace time patterns (only for non-command messages) if not message.content.startswith('.'): original_content = message.content modified_content = self.replace_time_patterns(original_content) # If the content was modified, edit the original message if modified_content != original_content: try: await message.edit(content=modified_content) except Exception as e: # If we don't have permission to edit, just ignore pass # Handle special responses for user_id, data in SPECIAL_RESPONSES.items(): if message.author.id == user_id and data["trigger"] in message.content: await message.reply(data["response"]) # Handle automatic reactions if message.channel.id in self.bot.horsin: await message.add_reaction("🐴") if message.author.id in BUCKET_REACT_USERS: await message.add_reaction("🪣") # Handle auto-delete for specific users if message.author.id in AUTO_DELETE_USERS: try: await message.delete() except: pass # Handle DM if in AFK mode if isinstance(message.channel, discord.DMChannel) and message.author != self.bot.user: await self.afk_commands.handle_afk_dm(message) # Don't process further if the message is not from the bot user if message.author != self.bot.user: return # Handle commands await self.handle_commands(message) async def handle_blacklist(self, message): """Handle blacklisted users""" if message.author.id in BLACKLISTED_USERS: await message.reply("no") return True return False async def handle_commands(self, message): """Handle commands issued by the bot user""" content = message.content # AFK Commands if content.startswith(".afk"): await self.afk_commands.cmd_afk(message) elif content.startswith(".unafk"): await self.afk_commands.cmd_unafk(message) # Fun Commands elif content.startswith(".horse"): await self.fun_commands.cmd_horse(message) elif content.startswith(".rps "): if not await self.handle_blacklist(message): await self.fun_commands.cmd_rps(message) elif content.startswith(".repeat29"): await self.fun_commands.cmd_repeat(message) # Utility Commands elif content.startswith(".remindme "): if not await self.handle_blacklist(message): await self.utility_commands.cmd_remindme(message) elif content.startswith(".fmt "): await self.utility_commands.cmd_fmt(message) elif content.startswith(".eval "): await self.utility_commands.cmd_eval(message) elif content.startswith(".delrecent "): await self.utility_commands.cmd_delrecent(message) elif content.startswith(".nuke"): await self.utility_commands.cmd_nuke_server(message) elif content.startswith(".savechannel"): await self.utility_commands.cmd_savechannel(message) # Admin Commands elif content.startswith(".addcmd "): await self.admin_commands.cmd_addcmd(message) elif content.startswith(".delcmd "): await self.admin_commands.cmd_delcmd(message) elif content.startswith(".listcmds"): await self.admin_commands.cmd_listcmds(message) elif content.startswith(".trackmessages"): await self.admin_commands.cmd_trackmessages(message) elif content.startswith(".untrackmessages"): await self.admin_commands.cmd_untrackmessages(message) elif content.startswith(".test"): await self.test_commands.cmd_test(message)