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)
|
client.run(TOKEN)
|
||||||
await message.reply(f"{message.author.mention} Reminder: {reminder_text.replace("@", "at")}")
|
|
||||||
|
|
||||||
asyncio.create_task(reminder_task())
|
if __name__ == "__main__":
|
||||||
elif message.content.startswith(".rps "):
|
main()
|
||||||
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/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