diff --git a/README.md b/README.md index 1c2bb44..95a2682 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # booly -booly is a fork of tooly beacuse og tooly got bad \ No newline at end of file +Revamped tooly designed for ease of use. + +two use modes: +1. premade *servers may lag +2. host your self it comes with the whole bot all you need is a token and a host + +you can fork expand pull request host just but this in the readme or site ![Powered by Booly](https://img.shields.io/github/v/release/chersbobers/booly?label=powered%20by%20booly&color=5865F2&logo=discord&logoColor=white) diff --git a/bot.py b/bot.py index 0ad2a24..e0feba7 100644 --- a/bot.py +++ b/bot.py @@ -1,21 +1,14 @@ import discord -from discord import app_commands -from discord.ext import commands, tasks +from discord.ext import commands import os import json -import random -import asyncio -from datetime import datetime -import aiohttp import logging from aiohttp import web -import feedparser +import asyncio -# Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger('bot') -# Configuration CONFIG = { 'xp_min': 15, 'xp_max': 25, @@ -23,10 +16,9 @@ CONFIG = { 'xp_per_level': 100, 'level_up_multiplier': 10, 'data_file': 'data.json', - 'video_check_interval': 300 # 5 minutes + 'video_check_interval': 300 } -# Simple JSON Database class SimpleDB: def __init__(self, filename): self.filename = filename @@ -73,7 +65,6 @@ class SimpleDB: users.sort(key=lambda x: (x['data']['level'], x['data']['xp']), reverse=True) return users -# Bot setup class MyBot(commands.Bot): def __init__(self): intents = discord.Intents.default() @@ -82,100 +73,23 @@ class MyBot(commands.Bot): super().__init__(command_prefix='/', intents=intents) self.db = SimpleDB(CONFIG['data_file']) + self.config = CONFIG async def setup_hook(self): - # Start YouTube checker - self.check_youtube.start() - # Sync slash commands + await self.load_extension('cogs.leveling') + await self.load_extension('cogs.system') + await self.load_extension('cogs.economy') + await self.load_extension('cogs.fun') + await self.tree.sync() - logger.info('āœ… Slash commands synced!') - - @tasks.loop(seconds=CONFIG['video_check_interval']) - async def check_youtube(self): - """Check for new YouTube videos""" - youtube_channel_id = os.getenv('YOUTUBE_CHANNEL_ID') - - if not youtube_channel_id: - return - - try: - feed_url = f'https://www.youtube.com/feeds/videos.xml?channel_id={youtube_channel_id}' - - # Parse feed - feed = await asyncio.to_thread(feedparser.parse, feed_url) - - if not feed.entries: - return - - latest = feed.entries[0] - video_id = latest.yt_videoid if hasattr(latest, 'yt_videoid') else latest.id.split(':')[-1] - - # Check each guild for notifications - for guild in self.guilds: - guild_id = str(guild.id) - - # Get guild settings - if 'youtube' not in self.db.data: - self.db.data['youtube'] = {} - - if guild_id not in self.db.data['youtube']: - self.db.data['youtube'][guild_id] = { - 'enabled': True, - 'channel_id': None, - 'last_video_id': None - } - - guild_settings = self.db.data['youtube'][guild_id] - - # Skip if disabled or no channel set - if not guild_settings['enabled'] or not guild_settings['channel_id']: - continue - - # Check if this is a new video - if video_id != guild_settings['last_video_id'] and guild_settings['last_video_id']: - channel = self.get_channel(int(guild_settings['channel_id'])) - - if channel: - embed = discord.Embed( - title='šŸŽ¬ New YouTube Video!', - description=f'**{latest.title}**', - url=latest.link, - color=0xFF0000, - timestamp=datetime.utcnow() - ) - - # Add thumbnail if available - if hasattr(latest, 'media_thumbnail') and latest.media_thumbnail: - embed.set_thumbnail(url=latest.media_thumbnail[0]['url']) - - embed.add_field(name='Channel', value=latest.author, inline=True) - - # Parse published date - if hasattr(latest, 'published'): - embed.add_field(name='Published', value=latest.published, inline=True) - - await channel.send('šŸ“ŗ New video alert! @everyone', embed=embed) - logger.info(f'šŸ“ŗ Sent notification for: {latest.title}') - - # Update last video ID - guild_settings['last_video_id'] = video_id - self.db.save() - - except Exception as e: - logger.error(f'āŒ Error checking YouTube: {e}') - - @check_youtube.before_loop - async def before_check_youtube(self): - await self.wait_until_ready() + logger.info('āœ… All cogs loaded and commands synced!') bot = MyBot() -# Simple HTTP server for Render health checks async def health_check(request): return web.Response(text="Bot is running! āœ…") async def start_web_server(): - """Start a simple web server for Render health checks""" app = web.Application() app.router.add_get('/', health_check) app.router.add_get('/health', health_check) @@ -188,10 +102,8 @@ async def start_web_server(): await site.start() logger.info(f'🌐 Health check server running on port {port}') -# Events @bot.event async def on_ready(): - # Start web server for Render asyncio.create_task(start_web_server()) logger.info(f'āœ… Logged in as {bot.user}') @@ -199,43 +111,6 @@ async def on_ready(): await bot.change_presence(activity=discord.Game(name="/help")) logger.info('šŸš€ All systems operational!') -@bot.event -async def on_message(message): - if message.author.bot or not message.guild: - return - - # XP System - user_data = bot.db.get_user(str(message.guild.id), str(message.author.id)) - now = datetime.now().timestamp() - - if now - user_data['last_message'] >= CONFIG['xp_cooldown']: - user_data['last_message'] = now - xp_gain = random.randint(CONFIG['xp_min'], CONFIG['xp_max']) - user_data['xp'] += xp_gain - xp_needed = user_data['level'] * CONFIG['xp_per_level'] - - if user_data['xp'] >= xp_needed: - user_data['level'] += 1 - user_data['xp'] = 0 - - coin_reward = user_data['level'] * CONFIG['level_up_multiplier'] - user_data['coins'] += coin_reward - - messages = [ - f'šŸŽ‰ GG {message.author.mention}! You leveled up to **Level {user_data["level"]}**!', - f'⭐ Congrats {message.author.mention}! You\'re now **Level {user_data["level"]}**!', - f'šŸš€ Level up! {message.author.mention} reached **Level {user_data["level"]}**!' - ] - - await message.channel.send( - f'{random.choice(messages)} You earned **{coin_reward:,} coins**! šŸ’°' - ) - - bot.db.set_user(str(message.guild.id), str(message.author.id), user_data) - - await bot.process_commands(message) - -# Slash Commands @bot.tree.command(name='help', description='Show all available commands') async def help_command(interaction: discord.Interaction): embed = discord.Embed( @@ -244,13 +119,8 @@ async def help_command(interaction: discord.Interaction): color=0x5865F2 ) embed.add_field( - name='šŸ“Š Leveling', - value='`/rank` - View your rank\n`/leaderboard` - Top 10 users', - inline=False - ) - embed.add_field( - name='šŸ’° Economy', - value='`/balance` - Check balance\n`/daily` - Daily reward\n`/work` - Work for coins', + name='šŸ“Š Leveling & Economy', + value='`/rank` - View your rank\n`/leaderboard` - Top 10 users\n`/balance` - Check balance\n`/daily` - Daily reward\n`/work` - Work for coins', inline=False ) embed.add_field( @@ -258,535 +128,29 @@ async def help_command(interaction: discord.Interaction): value='`/8ball` - Magic 8ball\n`/roll` - Roll dice\n`/flip` - Flip coin\n`/cat` - Random cat\n`/dog` - Random dog', inline=False ) + embed.add_field( + name='šŸ›”ļø System & Moderation', + value='`/kick` - Kick member\n`/ban` - Ban member\n`/unban` - Unban user\n`/timeout` - Timeout member\n`/warn` - Warn member\n`/warnings` - View warnings\n`/clearwarnings` - Clear warnings\n`/purge` - Delete messages\n`/lock` - Lock channel\n`/unlock` - Unlock channel', + inline=False + ) + embed.add_field( + name='šŸŽ­ Reaction Roles & YouTube', + value='`/reactionrole` - Create reaction role\n`/removereactionrole` - Remove reaction role\n`/listreactionroles` - List reaction roles\n`/createreactionpanel` - Create panel\n`/setupyoutube` - Setup YouTube\n`/toggleyoutube` - Toggle YouTube\n`/youtubestatus` - YouTube status\n`/testlastvideo` - Test video', + inline=False + ) embed.add_field( name='ā„¹ļø Info', - value='`/ping` - Check bot latency\n`/serverinfo` - Server information', + value='`/ping` - Bot latency\n`/serverinfo` - Server info\n`/userinfo` - User info', inline=False ) await interaction.response.send_message(embed=embed) -@bot.tree.command(name='ping', description='Check bot latency') -async def ping(interaction: discord.Interaction): - latency = round(bot.latency * 1000) - await interaction.response.send_message(f'šŸ“ Pong! Latency: `{latency}ms`') - -@bot.tree.command(name='rank', description='View your rank and level') -async def rank(interaction: discord.Interaction, member: discord.Member = None): - target = member or interaction.user - user_data = bot.db.get_user(str(interaction.guild.id), str(target.id)) - xp_needed = user_data['level'] * CONFIG['xp_per_level'] - - all_users = bot.db.get_all_guild_users(str(interaction.guild.id)) - rank = next((i + 1 for i, u in enumerate(all_users) if u['user_id'] == str(target.id)), 'Unranked') - - progress = int((user_data['xp'] / xp_needed) * 20) if xp_needed > 0 else 0 - bar = 'ā–ˆ' * progress + 'ā–‘' * (20 - progress) - - color = 0xFF6B6B if user_data['level'] >= 50 else 0xFFD93D if user_data['level'] >= 30 else 0x6BCB77 if user_data['level'] >= 15 else 0x4D96FF - - embed = discord.Embed(color=color) - embed.set_author(name=f"{target.display_name}'s Profile", icon_url=target.display_avatar.url) - embed.description = f""" -**RANK** • #{rank} / {len(all_users)} -**LEVEL** • {user_data['level']} -**XP** • {user_data['xp']:,} / {xp_needed:,} - -`{bar}` - -**šŸ’° BALANCE** • {user_data['coins']:,} coins - """ - embed.set_thumbnail(url=target.display_avatar.url) - await interaction.response.send_message(embed=embed) - -@bot.tree.command(name='leaderboard', description='View the server leaderboard') -async def leaderboard(interaction: discord.Interaction): - all_users = bot.db.get_all_guild_users(str(interaction.guild.id))[:10] - - description = [] - for i, u in enumerate(all_users): - medal = 'šŸ„‡' if i == 0 else '🄈' if i == 1 else 'šŸ„‰' if i == 2 else f'**{i+1}.**' - description.append( - f'{medal} <@{u["user_id"]}>\n' - f'ā”” Level {u["data"]["level"]} ({u["data"]["xp"]:,} XP) • {u["data"]["coins"]:,} coins' - ) - - embed = discord.Embed( - title='šŸ† Server Leaderboard', - description='\n'.join(description) if description else 'No users yet!', - color=0x9B59B6, - timestamp=datetime.utcnow() - ) - embed.set_footer(text='Top 10 users by level and XP') - await interaction.response.send_message(embed=embed) - -@bot.tree.command(name='balance', description='Check your coin balance') -async def balance(interaction: discord.Interaction, member: discord.Member = None): - target = member or interaction.user - user_data = bot.db.get_user(str(interaction.guild.id), str(target.id)) - - embed = discord.Embed( - title='šŸ’° Balance', - description=f'{target.mention} has **{user_data["coins"]:,}** coins in wallet and **{user_data["bank"]:,}** in bank!\n**Total:** {user_data["coins"] + user_data["bank"]:,} coins', - color=0xFFD700 - ) - await interaction.response.send_message(embed=embed) - -@bot.tree.command(name='daily', description='Claim your daily reward') -async def daily(interaction: discord.Interaction): - user_data = bot.db.get_user(str(interaction.guild.id), str(interaction.user.id)) - now = datetime.now().timestamp() - - if now - user_data['last_daily'] < 86400: - time_left = 86400 - (now - user_data['last_daily']) - hours = int(time_left / 3600) - await interaction.response.send_message(f'ā³ You already claimed your daily! Come back in {hours} hours.', ephemeral=True) - return - - reward = 100 - user_data['coins'] += reward - user_data['last_daily'] = now - bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data) - - await interaction.response.send_message(f'āœ… You claimed your daily reward of **{reward:,}** coins!\nšŸ’° New balance: **{user_data["coins"]:,}** coins') - -@bot.tree.command(name='work', description='Work to earn coins') -async def work(interaction: discord.Interaction): - user_data = bot.db.get_user(str(interaction.guild.id), str(interaction.user.id)) - now = datetime.now().timestamp() - - if now - user_data['last_work'] < 3600: - time_left = 3600 - (now - user_data['last_work']) - minutes = int(time_left / 60) - await interaction.response.send_message(f'ā³ You need to rest! Come back in {minutes} minutes.', ephemeral=True) - return - - earnings = random.randint(10, 50) - user_data['coins'] += earnings - user_data['last_work'] = now - bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data) - - jobs = [ - 'You worked as a programmer and earned', - 'You delivered pizza and earned', - 'You streamed on Twitch and earned', - 'You mowed lawns and earned', - 'You washed cars and earned', - 'You tutored students and earned' - ] - - await interaction.response.send_message(f'šŸ’¼ {random.choice(jobs)} **{earnings:,}** coins!\nšŸ’° New balance: **{user_data["coins"]:,}** coins') - -@bot.tree.command(name='8ball', description='Ask the magic 8ball a question') -async def eightball(interaction: discord.Interaction, question: str): - responses = [ - 'Yes, definitely!', 'It is certain.', 'Without a doubt.', 'You may rely on it.', - 'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Signs point to yes.', - 'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.', - 'Cannot predict now.', "Don't count on it.", 'My reply is no.', 'Very doubtful.' - ] - await interaction.response.send_message(f'šŸ”® **{question}**\n{random.choice(responses)}') - -@bot.tree.command(name='roll', description='Roll a dice') -async def roll(interaction: discord.Interaction, sides: int = 6): - if sides < 2 or sides > 100: - await interaction.response.send_message('āŒ Dice must have between 2 and 100 sides!', ephemeral=True) - return - result = random.randint(1, sides) - await interaction.response.send_message(f'šŸŽ² You rolled a **{result}** (1-{sides})') - -@bot.tree.command(name='flip', description='Flip a coin') -async def flip(interaction: discord.Interaction): - result = random.choice(['Heads', 'Tails']) - await interaction.response.send_message(f'šŸŖ™ The coin landed on **{result}**!') - -@bot.tree.command(name='cat', description='Get a random cat picture') -async def cat(interaction: discord.Interaction): - await interaction.response.defer() - async with aiohttp.ClientSession() as session: - try: - async with session.get('https://api.thecatapi.com/v1/images/search') as resp: - data = await resp.json() - embed = discord.Embed(title='🐱 Random Kitty!', color=0xFF69B4) - embed.set_image(url=data[0]['url']) - embed.set_footer(text=f'Requested by {interaction.user.name}') - await interaction.followup.send(embed=embed) - except: - await interaction.followup.send('Failed to fetch a cat picture 😿') - -@bot.tree.command(name='dog', description='Get a random dog picture') -async def dog(interaction: discord.Interaction): - await interaction.response.defer() - async with aiohttp.ClientSession() as session: - try: - async with session.get('https://api.thedogapi.com/v1/images/search') as resp: - data = await resp.json() - embed = discord.Embed(title='🐶 Random Doggy!', color=0xFF69B4) - embed.set_image(url=data[0]['url']) - embed.set_footer(text=f'Requested by {interaction.user.name}') - await interaction.followup.send(embed=embed) - except: - await interaction.followup.send('Failed to fetch a dog picture 😄') - -@bot.tree.command(name='serverinfo', description='Display server information') -async def serverinfo(interaction: discord.Interaction): - guild = interaction.guild - - embed = discord.Embed( - title=f'šŸ“Š {guild.name}', - color=0x5865F2, - timestamp=datetime.utcnow() - ) - - if guild.icon: - embed.set_thumbnail(url=guild.icon.url) - - embed.add_field(name='šŸ‘‘ Owner', value=guild.owner.mention, inline=True) - embed.add_field(name='šŸ‘„ Members', value=guild.member_count, inline=True) - embed.add_field(name='šŸ“… Created', value=guild.created_at.strftime('%Y-%m-%d'), inline=True) - embed.add_field(name='šŸ’¬ Channels', value=len(guild.channels), inline=True) - embed.add_field(name='šŸ˜€ Emojis', value=len(guild.emojis), inline=True) - embed.add_field(name='šŸŽ­ Roles', value=len(guild.roles), inline=True) - - await interaction.response.send_message(embed=embed) - -# Reaction Roles -@bot.event -async def on_raw_reaction_add(payload): - if payload.user_id == bot.user.id: - return - - # Get reaction role data - guild_id = str(payload.guild_id) - message_id = str(payload.message_id) - - if 'reaction_roles' not in bot.db.data: - bot.db.data['reaction_roles'] = {} - - if guild_id not in bot.db.data['reaction_roles']: - return - - if message_id not in bot.db.data['reaction_roles'][guild_id]: - return - - emoji_str = str(payload.emoji) - role_id = bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str) - - if not role_id: - return - - guild = bot.get_guild(payload.guild_id) - if not guild: - return - - role = guild.get_role(int(role_id)) - if not role: - return - - member = guild.get_member(payload.user_id) - if not member: - return - - try: - await member.add_roles(role) - logger.info(f'āœ… Added role {role.name} to {member.name}') - except Exception as e: - logger.error(f'āŒ Error adding role: {e}') - -@bot.event -async def on_raw_reaction_remove(payload): - if payload.user_id == bot.user.id: - return - - guild_id = str(payload.guild_id) - message_id = str(payload.message_id) - - if 'reaction_roles' not in bot.db.data: - return - - if guild_id not in bot.db.data.get('reaction_roles', {}): - return - - if message_id not in bot.db.data['reaction_roles'][guild_id]: - return - - emoji_str = str(payload.emoji) - role_id = bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str) - - if not role_id: - return - - guild = bot.get_guild(payload.guild_id) - if not guild: - return - - role = guild.get_role(int(role_id)) - if not role: - return - - member = guild.get_member(payload.user_id) - if not member: - return - - try: - await member.remove_roles(role) - logger.info(f'āž– Removed role {role.name} from {member.name}') - except Exception as e: - logger.error(f'āŒ Error removing role: {e}') - -@bot.tree.command(name='reactionrole', description='[ADMIN] Create a reaction role') -@app_commands.describe( - message_id='Message ID to add reactions to', - emoji='Emoji to use (e.g., šŸŽ®)', - role='Role to assign' -) -@app_commands.default_permissions(administrator=True) -async def reactionrole(interaction: discord.Interaction, message_id: str, emoji: str, role: discord.Role): - try: - message = await interaction.channel.fetch_message(int(message_id)) - except discord.NotFound: - await interaction.response.send_message('āŒ Message not found in this channel!', ephemeral=True) - return - except ValueError: - await interaction.response.send_message('āŒ Invalid message ID!', ephemeral=True) - return - - try: - await message.add_reaction(emoji) - except discord.HTTPException: - await interaction.response.send_message('āŒ Invalid emoji or unable to add reaction!', ephemeral=True) - return - - # Save reaction role - guild_id = str(interaction.guild.id) - - if 'reaction_roles' not in bot.db.data: - bot.db.data['reaction_roles'] = {} - - if guild_id not in bot.db.data['reaction_roles']: - bot.db.data['reaction_roles'][guild_id] = {} - - if message_id not in bot.db.data['reaction_roles'][guild_id]: - bot.db.data['reaction_roles'][guild_id][message_id] = {} - - bot.db.data['reaction_roles'][guild_id][message_id][emoji] = str(role.id) - bot.db.save() - - embed = discord.Embed( - title='āœ… Reaction Role Created', - description=f'React with {emoji} on the message to get {role.mention}', - color=0x00FF00 - ) - embed.add_field(name='Message ID', value=message_id, inline=True) - embed.add_field(name='Emoji', value=emoji, inline=True) - embed.add_field(name='Role', value=role.mention, inline=True) - - await interaction.response.send_message(embed=embed) - logger.info(f'āœ… Created reaction role: {emoji} -> {role.name}') - -@bot.tree.command(name='removereactionrole', description='[ADMIN] Remove a reaction role') -@app_commands.describe( - message_id='Message ID', - emoji='Emoji to remove (leave empty to remove all)' -) -@app_commands.default_permissions(administrator=True) -async def removereactionrole(interaction: discord.Interaction, message_id: str, emoji: str = None): - guild_id = str(interaction.guild.id) - - if 'reaction_roles' not in bot.db.data: - bot.db.data['reaction_roles'] = {} - - if guild_id not in bot.db.data['reaction_roles'] or message_id not in bot.db.data['reaction_roles'].get(guild_id, {}): - await interaction.response.send_message('āŒ No reaction roles found for that message!', ephemeral=True) - return - - if emoji: - if emoji not in bot.db.data['reaction_roles'][guild_id][message_id]: - await interaction.response.send_message('āŒ That emoji is not set up for reaction roles!', ephemeral=True) - return - - del bot.db.data['reaction_roles'][guild_id][message_id][emoji] - bot.db.save() - await interaction.response.send_message(f'āœ… Removed reaction role for {emoji}') - else: - del bot.db.data['reaction_roles'][guild_id][message_id] - bot.db.save() - await interaction.response.send_message(f'āœ… Removed all reaction roles from message {message_id}') - -@bot.tree.command(name='listreactionroles', description='List all reaction roles') -async def listreactionroles(interaction: discord.Interaction): - guild_id = str(interaction.guild.id) - - if 'reaction_roles' not in bot.db.data: - bot.db.data['reaction_roles'] = {} - - guild_reactions = bot.db.data['reaction_roles'].get(guild_id, {}) - - if not guild_reactions: - await interaction.response.send_message('No reaction roles configured yet!') - return - - embed = discord.Embed( - title='šŸŽ­ Reaction Roles', - color=0x9B59B6 - ) - - for msg_id, reactions in guild_reactions.items(): - roles_text = [] - for emoji, role_id in reactions.items(): - role = interaction.guild.get_role(int(role_id)) - role_name = role.mention if role else f'Role ID: {role_id}' - roles_text.append(f'{emoji} → {role_name}') - - embed.add_field( - name=f'Message ID: {msg_id}', - value='\n'.join(roles_text) if roles_text else 'No reactions', - inline=False - ) - - await interaction.response.send_message(embed=embed) - -@bot.tree.command(name='createreactionpanel', description='[ADMIN] Create a reaction role panel') -@app_commands.describe( - title='Panel title', - description='Panel description' -) -@app_commands.default_permissions(administrator=True) -async def createreactionpanel(interaction: discord.Interaction, title: str, description: str): - embed = discord.Embed( - title=f'šŸŽ­ {title}', - description=description, - color=0x9B59B6 - ) - embed.set_footer(text='React below to get your roles!') - - message = await interaction.channel.send(embed=embed) - - await interaction.response.send_message( - f'āœ… Panel created! Message ID: `{message.id}`\n' - f'Use `/reactionrole {message.id} ` to add roles to it.', - ephemeral=True - ) - -# YouTube Notifications Commands -@bot.tree.command(name='setupyoutube', description='[ADMIN] Set up YouTube notifications in this channel') -@app_commands.default_permissions(administrator=True) -async def setupyoutube(interaction: discord.Interaction): - guild_id = str(interaction.guild.id) - - if 'youtube' not in bot.db.data: - bot.db.data['youtube'] = {} - - if guild_id not in bot.db.data['youtube']: - bot.db.data['youtube'][guild_id] = { - 'enabled': True, - 'channel_id': None, - 'last_video_id': None - } - - bot.db.data['youtube'][guild_id]['channel_id'] = str(interaction.channel.id) - bot.db.data['youtube'][guild_id]['enabled'] = True - bot.db.save() - - youtube_channel_id = os.getenv('YOUTUBE_CHANNEL_ID') - - if not youtube_channel_id: - await interaction.response.send_message( - 'āš ļø YouTube notifications set up in this channel, but `YOUTUBE_CHANNEL_ID` environment variable is not set!\n' - 'Ask the bot owner to set it on Render.', - ephemeral=True - ) - return - - embed = discord.Embed( - title='āœ… YouTube Notifications Enabled', - description=f'New video notifications will be posted in {interaction.channel.mention}', - color=0xFF0000 - ) - embed.add_field(name='Check Interval', value='Every 5 minutes', inline=True) - embed.add_field(name='Status', value='🟢 Active', inline=True) - - await interaction.response.send_message(embed=embed) - logger.info(f'āœ… YouTube notifications enabled in {interaction.guild.name} → #{interaction.channel.name}') - -@bot.tree.command(name='toggleyoutube', description='[ADMIN] Toggle YouTube notifications on/off') -@app_commands.default_permissions(administrator=True) -async def toggleyoutube(interaction: discord.Interaction): - guild_id = str(interaction.guild.id) - - if 'youtube' not in bot.db.data: - bot.db.data['youtube'] = {} - - if guild_id not in bot.db.data['youtube']: - bot.db.data['youtube'][guild_id] = { - 'enabled': False, - 'channel_id': None, - 'last_video_id': None - } - - # Toggle - bot.db.data['youtube'][guild_id]['enabled'] = not bot.db.data['youtube'][guild_id].get('enabled', False) - bot.db.save() - - status = 'enabled āœ…' if bot.db.data['youtube'][guild_id]['enabled'] else 'disabled āŒ' - color = 0x00FF00 if bot.db.data['youtube'][guild_id]['enabled'] else 0x808080 - - embed = discord.Embed( - title='šŸ”” YouTube Notifications', - description=f'YouTube notifications are now **{status}**', - color=color, - timestamp=datetime.utcnow() - ) - - await interaction.response.send_message(embed=embed) - -@bot.tree.command(name='youtubestatus', description='Check YouTube notification status') -async def youtubestatus(interaction: discord.Interaction): - guild_id = str(interaction.guild.id) - - if 'youtube' not in bot.db.data: - bot.db.data['youtube'] = {} - - settings = bot.db.data['youtube'].get(guild_id, { - 'enabled': False, - 'channel_id': None, - 'last_video_id': None - }) - - youtube_channel_id = os.getenv('YOUTUBE_CHANNEL_ID') - - embed = discord.Embed( - title='šŸ“ŗ YouTube Notification Status', - color=0xFF0000 - ) - - status = '🟢 Enabled' if settings.get('enabled') else 'šŸ”“ Disabled' - embed.add_field(name='Status', value=status, inline=True) - - if settings.get('channel_id'): - channel = interaction.guild.get_channel(int(settings['channel_id'])) - channel_name = channel.mention if channel else 'Channel not found' - embed.add_field(name='Notification Channel', value=channel_name, inline=True) - else: - embed.add_field(name='Notification Channel', value='Not set', inline=True) - - if youtube_channel_id: - embed.add_field(name='YouTube Channel ID', value=f'`{youtube_channel_id}`', inline=False) - else: - embed.add_field(name='āš ļø Warning', value='`YOUTUBE_CHANNEL_ID` not set in environment variables', inline=False) - - if settings.get('last_video_id'): - embed.add_field(name='Last Video ID', value=f'`{settings["last_video_id"]}`', inline=False) - - embed.set_footer(text='Use /setupyoutube to configure') - - await interaction.response.send_message(embed=embed) - -# Run bot if __name__ == '__main__': token = os.getenv('DISCORD_TOKEN') if not token: logger.error('āŒ DISCORD_TOKEN not set!') exit(1) - logger.info('šŸš€ Starting bot...') - bot.run(token) \ No newline at end of file + logger.info("everythings working") + logger.info('hello from chersbobers and booly co :3') + bot.run(token) diff --git a/cogs/economy.py b/cogs/economy.py new file mode 100644 index 0000000..425e13a --- /dev/null +++ b/cogs/economy.py @@ -0,0 +1,69 @@ +import discord +from discord import app_commands +from discord.ext import commands +import random +from datetime import datetime + +class Economy(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @app_commands.command(name='balance', description='Check your coin balance') + async def balance(self, interaction: discord.Interaction, member: discord.Member = None): + target = member or interaction.user + user_data = self.bot.db.get_user(str(interaction.guild.id), str(target.id)) + + embed = discord.Embed( + title='šŸ’° Balance', + description=f'{target.mention} has **{user_data["coins"]:,}** coins in wallet and **{user_data["bank"]:,}** in bank!\n**Total:** {user_data["coins"] + user_data["bank"]:,} coins', + color=0xFFD700 + ) + await interaction.response.send_message(embed=embed) + + @app_commands.command(name='daily', description='Claim your daily reward') + async def daily(self, interaction: discord.Interaction): + user_data = self.bot.db.get_user(str(interaction.guild.id), str(interaction.user.id)) + now = datetime.now().timestamp() + + if now - user_data['last_daily'] < 86400: + time_left = 86400 - (now - user_data['last_daily']) + hours = int(time_left / 3600) + await interaction.response.send_message(f'ā³ You already claimed your daily! Come back in {hours} hours.', ephemeral=True) + return + + reward = 100 + user_data['coins'] += reward + user_data['last_daily'] = now + self.bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data) + + await interaction.response.send_message(f'āœ… You claimed your daily reward of **{reward:,}** coins!\nšŸ’° New balance: **{user_data["coins"]:,}** coins') + + @app_commands.command(name='work', description='Work to earn coins') + async def work(self, interaction: discord.Interaction): + user_data = self.bot.db.get_user(str(interaction.guild.id), str(interaction.user.id)) + now = datetime.now().timestamp() + + if now - user_data['last_work'] < 3600: + time_left = 3600 - (now - user_data['last_work']) + minutes = int(time_left / 60) + await interaction.response.send_message(f'ā³ You need to rest! Come back in {minutes} minutes.', ephemeral=True) + return + + earnings = random.randint(10, 50) + user_data['coins'] += earnings + user_data['last_work'] = now + self.bot.db.set_user(str(interaction.guild.id), str(interaction.user.id), user_data) + + jobs = [ + 'You worked as a programmer and earned', + 'You delivered pizza and earned', + 'You streamed on Twitch and earned', + 'You mowed lawns and earned', + 'You washed cars and earned', + 'You tutored students and earned' + ] + + await interaction.response.send_message(f'šŸ’¼ {random.choice(jobs)} **{earnings:,}** coins!\nšŸ’° New balance: **{user_data["coins"]:,}** coins') + +async def setup(bot): + await bot.add_cog(Economy(bot)) \ No newline at end of file diff --git a/cogs/fun.py b/cogs/fun.py new file mode 100644 index 0000000..bbcef76 --- /dev/null +++ b/cogs/fun.py @@ -0,0 +1,63 @@ +import discord +from discord import app_commands +from discord.ext import commands +import random +import aiohttp + +class Fun(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @app_commands.command(name='8ball', description='Ask the magic 8ball a question') + async def eightball(self, interaction: discord.Interaction, question: str): + responses = [ + 'Yes, definitely!', 'It is certain.', 'Without a doubt.', 'You may rely on it.', + 'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Signs point to yes.', + 'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.', + 'Cannot predict now.', "Don't count on it.", 'My reply is no.', 'Very doubtful.' + ] + await interaction.response.send_message(f'šŸ”® **{question}**\n{random.choice(responses)}') + + @app_commands.command(name='roll', description='Roll a dice') + async def roll(self, interaction: discord.Interaction, sides: int = 6): + if sides < 2 or sides > 100: + await interaction.response.send_message('āŒ Dice must have between 2 and 100 sides!', ephemeral=True) + return + result = random.randint(1, sides) + await interaction.response.send_message(f'šŸŽ² You rolled a **{result}** (1-{sides})') + + @app_commands.command(name='flip', description='Flip a coin') + async def flip(self, interaction: discord.Interaction): + result = random.choice(['Heads', 'Tails']) + await interaction.response.send_message(f'šŸŖ™ The coin landed on **{result}**!') + + @app_commands.command(name='cat', description='Get a random cat picture') + async def cat(self, interaction: discord.Interaction): + await interaction.response.defer() + async with aiohttp.ClientSession() as session: + try: + async with session.get('https://api.thecatapi.com/v1/images/search') as resp: + data = await resp.json() + embed = discord.Embed(title='🐱 Random Kitty!', color=0xFF69B4) + embed.set_image(url=data[0]['url']) + embed.set_footer(text=f'Requested by {interaction.user.name}') + await interaction.followup.send(embed=embed) + except: + await interaction.followup.send('Failed to fetch a cat picture 😿') + + @app_commands.command(name='dog', description='Get a random dog picture') + async def dog(self, interaction: discord.Interaction): + await interaction.response.defer() + async with aiohttp.ClientSession() as session: + try: + async with session.get('https://api.thedogapi.com/v1/images/search') as resp: + data = await resp.json() + embed = discord.Embed(title='🐶 Random Doggy!', color=0xFF69B4) + embed.set_image(url=data[0]['url']) + embed.set_footer(text=f'Requested by {interaction.user.name}') + await interaction.followup.send(embed=embed) + except: + await interaction.followup.send('Failed to fetch a dog picture 😄') + +async def setup(bot): + await bot.add_cog(Fun(bot)) \ No newline at end of file diff --git a/cogs/leveling.py b/cogs/leveling.py new file mode 100644 index 0000000..d4ca777 --- /dev/null +++ b/cogs/leveling.py @@ -0,0 +1,94 @@ +import discord +from discord import app_commands +from discord.ext import commands +import random +from datetime import datetime + +class Leveling(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.Cog.listener() + async def on_message(self, message): + if message.author.bot or not message.guild: + return + + user_data = self.bot.db.get_user(str(message.guild.id), str(message.author.id)) + now = datetime.now().timestamp() + + if now - user_data['last_message'] >= self.bot.config['xp_cooldown']: + user_data['last_message'] = now + xp_gain = random.randint(self.bot.config['xp_min'], self.bot.config['xp_max']) + user_data['xp'] += xp_gain + xp_needed = user_data['level'] * self.bot.config['xp_per_level'] + + if user_data['xp'] >= xp_needed: + user_data['level'] += 1 + user_data['xp'] = 0 + + coin_reward = user_data['level'] * self.bot.config['level_up_multiplier'] + user_data['coins'] += coin_reward + + messages = [ + f'šŸŽ‰ GG {message.author.mention}! You leveled up to **Level {user_data["level"]}**!', + f'⭐ Congrats {message.author.mention}! You\'re now **Level {user_data["level"]}**!', + f'šŸš€ Level up! {message.author.mention} reached **Level {user_data["level"]}**!' + ] + + await message.channel.send( + f'{random.choice(messages)} You earned **{coin_reward:,} coins**! šŸ’°' + ) + + self.bot.db.set_user(str(message.guild.id), str(message.author.id), user_data) + + @app_commands.command(name='rank', description='View your rank and level') + async def rank(self, interaction: discord.Interaction, member: discord.Member = None): + target = member or interaction.user + user_data = self.bot.db.get_user(str(interaction.guild.id), str(target.id)) + xp_needed = user_data['level'] * self.bot.config['xp_per_level'] + + all_users = self.bot.db.get_all_guild_users(str(interaction.guild.id)) + rank = next((i + 1 for i, u in enumerate(all_users) if u['user_id'] == str(target.id)), 'Unranked') + + progress = int((user_data['xp'] / xp_needed) * 20) if xp_needed > 0 else 0 + bar = 'ā–ˆ' * progress + 'ā–‘' * (20 - progress) + + color = 0xFF6B6B if user_data['level'] >= 50 else 0xFFD93D if user_data['level'] >= 30 else 0x6BCB77 if user_data['level'] >= 15 else 0x4D96FF + + embed = discord.Embed(color=color) + embed.set_author(name=f"{target.display_name}'s Profile", icon_url=target.display_avatar.url) + embed.description = f""" +**RANK** • #{rank} / {len(all_users)} +**LEVEL** • {user_data['level']} +**XP** • {user_data['xp']:,} / {xp_needed:,} + +`{bar}` + +**šŸ’° BALANCE** • {user_data['coins']:,} coins + """ + embed.set_thumbnail(url=target.display_avatar.url) + await interaction.response.send_message(embed=embed) + + @app_commands.command(name='leaderboard', description='View the server leaderboard') + async def leaderboard(self, interaction: discord.Interaction): + all_users = self.bot.db.get_all_guild_users(str(interaction.guild.id))[:10] + + description = [] + for i, u in enumerate(all_users): + medal = 'šŸ„‡' if i == 0 else '🄈' if i == 1 else 'šŸ„‰' if i == 2 else f'**{i+1}.**' + description.append( + f'{medal} <@{u["user_id"]}>\n' + f'ā”” Level {u["data"]["level"]} ({u["data"]["xp"]:,} XP) • {u["data"]["coins"]:,} coins' + ) + + embed = discord.Embed( + title='šŸ† Server Leaderboard', + description='\n'.join(description) if description else 'No users yet!', + color=0x9B59B6, + timestamp=datetime.utcnow() + ) + embed.set_footer(text='Top 10 users by level and XP') + await interaction.response.send_message(embed=embed) + +async def setup(bot): + await bot.add_cog(Leveling(bot)) \ No newline at end of file diff --git a/cogs/system.py b/cogs/system.py new file mode 100644 index 0000000..cc46f42 --- /dev/null +++ b/cogs/system.py @@ -0,0 +1,600 @@ +import discord +from discord import app_commands +from discord.ext import commands, tasks +import asyncio +from datetime import datetime, timedelta +import logging +import feedparser + +logger = logging.getLogger('bot') + +class System(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.check_youtube.start() + + def cog_unload(self): + self.check_youtube.cancel() + + @tasks.loop(seconds=300) + async def check_youtube(self): + if 'youtube' not in self.bot.db.data: + return + + for guild_id, settings in self.bot.db.data['youtube'].items(): + if not settings.get('enabled') or not settings.get('channel_id') or not settings.get('youtube_channel_id'): + continue + + try: + feed_url = f'https://www.youtube.com/feeds/videos.xml?channel_id={settings["youtube_channel_id"]}' + feed = await asyncio.to_thread(feedparser.parse, feed_url) + + if not feed.entries: + continue + + latest = feed.entries[0] + video_id = latest.yt_videoid if hasattr(latest, 'yt_videoid') else latest.id.split(':')[-1] + + if video_id != settings.get('last_video_id') and settings.get('last_video_id'): + guild = self.bot.get_guild(int(guild_id)) + if not guild: + continue + + channel = guild.get_channel(int(settings['channel_id'])) + if not channel: + continue + + embed = discord.Embed( + title='šŸŽ¬ New YouTube Video!', + description=f'**{latest.title}**', + url=latest.link, + color=0xFF0000, + timestamp=datetime.utcnow() + ) + + if hasattr(latest, 'media_thumbnail') and latest.media_thumbnail: + embed.set_thumbnail(url=latest.media_thumbnail[0]['url']) + + embed.add_field(name='Channel', value=latest.author, inline=True) + + if hasattr(latest, 'published'): + embed.add_field(name='Published', value=latest.published, inline=True) + + await channel.send('šŸ“ŗ New video alert! @everyone', embed=embed) + logger.info(f'šŸ“ŗ Sent notification for: {latest.title}') + + settings['last_video_id'] = video_id + self.bot.db.save() + + except Exception as e: + logger.error(f'āŒ Error checking YouTube: {e}') + + @check_youtube.before_loop + async def before_check_youtube(self): + await self.bot.wait_until_ready() + + @app_commands.command(name='ping', description='Check bot latency') + async def ping(self, interaction: discord.Interaction): + latency = round(self.bot.latency * 1000) + await interaction.response.send_message(f'šŸ“ Pong! Latency: `{latency}ms`') + + @app_commands.command(name='serverinfo', description='Display server information') + async def serverinfo(self, interaction: discord.Interaction): + guild = interaction.guild + + embed = discord.Embed( + title=f'šŸ“Š {guild.name}', + color=0x5865F2, + timestamp=datetime.utcnow() + ) + + if guild.icon: + embed.set_thumbnail(url=guild.icon.url) + + embed.add_field(name='šŸ‘‘ Owner', value=guild.owner.mention, inline=True) + embed.add_field(name='šŸ‘„ Members', value=guild.member_count, inline=True) + embed.add_field(name='šŸ“… Created', value=guild.created_at.strftime('%Y-%m-%d'), inline=True) + embed.add_field(name='šŸ’¬ Channels', value=len(guild.channels), inline=True) + embed.add_field(name='šŸ˜€ Emojis', value=len(guild.emojis), inline=True) + embed.add_field(name='šŸŽ­ Roles', value=len(guild.roles), inline=True) + + await interaction.response.send_message(embed=embed) + + @app_commands.command(name='userinfo', description='Display user information') + async def userinfo(self, interaction: discord.Interaction, member: discord.Member = None): + target = member or interaction.user + + embed = discord.Embed( + title=f'šŸ‘¤ {target.name}', + color=target.color if target.color != discord.Color.default() else 0x5865F2, + timestamp=datetime.utcnow() + ) + + embed.set_thumbnail(url=target.display_avatar.url) + + embed.add_field(name='šŸ†” ID', value=target.id, inline=True) + embed.add_field(name='šŸ“› Nickname', value=target.nick or 'None', inline=True) + embed.add_field(name='šŸ¤– Bot', value='Yes' if target.bot else 'No', inline=True) + embed.add_field(name='šŸ“… Joined Server', value=target.joined_at.strftime('%Y-%m-%d %H:%M') if target.joined_at else 'Unknown', inline=True) + embed.add_field(name='šŸ“… Account Created', value=target.created_at.strftime('%Y-%m-%d %H:%M'), inline=True) + + roles = [role.mention for role in target.roles if role.name != '@everyone'] + embed.add_field(name=f'šŸŽ­ Roles ({len(roles)})', value=' '.join(roles) if roles else 'None', inline=False) + + await interaction.response.send_message(embed=embed) + + @app_commands.command(name='kick', description='[MOD] Kick a member from the server') + @app_commands.describe(member='Member to kick', reason='Reason for kick') + @app_commands.default_permissions(kick_members=True) + async def kick(self, interaction: discord.Interaction, member: discord.Member, reason: str = 'No reason provided'): + if member.top_role >= interaction.user.top_role and interaction.user != interaction.guild.owner: + await interaction.response.send_message('āŒ You cannot kick this member!', ephemeral=True) + return + + if member == interaction.guild.owner: + await interaction.response.send_message('āŒ You cannot kick the server owner!', ephemeral=True) + return + + try: + await member.kick(reason=f'{reason} | Kicked by {interaction.user}') + + embed = discord.Embed( + title='šŸ‘¢ Member Kicked', + description=f'{member.mention} has been kicked from the server', + color=0xFF9500, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + embed.add_field(name='Reason', value=reason, inline=False) + + await interaction.response.send_message(embed=embed) + logger.info(f'šŸ‘¢ {member} kicked by {interaction.user} - Reason: {reason}') + except discord.Forbidden: + await interaction.response.send_message('āŒ I do not have permission to kick this member!', ephemeral=True) + except Exception as e: + await interaction.response.send_message(f'āŒ An error occurred: {e}', ephemeral=True) + + @app_commands.command(name='ban', description='[MOD] Ban a member from the server') + @app_commands.describe(member='Member to ban', reason='Reason for ban', delete_days='Days of messages to delete (0-7)') + @app_commands.default_permissions(ban_members=True) + async def ban(self, interaction: discord.Interaction, member: discord.Member, reason: str = 'No reason provided', delete_days: int = 0): + if member.top_role >= interaction.user.top_role and interaction.user != interaction.guild.owner: + await interaction.response.send_message('āŒ You cannot ban this member!', ephemeral=True) + return + + if member == interaction.guild.owner: + await interaction.response.send_message('āŒ You cannot ban the server owner!', ephemeral=True) + return + + if delete_days < 0 or delete_days > 7: + await interaction.response.send_message('āŒ Delete days must be between 0 and 7!', ephemeral=True) + return + + try: + await member.ban(reason=f'{reason} | Banned by {interaction.user}', delete_message_days=delete_days) + + embed = discord.Embed( + title='šŸ”Ø Member Banned', + description=f'{member.mention} has been banned from the server', + color=0xFF0000, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + embed.add_field(name='Reason', value=reason, inline=False) + + await interaction.response.send_message(embed=embed) + logger.info(f'šŸ”Ø {member} banned by {interaction.user} - Reason: {reason}') + except discord.Forbidden: + await interaction.response.send_message('āŒ I do not have permission to ban this member!', ephemeral=True) + except Exception as e: + await interaction.response.send_message(f'āŒ An error occurred: {e}', ephemeral=True) + + @app_commands.command(name='unban', description='[MOD] Unban a user from the server') + @app_commands.describe(user_id='User ID to unban', reason='Reason for unban') + @app_commands.default_permissions(ban_members=True) + async def unban(self, interaction: discord.Interaction, user_id: str, reason: str = 'No reason provided'): + try: + user = await self.bot.fetch_user(int(user_id)) + except: + await interaction.response.send_message('āŒ Invalid user ID!', ephemeral=True) + return + + try: + await interaction.guild.unban(user, reason=f'{reason} | Unbanned by {interaction.user}') + + embed = discord.Embed( + title='āœ… User Unbanned', + description=f'{user.mention} has been unbanned from the server', + color=0x00FF00, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + embed.add_field(name='Reason', value=reason, inline=False) + + await interaction.response.send_message(embed=embed) + logger.info(f'āœ… {user} unbanned by {interaction.user} - Reason: {reason}') + except discord.NotFound: + await interaction.response.send_message('āŒ This user is not banned!', ephemeral=True) + except discord.Forbidden: + await interaction.response.send_message('āŒ I do not have permission to unban users!', ephemeral=True) + except Exception as e: + await interaction.response.send_message(f'āŒ An error occurred: {e}', ephemeral=True) + + @app_commands.command(name='timeout', description='[MOD] Timeout a member') + @app_commands.describe(member='Member to timeout', duration='Duration in minutes', reason='Reason for timeout') + @app_commands.default_permissions(moderate_members=True) + async def timeout(self, interaction: discord.Interaction, member: discord.Member, duration: int, reason: str = 'No reason provided'): + if member.top_role >= interaction.user.top_role and interaction.user != interaction.guild.owner: + await interaction.response.send_message('āŒ You cannot timeout this member!', ephemeral=True) + return + + if member == interaction.guild.owner: + await interaction.response.send_message('āŒ You cannot timeout the server owner!', ephemeral=True) + return + + if duration < 1 or duration > 40320: + await interaction.response.send_message('āŒ Duration must be between 1 minute and 28 days!', ephemeral=True) + return + + try: + timeout_until = datetime.utcnow() + timedelta(minutes=duration) + await member.timeout(timeout_until, reason=f'{reason} | Timed out by {interaction.user}') + + embed = discord.Embed( + title='ā° Member Timed Out', + description=f'{member.mention} has been timed out', + color=0xFFA500, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + embed.add_field(name='Duration', value=f'{duration} minutes', inline=True) + embed.add_field(name='Reason', value=reason, inline=False) + + await interaction.response.send_message(embed=embed) + logger.info(f'ā° {member} timed out by {interaction.user} for {duration}m - Reason: {reason}') + except discord.Forbidden: + await interaction.response.send_message('āŒ I do not have permission to timeout this member!', ephemeral=True) + except Exception as e: + await interaction.response.send_message(f'āŒ An error occurred: {e}', ephemeral=True) + + @app_commands.command(name='warn', description='[MOD] Warn a member') + @app_commands.describe(member='Member to warn', reason='Reason for warning') + @app_commands.default_permissions(moderate_members=True) + async def warn(self, interaction: discord.Interaction, member: discord.Member, reason: str): + guild_id = str(interaction.guild.id) + user_id = str(member.id) + + if 'warnings' not in self.bot.db.data: + self.bot.db.data['warnings'] = {} + + if guild_id not in self.bot.db.data['warnings']: + self.bot.db.data['warnings'][guild_id] = {} + + if user_id not in self.bot.db.data['warnings'][guild_id]: + self.bot.db.data['warnings'][guild_id][user_id] = [] + + warning = { + 'reason': reason, + 'moderator': str(interaction.user.id), + 'timestamp': datetime.utcnow().isoformat() + } + + self.bot.db.data['warnings'][guild_id][user_id].append(warning) + self.bot.db.save() + + warning_count = len(self.bot.db.data['warnings'][guild_id][user_id]) + + embed = discord.Embed( + title='āš ļø Member Warned', + description=f'{member.mention} has been warned', + color=0xFFFF00, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + embed.add_field(name='Total Warnings', value=warning_count, inline=True) + embed.add_field(name='Reason', value=reason, inline=False) + + await interaction.response.send_message(embed=embed) + + try: + await member.send(f'āš ļø You have been warned in **{interaction.guild.name}**\n**Reason:** {reason}\n**Total Warnings:** {warning_count}') + except: + pass + + logger.info(f'āš ļø {member} warned by {interaction.user} - Reason: {reason}') + + @app_commands.command(name='warnings', description='View warnings for a member') + @app_commands.describe(member='Member to check warnings for') + async def warnings(self, interaction: discord.Interaction, member: discord.Member = None): + target = member or interaction.user + guild_id = str(interaction.guild.id) + user_id = str(target.id) + + if 'warnings' not in self.bot.db.data: + self.bot.db.data['warnings'] = {} + + user_warnings = self.bot.db.data['warnings'].get(guild_id, {}).get(user_id, []) + + if not user_warnings: + await interaction.response.send_message(f'{target.mention} has no warnings!', ephemeral=True) + return + + embed = discord.Embed( + title=f'āš ļø Warnings for {target.display_name}', + color=0xFFFF00, + timestamp=datetime.utcnow() + ) + + for i, warning in enumerate(user_warnings[-10:], 1): + moderator = interaction.guild.get_member(int(warning['moderator'])) + mod_name = moderator.mention if moderator else f"<@{warning['moderator']}>" + timestamp = datetime.fromisoformat(warning['timestamp']).strftime('%Y-%m-%d %H:%M') + + embed.add_field( + name=f'Warning #{i}', + value=f'**Moderator:** {mod_name}\n**Reason:** {warning["reason"]}\n**Date:** {timestamp}', + inline=False + ) + + embed.set_footer(text=f'Total warnings: {len(user_warnings)} | Showing last 10') + + await interaction.response.send_message(embed=embed) + + @app_commands.command(name='clearwarnings', description='[MOD] Clear warnings for a member') + @app_commands.describe(member='Member to clear warnings for') + @app_commands.default_permissions(moderate_members=True) + async def clearwarnings(self, interaction: discord.Interaction, member: discord.Member): + guild_id = str(interaction.guild.id) + user_id = str(member.id) + + if 'warnings' not in self.bot.db.data: + self.bot.db.data['warnings'] = {} + + if guild_id not in self.bot.db.data['warnings'] or user_id not in self.bot.db.data['warnings'][guild_id]: + await interaction.response.send_message(f'{member.mention} has no warnings to clear!', ephemeral=True) + return + + warning_count = len(self.bot.db.data['warnings'][guild_id][user_id]) + self.bot.db.data['warnings'][guild_id][user_id] = [] + self.bot.db.save() + + embed = discord.Embed( + title='āœ… Warnings Cleared', + description=f'Cleared {warning_count} warning(s) for {member.mention}', + color=0x00FF00, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + + await interaction.response.send_message(embed=embed) + logger.info(f'āœ… Cleared {warning_count} warnings for {member} by {interaction.user}') + + @app_commands.command(name='purge', description='[MOD] Delete multiple messages') + @app_commands.describe(amount='Number of messages to delete (1-100)', member='Only delete messages from this member (optional)') + @app_commands.default_permissions(manage_messages=True) + async def purge(self, interaction: discord.Interaction, amount: int, member: discord.Member = None): + if amount < 1 or amount > 100: + await interaction.response.send_message('āŒ Amount must be between 1 and 100!', ephemeral=True) + return + + await interaction.response.defer(ephemeral=True) + + try: + if member: + deleted = await interaction.channel.purge(limit=amount, check=lambda m: m.author == member) + else: + deleted = await interaction.channel.purge(limit=amount) + + await interaction.followup.send(f'āœ… Deleted {len(deleted)} message(s)!', ephemeral=True) + logger.info(f'šŸ—‘ļø {interaction.user} purged {len(deleted)} messages in #{interaction.channel.name}') + except discord.Forbidden: + await interaction.followup.send('āŒ I do not have permission to delete messages!', ephemeral=True) + except Exception as e: + await interaction.followup.send(f'āŒ An error occurred: {e}', ephemeral=True) + + @app_commands.command(name='lock', description='[MOD] Lock a channel') + @app_commands.describe(channel='Channel to lock (defaults to current channel)') + @app_commands.default_permissions(manage_channels=True) + async def lock(self, interaction: discord.Interaction, channel: discord.TextChannel = None): + target_channel = channel or interaction.channel + + try: + await target_channel.set_permissions(interaction.guild.default_role, send_messages=False) + + embed = discord.Embed( + title='šŸ”’ Channel Locked', + description=f'{target_channel.mention} has been locked', + color=0xFF0000, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + + await interaction.response.send_message(embed=embed) + logger.info(f'šŸ”’ {target_channel.name} locked by {interaction.user}') + except discord.Forbidden: + await interaction.response.send_message('āŒ I do not have permission to lock this channel!', ephemeral=True) + except Exception as e: + await interaction.response.send_message(f'āŒ An error occurred: {e}', ephemeral=True) + + @app_commands.command(name='unlock', description='[MOD] Unlock a channel') + @app_commands.describe(channel='Channel to unlock (defaults to current channel)') + @app_commands.default_permissions(manage_channels=True) + async def unlock(self, interaction: discord.Interaction, channel: discord.TextChannel = None): + target_channel = channel or interaction.channel + + try: + await target_channel.set_permissions(interaction.guild.default_role, send_messages=None) + + embed = discord.Embed( + title='šŸ”“ Channel Unlocked', + description=f'{target_channel.mention} has been unlocked', + color=0x00FF00, + timestamp=datetime.utcnow() + ) + embed.add_field(name='Moderator', value=interaction.user.mention, inline=True) + + await interaction.response.send_message(embed=embed) + logger.info(f'šŸ”“ {target_channel.name} unlocked by {interaction.user}') + except discord.Forbidden: + await interaction.response.send_message('āŒ I do not have permission to unlock this channel!', ephemeral=True) + except Exception as e: + await interaction.response.send_message(f'āŒ An error occurred: {e}', ephemeral=True) + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload): + if payload.user_id == self.bot.user.id: + return + + guild_id = str(payload.guild_id) + message_id = str(payload.message_id) + + if 'reaction_roles' not in self.bot.db.data: + self.bot.db.data['reaction_roles'] = {} + + if guild_id not in self.bot.db.data['reaction_roles']: + return + + if message_id not in self.bot.db.data['reaction_roles'][guild_id]: + return + + emoji_str = str(payload.emoji) + role_id = self.bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str) + + if not role_id: + return + + guild = self.bot.get_guild(payload.guild_id) + if not guild: + return + + role = guild.get_role(int(role_id)) + if not role: + return + + member = guild.get_member(payload.user_id) + if not member: + return + + try: + await member.add_roles(role) + logger.info(f'āœ… Added role {role.name} to {member.name}') + except Exception as e: + logger.error(f'āŒ Error adding role: {e}') + + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload): + if payload.user_id == self.bot.user.id: + return + + guild_id = str(payload.guild_id) + message_id = str(payload.message_id) + + if 'reaction_roles' not in self.bot.db.data: + return + + if guild_id not in self.bot.db.data.get('reaction_roles', {}): + return + + if message_id not in self.bot.db.data['reaction_roles'][guild_id]: + return + + emoji_str = str(payload.emoji) + role_id = self.bot.db.data['reaction_roles'][guild_id][message_id].get(emoji_str) + + if not role_id: + return + + guild = self.bot.get_guild(payload.guild_id) + if not guild: + return + + role = guild.get_role(int(role_id)) + if not role: + return + + member = guild.get_member(payload.user_id) + if not member: + return + + try: + await member.remove_roles(role) + logger.info(f'āž– Removed role {role.name} from {member.name}') + except Exception as e: + logger.error(f'āŒ Error removing role: {e}') + + @app_commands.command(name='reactionrole', description='[ADMIN] Create a reaction role') + @app_commands.describe(message_id='Message ID to add reactions to', emoji='Emoji to use (e.g., šŸŽ®)', role='Role to assign') + @app_commands.default_permissions(administrator=True) + async def reactionrole(self, interaction: discord.Interaction, message_id: str, emoji: str, role: discord.Role): + try: + message = await interaction.channel.fetch_message(int(message_id)) + except discord.NotFound: + await interaction.response.send_message('āŒ Message not found in this channel!', ephemeral=True) + return + except ValueError: + await interaction.response.send_message('āŒ Invalid message ID!', ephemeral=True) + return + + try: + await message.add_reaction(emoji) + except discord.HTTPException: + await interaction.response.send_message('āŒ Invalid emoji or unable to add reaction!', ephemeral=True) + return + + guild_id = str(interaction.guild.id) + + if 'reaction_roles' not in self.bot.db.data: + self.bot.db.data['reaction_roles'] = {} + + if guild_id not in self.bot.db.data['reaction_roles']: + self.bot.db.data['reaction_roles'][guild_id] = {} + + if message_id not in self.bot.db.data['reaction_roles'][guild_id]: + self.bot.db.data['reaction_roles'][guild_id][message_id] = {} + + self.bot.db.data['reaction_roles'][guild_id][message_id][emoji] = str(role.id) + self.bot.db.save() + + embed = discord.Embed( + title='āœ… Reaction Role Created', + description=f'React with {emoji} on the message to get {role.mention}', + color=0x00FF00 + ) + embed.add_field(name='Message ID', value=message_id, inline=True) + embed.add_field(name='Emoji', value=emoji, inline=True) + embed.add_field(name='Role', value=role.mention, inline=True) + + await interaction.response.send_message(embed=embed) + logger.info(f'āœ… Created reaction role: {emoji} -> {role.name}') + + @app_commands.command(name='removereactionrole', description='[ADMIN] Remove a reaction role') + @app_commands.describe(message_id='Message ID', emoji='Emoji to remove (leave empty to remove all)') + @app_commands.default_permissions(administrator=True) + async def removereactionrole(self, interaction: discord.Interaction, message_id: str, emoji: str = None): + guild_id = str(interaction.guild.id) + + if 'reaction_roles' not in self.bot.db.data: + self.bot.db.data['reaction_roles'] = {} + + if guild_id not in self.bot.db.data['reaction_roles'] or message_id not in self.bot.db.data['reaction_roles'].get(guild_id, {}): + await interaction.response.send_message('āŒ No reaction roles found for that message!', ephemeral=True) + return + + if emoji: + if emoji not in self.bot.db.data['reaction_roles'][guild_id][message_id]: + await interaction.response.send_message('āŒ That emoji is not set up for reaction roles!', ephemeral=True) + return + + del self.bot.db.data['reaction_roles'][guild_id][message_id][emoji] + self.bot.db.save() + await interaction.response.send_message(f'āœ… Removed reaction role for {emoji}') + else: + del self.bot.db.data['reaction_roles'][guild_id][message_id] + self.bot.db.save() + await interaction.response.send_message(f'āœ… Removed all reaction roles from message {message_id}') + + @app_commands.command(name='listreactionroles', description='List all reaction roles') + async def listreactionroles(self, interaction: discord.Interaction): + guild_id = str(interaction.guild.id) + + if 'reaction \ No newline at end of file