Initial commit v2 (token free)
This commit is contained in:
		
							parent
							
								
									93235081ea
								
							
						
					
					
						commit
						0cd926b9a7
					
				
							
								
								
									
										402
									
								
								app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								app.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,402 @@ | ||||||
|  | import random | ||||||
|  | import discord | ||||||
|  | import asyncio | ||||||
|  | import traceback | ||||||
|  | import random | ||||||
|  | import datetime | ||||||
|  | import os | ||||||
|  | import importlib.util | ||||||
|  | import ast | ||||||
|  | import websockets | ||||||
|  | import json | ||||||
|  | import difflib | ||||||
|  | import io | ||||||
|  | import gzip | ||||||
|  | import re | ||||||
|  | from dotenv import load_dotenv | ||||||
|  | 
 | ||||||
|  | # Load environment variables from .env file | ||||||
|  | load_dotenv() | ||||||
|  | 
 | ||||||
|  | time_regex = re.compile(r'(\d+)([smhd])')  # Matches 4m2s, 1h30m, etc. | ||||||
|  | 
 | ||||||
|  | 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 "): | ||||||
|  |             await handle_blacklist(message) | ||||||
|  | 
 | ||||||
|  |             parts = message.content.split(" ", 2) | ||||||
|  |             if len(parts) < 3: | ||||||
|  |                 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(): | ||||||
|  |                         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) | ||||||
							
								
								
									
										1
									
								
								bot/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								bot/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | # Empty init to make directory a package | ||||||
							
								
								
									
										1
									
								
								bot/commands/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								bot/commands/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | # Empty init to make directory a package | ||||||
							
								
								
									
										65
									
								
								bot/commands/admin_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								bot/commands/admin_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | import os | ||||||
|  | from config import COMMANDS_DIR | ||||||
|  | from utils.storage import save_tracked_channels | ||||||
|  | 
 | ||||||
|  | class AdminCommands: | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |          | ||||||
|  |     async def cmd_addcmd(self, message): | ||||||
|  |         """Add a custom command""" | ||||||
|  |         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.bot.reload_commands() | ||||||
|  |             await message.reply(f"Command {cmd_name} saved.", silent=True) | ||||||
|  |         except Exception as e: | ||||||
|  |             await message.reply(f"Error: {e}", silent=True) | ||||||
|  |              | ||||||
|  |     async def cmd_delcmd(self, message): | ||||||
|  |         """Delete a custom command""" | ||||||
|  |         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.bot.reload_commands() | ||||||
|  |             await message.reply(f"Command {cmd_name} deleted.", silent=True) | ||||||
|  |         else: | ||||||
|  |             await message.reply(f"Command {cmd_name} not found.", silent=True) | ||||||
|  |              | ||||||
|  |     async def cmd_listcmds(self, message): | ||||||
|  |         """List all custom commands""" | ||||||
|  |         cmds = list(self.bot.loaded_commands.keys()) | ||||||
|  |         await message.reply("Saved commands:\n" + ", ".join(cmds) if cmds else "No saved commands.", silent=True) | ||||||
|  |          | ||||||
|  |     async def cmd_trackmessages(self, message): | ||||||
|  |         """Start tracking messages in the current channel""" | ||||||
|  |         channel_id = message.channel.id | ||||||
|  |         if channel_id not in self.bot.tracked_channels: | ||||||
|  |             self.bot.tracked_channels.append(channel_id) | ||||||
|  |             save_tracked_channels(self.bot.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) | ||||||
|  |              | ||||||
|  |     async def cmd_untrackmessages(self, message): | ||||||
|  |         """Stop tracking messages in the current channel""" | ||||||
|  |         channel_id = message.channel.id | ||||||
|  |         if channel_id in self.bot.tracked_channels: | ||||||
|  |             self.bot.tracked_channels.remove(channel_id) | ||||||
|  |             save_tracked_channels(self.bot.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) | ||||||
							
								
								
									
										38
									
								
								bot/commands/afk_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								bot/commands/afk_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | class AfkCommands: | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |          | ||||||
|  |     async def handle_afk_dm(self, message): | ||||||
|  |         """Handle DMs when in AFK mode""" | ||||||
|  |         if self.bot.AFK_STATUS and message.author.id not in self.bot.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.bot.AFK_NOTIFIED_USERS.append(message.author.id) | ||||||
|  |              | ||||||
|  |     async def cmd_afk(self, message): | ||||||
|  |         """Enable AFK mode""" | ||||||
|  |         if not self.bot.AFK_STATUS: | ||||||
|  |             self.bot.AFK_STATUS = True | ||||||
|  |             await message.reply("k") | ||||||
|  |         else: | ||||||
|  |             await message.reply("You are already in AFK mode.", silent=True) | ||||||
|  |              | ||||||
|  |     async def cmd_unafk(self, message): | ||||||
|  |         """Disable AFK mode""" | ||||||
|  |         if self.bot.AFK_STATUS: | ||||||
|  |             self.bot.AFK_STATUS = False | ||||||
|  |              | ||||||
|  |             for i in self.bot.AFK_NOTIFIED_USERS: | ||||||
|  |                 try: | ||||||
|  |                     user = await self.bot.fetch_user(i) | ||||||
|  |                     if user: | ||||||
|  |                         dm_channel = await user.create_dm() | ||||||
|  |                         await dm_channel.send("Hey, I'm back, human me will take over now!") | ||||||
|  |                 except Exception as e: | ||||||
|  |                     raise RuntimeWarning from e | ||||||
|  |                      | ||||||
|  |             self.bot.AFK_NOTIFIED_USERS.clear() | ||||||
|  |             await message.reply("should work") | ||||||
|  |         else: | ||||||
|  |             await message.reply("You are not in AFK mode.", silent=True) | ||||||
							
								
								
									
										65
									
								
								bot/commands/fun_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								bot/commands/fun_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | import random | ||||||
|  | import asyncio | ||||||
|  | 
 | ||||||
|  | class FunCommands: | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |          | ||||||
|  |     async def cmd_horse(self, message): | ||||||
|  |         """Toggle horse reactions in a channel""" | ||||||
|  |         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""" | ||||||
|  |         parts = message.content.split(" ", 1) | ||||||
|  |         if len(parts) != 2: | ||||||
|  |             await message.reply("Usage: `.rps <item>`", silent=True) | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         item = parts[1] | ||||||
|  |          | ||||||
|  |         # Easter eggs | ||||||
|  |         if item.lower() == "dick": | ||||||
|  |             await message.reply("Scissors beats dick any day :3", silent=True) | ||||||
|  |             return | ||||||
|  |         if item == "<@696800726084747314>": | ||||||
|  |             await message.reply("Head so thick that i would try rock but the rock would break") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         # The main game | ||||||
|  |         choice = random.choice([1, 2, 3])  # 1=rock, 2=paper, 3=scissors | ||||||
|  |         rps_map = {1: 'r', 2: 'p', 3: 's'} | ||||||
|  |         rps_map_reverse = {'r': 1, 'p': 2, 's': 3}  # Fixed the reversed mapping | ||||||
|  |         shortmaps = {'r': 'rock', 'p': 'paper', 's': 'scissors'} | ||||||
|  |          | ||||||
|  |         beat_map = {1: 3, 2: 1, 3: 2}  # What beats what: rock>scissors, paper>rock, scissors>paper | ||||||
|  |          | ||||||
|  |         # Determine user choice | ||||||
|  |         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) | ||||||
|  |         elif 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) | ||||||
|  |              | ||||||
|  |     async def cmd_repeat(self, message): | ||||||
|  |         """Repeat a message every 29 minutes""" | ||||||
|  |         await message.reply("oop ok") | ||||||
|  |         while True: | ||||||
|  |             await message.channel.send("you asked dad") | ||||||
|  |             await asyncio.sleep(29 * 60)  # 29 minutes | ||||||
							
								
								
									
										430
									
								
								bot/commands/test_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								bot/commands/test_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,430 @@ | ||||||
|  | import asyncio | ||||||
|  | import time | ||||||
|  | import inspect | ||||||
|  | import traceback | ||||||
|  | from datetime import datetime | ||||||
|  | 
 | ||||||
|  | class TestCommands: | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |         self.tests = { | ||||||
|  |             "afk": self.test_afk, | ||||||
|  |             "remindme": self.test_remindme, | ||||||
|  |             "commands": self.test_commands, | ||||||
|  |             "storage": self.test_storage, | ||||||
|  |             "health": self.test_health, | ||||||
|  |             "all": self.test_all, | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |     async def cmd_test(self, message): | ||||||
|  |         """Run self-tests on the bot""" | ||||||
|  |         parts = message.content.split(" ", 1) | ||||||
|  |         test_name = parts[1] if len(parts) > 1 else "all" | ||||||
|  |          | ||||||
|  |         if test_name not in self.tests: | ||||||
|  |             await message.channel.send( | ||||||
|  |                 f"Unknown test '{test_name}'. Available tests: {', '.join(self.tests.keys())}" | ||||||
|  |             ) | ||||||
|  |             return | ||||||
|  |          | ||||||
|  |         # Create a status message without silent flag for better visibility | ||||||
|  |         status_msg = await message.channel.send(f"🔄 Running test: {test_name}...") | ||||||
|  |          | ||||||
|  |         start_time = time.time() | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             if test_name == "all": | ||||||
|  |                 # For "all" tests, update status more frequently | ||||||
|  |                 await status_msg.edit(content="🔄 Initializing test suite...") | ||||||
|  |                 results = await self.test_all(status_msg) | ||||||
|  |             else: | ||||||
|  |                 test_func = self.tests[test_name] | ||||||
|  |                 results = await test_func(status_msg) | ||||||
|  |              | ||||||
|  |             elapsed = time.time() - start_time | ||||||
|  |              | ||||||
|  |             # Format results into a nice report | ||||||
|  |             report = self._format_test_report(results, elapsed) | ||||||
|  |              | ||||||
|  |             # Make sure report isn't too long for Discord | ||||||
|  |             if len(report) > 2000: | ||||||
|  |                 report = report[:1997] + "..." | ||||||
|  |              | ||||||
|  |             # Update status with results | ||||||
|  |             await status_msg.edit(content=report) | ||||||
|  |              | ||||||
|  |         except Exception as e: | ||||||
|  |             error_msg = f"❌ Test failed with error:\n\n{traceback.format_exc()[:1500]}\n" | ||||||
|  |             print(f"Test error: {str(e)}") | ||||||
|  |             await status_msg.edit(content=error_msg) | ||||||
|  |      | ||||||
|  |     def _format_test_report(self, results, elapsed): | ||||||
|  |         """Format test results into a readable report""" | ||||||
|  |         if isinstance(results, dict): | ||||||
|  |             # We have multiple test suites | ||||||
|  |             total_passed = sum(r['passed'] for r in results.values()) | ||||||
|  |             total_failed = sum(r['failed'] for r in results.values()) | ||||||
|  |             total_tests = total_passed + total_failed | ||||||
|  |              | ||||||
|  |             report = f"# Self-Test Report ({elapsed:.2f}s)\n\n" | ||||||
|  |             report += f"✅ **{total_passed}/{total_tests}** tests passed\n" | ||||||
|  |              | ||||||
|  |             if total_failed > 0: | ||||||
|  |                 report += f"❌ **{total_failed}** tests failed\n\n" | ||||||
|  |             else: | ||||||
|  |                 report += "\n" | ||||||
|  |                  | ||||||
|  |             # Add individual test suite results | ||||||
|  |             for suite_name, suite_result in results.items(): | ||||||
|  |                 passed = suite_result['passed'] | ||||||
|  |                 failed = suite_result['failed'] | ||||||
|  |                 total = passed + failed | ||||||
|  |                 status = "✅" if failed == 0 else "⚠️" | ||||||
|  |                  | ||||||
|  |                 report += f"{status} **{suite_name}**: {passed}/{total} passed\n" | ||||||
|  |                  | ||||||
|  |                 # Add failure details if any | ||||||
|  |                 if failed > 0 and 'failures' in suite_result: | ||||||
|  |                     report += "\n" | ||||||
|  |                     for failure in suite_result['failures']: | ||||||
|  |                         report += f"  ❌ {failure}\n" | ||||||
|  |                     report += "\n" | ||||||
|  |         else: | ||||||
|  |             # Single test suite | ||||||
|  |             passed = results['passed'] | ||||||
|  |             failed = results['failed'] | ||||||
|  |             total = passed + failed | ||||||
|  |              | ||||||
|  |             report = f"# Test Results ({elapsed:.2f}s)\n\n" | ||||||
|  |             report += f"✅ **{passed}/{total}** tests passed\n" | ||||||
|  |              | ||||||
|  |             if failed > 0: | ||||||
|  |                 report += f"❌ **{failed}** tests failed\n\n" | ||||||
|  |                 report += "\n" | ||||||
|  |                 for failure in results.get('failures', []): | ||||||
|  |                     report += f"  ❌ {failure}\n" | ||||||
|  |                 report += "\n" | ||||||
|  |                  | ||||||
|  |         return report | ||||||
|  |      | ||||||
|  |     async def test_all(self, status_msg): | ||||||
|  |         """Run all available tests""" | ||||||
|  |         results = {} | ||||||
|  |         test_funcs = [v for k, v in self.tests.items() if k != "all"] | ||||||
|  |          | ||||||
|  |         for i, test_func in enumerate(test_funcs): | ||||||
|  |             test_name = test_func.__name__.replace('test_', '') | ||||||
|  |             try: | ||||||
|  |                 # Update status before each test | ||||||
|  |                 await status_msg.edit(content=f"🔄 Running tests ({i+1}/{len(test_funcs)}): {test_name}...") | ||||||
|  |                 results[test_name] = await test_func(status_msg) | ||||||
|  |                 # Quick status after each test | ||||||
|  |                 passed = results[test_name]['passed'] | ||||||
|  |                 failed = results[test_name]['failed'] | ||||||
|  |                 await status_msg.edit(content=f"🔄 Test {test_name}: ✅{passed} ❌{failed} | Continuing tests...") | ||||||
|  |             except Exception as e: | ||||||
|  |                 results[test_name] = {'passed': 0, 'failed': 1, 'failures': [f"Exception: {str(e)}"]} | ||||||
|  |                 await status_msg.edit(content=f"⚠️ Error in test {test_name}, continuing with next test...") | ||||||
|  |              | ||||||
|  |         return results | ||||||
|  |      | ||||||
|  |     async def test_afk(self, status_msg): | ||||||
|  |         """Test AFK functionality""" | ||||||
|  |         results = {'passed': 0, 'failed': 0, 'failures': []} | ||||||
|  |          | ||||||
|  |         # Save original state | ||||||
|  |         original_afk = self.bot.AFK_STATUS | ||||||
|  |         original_notified = self.bot.AFK_NOTIFIED_USERS.copy() | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Test 1: Enable AFK | ||||||
|  |             self.bot.AFK_STATUS = False | ||||||
|  |             await self.bot.afk_commands.cmd_afk(status_msg) | ||||||
|  |              | ||||||
|  |             if self.bot.AFK_STATUS: | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append("Failed to enable AFK mode") | ||||||
|  |                  | ||||||
|  |             # Test 2: Disable AFK | ||||||
|  |             await self.bot.afk_commands.cmd_unafk(status_msg) | ||||||
|  |              | ||||||
|  |             if not self.bot.AFK_STATUS: | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append("Failed to disable AFK mode") | ||||||
|  |                  | ||||||
|  |             # Test 3: AFK notification list clears | ||||||
|  |             if len(self.bot.AFK_NOTIFIED_USERS) == 0: | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append("AFK notified users list not cleared") | ||||||
|  |                  | ||||||
|  |         finally: | ||||||
|  |             # Restore original state | ||||||
|  |             self.bot.AFK_STATUS = original_afk | ||||||
|  |             self.bot.AFK_NOTIFIED_USERS = original_notified | ||||||
|  |              | ||||||
|  |         return results | ||||||
|  |      | ||||||
|  |     async def test_remindme(self, status_msg): | ||||||
|  |         """Test reminder functionality with a very short reminder""" | ||||||
|  |         results = {'passed': 0, 'failed': 0, 'failures': []} | ||||||
|  |          | ||||||
|  |         # Create a test reminder message | ||||||
|  |         test_content = ".remindme 1s Test reminder" | ||||||
|  |         mock_message = MockMessage( | ||||||
|  |             author=self.bot.user, | ||||||
|  |             content=test_content, | ||||||
|  |             channel=status_msg.channel | ||||||
|  |         ) | ||||||
|  |          | ||||||
|  |         # Set up a flag to verify the reminder was triggered | ||||||
|  |         reminder_triggered = False | ||||||
|  |         original_reply = mock_message.reply | ||||||
|  |          | ||||||
|  |         async def mock_reply(content, **kwargs): | ||||||
|  |             nonlocal reminder_triggered | ||||||
|  |             if "Reminder:" in content: | ||||||
|  |                 reminder_triggered = True | ||||||
|  |             return await original_reply(content, **kwargs) | ||||||
|  |              | ||||||
|  |         mock_message.reply = mock_reply | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Test reminder setup | ||||||
|  |             await self.bot.utility_commands.cmd_remindme(mock_message) | ||||||
|  |              | ||||||
|  |             # Wait for the reminder to trigger (slightly more than 1s) | ||||||
|  |             await asyncio.sleep(1.5) | ||||||
|  |              | ||||||
|  |             if reminder_triggered: | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append("Reminder did not trigger") | ||||||
|  |                  | ||||||
|  |         except Exception as e: | ||||||
|  |             results['failed'] += 1 | ||||||
|  |             results['failures'].append(f"Reminder test error: {str(e)}") | ||||||
|  |              | ||||||
|  |         return results | ||||||
|  |      | ||||||
|  |     async def test_commands(self, status_msg): | ||||||
|  |         """Test custom command functionality""" | ||||||
|  |         results = {'passed': 0, 'failed': 0, 'failures': []} | ||||||
|  |          | ||||||
|  |         # Test command name | ||||||
|  |         test_cmd_name = "__test_cmd__" | ||||||
|  |         test_cmd_path = f"commands/{test_cmd_name}.py" | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Test 1: Add a command | ||||||
|  |             add_cmd_msg = MockMessage( | ||||||
|  |                 author=self.bot.user, | ||||||
|  |                 content=f".addcmd {test_cmd_name} return await msg.reply('test success', silent=True)", | ||||||
|  |                 channel=status_msg.channel | ||||||
|  |             ) | ||||||
|  |              | ||||||
|  |             await self.bot.admin_commands.cmd_addcmd(add_cmd_msg) | ||||||
|  |              | ||||||
|  |             if test_cmd_name in self.bot.loaded_commands: | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append("Failed to add test command") | ||||||
|  |                  | ||||||
|  |             # Test 2: Execute the command | ||||||
|  |             if test_cmd_name in self.bot.loaded_commands: | ||||||
|  |                 try: | ||||||
|  |                     test_msg = MockMessage( | ||||||
|  |                         author=self.bot.user, | ||||||
|  |                         content="test content", | ||||||
|  |                         channel=status_msg.channel | ||||||
|  |                     ) | ||||||
|  |                      | ||||||
|  |                     reply_received = False | ||||||
|  |                     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) | ||||||
|  |                          | ||||||
|  |                     test_msg.reply = test_reply | ||||||
|  |                      | ||||||
|  |                     await self.bot.loaded_commands[test_cmd_name](test_msg) | ||||||
|  |                      | ||||||
|  |                     if reply_received: | ||||||
|  |                         results['passed'] += 1 | ||||||
|  |                     else: | ||||||
|  |                         results['failed'] += 1 | ||||||
|  |                         results['failures'].append("Command execution failed") | ||||||
|  |                          | ||||||
|  |                 except Exception as e: | ||||||
|  |                     results['failed'] += 1 | ||||||
|  |                     results['failures'].append(f"Command execution error: {str(e)}") | ||||||
|  |              | ||||||
|  |             # Test 3: Delete the command | ||||||
|  |             del_cmd_msg = MockMessage( | ||||||
|  |                 author=self.bot.user, | ||||||
|  |                 content=f".delcmd {test_cmd_name}", | ||||||
|  |                 channel=status_msg.channel | ||||||
|  |             ) | ||||||
|  |              | ||||||
|  |             await self.bot.admin_commands.cmd_delcmd(del_cmd_msg) | ||||||
|  |              | ||||||
|  |             if test_cmd_name not in self.bot.loaded_commands: | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append("Failed to delete test command") | ||||||
|  |                  | ||||||
|  |         except Exception as e: | ||||||
|  |             results['failed'] += 1 | ||||||
|  |             results['failures'].append(f"Command test error: {str(e)}") | ||||||
|  |              | ||||||
|  |         # Clean up any leftovers | ||||||
|  |         import os | ||||||
|  |         if os.path.exists(test_cmd_path): | ||||||
|  |             try: | ||||||
|  |                 os.remove(test_cmd_path) | ||||||
|  |                 self.bot.reload_commands() | ||||||
|  |             except: | ||||||
|  |                 pass | ||||||
|  |              | ||||||
|  |         return results | ||||||
|  |      | ||||||
|  |     async def test_storage(self, status_msg): | ||||||
|  |         """Test channel tracking functionality""" | ||||||
|  |         results = {'passed': 0, 'failed': 0, 'failures': []} | ||||||
|  |          | ||||||
|  |         # Save original state | ||||||
|  |         original_tracked = self.bot.tracked_channels.copy() | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Test channel ID that likely doesn't exist | ||||||
|  |             test_channel_id = 1349095349905788968 | ||||||
|  |              | ||||||
|  |             # Make sure it's not in the tracked channels | ||||||
|  |             if test_channel_id in self.bot.tracked_channels: | ||||||
|  |                 self.bot.tracked_channels.remove(test_channel_id) | ||||||
|  |                  | ||||||
|  |             # Test 1: Add a tracked channel | ||||||
|  |             self.bot.tracked_channels.append(test_channel_id) | ||||||
|  |             from utils.storage import save_tracked_channels | ||||||
|  |             save_tracked_channels(self.bot.tracked_channels) | ||||||
|  |              | ||||||
|  |             # Test 2: Load tracked channels | ||||||
|  |             from utils.storage import load_tracked_channels | ||||||
|  |             loaded_channels = load_tracked_channels() | ||||||
|  |              | ||||||
|  |             if test_channel_id in loaded_channels: | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append("Failed to save/load tracked channel") | ||||||
|  |                  | ||||||
|  |             # Test 3: Remove tracked channel | ||||||
|  |             if test_channel_id in self.bot.tracked_channels: | ||||||
|  |                 self.bot.tracked_channels.remove(test_channel_id) | ||||||
|  |                 save_tracked_channels(self.bot.tracked_channels) | ||||||
|  |                  | ||||||
|  |                 loaded_channels = load_tracked_channels() | ||||||
|  |                 if test_channel_id not in loaded_channels: | ||||||
|  |                     results['passed'] += 1 | ||||||
|  |                 else: | ||||||
|  |                     results['failed'] += 1 | ||||||
|  |                     results['failures'].append("Failed to remove tracked channel") | ||||||
|  |                      | ||||||
|  |         finally: | ||||||
|  |             # Restore original state | ||||||
|  |             self.bot.tracked_channels = original_tracked | ||||||
|  |             from utils.storage import save_tracked_channels | ||||||
|  |             save_tracked_channels(self.bot.tracked_channels) | ||||||
|  |              | ||||||
|  |         return results | ||||||
|  | 
 | ||||||
|  |     async def test_health(self, status_msg): | ||||||
|  |         """Test basic bot health and connectivity""" | ||||||
|  |         results = {'passed': 0, 'failed': 0, 'failures': []} | ||||||
|  |          | ||||||
|  |         # Test 1: Check if the bot is logged in | ||||||
|  |         if self.bot.user is not None: | ||||||
|  |             results['passed'] += 1 | ||||||
|  |         else: | ||||||
|  |             results['failed'] += 1 | ||||||
|  |             results['failures'].append("Bot is not logged in") | ||||||
|  |          | ||||||
|  |         # Test 2: Check discord API connection by getting client latency | ||||||
|  |         try: | ||||||
|  |             latency = self.bot.latency | ||||||
|  |             if isinstance(latency, float): | ||||||
|  |                 results['passed'] += 1 | ||||||
|  |             else: | ||||||
|  |                 results['failed'] += 1 | ||||||
|  |                 results['failures'].append(f"Invalid latency value: {latency}") | ||||||
|  |         except Exception as e: | ||||||
|  |             results['failed'] += 1 | ||||||
|  |             results['failures'].append(f"Failed to get latency: {str(e)}") | ||||||
|  |          | ||||||
|  |         # Test 3: Test message sending/editing (core functionality) | ||||||
|  |         try: | ||||||
|  |             test_msg = await status_msg.channel.send( | ||||||
|  |                 "Test message - will be deleted", silent=False | ||||||
|  |             ) | ||||||
|  |             await test_msg.edit(content="Test message edited - will be deleted") | ||||||
|  |             await test_msg.delete() | ||||||
|  |             results['passed'] += 1 | ||||||
|  |         except Exception as e: | ||||||
|  |             results['failed'] += 1 | ||||||
|  |             results['failures'].append(f"Message operations failed: {str(e)}") | ||||||
|  |          | ||||||
|  |         return results | ||||||
|  | 
 | ||||||
|  |     def _assert(self, results, condition, message): | ||||||
|  |         """Helper method for assertions in tests""" | ||||||
|  |         if condition: | ||||||
|  |             results['passed'] += 1 | ||||||
|  |         else: | ||||||
|  |             results['failed'] += 1 | ||||||
|  |             results['failures'].append(message) | ||||||
|  |         return condition | ||||||
|  | 
 | ||||||
|  | class MockMessage: | ||||||
|  |     """A mock message class for testing""" | ||||||
|  |     def __init__(self, author, content, channel): | ||||||
|  |         self.author = author | ||||||
|  |         self.content = content | ||||||
|  |         self.channel = channel | ||||||
|  |         self.id = int(time.time() * 1000) | ||||||
|  |         self.created_at = datetime.now() | ||||||
|  |          | ||||||
|  |     async def reply(self, content, **kwargs): | ||||||
|  |         """Mock reply method""" | ||||||
|  |         return await self.channel.send(f"Reply to {self.id}: {content}", **kwargs) | ||||||
|  |          | ||||||
|  |     async def edit(self, **kwargs): | ||||||
|  |         """Mock edit method""" | ||||||
|  |         self.content = kwargs.get('content', self.content) | ||||||
|  |         return self | ||||||
|  | 
 | ||||||
|  |     async def cmd_test_debug(self, message): | ||||||
|  |         """Run a simple debug test to verify command functionality""" | ||||||
|  |         try: | ||||||
|  |             # Send a simple message that should always work | ||||||
|  |             debug_msg = await message.channel.send("🔍 Debug test initiated...") | ||||||
|  |              | ||||||
|  |             # Wait a moment | ||||||
|  |             await asyncio.sleep(1) | ||||||
|  |              | ||||||
|  |             # Edit the message | ||||||
|  |             await debug_msg.edit(content="✅ Debug test successful - message editing works!") | ||||||
|  |              | ||||||
|  |         except Exception as e: | ||||||
|  |             # If this fails, there's a fundamental issue | ||||||
|  |             await message.channel.send(f"❌ Debug test failed: {str(e)}") | ||||||
							
								
								
									
										142
									
								
								bot/commands/utility_commands.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								bot/commands/utility_commands.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,142 @@ | ||||||
|  | import asyncio | ||||||
|  | import traceback | ||||||
|  | import datetime | ||||||
|  | import os | ||||||
|  | import ast | ||||||
|  | import gzip | ||||||
|  | import json | ||||||
|  | from utils.time_parser import parse_time | ||||||
|  | from config import DOWNLOADS_DIR | ||||||
|  | 
 | ||||||
|  | class UtilityCommands: | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |          | ||||||
|  |     async def cmd_remindme(self, message): | ||||||
|  |         """Set a reminder""" | ||||||
|  |         parts = message.content.split(" ", 2) | ||||||
|  |         if len(parts) < 3: | ||||||
|  |             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(): | ||||||
|  |                     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 a string using f-string""" | ||||||
|  |         try: | ||||||
|  |             formatted = eval(f"f'{message.content[5:]}'", globals(), locals()) | ||||||
|  |             print(formatted) | ||||||
|  |             await asyncio.wait_for(message.edit(formatted), 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 | ||||||
|  |              | ||||||
|  |     async def cmd_eval(self, message): | ||||||
|  |         """Evaluate Python code""" | ||||||
|  |         try: | ||||||
|  |             formatted = message.content[6:] | ||||||
|  |             print(repr(formatted)) | ||||||
|  |              | ||||||
|  |             exec_scope = { | ||||||
|  |                 "msg": message, | ||||||
|  |                 "asyncio": asyncio, | ||||||
|  |                 "random": __import__("random"), | ||||||
|  |                 **self.bot.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) | ||||||
|  |              | ||||||
|  |             await exec_scope["__eval"]() | ||||||
|  |              | ||||||
|  |         except Exception: | ||||||
|  |             await message.edit(content=traceback.format_exc()) | ||||||
|  |         finally: | ||||||
|  |             await message.delete() | ||||||
|  |              | ||||||
|  |     async def cmd_delrecent(self, message): | ||||||
|  |         """Delete recent messages""" | ||||||
|  |         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.bot.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) | ||||||
|  |              | ||||||
|  |     async def cmd_savechannel(self, message): | ||||||
|  |         """Save all messages in a channel to a file""" | ||||||
|  |         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" | ||||||
|  |             os.makedirs(DOWNLOADS_DIR, exist_ok=True) | ||||||
|  |             filepath = os.path.join(DOWNLOADS_DIR, filename) | ||||||
|  |              | ||||||
|  |             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}") | ||||||
							
								
								
									
										1
									
								
								bot/handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								bot/handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | # Empty init to make directory a package | ||||||
							
								
								
									
										109
									
								
								bot/handlers/message_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								bot/handlers/message_handler.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,109 @@ | ||||||
|  | import discord | ||||||
|  | import asyncio | ||||||
|  | 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 | ||||||
|  | 
 | ||||||
|  | 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 | ||||||
|  |          | ||||||
|  |     async def handle_message(self, message): | ||||||
|  |         # 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(".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) | ||||||
							
								
								
									
										19
									
								
								bot/handlers/presence_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								bot/handlers/presence_handler.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | import discord | ||||||
|  | from config import WELCOME_BACK_CHANNEL_ID | ||||||
|  | 
 | ||||||
|  | class PresenceHandler: | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |          | ||||||
|  |     async def handle_presence_update(self, before, after): | ||||||
|  |         """Handle user presence updates""" | ||||||
|  |         if after.id == 627566973869359104: | ||||||
|  |             old_status = self.bot.last_status.get(after.id, discord.Status.offline) | ||||||
|  |             self.bot.last_status[after.id] = after.status | ||||||
|  |              | ||||||
|  |             if old_status == discord.Status.offline and after.status == discord.Status.online: | ||||||
|  |                 channel = self.bot.get_channel(WELCOME_BACK_CHANNEL_ID) | ||||||
|  |                 if channel: | ||||||
|  |                     # Commented out for now as in original code | ||||||
|  |                     # await channel.send(f"[BOT] Welcome back, {after.mention}!", silent=True) | ||||||
|  |                     pass | ||||||
							
								
								
									
										51
									
								
								bot/handlers/tracking_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								bot/handlers/tracking_handler.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | ||||||
|  | import discord | ||||||
|  | import io | ||||||
|  | import difflib | ||||||
|  | 
 | ||||||
|  | class TrackingHandler: | ||||||
|  |     def __init__(self, bot): | ||||||
|  |         self.bot = bot | ||||||
|  |          | ||||||
|  |     async def process_log_whitelist(self, message): | ||||||
|  |         """Process whitelist for logging""" | ||||||
|  |         if message.author.id == 627566973869359104: | ||||||
|  |             return True | ||||||
|  |         return False | ||||||
|  |          | ||||||
|  |     async def handle_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 handle_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, "cutie.diff"), | ||||||
|  |                     silent=True | ||||||
|  |                 ) | ||||||
							
								
								
									
										41
									
								
								bot/selfbot.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								bot/selfbot.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | import discord | ||||||
|  | from utils.storage import load_commands, load_tracked_channels | ||||||
|  | from bot.handlers.message_handler import MessageHandler | ||||||
|  | from bot.handlers.tracking_handler import TrackingHandler | ||||||
|  | from bot.handlers.presence_handler import PresenceHandler | ||||||
|  | 
 | ||||||
|  | class Selfbot(discord.Client): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         # State variables | ||||||
|  |         self.default_status = None | ||||||
|  |         self.loaded_commands = load_commands() | ||||||
|  |         self.tracked_channels = load_tracked_channels() | ||||||
|  |         self.last_status = {} | ||||||
|  |         self.AFK_STATUS = False | ||||||
|  |         self.AFK_NOTIFIED_USERS = [] | ||||||
|  |         self.horsin = [] | ||||||
|  |          | ||||||
|  |         # Initialize handlers | ||||||
|  |         self.message_handler = MessageHandler(self) | ||||||
|  |         self.tracking_handler = TrackingHandler(self) | ||||||
|  |         self.presence_handler = PresenceHandler(self) | ||||||
|  |          | ||||||
|  |     async def on_ready(self): | ||||||
|  |         print(f"Logged in as {self.user}") | ||||||
|  |          | ||||||
|  |     async def on_message(self, message): | ||||||
|  |         await self.message_handler.handle_message(message) | ||||||
|  |          | ||||||
|  |     async def on_message_delete(self, message): | ||||||
|  |         await self.tracking_handler.handle_message_delete(message) | ||||||
|  |          | ||||||
|  |     async def on_message_edit(self, before, after): | ||||||
|  |         await self.tracking_handler.handle_message_edit(before, after) | ||||||
|  |          | ||||||
|  |     async def on_presence_update(self, before, after): | ||||||
|  |         await self.presence_handler.handle_presence_update(before, after) | ||||||
|  |          | ||||||
|  |     def reload_commands(self): | ||||||
|  |         """Reload user-defined commands""" | ||||||
|  |         self.loaded_commands = load_commands() | ||||||
							
								
								
									
										19
									
								
								config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								config.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | import os | ||||||
|  | 
 | ||||||
|  | # Directories | ||||||
|  | COMMANDS_DIR = "commands" | ||||||
|  | DOWNLOADS_DIR = "downloads" | ||||||
|  | 
 | ||||||
|  | # Files | ||||||
|  | TRACKED_CHANNELS_FILE = "tracked_channels.json" | ||||||
|  | 
 | ||||||
|  | # User IDs for special handling | ||||||
|  | BLACKLISTED_USERS = [] | ||||||
|  | BUCKET_REACT_USERS = [] | ||||||
|  | AUTO_DELETE_USERS = [] | ||||||
|  | SPECIAL_RESPONSES = { | ||||||
|  |     1236667927944761396: {"trigger": "<@1168626335942455326>", "response": "shut the fuck up"} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # Channels | ||||||
|  | WELCOME_BACK_CHANNEL_ID = 0 | ||||||
							
								
								
									
										19
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								main.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | import os | ||||||
|  | from dotenv import load_dotenv | ||||||
|  | from bot.selfbot import Selfbot | ||||||
|  | 
 | ||||||
|  | def main(): | ||||||
|  |     # Load environment variables from .env file | ||||||
|  |     load_dotenv() | ||||||
|  |      | ||||||
|  |     # 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.") | ||||||
|  |      | ||||||
|  |     # Initialize and run the bot | ||||||
|  |     bot = Selfbot() | ||||||
|  |     bot.run(TOKEN) | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
							
								
								
									
										1
									
								
								utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | # Empty init to make directory a package | ||||||
							
								
								
									
										33
									
								
								utils/storage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								utils/storage.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | import os | ||||||
|  | import json | ||||||
|  | from config import COMMANDS_DIR, TRACKED_CHANNELS_FILE | ||||||
|  | import importlib.util | ||||||
|  | 
 | ||||||
|  | def load_tracked_channels(): | ||||||
|  |     """Load tracked channel IDs from file""" | ||||||
|  |     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): | ||||||
|  |     """Save tracked channel IDs to file""" | ||||||
|  |     with open(TRACKED_CHANNELS_FILE, 'w') as f: | ||||||
|  |         json.dump(tracked_channels, f) | ||||||
|  | 
 | ||||||
|  | def load_commands(): | ||||||
|  |     """Load user-defined commands from the commands directory""" | ||||||
|  |     os.makedirs(COMMANDS_DIR, exist_ok=True) | ||||||
|  |     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 | ||||||
							
								
								
									
										20
									
								
								utils/time_parser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								utils/time_parser.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import re | ||||||
|  | 
 | ||||||
|  | time_regex = re.compile(r'(\d+)([smhd])')  # Matches 4m2s, 1h30m, etc. | ||||||
|  | 
 | ||||||
|  | def parse_time(time_str): | ||||||
|  |     """ | ||||||
|  |     Parse time strings like "4m2s", "1h30m" into seconds. | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         time_str: String in format like "4m2s", "1h30m" | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         Integer of total seconds or None if invalid | ||||||
|  |     """ | ||||||
|  |     units = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400} | ||||||
|  |     try: | ||||||
|  |         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 | ||||||
|  |     except: | ||||||
|  |         return None | ||||||
		Loading…
	
		Reference in a new issue
	
	 Xargana
						Xargana