i left skid marks in the code

This commit is contained in:
Xargana 2025-05-09 22:43:55 +03:00
parent fbfa27c979
commit 282ca4374a
13 changed files with 942 additions and 523 deletions

409
app.py
View file

@ -1,402 +1,25 @@
import random
import discord import discord
import asyncio import asyncio
import traceback import logging
import random
import datetime
import os import os
import importlib.util from bot.selfbot import SelfBot
import ast from config import TOKEN
import websockets
import json
import difflib
import io
import gzip
import re
from dotenv import load_dotenv
# Load environment variables from .env file def main():
load_dotenv() # Configure logging
logging.basicConfig(
time_regex = re.compile(r'(\d+)([smhd])') # Matches 4m2s, 1h30m, etc. level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
def parse_time(time_str):
units = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
total_seconds = sum(int(amount) * units[unit] for amount, unit in time_regex.findall(time_str))
return total_seconds if total_seconds > 0 else None
COMMANDS_DIR = "commands"
os.makedirs(COMMANDS_DIR, exist_ok=True)
def load_commands():
commands = {}
for filename in os.listdir(COMMANDS_DIR):
if filename.endswith(".py"):
cmd_name = filename[:-3]
cmd_path = os.path.join(COMMANDS_DIR, filename)
spec = importlib.util.spec_from_file_location(cmd_name, cmd_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if hasattr(module, "run") and callable(module.run):
commands[cmd_name] = module.run
return commands
# Add a tracking file to save channel IDs
TRACKED_CHANNELS_FILE = "tracked_channels.json"
def load_tracked_channels():
if os.path.exists(TRACKED_CHANNELS_FILE):
with open(TRACKED_CHANNELS_FILE, 'r') as f:
return json.load(f)
return []
def save_tracked_channels(tracked_channels):
with open(TRACKED_CHANNELS_FILE, 'w') as f:
json.dump(tracked_channels, f)
async def handle_blacklist(message):
if message.author in [696800726084747314]:
await message.reply("no")
class Selfbot(discord.Client):
def __init__(self):
super().__init__()
self.default_status = None
self.loaded_commands = load_commands()
self.tracked_channels = load_tracked_channels()
self.last_status = {}
self.AFK_STATUS = False # Static variable to track AFK status
self.AFK_NOTIFIED_USERS = [] # Static list to store users notified of AFK status
self.horsin = []
async def on_ready(self):
print(f"Logged in as {self.user}")
async def handle_afk_dm(self,message):
if self.AFK_STATUS and message.author.id not in self.AFK_NOTIFIED_USERS:
await message.reply(
"Heya, I'm not at my computer right now, if you're requesting something please follow <https://nohello.club>. I'll let you know when I'm back :) \n\n-# This action was automated."
) )
self.AFK_NOTIFIED_USERS.append(message.author.id)
async def on_message(self, message):
if message.author.id == 1169111190824308768 and "<@1236667927944761396>" in message.content:
await message.reply("shut the fuck up")
if message.content.startswith(".remindme "): # Set up Discord client
await handle_blacklist(message) intents = discord.Intents.all()
parts = message.content.split(" ", 2) # Create the selfbot
if len(parts) < 3: client = SelfBot(intents=intents)
await message.reply("Usage: `.remindme <time> <message>`", silent=True)
else:
duration = parse_time(parts[1])
if duration is None:
await message.reply("Invalid time format. Example: `4m2s`, `1h30m`.", silent=True)
else:
reminder_text = parts[2]
await message.reply(f"Reminder set for {parts[1]}.", silent=True)
async def reminder_task(): # Run the bot
await asyncio.sleep(duration)
await message.reply(f"{message.author.mention} Reminder: {reminder_text.replace("@", "at")}")
asyncio.create_task(reminder_task())
elif message.content.startswith(".rps "):
await handle_blacklist(message)
parts = message.content.split(" ",2)
if len(parts) != 2:
await message.reply("Usage: `.rps <item`",silent=True)
item
item = parts[1]
if item.lower() == "dick":
await message.reply("Scissors beats dick any day :3",silent=True) # little easter egg
return
if item == "<@696800726084747314>":
await message.reply("Head so thick that i would try rock but the rock would break")
return
choice = random.choice([1,2,3])
rps_map = dict(zip((1,2,3),[i for i in "rps"]))
rps_map_reverse = dict(zip([i for i in "rps"],(1,2,3))) # FIXME: if you cant see the issue you're blind
shortmaps = {word[0]: word for word in ["rock", "paper", "scissors"]}
beat_map = {1:3,2:1,3:2}
iid = 0
for k,v in rps_map_reverse.items():
if item.lower().startswith(k):
iid = v
break
if iid == 0:
await message.reply("Invalid choice!",silent=True)
return
if choice == iid:
await message.reply(f"Huh we chose the same thing. Try again!",silent=True)
return
if beat_map[iid] == choice:
await message.reply(f"Welp, ggs, I won. I chose `{shortmaps[rps_map[choice]]}`",silent=True)
else:
await message.reply(f"Oop, you lost. Try again later! I chose `{shortmaps[rps_map[choice]]}`",silent=True)
# Handle DM if in AFK mode
if isinstance(message.channel, discord.DMChannel) and message.author != self.user:
await self.handle_afk_dm(message)
if message.channel.id in self.horsin:
await message.add_reaction("🐴")
if message.author.id == 1341423498618208257:
await message.add_reaction("🪣")
if message.author != self.user:
if message.author.id == 1351739454141763626:
try:
await message.delete()
except:
pass
return
if message.content.startswith(".horse"):
if message.channel.id in self.horsin:
self.horsin.remove(message.channel.id)
await message.reply("no longer horsin around D:")
else:
self.horsin.append(message.channel.id)
await message.reply(":D")
if message.content.startswith(".afk"):
if not self.AFK_STATUS:
self.AFK_STATUS = True
await message.reply("k")
else:
await message.reply("You are already in AFK mode.", silent=True)
# UNAFK Command
elif message.content.startswith(".unafk"):
if self.AFK_STATUS:
self.AFK_STATUS = False
for i in self.AFK_NOTIFIED_USERS:
try:
user = await self.fetch_user(i) # Fetch user by ID
if user:
dm_channel = await user.create_dm() # Create DM channel
await dm_channel.send("Hey, I'm back, human me will take over now!")
except Exception as e:
raise RuntimeWarning from e
self.AFK_NOTIFIED_USERS.clear() # Clear the AFK notified users list
await message.reply("should work")
else:
await message.reply("You are not in AFK mode.", silent=True)
if message.content.startswith(".fmt "):
try:
formated = eval(f"f'{message.content[5:]}'", globals(),locals())
print(formated)
await asyncio.wait_for(message.edit(formated), timeout=5)
except Exception as e:
flashdel = await message.channel.send(f"Error: {e}",silent=True)
await message.delete()
await flashdel.delete()
raise RuntimeWarning from e
elif message.content.startswith(".eval "):
try:
formatted = message.content[6:]
print(repr(formatted))
exec_scope = {
"msg": message,
"asyncio": asyncio,
"random": random,
**self.loaded_commands, # Inject loaded commands
"out": lambda content: message.reply(content, silent=True),
}
# Parse the code to detect non-async function calls
tree = ast.parse(formatted)
for node in ast.walk(tree):
if isinstance(node, ast.Call) and not isinstance(node.func, ast.Attribute):
# Check if the function is a coroutine
func_name = node.func.id
if func_name in exec_scope and asyncio.iscoroutinefunction(exec_scope[func_name]):
# Replace the call with an await expression
formatted = formatted.replace(f"{func_name}(", f"await {func_name}(")
exec(f"async def __eval():\n {formatted.replace(chr(10), chr(10) + ' ')}", exec_scope)
result = await exec_scope["__eval"]()
except Exception:
await message.edit(content=traceback.format_exc())
finally:
await message.delete()
elif message.content.startswith(".addcmd "):
try:
parts = message.content.split(" ", 2)
if len(parts) < 3:
await message.reply("Usage: .addcmd <name> <code>",silent=True)
return
cmd_name, code = parts[1], parts[2]
cmd_path = os.path.join(COMMANDS_DIR, f"{cmd_name}.py")
with open(cmd_path, "w") as f:
f.write("async def run(msg):\n")
for line in code.split("\n"):
f.write(f" {line}\n")
self.loaded_commands = load_commands()
await message.reply(f"Command {cmd_name} saved.",silent=True)
except Exception as e:
await message.reply(f"Error: {e}",silent=True)
elif message.content.startswith(".delcmd "):
cmd_name = message.content.split(" ", 1)[1]
cmd_path = os.path.join(COMMANDS_DIR, f"{cmd_name}.py")
if os.path.exists(cmd_path):
os.remove(cmd_path)
self.loaded_commands = load_commands()
await message.reply(f"Command {cmd_name} deleted.",silent=True)
else:
await message.reply(f"Command {cmd_name} not found.",silent=True)
elif message.content.startswith(".listcmds"):
cmds = list(self.loaded_commands.keys())
await message.reply("Saved commands:\n" + ", ".join(cmds) if cmds else "No saved commands.",silent=True)
elif message.content.startswith(".delrecent "):
try:
minutes = float(message.content.split(" ", 1)[1])
cutoff_time = datetime.datetime.now(datetime.UTC) - datetime.timedelta(minutes=minutes)
deleted = 0
async for msg in message.channel.history(limit=100):
if msg.author == self.user and msg.created_at >= cutoff_time:
await msg.delete()
deleted += 1
await message.channel.send(f"Deleted {deleted} messages.", delete_after=0,silent=True)
except Exception as e:
await message.channel.send(f"Error: {e}", delete_after=0,silent=True)
elif message.content.startswith(".trackmessages"):
channel_id = message.channel.id
if channel_id not in self.tracked_channels:
self.tracked_channels.append(channel_id)
save_tracked_channels(self.tracked_channels)
await message.reply(f"Tracking messages in this channel {message.channel.name}.",silent=True)
else:
await message.reply("This channel is already being tracked.",silent=True)
elif message.content.startswith(".untrackmessages"):
channel_id = message.channel.id
if channel_id in self.tracked_channels:
self.tracked_channels.remove(channel_id)
save_tracked_channels(self.tracked_channels)
await message.reply(f"Stopped tracking messages in {message.channel.name}.",silent=True)
else:
await message.reply("This channel is not being tracked.",silent=True)
elif message.content.startswith(".savechannel"):
try:
messages = []
async for msg in message.channel.history(limit=None):
msg_dict = {
"id": msg.id,
"content": msg.content,
"created_at": msg.created_at.isoformat() if msg.created_at else None,
"edited_at": msg.edited_at.isoformat() if msg.edited_at else None,
"author": {
"id": msg.author.id,
"name": msg.author.name,
"discriminator": msg.author.discriminator,
"bot": msg.author.bot
} if msg.author else None,
"channel_id": msg.channel.id,
"attachments": [
{"filename": a.filename, "url": a.url} for a in msg.attachments
] if msg.attachments else [],
"embeds": [embed.to_dict() for embed in msg.embeds] if msg.embeds else [],
"reactions": [
{"emoji": str(r.emoji), "count": r.count} for r in msg.reactions
] if msg.reactions else [],
"mentions": [user.id for user in msg.mentions] if msg.mentions else [],
"role_mentions": [role.id for role in msg.role_mentions] if msg.role_mentions else [],
"pinned": msg.pinned,
"tts": msg.tts,
"type": str(msg.type),
"reference": {
"message_id": msg.reference.message_id,
"channel_id": msg.reference.channel_id,
"guild_id": msg.reference.guild_id
} if msg.reference else None,
}
messages.append(msg_dict)
filename = f"{message.channel.id}.json.gz"
filepath = os.path.join("downloads", filename) # Save to a "downloads" folder
os.makedirs("downloads", exist_ok=True)
with gzip.open(filepath, "wt", encoding="utf-8") as f:
json.dump(messages, f, ensure_ascii=False, indent=4)
await message.edit(content=f"Saved messages to `{filepath}`")
except Exception as e:
await message.edit(content=f"Error: {e}")
elif message.content.startswith(".repeat29"):
await message.reply("oop ok")
while 1:
await message.channel.send("you asked dad")
await asyncio.sleep(29*60)
@staticmethod
async def process_log_whitelist(message):
if message.author in [627566973869359104,]:
return
async def on_message_delete(self, message):
await self.process_log_whitelist(message)
if message.channel.id in self.tracked_channels:
member = message.author
if member != self.user:
await message.channel.send(f"<@{member.id}> deleted {message.content}",silent=True)
async def on_message_edit(self, before, after):
await self.process_log_whitelist(before)
if before.channel.id in self.tracked_channels:
member = after.author
if member == self.user: return
if before.content == after.content: return
diff = difflib.unified_diff(before.content.splitlines(), after.content.splitlines())
diff_result = '\n'.join(diff)
# Use BytesIO to create an in-memory file-like object with the full diff (no line removal)
with io.BytesIO(diff_result.encode('utf-8')) as diff_file:
diff_file.seek(0) # Ensure we're at the start of the BytesIO buffer
# Send the file to the channel
await after.channel.send(
f"<@{member.id}> edited a message",
file=discord.File(diff_file, "cutie.diff"),silent=True
)
async def on_presence_update(self, before, after):
if after.id == 627566973869359104:
old_status = self.last_status.get(after.id, discord.Status.offline)
self.last_status[after.id] = after.status
if old_status in [discord.Status.offline] and after.status == discord.Status.online:
channel = self.get_channel(1302691935152246851)
if channel:
...
#await channel.send(f"[BOT] Welcome back, {after.mention}!",silent=True)
client = Selfbot()
# get token from environment variable
TOKEN = os.getenv("TOKEN")
if not TOKEN:
raise ValueError("No TOKEN found in environment variables. Please add it to your .env file.")
client.run(TOKEN) client.run(TOKEN)
if __name__ == "__main__":
main()

1
bot/cogs/__init__.py Normal file
View file

@ -0,0 +1 @@
# This makes the cogs directory a proper Python package

129
bot/cogs/admin_commands.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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")

View 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)}")

View 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)

View 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

View file

@ -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)

View file

@ -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")