i left skid marks in the code
This commit is contained in:
		
							parent
							
								
									fbfa27c979
								
							
						
					
					
						commit
						282ca4374a
					
				
							
								
								
									
										409
									
								
								app.py
									
									
									
									
									
								
							
							
						
						
									
										409
									
								
								app.py
									
									
									
									
									
								
							|  | @ -1,402 +1,25 @@ | ||||||
| import random |  | ||||||
| import discord | import discord | ||||||
| import asyncio | import asyncio | ||||||
| import traceback | import logging | ||||||
| import random |  | ||||||
| import datetime |  | ||||||
| import os | import os | ||||||
| import importlib.util | from bot.selfbot import SelfBot | ||||||
| import ast | from config import TOKEN | ||||||
| import websockets |  | ||||||
| import json |  | ||||||
| import difflib |  | ||||||
| import io |  | ||||||
| import gzip |  | ||||||
| import re |  | ||||||
| from dotenv import load_dotenv |  | ||||||
| 
 | 
 | ||||||
| # Load environment variables from .env file | def main(): | ||||||
| load_dotenv() |     # Configure logging | ||||||
| 
 |     logging.basicConfig( | ||||||
| time_regex = re.compile(r'(\d+)([smhd])')  # Matches 4m2s, 1h30m, etc. |         level=logging.INFO, | ||||||
| 
 |         format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | ||||||
| def parse_time(time_str): |  | ||||||
|     units = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400} |  | ||||||
|     total_seconds = sum(int(amount) * units[unit] for amount, unit in time_regex.findall(time_str)) |  | ||||||
|     return total_seconds if total_seconds > 0 else None |  | ||||||
| 
 |  | ||||||
| COMMANDS_DIR = "commands" |  | ||||||
| os.makedirs(COMMANDS_DIR, exist_ok=True) |  | ||||||
| 
 |  | ||||||
| def load_commands(): |  | ||||||
|     commands = {} |  | ||||||
|     for filename in os.listdir(COMMANDS_DIR): |  | ||||||
|         if filename.endswith(".py"): |  | ||||||
|             cmd_name = filename[:-3] |  | ||||||
|             cmd_path = os.path.join(COMMANDS_DIR, filename) |  | ||||||
| 
 |  | ||||||
|             spec = importlib.util.spec_from_file_location(cmd_name, cmd_path) |  | ||||||
|             module = importlib.util.module_from_spec(spec) |  | ||||||
|             spec.loader.exec_module(module) |  | ||||||
| 
 |  | ||||||
|             if hasattr(module, "run") and callable(module.run): |  | ||||||
|                 commands[cmd_name] = module.run |  | ||||||
|     return commands |  | ||||||
| # Add a tracking file to save channel IDs |  | ||||||
| TRACKED_CHANNELS_FILE = "tracked_channels.json" |  | ||||||
| 
 |  | ||||||
| def load_tracked_channels(): |  | ||||||
|     if os.path.exists(TRACKED_CHANNELS_FILE): |  | ||||||
|         with open(TRACKED_CHANNELS_FILE, 'r') as f: |  | ||||||
|             return json.load(f) |  | ||||||
|     return [] |  | ||||||
| 
 |  | ||||||
| def save_tracked_channels(tracked_channels): |  | ||||||
|     with open(TRACKED_CHANNELS_FILE, 'w') as f: |  | ||||||
|         json.dump(tracked_channels, f) |  | ||||||
| 
 |  | ||||||
| async def handle_blacklist(message): |  | ||||||
|     if message.author in [696800726084747314]: |  | ||||||
|         await message.reply("no") |  | ||||||
| 
 |  | ||||||
| class Selfbot(discord.Client): |  | ||||||
|     def __init__(self): |  | ||||||
|         super().__init__() |  | ||||||
|         self.default_status = None |  | ||||||
|         self.loaded_commands = load_commands() |  | ||||||
|         self.tracked_channels = load_tracked_channels() |  | ||||||
|         self.last_status = {} |  | ||||||
|         self.AFK_STATUS = False  # Static variable to track AFK status |  | ||||||
|         self.AFK_NOTIFIED_USERS = []  # Static list to store users notified of AFK status |  | ||||||
|         self.horsin = [] |  | ||||||
|     async def on_ready(self): |  | ||||||
|         print(f"Logged in as {self.user}") |  | ||||||
|     async def handle_afk_dm(self,message): |  | ||||||
|         if self.AFK_STATUS and message.author.id not in self.AFK_NOTIFIED_USERS: |  | ||||||
|             await message.reply( |  | ||||||
|                 "Heya, I'm not at my computer right now, if you're requesting something please follow <https://nohello.club>. I'll let you know when I'm back :) \n\n-# This action was automated." |  | ||||||
|     ) |     ) | ||||||
|             self.AFK_NOTIFIED_USERS.append(message.author.id) |  | ||||||
|     async def on_message(self, message): |  | ||||||
|         if message.author.id == 1169111190824308768 and "<@1236667927944761396>" in message.content: |  | ||||||
|             await message.reply("shut the fuck up") |  | ||||||
|      |      | ||||||
|         if message.content.startswith(".remindme "): |     # Set up Discord client | ||||||
|             await handle_blacklist(message) |     intents = discord.Intents.all() | ||||||
|      |      | ||||||
|             parts = message.content.split(" ", 2) |     # Create the selfbot | ||||||
|             if len(parts) < 3: |     client = SelfBot(intents=intents) | ||||||
|                 await message.reply("Usage: `.remindme <time> <message>`", silent=True) |  | ||||||
|             else: |  | ||||||
|                 duration = parse_time(parts[1]) |  | ||||||
|                 if duration is None: |  | ||||||
|                     await message.reply("Invalid time format. Example: `4m2s`, `1h30m`.", silent=True) |  | ||||||
|                 else: |  | ||||||
|                     reminder_text = parts[2] |  | ||||||
|                     await message.reply(f"Reminder set for {parts[1]}.", silent=True) |  | ||||||
|      |      | ||||||
|                     async def reminder_task(): |     # Run the bot | ||||||
|                         await asyncio.sleep(duration) |  | ||||||
|                         await message.reply(f"{message.author.mention} Reminder: {reminder_text.replace("@", "at")}") |  | ||||||
| 
 |  | ||||||
|                     asyncio.create_task(reminder_task()) |  | ||||||
|         elif message.content.startswith(".rps "): |  | ||||||
|             await handle_blacklist(message) |  | ||||||
|             parts = message.content.split(" ",2) |  | ||||||
|             if len(parts) != 2: |  | ||||||
|                 await message.reply("Usage: `.rps <item`",silent=True) |  | ||||||
|                 item  |  | ||||||
|             item = parts[1] |  | ||||||
|             if item.lower() == "dick": |  | ||||||
|                 await message.reply("Scissors beats dick any day :3",silent=True) # little easter egg |  | ||||||
|                 return |  | ||||||
|             if item == "<@696800726084747314>": |  | ||||||
|                 await message.reply("Head so thick that i would try rock but the rock would break") |  | ||||||
|                 return |  | ||||||
|             choice = random.choice([1,2,3])  |  | ||||||
|             rps_map = dict(zip((1,2,3),[i for i in "rps"])) |  | ||||||
|             rps_map_reverse = dict(zip([i for i in "rps"],(1,2,3))) # FIXME: if you cant see the issue you're blind |  | ||||||
|             shortmaps = {word[0]: word for word in ["rock", "paper", "scissors"]} |  | ||||||
| 
 |  | ||||||
|             beat_map = {1:3,2:1,3:2} |  | ||||||
|             iid = 0 |  | ||||||
|             for k,v in rps_map_reverse.items(): |  | ||||||
|                 if item.lower().startswith(k): |  | ||||||
|                     iid = v |  | ||||||
|                     break |  | ||||||
|             if iid == 0: |  | ||||||
|                 await message.reply("Invalid choice!",silent=True) |  | ||||||
|                 return |  | ||||||
|             if choice == iid: |  | ||||||
|                 await message.reply(f"Huh we chose the same thing. Try again!",silent=True) |  | ||||||
|                 return |  | ||||||
|             if beat_map[iid] == choice: |  | ||||||
|                 await message.reply(f"Welp, ggs, I won. I chose `{shortmaps[rps_map[choice]]}`",silent=True) |  | ||||||
|             else: |  | ||||||
|                 await message.reply(f"Oop, you lost. Try again later! I chose `{shortmaps[rps_map[choice]]}`",silent=True) |  | ||||||
|              |  | ||||||
|              |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         # Handle DM if in AFK mode |  | ||||||
|         if isinstance(message.channel, discord.DMChannel) and message.author != self.user: |  | ||||||
|             await self.handle_afk_dm(message) |  | ||||||
|              |  | ||||||
|         if message.channel.id in self.horsin: |  | ||||||
|             await message.add_reaction("🐴") |  | ||||||
|         if message.author.id == 1341423498618208257: |  | ||||||
|             await message.add_reaction("🪣") |  | ||||||
|         if message.author != self.user: |  | ||||||
|             if message.author.id == 1351739454141763626: |  | ||||||
|                 try: |  | ||||||
|                     await message.delete() |  | ||||||
|                 except: |  | ||||||
|                     pass |  | ||||||
|             return |  | ||||||
|          |  | ||||||
|         if message.content.startswith(".horse"): |  | ||||||
|             if message.channel.id in self.horsin: |  | ||||||
|                 self.horsin.remove(message.channel.id) |  | ||||||
|                 await message.reply("no longer horsin around D:") |  | ||||||
|             else: |  | ||||||
|                 self.horsin.append(message.channel.id) |  | ||||||
|                 await message.reply(":D") |  | ||||||
| 
 |  | ||||||
|         if message.content.startswith(".afk"): |  | ||||||
|             if not self.AFK_STATUS: |  | ||||||
|                 self.AFK_STATUS = True |  | ||||||
|                 await message.reply("k") |  | ||||||
|             else: |  | ||||||
|                 await message.reply("You are already in AFK mode.", silent=True) |  | ||||||
| 
 |  | ||||||
|         # UNAFK Command |  | ||||||
|         elif message.content.startswith(".unafk"): |  | ||||||
|             if self.AFK_STATUS: |  | ||||||
|                 self.AFK_STATUS = False |  | ||||||
|                 for i in self.AFK_NOTIFIED_USERS: |  | ||||||
|                     try: |  | ||||||
|                         user = await self.fetch_user(i)  # Fetch user by ID |  | ||||||
|                         if user: |  | ||||||
|                             dm_channel = await user.create_dm()  # Create DM channel |  | ||||||
|                             await dm_channel.send("Hey, I'm back, human me will take over now!") |  | ||||||
|                     except Exception as e: |  | ||||||
|                         raise RuntimeWarning from e |  | ||||||
| 
 |  | ||||||
|                 self.AFK_NOTIFIED_USERS.clear()  # Clear the AFK notified users list |  | ||||||
|                 await message.reply("should work") |  | ||||||
|             else: |  | ||||||
|                 await message.reply("You are not in AFK mode.", silent=True) |  | ||||||
| 
 |  | ||||||
|          |  | ||||||
|          |  | ||||||
|          |  | ||||||
|         if message.content.startswith(".fmt "): |  | ||||||
|             try: |  | ||||||
|                 formated = eval(f"f'{message.content[5:]}'", globals(),locals()) |  | ||||||
|                 print(formated) |  | ||||||
|                 await asyncio.wait_for(message.edit(formated), timeout=5) |  | ||||||
| 
 |  | ||||||
|             except Exception as e: |  | ||||||
|                 flashdel = await message.channel.send(f"Error: {e}",silent=True) |  | ||||||
|                 await message.delete() |  | ||||||
|                 await flashdel.delete() |  | ||||||
|                 raise RuntimeWarning from e |  | ||||||
| 
 |  | ||||||
|         elif message.content.startswith(".eval "): |  | ||||||
|             try: |  | ||||||
|                 formatted = message.content[6:] |  | ||||||
|                 print(repr(formatted)) |  | ||||||
|                 exec_scope = { |  | ||||||
|                     "msg": message, |  | ||||||
|                     "asyncio": asyncio, |  | ||||||
|                     "random": random, |  | ||||||
|                     **self.loaded_commands,  # Inject loaded commands |  | ||||||
|                     "out": lambda content: message.reply(content, silent=True), |  | ||||||
|                 } |  | ||||||
|                 # Parse the code to detect non-async function calls |  | ||||||
|                 tree = ast.parse(formatted) |  | ||||||
|                 for node in ast.walk(tree): |  | ||||||
|                     if isinstance(node, ast.Call) and not isinstance(node.func, ast.Attribute): |  | ||||||
|                         # Check if the function is a coroutine |  | ||||||
|                         func_name = node.func.id |  | ||||||
|                         if func_name in exec_scope and asyncio.iscoroutinefunction(exec_scope[func_name]): |  | ||||||
|                             # Replace the call with an await expression |  | ||||||
|                             formatted = formatted.replace(f"{func_name}(", f"await {func_name}(") |  | ||||||
| 
 |  | ||||||
|                  |  | ||||||
|                 exec(f"async def __eval():\n    {formatted.replace(chr(10), chr(10) + '    ')}", exec_scope) |  | ||||||
| 
 |  | ||||||
|                 result = await exec_scope["__eval"]() |  | ||||||
| 
 |  | ||||||
|             except Exception: |  | ||||||
|                 await message.edit(content=traceback.format_exc()) |  | ||||||
|             finally: |  | ||||||
|                 await message.delete() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         elif message.content.startswith(".addcmd "): |  | ||||||
|             try: |  | ||||||
|                 parts = message.content.split(" ", 2) |  | ||||||
|                 if len(parts) < 3: |  | ||||||
|                     await message.reply("Usage: .addcmd <name> <code>",silent=True) |  | ||||||
|                     return |  | ||||||
| 
 |  | ||||||
|                 cmd_name, code = parts[1], parts[2] |  | ||||||
|                 cmd_path = os.path.join(COMMANDS_DIR, f"{cmd_name}.py") |  | ||||||
| 
 |  | ||||||
|                 with open(cmd_path, "w") as f: |  | ||||||
|                     f.write("async def run(msg):\n") |  | ||||||
|                     for line in code.split("\n"): |  | ||||||
|                         f.write(f"    {line}\n") |  | ||||||
| 
 |  | ||||||
|                 self.loaded_commands = load_commands() |  | ||||||
|                 await message.reply(f"Command {cmd_name} saved.",silent=True) |  | ||||||
|             except Exception as e: |  | ||||||
|                 await message.reply(f"Error: {e}",silent=True) |  | ||||||
| 
 |  | ||||||
|         elif message.content.startswith(".delcmd "): |  | ||||||
|             cmd_name = message.content.split(" ", 1)[1] |  | ||||||
|             cmd_path = os.path.join(COMMANDS_DIR, f"{cmd_name}.py") |  | ||||||
| 
 |  | ||||||
|             if os.path.exists(cmd_path): |  | ||||||
|                 os.remove(cmd_path) |  | ||||||
|                 self.loaded_commands = load_commands() |  | ||||||
|                 await message.reply(f"Command {cmd_name} deleted.",silent=True) |  | ||||||
|             else: |  | ||||||
|                 await message.reply(f"Command {cmd_name} not found.",silent=True) |  | ||||||
| 
 |  | ||||||
|         elif message.content.startswith(".listcmds"): |  | ||||||
|             cmds = list(self.loaded_commands.keys()) |  | ||||||
|             await message.reply("Saved commands:\n" + ", ".join(cmds) if cmds else "No saved commands.",silent=True) |  | ||||||
|         elif message.content.startswith(".delrecent "): |  | ||||||
|             try: |  | ||||||
|                 minutes = float(message.content.split(" ", 1)[1]) |  | ||||||
|                 cutoff_time = datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=minutes) |  | ||||||
| 
 |  | ||||||
|                 deleted = 0 |  | ||||||
|                 async for msg in message.channel.history(limit=100): |  | ||||||
|                     if msg.author == self.user and msg.created_at >= cutoff_time: |  | ||||||
|                         await msg.delete() |  | ||||||
|                         deleted += 1 |  | ||||||
| 
 |  | ||||||
|                 await message.channel.send(f"Deleted {deleted} messages.", delete_after=0,silent=True) |  | ||||||
|             except Exception as e: |  | ||||||
|                 await message.channel.send(f"Error: {e}", delete_after=0,silent=True) |  | ||||||
|         elif message.content.startswith(".trackmessages"): |  | ||||||
|             channel_id = message.channel.id |  | ||||||
|             if channel_id not in self.tracked_channels: |  | ||||||
|                 self.tracked_channels.append(channel_id) |  | ||||||
|                 save_tracked_channels(self.tracked_channels) |  | ||||||
|                 await message.reply(f"Tracking messages in this channel {message.channel.name}.",silent=True) |  | ||||||
|             else: |  | ||||||
|                 await message.reply("This channel is already being tracked.",silent=True) |  | ||||||
| 
 |  | ||||||
|         elif message.content.startswith(".untrackmessages"): |  | ||||||
|             channel_id = message.channel.id |  | ||||||
|             if channel_id in self.tracked_channels: |  | ||||||
|                 self.tracked_channels.remove(channel_id) |  | ||||||
|                 save_tracked_channels(self.tracked_channels) |  | ||||||
|                 await message.reply(f"Stopped tracking messages in {message.channel.name}.",silent=True) |  | ||||||
|             else: |  | ||||||
|                 await message.reply("This channel is not being tracked.",silent=True) |  | ||||||
|         elif message.content.startswith(".savechannel"): |  | ||||||
|             try: |  | ||||||
|                 messages = [] |  | ||||||
|                 async for msg in message.channel.history(limit=None): |  | ||||||
|                     msg_dict = { |  | ||||||
|                         "id": msg.id, |  | ||||||
|                         "content": msg.content, |  | ||||||
|                         "created_at": msg.created_at.isoformat() if msg.created_at else None, |  | ||||||
|                         "edited_at": msg.edited_at.isoformat() if msg.edited_at else None, |  | ||||||
|                         "author": { |  | ||||||
|                             "id": msg.author.id, |  | ||||||
|                             "name": msg.author.name, |  | ||||||
|                             "discriminator": msg.author.discriminator, |  | ||||||
|                             "bot": msg.author.bot |  | ||||||
|                         } if msg.author else None, |  | ||||||
|                         "channel_id": msg.channel.id, |  | ||||||
|                         "attachments": [ |  | ||||||
|                             {"filename": a.filename, "url": a.url} for a in msg.attachments |  | ||||||
|                         ] if msg.attachments else [], |  | ||||||
|                         "embeds": [embed.to_dict() for embed in msg.embeds] if msg.embeds else [], |  | ||||||
|                         "reactions": [ |  | ||||||
|                             {"emoji": str(r.emoji), "count": r.count} for r in msg.reactions |  | ||||||
|                         ] if msg.reactions else [], |  | ||||||
|                         "mentions": [user.id for user in msg.mentions] if msg.mentions else [], |  | ||||||
|                         "role_mentions": [role.id for role in msg.role_mentions] if msg.role_mentions else [], |  | ||||||
|                         "pinned": msg.pinned, |  | ||||||
|                         "tts": msg.tts, |  | ||||||
|                         "type": str(msg.type), |  | ||||||
|                         "reference": { |  | ||||||
|                             "message_id": msg.reference.message_id, |  | ||||||
|                             "channel_id": msg.reference.channel_id, |  | ||||||
|                             "guild_id": msg.reference.guild_id |  | ||||||
|                         } if msg.reference else None, |  | ||||||
|                     } |  | ||||||
|                     messages.append(msg_dict) |  | ||||||
| 
 |  | ||||||
|                 filename = f"{message.channel.id}.json.gz" |  | ||||||
|                 filepath = os.path.join("downloads", filename)  # Save to a "downloads" folder |  | ||||||
|                 os.makedirs("downloads", exist_ok=True) |  | ||||||
| 
 |  | ||||||
|                 with gzip.open(filepath, "wt", encoding="utf-8") as f: |  | ||||||
|                     json.dump(messages, f, ensure_ascii=False, indent=4) |  | ||||||
| 
 |  | ||||||
|                 await message.edit(content=f"Saved messages to `{filepath}`") |  | ||||||
| 
 |  | ||||||
|             except Exception as e: |  | ||||||
|                 await message.edit(content=f"Error: {e}") |  | ||||||
|         elif message.content.startswith(".repeat29"): |  | ||||||
|             await message.reply("oop ok") |  | ||||||
|             while 1: |  | ||||||
|                 await message.channel.send("you asked dad") |  | ||||||
|                 await asyncio.sleep(29*60) |  | ||||||
|     @staticmethod |  | ||||||
|     async def process_log_whitelist(message): |  | ||||||
|         if message.author in [627566973869359104,]: |  | ||||||
|             return |  | ||||||
| 
 |  | ||||||
|     async def on_message_delete(self, message): |  | ||||||
|         await self.process_log_whitelist(message) |  | ||||||
|         if message.channel.id in self.tracked_channels: |  | ||||||
|             member = message.author |  | ||||||
|             if member != self.user: |  | ||||||
|                 await message.channel.send(f"<@{member.id}> deleted {message.content}",silent=True) |  | ||||||
| 
 |  | ||||||
|     async def on_message_edit(self, before, after): |  | ||||||
|         await self.process_log_whitelist(before) |  | ||||||
|         if before.channel.id in self.tracked_channels: |  | ||||||
|             member = after.author |  | ||||||
|             if member == self.user: return |  | ||||||
|             if before.content == after.content: return |  | ||||||
|             diff = difflib.unified_diff(before.content.splitlines(), after.content.splitlines()) |  | ||||||
|             diff_result = '\n'.join(diff) |  | ||||||
|      |  | ||||||
|             # Use BytesIO to create an in-memory file-like object with the full diff (no line removal) |  | ||||||
|             with io.BytesIO(diff_result.encode('utf-8')) as diff_file: |  | ||||||
|                 diff_file.seek(0)  # Ensure we're at the start of the BytesIO buffer |  | ||||||
|      |  | ||||||
|                 # Send the file to the channel |  | ||||||
|                 await after.channel.send( |  | ||||||
|                     f"<@{member.id}> edited a message", |  | ||||||
|                     file=discord.File(diff_file, "cutie.diff"),silent=True |  | ||||||
|                 ) |  | ||||||
|     async def on_presence_update(self, before, after): |  | ||||||
|         if after.id == 627566973869359104: |  | ||||||
|             old_status = self.last_status.get(after.id, discord.Status.offline) |  | ||||||
|             self.last_status[after.id] = after.status |  | ||||||
| 
 |  | ||||||
|             if old_status in [discord.Status.offline] and after.status == discord.Status.online: |  | ||||||
|                 channel = self.get_channel(1302691935152246851) |  | ||||||
|                 if channel: |  | ||||||
|                     ... |  | ||||||
|                     #await channel.send(f"[BOT] Welcome back, {after.mention}!",silent=True) |  | ||||||
| 
 |  | ||||||
| client = Selfbot() |  | ||||||
| # get token from environment variable |  | ||||||
| TOKEN = os.getenv("TOKEN") |  | ||||||
| if not TOKEN: |  | ||||||
|     raise ValueError("No TOKEN found in environment variables. Please add it to your .env file.") |  | ||||||
|     client.run(TOKEN) |     client.run(TOKEN) | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								bot/cogs/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								bot/cogs/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | # This makes the cogs directory a proper Python package | ||||||
							
								
								
									
										129
									
								
								bot/cogs/admin_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								bot/cogs/admin_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,129 @@ | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | import discord | ||||||
|  | import os | ||||||
|  | import importlib.util | ||||||
|  | 
 | ||||||
|  | class AdminCog(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |         self.bot = bot | ||||||
|  |         # Initialize tracked channels if not already present | ||||||
|  |         if not hasattr(self.bot, 'tracked_channels'): | ||||||
|  |             self.bot.tracked_channels = [] | ||||||
|  |          | ||||||
|  |     async def cmd_addcmd(self, message): | ||||||
|  |         """ | ||||||
|  |         Add a custom command | ||||||
|  |         Usage: .addcmd <name> <code> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         parts = content.split(None, 2) | ||||||
|  |          | ||||||
|  |         if len(parts) < 3: | ||||||
|  |             await message.edit(content="❌ Usage: `.addcmd <name> <code>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         cmd_name = parts[1] | ||||||
|  |         cmd_code = parts[2] | ||||||
|  |          | ||||||
|  |         # Create commands directory if it doesn't exist | ||||||
|  |         commands_dir = os.path.join("bot", "commands", "custom") | ||||||
|  |         os.makedirs(commands_dir, exist_ok=True) | ||||||
|  |          | ||||||
|  |         # Create command file | ||||||
|  |         cmd_path = os.path.join(commands_dir, f"{cmd_name}.py") | ||||||
|  |          | ||||||
|  |         with open(cmd_path, "w") as f: | ||||||
|  |             f.write(f""" | ||||||
|  | async def run(bot, message, args): | ||||||
|  |     \"\"\" | ||||||
|  |     Custom command: {cmd_name} | ||||||
|  |     \"\"\" | ||||||
|  |     try: | ||||||
|  | {cmd_code.replace(chr(10), chr(10) + ' ' * 8)} | ||||||
|  |     except Exception as e: | ||||||
|  |         await message.channel.send(f"Error executing command: {{str(e)}}") | ||||||
|  | """) | ||||||
|  |          | ||||||
|  |         # Reload commands | ||||||
|  |         self.bot.reload_commands() | ||||||
|  |          | ||||||
|  |         await message.edit(content=f"✅ Added command: `{cmd_name}`") | ||||||
|  |          | ||||||
|  |     async def cmd_delcmd(self, message): | ||||||
|  |         """ | ||||||
|  |         Delete a custom command | ||||||
|  |         Usage: .delcmd <name> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         parts = content.split() | ||||||
|  |          | ||||||
|  |         if len(parts) != 2: | ||||||
|  |             await message.edit(content="❌ Usage: `.delcmd <name>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         cmd_name = parts[1] | ||||||
|  |          | ||||||
|  |         # Check if command exists | ||||||
|  |         commands_dir = os.path.join("bot", "commands", "custom") | ||||||
|  |         cmd_path = os.path.join(commands_dir, f"{cmd_name}.py") | ||||||
|  |          | ||||||
|  |         if not os.path.exists(cmd_path): | ||||||
|  |             await message.edit(content=f"❌ Command `{cmd_name}` does not exist") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         # Delete command | ||||||
|  |         os.remove(cmd_path) | ||||||
|  |          | ||||||
|  |         # Reload commands | ||||||
|  |         self.bot.reload_commands() | ||||||
|  |          | ||||||
|  |         await message.edit(content=f"✅ Deleted command: `{cmd_name}`") | ||||||
|  |          | ||||||
|  |     async def cmd_listcmds(self, message): | ||||||
|  |         """ | ||||||
|  |         List all custom commands | ||||||
|  |         Usage: .listcmds | ||||||
|  |         """ | ||||||
|  |         commands_dir = os.path.join("bot", "commands", "custom") | ||||||
|  |         os.makedirs(commands_dir, exist_ok=True) | ||||||
|  |          | ||||||
|  |         cmds = [] | ||||||
|  |         for filename in os.listdir(commands_dir): | ||||||
|  |             if filename.endswith(".py"): | ||||||
|  |                 cmds.append(filename[:-3]) | ||||||
|  |                  | ||||||
|  |         if not cmds: | ||||||
|  |             await message.edit(content="No custom commands found.") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         cmd_list = "\n".join(f"• {cmd}" for cmd in sorted(cmds)) | ||||||
|  |         await message.edit(content=f"**Custom Commands:**\n{cmd_list}") | ||||||
|  |          | ||||||
|  |     async def cmd_trackmessages(self, message): | ||||||
|  |         """ | ||||||
|  |         Track message edits and deletions in a channel | ||||||
|  |         Usage: .trackmessages | ||||||
|  |         """ | ||||||
|  |         channel_id = message.channel.id | ||||||
|  |          | ||||||
|  |         if channel_id in self.bot.tracked_channels: | ||||||
|  |             await message.edit(content="❌ This channel is already being tracked.") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         self.bot.tracked_channels.append(channel_id) | ||||||
|  |         await message.edit(content=f"✅ Now tracking message edits and deletions in <#{channel_id}>") | ||||||
|  |          | ||||||
|  |     async def cmd_untrackmessages(self, message): | ||||||
|  |         """ | ||||||
|  |         Stop tracking message edits and deletions in a channel | ||||||
|  |         Usage: .untrackmessages | ||||||
|  |         """ | ||||||
|  |         channel_id = message.channel.id | ||||||
|  |          | ||||||
|  |         if channel_id not in self.bot.tracked_channels: | ||||||
|  |             await message.edit(content="❌ This channel is not being tracked.") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         self.bot.tracked_channels.remove(channel_id) | ||||||
|  |         await message.edit(content=f"✅ No longer tracking message edits and deletions in <#{channel_id}>") | ||||||
							
								
								
									
										96
									
								
								bot/cogs/afk_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								bot/cogs/afk_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | import discord | ||||||
|  | import datetime | ||||||
|  | import re | ||||||
|  | 
 | ||||||
|  | class AFKCog(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |         self.afk = False | ||||||
|  |         self.afk_since = None | ||||||
|  |         self.afk_reason = None | ||||||
|  |          | ||||||
|  |     def cleanup(self): | ||||||
|  |         self.afk = False | ||||||
|  |         self.afk_since = None | ||||||
|  |         self.afk_reason = None | ||||||
|  |          | ||||||
|  |     async def cmd_afk(self, message): | ||||||
|  |         """ | ||||||
|  |         Set AFK status | ||||||
|  |         Usage: .afk [reason] | ||||||
|  |         """ | ||||||
|  |         parts = message.content.split(' ', 1) | ||||||
|  |         reason = parts[1] if len(parts) > 1 else "AFK" | ||||||
|  |          | ||||||
|  |         self.afk = True | ||||||
|  |         self.afk_since = datetime.datetime.now() | ||||||
|  |         self.afk_reason = reason | ||||||
|  |          | ||||||
|  |         await message.edit(content=f"You are now AFK: {reason}") | ||||||
|  |          | ||||||
|  |     async def cmd_unafk(self, message): | ||||||
|  |         """ | ||||||
|  |         Remove AFK status | ||||||
|  |         Usage: .unafk | ||||||
|  |         """ | ||||||
|  |         if not self.afk: | ||||||
|  |             await message.edit(content="You are not AFK.") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         self.afk = False | ||||||
|  |         afk_duration = datetime.datetime.now() - self.afk_since | ||||||
|  |         hours, remainder = divmod(afk_duration.seconds, 3600) | ||||||
|  |         minutes, seconds = divmod(remainder, 60) | ||||||
|  |          | ||||||
|  |         duration_str = "" | ||||||
|  |         if hours > 0: | ||||||
|  |             duration_str += f"{hours} hour{'s' if hours != 1 else ''} " | ||||||
|  |         if minutes > 0: | ||||||
|  |             duration_str += f"{minutes} minute{'s' if minutes != 1 else ''} " | ||||||
|  |         if seconds > 0 or (hours == 0 and minutes == 0): | ||||||
|  |             duration_str += f"{seconds} second{'s' if seconds != 1 else ''}" | ||||||
|  |              | ||||||
|  |         await message.edit(content=f"Welcome back! You were AFK for {duration_str.strip()}.") | ||||||
|  |          | ||||||
|  |         self.afk_since = None | ||||||
|  |         self.afk_reason = None | ||||||
|  |          | ||||||
|  |     async def handle_afk_dm(self, message): | ||||||
|  |         """ | ||||||
|  |         Handle incoming DMs when in AFK mode | ||||||
|  |         """ | ||||||
|  |         if not self.afk: | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         # Don't respond to self | ||||||
|  |         if message.author == self.bot.user: | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         # Check if we already replied recently | ||||||
|  |         # This prevents spam if someone sends multiple messages | ||||||
|  |         async for msg in message.channel.history(limit=10): | ||||||
|  |             if msg.author == self.bot.user and "I'm currently AFK" in msg.content: | ||||||
|  |                 # Only reply once every 5 minutes to the same person | ||||||
|  |                 time_diff = datetime.datetime.now(datetime.timezone.utc) - msg.created_at | ||||||
|  |                 if time_diff.total_seconds() < 300:  # 5 minutes | ||||||
|  |                     return | ||||||
|  |                      | ||||||
|  |         # Calculate AFK duration | ||||||
|  |         afk_duration = datetime.datetime.now() - self.afk_since | ||||||
|  |         hours, remainder = divmod(afk_duration.seconds, 3600) | ||||||
|  |         minutes, seconds = divmod(remainder, 60) | ||||||
|  |          | ||||||
|  |         duration_str = "" | ||||||
|  |         if hours > 0: | ||||||
|  |             duration_str += f"{hours} hour{'s' if hours != 1 else ''} " | ||||||
|  |         if minutes > 0: | ||||||
|  |             duration_str += f"{minutes} minute{'s' if minutes != 1 else ''} " | ||||||
|  |         if seconds > 0 or (hours == 0 and minutes == 0): | ||||||
|  |             duration_str += f"{seconds} second{'s' if seconds != 1 else ''}" | ||||||
|  |              | ||||||
|  |         # Send AFK message | ||||||
|  |         await message.channel.send( | ||||||
|  |             f"I'm currently AFK ({duration_str.strip()}): {self.afk_reason}\n" | ||||||
|  |             f"I'll respond when I return." | ||||||
|  |         ) | ||||||
							
								
								
									
										85
									
								
								bot/cogs/cog_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								bot/cogs/cog_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,85 @@ | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | 
 | ||||||
|  | class CogCommands(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |          | ||||||
|  |     async def cmd_loadcog(self, message): | ||||||
|  |         """ | ||||||
|  |         Load a cog | ||||||
|  |         Usage: .loadcog <cog_name> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         parts = content.split() | ||||||
|  |          | ||||||
|  |         if len(parts) != 2: | ||||||
|  |             await message.edit(content="❌ Usage: `.loadcog <cog_name>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         cog_name = parts[1] | ||||||
|  |         if self.bot.cog_manager.load_cog(cog_name): | ||||||
|  |             await message.edit(content=f"✅ Loaded cog: `{cog_name}`") | ||||||
|  |         else: | ||||||
|  |             await message.edit(content=f"❌ Failed to load cog: `{cog_name}`") | ||||||
|  | 
 | ||||||
|  |     async def cmd_unloadcog(self, message): | ||||||
|  |         """ | ||||||
|  |         Unload a cog | ||||||
|  |         Usage: .unloadcog <cog_name> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         parts = content.split() | ||||||
|  |          | ||||||
|  |         if len(parts) != 2: | ||||||
|  |             await message.edit(content="❌ Usage: `.unloadcog <cog_name>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         cog_name = parts[1] | ||||||
|  |         # Prevent unloading the cog_commands cog | ||||||
|  |         if cog_name == "cog_commands": | ||||||
|  |             await message.edit(content="❌ Cannot unload the cog management commands.") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         if self.bot.cog_manager.unload_cog(cog_name): | ||||||
|  |             await message.edit(content=f"✅ Unloaded cog: `{cog_name}`") | ||||||
|  |         else: | ||||||
|  |             await message.edit(content=f"❌ Failed to unload cog: `{cog_name}`") | ||||||
|  | 
 | ||||||
|  |     async def cmd_reloadcog(self, message): | ||||||
|  |         """ | ||||||
|  |         Reload a cog | ||||||
|  |         Usage: .reloadcog <cog_name> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         parts = content.split() | ||||||
|  |          | ||||||
|  |         if len(parts) != 2: | ||||||
|  |             await message.edit(content="❌ Usage: `.reloadcog <cog_name>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         cog_name = parts[1] | ||||||
|  |         if self.bot.cog_manager.reload_cog(cog_name): | ||||||
|  |             await message.edit(content=f"✅ Reloaded cog: `{cog_name}`") | ||||||
|  |         else: | ||||||
|  |             await message.edit(content=f"❌ Failed to reload cog: `{cog_name}`") | ||||||
|  |      | ||||||
|  |     async def cmd_reloadall(self, message): | ||||||
|  |         """ | ||||||
|  |         Reload all cogs | ||||||
|  |         Usage: .reloadall | ||||||
|  |         """ | ||||||
|  |         self.bot.cog_manager.unload_all_cogs() | ||||||
|  |         num_loaded = self.bot.cog_manager.load_all_cogs() | ||||||
|  |         await message.edit(content=f"✅ Reloaded {num_loaded} cogs.") | ||||||
|  | 
 | ||||||
|  |     async def cmd_listcogs(self, message): | ||||||
|  |         """ | ||||||
|  |         List all loaded cogs | ||||||
|  |         Usage: .listcogs | ||||||
|  |         """ | ||||||
|  |         if not self.bot.cog_manager.cogs: | ||||||
|  |             await message.edit(content="No cogs are currently loaded.") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         cog_list = "\n".join(f"• {name}" for name in sorted(self.bot.cog_manager.cogs.keys())) | ||||||
|  |         await message.edit(content=f"**Loaded Cogs:**\n{cog_list}") | ||||||
							
								
								
									
										72
									
								
								bot/cogs/fun_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								bot/cogs/fun_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | import discord | ||||||
|  | import random | ||||||
|  | import asyncio | ||||||
|  | 
 | ||||||
|  | class FunCog(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |          | ||||||
|  |     async def cmd_horse(self, message): | ||||||
|  |         """ | ||||||
|  |         Toggle horse reactions in a channel | ||||||
|  |         Usage: .horse | ||||||
|  |         """ | ||||||
|  |         if message.channel.id in self.bot.horsin: | ||||||
|  |             self.bot.horsin.remove(message.channel.id) | ||||||
|  |             await message.reply("no longer horsin around D:") | ||||||
|  |         else: | ||||||
|  |             self.bot.horsin.append(message.channel.id) | ||||||
|  |             await message.reply(":D") | ||||||
|  |              | ||||||
|  |     async def cmd_rps(self, message): | ||||||
|  |         """ | ||||||
|  |         Play Rock Paper Scissors | ||||||
|  |         Usage: .rps <rock|paper|scissors> | ||||||
|  |         """ | ||||||
|  |         choices = ["rock", "paper", "scissors"] | ||||||
|  |         content = message.content.strip().lower() | ||||||
|  |         parts = content.split(maxsplit=1) | ||||||
|  |          | ||||||
|  |         if len(parts) != 2 or parts[1] not in choices: | ||||||
|  |             await message.edit(content="❌ Usage: `.rps <rock|paper|scissors>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         user_choice = parts[1] | ||||||
|  |         bot_choice = random.choice(choices) | ||||||
|  |          | ||||||
|  |         # Determine winner | ||||||
|  |         if user_choice == bot_choice: | ||||||
|  |             result = "It's a tie! 🤝" | ||||||
|  |         elif (user_choice == "rock" and bot_choice == "scissors") or \ | ||||||
|  |              (user_choice == "paper" and bot_choice == "rock") or \ | ||||||
|  |              (user_choice == "scissors" and bot_choice == "paper"): | ||||||
|  |             result = "You win! 🎉" | ||||||
|  |         else: | ||||||
|  |             result = "I win! 🎮" | ||||||
|  |              | ||||||
|  |         await message.edit(content=f"You chose {user_choice}, I chose {bot_choice}. {result}") | ||||||
|  |          | ||||||
|  |     async def cmd_repeat(self, message): | ||||||
|  |         """ | ||||||
|  |         Repeat a message multiple times | ||||||
|  |         Usage: .repeat29 <text> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         if " " not in content: | ||||||
|  |             await message.edit(content="❌ Usage: `.repeat29 <text>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         text = content.split(" ", 1)[1] | ||||||
|  |          | ||||||
|  |         # Don't allow mentions in repeated text | ||||||
|  |         if "@" in text: | ||||||
|  |             text = text.replace("@", "@\u200b")  # Insert zero-width space to break mentions | ||||||
|  |              | ||||||
|  |         # Delete original command | ||||||
|  |         await message.delete() | ||||||
|  |          | ||||||
|  |         # Send 29 times | ||||||
|  |         for _ in range(29): | ||||||
|  |             await message.channel.send(text) | ||||||
|  |             await asyncio.sleep(0.5)  # Add small delay to avoid rate limiting | ||||||
							
								
								
									
										104
									
								
								bot/cogs/test_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								bot/cogs/test_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | import discord | ||||||
|  | import asyncio | ||||||
|  | import traceback | ||||||
|  | from unittest.mock import patch | ||||||
|  | 
 | ||||||
|  | class TestCog(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |          | ||||||
|  |     async def cmd_test(self, message): | ||||||
|  |         """ | ||||||
|  |         Run tests to verify bot functionality | ||||||
|  |         Usage: .test | ||||||
|  |         """ | ||||||
|  |         await message.edit(content="🧪 Running tests...") | ||||||
|  |          | ||||||
|  |         results = [] | ||||||
|  |         total = 0 | ||||||
|  |         passed = 0 | ||||||
|  |          | ||||||
|  |         # Test AFK feature | ||||||
|  |         total += 1 | ||||||
|  |         try: | ||||||
|  |             # Check if AFK cog is loaded | ||||||
|  |             afk_cog = next((cog for cog in self.bot.cog_manager.cogs.values()  | ||||||
|  |                           if hasattr(cog, "afk")), None) | ||||||
|  |              | ||||||
|  |             if afk_cog: | ||||||
|  |                 original_afk = afk_cog.afk | ||||||
|  |                 afk_cog.afk = False | ||||||
|  |                  | ||||||
|  |                 # Create a test message | ||||||
|  |                 test_msg = await message.channel.send(".afk Test AFK") | ||||||
|  |                  | ||||||
|  |                 # Run the AFK command | ||||||
|  |                 await self.bot.message_handler.handle_commands(test_msg) | ||||||
|  |                  | ||||||
|  |                 # Check if AFK was set correctly | ||||||
|  |                 if afk_cog.afk and afk_cog.afk_reason == "Test AFK": | ||||||
|  |                     results.append("✅ AFK test passed") | ||||||
|  |                     passed += 1 | ||||||
|  |                 else: | ||||||
|  |                     results.append("❌ AFK test failed") | ||||||
|  |                  | ||||||
|  |                 # Clean up | ||||||
|  |                 await test_msg.delete() | ||||||
|  |                 afk_cog.afk = original_afk | ||||||
|  |             else: | ||||||
|  |                 results.append("⚠️ AFK test skipped - cog not loaded") | ||||||
|  |         except Exception as e: | ||||||
|  |             results.append(f"❌ AFK test error: {str(e)}") | ||||||
|  |          | ||||||
|  |         # Test message detection | ||||||
|  |         total += 1 | ||||||
|  |         try: | ||||||
|  |             reply_received = False | ||||||
|  |              | ||||||
|  |             # Create a message that should trigger a reply | ||||||
|  |             test_msg = await message.channel.send("This is a test message") | ||||||
|  |              | ||||||
|  |             # Mock the reply method | ||||||
|  |             original_reply = test_msg.reply | ||||||
|  |              | ||||||
|  |             async def test_reply(content, **kwargs): | ||||||
|  |                 nonlocal reply_received | ||||||
|  |                 if content == "test success": | ||||||
|  |                     reply_received = True | ||||||
|  |                 return await original_reply(content, **kwargs) | ||||||
|  |                  | ||||||
|  |             # Patch the reply method | ||||||
|  |             with patch.object(test_msg, 'reply', side_effect=test_reply): | ||||||
|  |                 # Create a handler specifically for this test | ||||||
|  |                 test_handler = lambda m: asyncio.ensure_future(test_msg.reply("test success")) | ||||||
|  |                  | ||||||
|  |                 # Add the handler | ||||||
|  |                 self.bot.add_listener(test_handler, name='on_message') | ||||||
|  |                  | ||||||
|  |                 # Trigger it | ||||||
|  |                 await self.bot.message_handler.handle_message(test_msg) | ||||||
|  |                  | ||||||
|  |                 # Wait a moment | ||||||
|  |                 await asyncio.sleep(1) | ||||||
|  |                  | ||||||
|  |                 # Remove the handler | ||||||
|  |                 self.bot.remove_listener(test_handler, name='on_message') | ||||||
|  |                  | ||||||
|  |                 if reply_received: | ||||||
|  |                     results.append("✅ Message handler test passed") | ||||||
|  |                     passed += 1 | ||||||
|  |                 else: | ||||||
|  |                     results.append("❌ Message handler test failed") | ||||||
|  |                      | ||||||
|  |             # Clean up | ||||||
|  |             await test_msg.delete() | ||||||
|  |              | ||||||
|  |         except Exception as e: | ||||||
|  |             results.append(f"❌ Message handler test error: {traceback.format_exc()}") | ||||||
|  |          | ||||||
|  |         # Show results | ||||||
|  |         result_text = "\n".join(results) | ||||||
|  |         summary = f"**Test Results:** {passed}/{total} passed" | ||||||
|  |          | ||||||
|  |         await message.edit(content=f"{summary}\n\n{result_text}") | ||||||
							
								
								
									
										98
									
								
								bot/cogs/tracking.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								bot/cogs/tracking.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | import discord | ||||||
|  | import difflib | ||||||
|  | import io | ||||||
|  | 
 | ||||||
|  | class TrackingCog(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |         self.whitelist = set() | ||||||
|  |          | ||||||
|  |     async def on_message_delete(self, message): | ||||||
|  |         """Handle when messages are deleted""" | ||||||
|  |         if await self.process_log_whitelist(message): | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         if message.channel.id in self.bot.tracked_channels: | ||||||
|  |             member = message.author | ||||||
|  |             if member != self.bot.user: | ||||||
|  |                 await message.channel.send( | ||||||
|  |                     f"<@{member.id}> deleted {message.content}",  | ||||||
|  |                     silent=True | ||||||
|  |                 ) | ||||||
|  |      | ||||||
|  |     async def on_message_edit(self, before, after): | ||||||
|  |         """Handle when messages are edited""" | ||||||
|  |         if await self.process_log_whitelist(before): | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         if before.channel.id in self.bot.tracked_channels: | ||||||
|  |             member = after.author | ||||||
|  |             if member == self.bot.user: | ||||||
|  |                 return | ||||||
|  |                  | ||||||
|  |             if before.content == after.content: | ||||||
|  |                 return | ||||||
|  |                  | ||||||
|  |             diff = difflib.unified_diff(before.content.splitlines(), after.content.splitlines()) | ||||||
|  |             diff_result = '\n'.join(diff) | ||||||
|  |              | ||||||
|  |             with io.BytesIO(diff_result.encode('utf-8')) as diff_file: | ||||||
|  |                 diff_file.seek(0) | ||||||
|  |                  | ||||||
|  |                 await after.channel.send( | ||||||
|  |                     f"<@{member.id}> edited a message", | ||||||
|  |                     file=discord.File(diff_file, "diff.txt"), | ||||||
|  |                     silent=True | ||||||
|  |                 ) | ||||||
|  |                  | ||||||
|  |     async def process_log_whitelist(self, message): | ||||||
|  |         """Check if channel or author is in whitelist""" | ||||||
|  |         return (message.channel.id in self.whitelist or  | ||||||
|  |                 (hasattr(message, 'author') and message.author.id in self.whitelist)) | ||||||
|  |                  | ||||||
|  |     async def cmd_addlogwhitelist(self, message): | ||||||
|  |         """ | ||||||
|  |         Add a channel or user to logging whitelist | ||||||
|  |         Usage: .addlogwhitelist [user/channel mention] | ||||||
|  |         """ | ||||||
|  |         if not message.mentions and not message.channel_mentions: | ||||||
|  |             # If no mentions, use current channel | ||||||
|  |             self.whitelist.add(message.channel.id) | ||||||
|  |             await message.edit(content=f"✅ Added <#{message.channel.id}> to log whitelist") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         for user in message.mentions: | ||||||
|  |             self.whitelist.add(user.id) | ||||||
|  |              | ||||||
|  |         for channel in message.channel_mentions: | ||||||
|  |             self.whitelist.add(channel.id) | ||||||
|  |              | ||||||
|  |         await message.edit(content="✅ Added mentioned entities to log whitelist") | ||||||
|  |          | ||||||
|  |     async def cmd_removelogwhitelist(self, message): | ||||||
|  |         """ | ||||||
|  |         Remove a channel or user from logging whitelist | ||||||
|  |         Usage: .removelogwhitelist [user/channel mention] | ||||||
|  |         """ | ||||||
|  |         if not message.mentions and not message.channel_mentions: | ||||||
|  |             # If no mentions, use current channel | ||||||
|  |             self.whitelist.discard(message.channel.id) | ||||||
|  |             await message.edit(content=f"✅ Removed <#{message.channel.id}> from log whitelist") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         for user in message.mentions: | ||||||
|  |             self.whitelist.discard(user.id) | ||||||
|  |              | ||||||
|  |         for channel in message.channel_mentions: | ||||||
|  |             self.whitelist.discard(channel.id) | ||||||
|  |              | ||||||
|  |         await message.edit(content="✅ Removed mentioned entities from log whitelist") | ||||||
|  |          | ||||||
|  |     async def cmd_clearlogwhitelist(self, message): | ||||||
|  |         """ | ||||||
|  |         Clear the logging whitelist | ||||||
|  |         Usage: .clearlogwhitelist | ||||||
|  |         """ | ||||||
|  |         self.whitelist.clear() | ||||||
|  |         await message.edit(content="✅ Cleared log whitelist") | ||||||
							
								
								
									
										47
									
								
								bot/cogs/user_management.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								bot/cogs/user_management.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | import discord | ||||||
|  | import asyncio | ||||||
|  | 
 | ||||||
|  | class UserManagementCog(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |          | ||||||
|  |     async def cmd_close(self, message): | ||||||
|  |         """ | ||||||
|  |         Close the DM channel | ||||||
|  |         Usage: .close | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             # Only works in DM channels | ||||||
|  |             if isinstance(message.channel, discord.DMChannel): | ||||||
|  |                 await message.edit(content="✅ Closing DM...") | ||||||
|  |                 await asyncio.sleep(1)  # Give a second for the message to be seen | ||||||
|  |                 await message.channel.close() | ||||||
|  |             else: | ||||||
|  |                 await message.edit(content="❌ This command only works in DM channels.") | ||||||
|  |         except Exception as e: | ||||||
|  |             await message.edit(content=f"❌ Error closing DM: {str(e)}") | ||||||
|  |      | ||||||
|  |     async def cmd_block(self, message): | ||||||
|  |         """ | ||||||
|  |         Block a user using Discord's native block function | ||||||
|  |         Usage: .block @user or .block [in reply to a message] | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             # Check if we have a mention or if it's a reply | ||||||
|  |             if message.mentions: | ||||||
|  |                 user = message.mentions[0] | ||||||
|  |             elif message.reference and message.reference.resolved: | ||||||
|  |                 user = message.reference.resolved.author | ||||||
|  |             else: | ||||||
|  |                 await message.edit(content="❌ Usage: `.block @user` or reply to a message with `.block`") | ||||||
|  |                 return | ||||||
|  | 
 | ||||||
|  |             if user == self.bot.user: | ||||||
|  |                 await message.edit(content="❌ Cannot block yourself.") | ||||||
|  |                 return | ||||||
|  |                  | ||||||
|  |             await user.block() | ||||||
|  |             await message.edit(content=f"✅ Blocked {user.name}#{user.discriminator}") | ||||||
|  |         except Exception as e: | ||||||
|  |             await message.edit(content=f"❌ Error blocking user: {str(e)}") | ||||||
							
								
								
									
										215
									
								
								bot/cogs/utility_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								bot/cogs/utility_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,215 @@ | ||||||
|  | import discord | ||||||
|  | import asyncio | ||||||
|  | import re | ||||||
|  | import io | ||||||
|  | import traceback | ||||||
|  | from bot.cogs.cog_manager import BaseCog | ||||||
|  | from utils.time_parser import parse_time | ||||||
|  | 
 | ||||||
|  | class UtilityCog(BaseCog): | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         super().__init__(bot) | ||||||
|  |          | ||||||
|  |     async def cmd_remindme(self, message): | ||||||
|  |         """ | ||||||
|  |         Set a reminder | ||||||
|  |         Usage: .remindme <time> <text> | ||||||
|  |         Example: .remindme 5m Check the oven | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         match = re.match(r'\.remindme\s+(\d+[smhd])\s+(.*)', content) | ||||||
|  |          | ||||||
|  |         if not match: | ||||||
|  |             await message.edit(content="❌ Usage: `.remindme <time> <text>`\nExample: `.remindme 5m Check the oven`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         time_str = match.group(1) | ||||||
|  |         reminder_text = match.group(2) | ||||||
|  |          | ||||||
|  |         duration = parse_time(time_str) | ||||||
|  |         if not duration: | ||||||
|  |             await message.edit(content="❌ Invalid time format. Use numbers followed by s (seconds), m (minutes), h (hours), or d (days).") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         await message.edit(content=f"✅ I'll remind you in {time_str}: {reminder_text}") | ||||||
|  |          | ||||||
|  |         # Schedule the reminder | ||||||
|  |         async def reminder_task(): | ||||||
|  |             await asyncio.sleep(duration) | ||||||
|  |             await message.reply(f"{message.author.mention} Reminder: {reminder_text.replace('@', 'at')}") | ||||||
|  |          | ||||||
|  |         asyncio.create_task(reminder_task()) | ||||||
|  |          | ||||||
|  |     async def cmd_fmt(self, message): | ||||||
|  |         """ | ||||||
|  |         Format text nicely (code blocks, etc) | ||||||
|  |         Usage: .fmt <language> <code> | ||||||
|  |         Example: .fmt py print("Hello World") | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         if len(content.split()) < 3: | ||||||
|  |             await message.edit(content="❌ Usage: `.fmt <language> <code>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         _, lang, *code_parts = content.split() | ||||||
|  |         code = " ".join(code_parts) | ||||||
|  |          | ||||||
|  |         formatted = f"{lang}\n{code}\n" | ||||||
|  |         await message.edit(content=formatted) | ||||||
|  |          | ||||||
|  |     async def cmd_eval(self, message): | ||||||
|  |         """ | ||||||
|  |         Evaluate Python code (dangerous, use with caution) | ||||||
|  |         Usage: .eval <code> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         if len(content.split()) < 2: | ||||||
|  |             await message.edit(content="❌ Usage: `.eval <code>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         code = content.split(" ", 1)[1] | ||||||
|  |          | ||||||
|  |         # Create a safe execution environment | ||||||
|  |         local_vars = { | ||||||
|  |             'bot': self.bot, | ||||||
|  |             'message': message, | ||||||
|  |             'discord': discord, | ||||||
|  |             'asyncio': asyncio | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Add return if it doesn't exist | ||||||
|  |             if not code.strip().startswith("return ") and "\n" not in code: | ||||||
|  |                 code = f"return {code}" | ||||||
|  |                  | ||||||
|  |             # Wrap in async function | ||||||
|  |             code = f"async def __eval_func__():\n{' ' * 4}{code.replace(chr(10), chr(10) + ' ' * 4)}\nresult = asyncio.run_coroutine_threadsafe(__eval_func__(), bot.loop).result()" | ||||||
|  |              | ||||||
|  |             # Execute code | ||||||
|  |             exec(code, globals(), local_vars) | ||||||
|  |             result = local_vars['result'] | ||||||
|  |              | ||||||
|  |             # Format result | ||||||
|  |             if result is None: | ||||||
|  |                 await message.edit(content="✅ Code executed successfully (no output)") | ||||||
|  |             else: | ||||||
|  |                 await message.edit(content=f"\n{result}\n") | ||||||
|  |                  | ||||||
|  |         except Exception as e: | ||||||
|  |             error = traceback.format_exc() | ||||||
|  |             await message.edit(content=f"❌ Error:\n\n{error}\n") | ||||||
|  |              | ||||||
|  |     async def cmd_delrecent(self, message): | ||||||
|  |         """ | ||||||
|  |         Delete recent messages from the user | ||||||
|  |         Usage: .delrecent <count> | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         parts = content.split() | ||||||
|  |          | ||||||
|  |         if len(parts) != 2: | ||||||
|  |             await message.edit(content="❌ Usage: `.delrecent <count>`") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         try: | ||||||
|  |             count = int(parts[1]) | ||||||
|  |             if count < 1 or count > 100:  # Discord limitation | ||||||
|  |                 await message.edit(content="❌ Count must be between 1 and 100") | ||||||
|  |                 return | ||||||
|  |         except ValueError: | ||||||
|  |             await message.edit(content="❌ Count must be a number") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         await message.edit(content=f"🗑️ Deleting {count} recent messages...") | ||||||
|  |          | ||||||
|  |         # Delete messages | ||||||
|  |         deleted = 0 | ||||||
|  |         async for msg in message.channel.history(limit=200): | ||||||
|  |             if msg.author == self.bot.user: | ||||||
|  |                 await msg.delete() | ||||||
|  |                 deleted += 1 | ||||||
|  |                 if deleted >= count: | ||||||
|  |                     break | ||||||
|  |                 await asyncio.sleep(0.5)  # Avoid rate limiting | ||||||
|  |                  | ||||||
|  |         # The original message likely got deleted too, so we send a new one | ||||||
|  |         await message.channel.send(f"✅ Deleted {deleted} message(s)", delete_after=5) | ||||||
|  |          | ||||||
|  |     async def cmd_nuke_server(self, message): | ||||||
|  |         """ | ||||||
|  |         Nuke a server (dangerous) | ||||||
|  |         Usage: .nuke | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             # Extra safety check | ||||||
|  |             confirmation = await message.channel.send("⚠️ Are you sure you want to nuke this server? Reply 'yes' to confirm.") | ||||||
|  |              | ||||||
|  |             def check(m): | ||||||
|  |                 return m.author == message.author and m.content.lower() == "yes" | ||||||
|  |                  | ||||||
|  |             try: | ||||||
|  |                 await self.bot.wait_for('message', check=check, timeout=15.0) | ||||||
|  |             except asyncio.TimeoutError: | ||||||
|  |                 await message.edit(content="❌ Nuke cancelled.") | ||||||
|  |                 await confirmation.delete() | ||||||
|  |                 return | ||||||
|  |                  | ||||||
|  |             await confirmation.delete() | ||||||
|  |             await message.edit(content="💣 Nuking server...") | ||||||
|  |              | ||||||
|  |             for channel in message.guild.channels: | ||||||
|  |                 await channel.delete() | ||||||
|  |                  | ||||||
|  |             for member in message.guild.members: | ||||||
|  |                 if member != self.bot.user and member != message.guild.owner: | ||||||
|  |                     await member.kick(reason="Server nuke") | ||||||
|  |                      | ||||||
|  |             await message.edit(content="💥 Server nuked successfully") | ||||||
|  |              | ||||||
|  |         except Exception as e: | ||||||
|  |             await message.edit(content=f"❌ Error nuking server: {str(e)}") | ||||||
|  |              | ||||||
|  |     async def cmd_savechannel(self, message): | ||||||
|  |         """ | ||||||
|  |         Save a channel's messages to a file | ||||||
|  |         Usage: .savechannel [limit] | ||||||
|  |         """ | ||||||
|  |         content = message.content.strip() | ||||||
|  |         parts = content.split() | ||||||
|  |          | ||||||
|  |         limit = 100  # Default limit | ||||||
|  |         if len(parts) >= 2: | ||||||
|  |             try: | ||||||
|  |                 limit = int(parts[1]) | ||||||
|  |                 if limit < 1: | ||||||
|  |                     await message.edit(content="❌ Limit must be positive") | ||||||
|  |                     return | ||||||
|  |             except ValueError: | ||||||
|  |                 await message.edit(content="❌ Limit must be a number") | ||||||
|  |                 return | ||||||
|  |                  | ||||||
|  |         await message.edit(content=f"📥 Saving the last {limit} messages...") | ||||||
|  |          | ||||||
|  |         # Get messages | ||||||
|  |         messages = [] | ||||||
|  |         async for msg in message.channel.history(limit=limit): | ||||||
|  |             time_str = msg.created_at.strftime("%Y-%m-%d %H:%M:%S") | ||||||
|  |             author = f"{msg.author.name}#{msg.author.discriminator}" | ||||||
|  |             content = msg.content or "[No Text Content]" | ||||||
|  |              | ||||||
|  |             # Handle attachments | ||||||
|  |             attachments = "" | ||||||
|  |             if msg.attachments: | ||||||
|  |                 attachments = f" [Attachments: {', '.join(a.url for a in msg.attachments)}]" | ||||||
|  |                  | ||||||
|  |             messages.append(f"[{time_str}] {author}: {content}{attachments}") | ||||||
|  |              | ||||||
|  |         # Reverse to get chronological order | ||||||
|  |         messages.reverse() | ||||||
|  |          | ||||||
|  |         # Create file | ||||||
|  |         content = "\n".join(messages) | ||||||
|  |         file = discord.File(io.BytesIO(content.encode()), filename=f"channel_{message.channel.name}.txt") | ||||||
|  |          | ||||||
|  |         await message.delete() | ||||||
|  |         await message.channel.send(f"📁 Here are the last {limit} messages from this channel:", file=file) | ||||||
|  | @ -1 +1 @@ | ||||||
| # Empty init to make directory a package | # This makes the handlers directory a proper Python package# Empty init to make directory a package | ||||||
|  | @ -3,32 +3,11 @@ import asyncio | ||||||
| import re | import re | ||||||
| import time | import time | ||||||
| from config import BLACKLISTED_USERS, BUCKET_REACT_USERS, AUTO_DELETE_USERS, SPECIAL_RESPONSES | 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 bot.commands.user_management_commands import UserManagementCommands |  | ||||||
| from utils.time_parser import parse_time | from utils.time_parser import parse_time | ||||||
| 
 | 
 | ||||||
| class MessageHandler: | class MessageHandler: | ||||||
|     def __init__(self, bot): |     def __init__(self, bot): | ||||||
|         self.bot = 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) |  | ||||||
|         self.user_management_commands = UserManagementCommands(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 |  | ||||||
|         bot.user_management_commands = self.user_management_commands |  | ||||||
|          |          | ||||||
|         # Regex for detecting "in X time" patterns |         # Regex for detecting "in X time" patterns | ||||||
|         self.time_pattern = re.compile( |         self.time_pattern = re.compile( | ||||||
|  | @ -36,7 +15,6 @@ class MessageHandler: | ||||||
|             re.IGNORECASE |             re.IGNORECASE | ||||||
|         ) |         ) | ||||||
|          |          | ||||||
|          |  | ||||||
|     def parse_relative_time(self, time_str): |     def parse_relative_time(self, time_str): | ||||||
|         """ |         """ | ||||||
|         Parse relative time strings like "2 hours", "30 minutes" |         Parse relative time strings like "2 hours", "30 minutes" | ||||||
|  | @ -80,13 +58,16 @@ class MessageHandler: | ||||||
|         if message.author.bot: |         if message.author.bot: | ||||||
|             return |             return | ||||||
|              |              | ||||||
|         # Look for and replace time patterns (only for non-command messages) |         # Skip command messages (they start with . usually) | ||||||
|         if not message.content.startswith('.'): |         if message.content.startswith('.'): | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         # Look for and replace time patterns | ||||||
|         original_content = message.content |         original_content = message.content | ||||||
|         modified_content = self.replace_time_patterns(original_content) |         modified_content = self.replace_time_patterns(original_content) | ||||||
|          |          | ||||||
|         # If the content was modified, edit the original message |         # If the content was modified, edit the original message | ||||||
|             if modified_content != original_content: |         if modified_content != original_content and message.author == self.bot.user: | ||||||
|             try: |             try: | ||||||
|                 await message.edit(content=modified_content) |                 await message.edit(content=modified_content) | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|  | @ -114,7 +95,11 @@ class MessageHandler: | ||||||
|                  |                  | ||||||
|         # Handle DM if in AFK mode |         # Handle DM if in AFK mode | ||||||
|         if isinstance(message.channel, discord.DMChannel) and message.author != self.bot.user: |         if isinstance(message.channel, discord.DMChannel) and message.author != self.bot.user: | ||||||
|             await self.afk_commands.handle_afk_dm(message) |             # Get the AFK cog if it's loaded | ||||||
|  |             afk_cog = next((cog for cog_name, cog in self.bot.cog_manager.cogs.items()  | ||||||
|  |                           if hasattr(cog, 'handle_afk_dm')), None) | ||||||
|  |             if afk_cog and hasattr(afk_cog, 'handle_afk_dm'): | ||||||
|  |                 await afk_cog.handle_afk_dm(message) | ||||||
|              |              | ||||||
|         # Don't process further if the message is not from the bot user |         # Don't process further if the message is not from the bot user | ||||||
|         if message.author != self.bot.user: |         if message.author != self.bot.user: | ||||||
|  | @ -139,66 +124,19 @@ class MessageHandler: | ||||||
|             return |             return | ||||||
|              |              | ||||||
|         cmd_parts = content.split() |         cmd_parts = content.split() | ||||||
|         cmd = cmd_parts[0][1:]  # Get command name without the '.' |         cmd_name = cmd_parts[0][1:]  # Get command name without the '.' | ||||||
|          |          | ||||||
|         # Check for custom/loaded commands first |         # Check if this is a command in one of our loaded cogs | ||||||
|         if hasattr(self.bot, 'loaded_commands') and cmd in self.bot.loaded_commands: |         if hasattr(self.bot, 'loaded_commands') and cmd_name in self.bot.loaded_commands: | ||||||
|             try: |             try: | ||||||
|                 await self.bot.loaded_commands[cmd](message) |                 await self.bot.loaded_commands[cmd_name](message) | ||||||
|                 return |                 return | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|                 print(f"Error executing command {cmd}: {e}") |                 print(f"Error executing command {cmd_name}: {e}") | ||||||
|                 await message.edit(content=f"❌ Error executing command: {e}") |                 import traceback | ||||||
|  |                 traceback.print_exc() | ||||||
|  |                 await message.edit(content=f"❌ Error executing command: {str(e)}") | ||||||
|                 return |                 return | ||||||
|                  |                  | ||||||
|         # User Management Commands - only keeping close and block |         # If we got here, command wasn't found | ||||||
|         if content.startswith(".close"): |         await message.edit(content=f"❌ Command not found: `{cmd_name}`") | ||||||
|             await self.user_management_commands.cmd_close(message) |  | ||||||
|         elif content.startswith(".block"): |  | ||||||
|             await self.user_management_commands.cmd_block(message) |  | ||||||
|         elif content.startswith(".unblock"): |  | ||||||
|             await self.user_management_commands.cmd_unblock(message) |  | ||||||
|          |  | ||||||
|         # AFK Commands |  | ||||||
|         elif 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) |  | ||||||
|  |  | ||||||
|  | @ -1,50 +1,61 @@ | ||||||
| import discord | import discord | ||||||
| from utils.storage import load_commands, load_tracked_channels | import asyncio | ||||||
|  | import logging | ||||||
|  | import os | ||||||
| from bot.handlers.message_handler import MessageHandler | from bot.handlers.message_handler import MessageHandler | ||||||
| from bot.handlers.tracking_handler import TrackingHandler | from bot.cogs.cog_manager import CogManager | ||||||
| from bot.handlers.presence_handler import PresenceHandler | from config import TOKEN | ||||||
| 
 | 
 | ||||||
| class Selfbot(discord.Client): | class SelfBot(discord.Client): | ||||||
|     def __init__(self): |     def __init__(self, *args, **kwargs): | ||||||
|         super().__init__() |         super().__init__(*args, **kwargs) | ||||||
|         # State variables |          | ||||||
|         self.default_status = None |         # Basic initialization | ||||||
|         self.loaded_commands = load_commands() |  | ||||||
|         self.tracked_channels = load_tracked_channels() |  | ||||||
|         self.last_status = {} |  | ||||||
|         self.AFK_STATUS = False |  | ||||||
|         self.AFK_NOTIFIED_USERS = [] |  | ||||||
|         self.horsin = [] |         self.horsin = [] | ||||||
|  |         self.tracked_channels = [] | ||||||
|  |         self.loaded_commands = {} | ||||||
|          |          | ||||||
|         # Initialize handlers |         # Initialize handlers | ||||||
|         self.message_handler = MessageHandler(self) |         self.message_handler = MessageHandler(self) | ||||||
|         self.tracking_handler = TrackingHandler(self) |  | ||||||
|         self.presence_handler = PresenceHandler(self) |  | ||||||
|          |          | ||||||
|         # Add to SelfBot.__init__ |         # Initialize cog manager | ||||||
|         from bot.cogs.cog_manager import CogManager |  | ||||||
| 
 |  | ||||||
|         # In the __init__ method: |  | ||||||
|         self.cog_manager = CogManager(self) |         self.cog_manager = CogManager(self) | ||||||
|         self.cog_manager.load_all_cogs() |  | ||||||
|          |          | ||||||
|     async def on_ready(self): |     async def on_ready(self): | ||||||
|         print(f"Logged in as {self.user}") |         """Called when the bot is ready and connected""" | ||||||
|  |         print(f'Logged in as {self.user.name}#{self.user.discriminator}') | ||||||
|  |          | ||||||
|  |         # Load all cogs | ||||||
|  |         num_cogs = self.cog_manager.load_all_cogs() | ||||||
|  |         print(f"Loaded {num_cogs} cogs") | ||||||
|  |          | ||||||
|  |         # Register event listeners from cogs | ||||||
|  |         self.register_cog_events() | ||||||
|  |          | ||||||
|  |     def register_cog_events(self): | ||||||
|  |         """Register event handlers from cogs""" | ||||||
|  |         for cog_name, cog in self.cog_manager.cogs.items(): | ||||||
|  |             for name, method in inspect.getmembers(cog, inspect.ismethod): | ||||||
|  |                 if name.startswith('on_'): | ||||||
|  |                     self.add_listener(method, name=name) | ||||||
|          |          | ||||||
|     async def on_message(self, message): |     async def on_message(self, message): | ||||||
|         # Don't use muted_channels anymore since we're using Discord's native functionality |         """Called when a message is sent""" | ||||||
|         # Instead, just process all messages |  | ||||||
|         await self.message_handler.handle_message(message) |         await self.message_handler.handle_message(message) | ||||||
|          |          | ||||||
|     async def on_message_delete(self, message): |     async def on_message_delete(self, message): | ||||||
|         await self.tracking_handler.handle_message_delete(message) |         """Called when a message is deleted""" | ||||||
|  |         # This will be handled by TrackingCog now | ||||||
|  |         pass | ||||||
|          |          | ||||||
|     async def on_message_edit(self, before, after): |     async def on_message_edit(self, before, after): | ||||||
|         await self.tracking_handler.handle_message_edit(before, after) |         """Called when a message is edited""" | ||||||
|          |         # This will be handled by TrackingCog now | ||||||
|     async def on_presence_update(self, before, after): |         pass | ||||||
|         await self.presence_handler.handle_presence_update(before, after) |  | ||||||
|          |          | ||||||
|     def reload_commands(self): |     def reload_commands(self): | ||||||
|         """Reload user-defined commands""" |         """Reload user-defined commands and cogs""" | ||||||
|         self.loaded_commands = load_commands() |         self.loaded_commands = {} | ||||||
|  |         self.cog_manager.unload_all_cogs() | ||||||
|  |         num_cogs = self.cog_manager.load_all_cogs() | ||||||
|  |         print(f"Reloaded {num_cogs} cogs") | ||||||
		Loading…
	
		Reference in a new issue
	
	 Xargana
						Xargana