booly/cogs/system.py

600 lines
No EOL
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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