|
| 1 | +import discord |
| 2 | +from discord.ext import commands |
| 3 | +import aiosqlite |
| 4 | +from utils.helpers import EmbedBuilder |
| 5 | + |
| 6 | +def is_staff(): |
| 7 | + async def predicate(ctx): |
| 8 | + if ctx.author.id == ctx.bot.config.owner_id: |
| 9 | + return True |
| 10 | + return ctx.author.guild_permissions.view_audit_log |
| 11 | + return commands.check(predicate) |
| 12 | + |
| 13 | +class Chowkidar(commands.Cog): |
| 14 | + def __init__(self, bot): |
| 15 | + self.bot = bot |
| 16 | + self.watched_users = set() |
| 17 | + self.log_channel_id = None |
| 18 | + |
| 19 | + async def cog_load(self): |
| 20 | + async with aiosqlite.connect("botdata.db") as db: |
| 21 | + await db.execute("CREATE TABLE IF NOT EXISTS chowkidar_config (guild_id INTEGER PRIMARY KEY, channel_id INTEGER)") |
| 22 | + await db.execute("CREATE TABLE IF NOT EXISTS chowkidar_tracked (user_id INTEGER PRIMARY KEY)") |
| 23 | + await db.commit() |
| 24 | + |
| 25 | + async with db.execute("SELECT channel_id FROM chowkidar_config LIMIT 1") as cursor: |
| 26 | + row = await cursor.fetchone() |
| 27 | + if row: |
| 28 | + self.log_channel_id = row[0] |
| 29 | + |
| 30 | + async with db.execute("SELECT user_id FROM chowkidar_tracked") as cursor: |
| 31 | + rows = await cursor.fetchall() |
| 32 | + self.watched_users = {row[0] for row in rows} |
| 33 | + |
| 34 | + async def send_log(self, embed: discord.Embed): |
| 35 | + if not self.log_channel_id: |
| 36 | + return |
| 37 | + channel = self.bot.get_channel(self.log_channel_id) |
| 38 | + if channel: |
| 39 | + await channel.send(embed=embed) |
| 40 | + |
| 41 | + @commands.hybrid_command(name="setwlchannel", description="Set the current channel as the watchlog channel.") |
| 42 | + @is_staff() |
| 43 | + async def setwlchannel(self, ctx): |
| 44 | + if not isinstance(ctx.channel, discord.TextChannel): |
| 45 | + await ctx.send(embed=EmbedBuilder.error_embed("Invalid Channel", "This command can only be used in a standard text channel.")) |
| 46 | + return |
| 47 | + |
| 48 | + self.log_channel_id = ctx.channel.id |
| 49 | + async with aiosqlite.connect("botdata.db") as db: |
| 50 | + await db.execute("INSERT OR REPLACE INTO chowkidar_config (guild_id, channel_id) VALUES (?, ?)", (ctx.guild.id, ctx.channel.id)) |
| 51 | + await db.commit() |
| 52 | + |
| 53 | + await ctx.send(embed=EmbedBuilder.success_embed("Channel Configured", f"Watchlog channel has been set to {ctx.channel.mention}.")) |
| 54 | + |
| 55 | + @commands.hybrid_command(name="chowkidar", description="Start tracking a user.") |
| 56 | + @is_staff() |
| 57 | + async def chowkidar(self, ctx, user: discord.Member): |
| 58 | + if user.id == self.bot.user.id: |
| 59 | + await ctx.send(embed=EmbedBuilder.error_embed("Invalid Target", "The bot cannot be tracked.")) |
| 60 | + return |
| 61 | + |
| 62 | + if user.guild_permissions.view_audit_log and ctx.author.id != ctx.guild.owner_id: |
| 63 | + await ctx.send(embed=EmbedBuilder.error_embed("Invalid Target", "You cannot track another staff member.")) |
| 64 | + return |
| 65 | + |
| 66 | + self.watched_users.add(user.id) |
| 67 | + async with aiosqlite.connect("botdata.db") as db: |
| 68 | + await db.execute("INSERT OR IGNORE INTO chowkidar_tracked (user_id) VALUES (?)", (user.id,)) |
| 69 | + await db.commit() |
| 70 | + |
| 71 | + await ctx.send(embed=EmbedBuilder.success_embed("Tracking Initiated", f"Now tracking actions for {user.mention}.")) |
| 72 | + |
| 73 | + @commands.hybrid_command(name="endwl", description="Stop tracking a user.") |
| 74 | + @is_staff() |
| 75 | + async def endwl(self, ctx, user: discord.Member): |
| 76 | + self.watched_users.discard(user.id) |
| 77 | + async with aiosqlite.connect("botdata.db") as db: |
| 78 | + await db.execute("DELETE FROM chowkidar_tracked WHERE user_id = ?", (user.id,)) |
| 79 | + await db.commit() |
| 80 | + |
| 81 | + await ctx.send(embed=EmbedBuilder.success_embed("Tracking Terminated", f"Stopped tracking {user.mention}.")) |
| 82 | + |
| 83 | + @commands.hybrid_command(name="purgewl", description="Delete all watchlogs for a specific user.") |
| 84 | + @is_staff() |
| 85 | + async def purgewl(self, ctx, user: discord.Member): |
| 86 | + if not self.log_channel_id: |
| 87 | + await ctx.send(embed=EmbedBuilder.error_embed("Configuration Error", "Watchlog channel is not set.")) |
| 88 | + return |
| 89 | + |
| 90 | + log_channel = self.bot.get_channel(self.log_channel_id) |
| 91 | + if not log_channel: |
| 92 | + await ctx.send(embed=EmbedBuilder.error_embed("Configuration Error", "Watchlog channel could not be found.")) |
| 93 | + return |
| 94 | + |
| 95 | + await ctx.defer() |
| 96 | + to_delete = [] |
| 97 | + |
| 98 | + async for msg in log_channel.history(limit=1000): |
| 99 | + if msg.author == self.bot.user and msg.embeds: |
| 100 | + embed = msg.embeds[0] |
| 101 | + if embed.footer and embed.footer.text and f"ID: {user.id}" in embed.footer.text: |
| 102 | + to_delete.append(msg) |
| 103 | + |
| 104 | + if to_delete: |
| 105 | + for i in range(0, len(to_delete), 100): |
| 106 | + await log_channel.delete_messages(to_delete[i:i+100]) |
| 107 | + |
| 108 | + await ctx.send(embed=EmbedBuilder.success_embed("Purge Complete", f"Deleted {len(to_delete)} log entries for {user.mention}.")) |
| 109 | + |
| 110 | + @commands.Cog.listener() |
| 111 | + async def on_message(self, message): |
| 112 | + if message.author.bot or message.author.id not in self.watched_users: |
| 113 | + return |
| 114 | + |
| 115 | + action = "Message Replied" if message.reference else "Message Sent" |
| 116 | + embed = discord.Embed(title=action, description=message.content, color=discord.Color.blue(), timestamp=message.created_at) |
| 117 | + embed.set_author(name=str(message.author), icon_url=message.author.display_avatar.url) |
| 118 | + embed.add_field(name="Channel", value=message.channel.mention) |
| 119 | + embed.add_field(name="Message ID", value=str(message.id)) |
| 120 | + embed.add_field(name="Message Link", value=f"[Jump to Message]({message.jump_url})", inline=False) |
| 121 | + embed.set_footer(text=f"User ID: {message.author.id}") |
| 122 | + |
| 123 | + await self.send_log(embed) |
| 124 | + |
| 125 | + @commands.Cog.listener() |
| 126 | + async def on_message_edit(self, before, after): |
| 127 | + if after.author.bot or after.author.id not in self.watched_users or before.content == after.content: |
| 128 | + return |
| 129 | + |
| 130 | + embed = discord.Embed(title="Message Edited", color=discord.Color.yellow(), timestamp=discord.utils.utcnow()) |
| 131 | + embed.set_author(name=str(after.author), icon_url=after.author.display_avatar.url) |
| 132 | + embed.add_field(name="Before", value=before.content or "None", inline=False) |
| 133 | + embed.add_field(name="After", value=after.content or "None", inline=False) |
| 134 | + embed.add_field(name="Channel", value=after.channel.mention) |
| 135 | + embed.add_field(name="Message ID", value=str(after.id)) |
| 136 | + embed.add_field(name="Message Link", value=f"[Jump to Message]({after.jump_url})", inline=False) |
| 137 | + embed.set_footer(text=f"User ID: {after.author.id}") |
| 138 | + |
| 139 | + await self.send_log(embed) |
| 140 | + |
| 141 | + @commands.Cog.listener() |
| 142 | + async def on_message_delete(self, message): |
| 143 | + if message.author.bot or message.author.id not in self.watched_users: |
| 144 | + return |
| 145 | + |
| 146 | + embed = discord.Embed(title="Message Deleted", description=message.content, color=discord.Color.red(), timestamp=discord.utils.utcnow()) |
| 147 | + embed.set_author(name=str(message.author), icon_url=message.author.display_avatar.url) |
| 148 | + embed.add_field(name="Channel", value=message.channel.mention) |
| 149 | + embed.add_field(name="Message ID", value=str(message.id)) |
| 150 | + embed.set_footer(text=f"User ID: {message.author.id}") |
| 151 | + |
| 152 | + await self.send_log(embed) |
| 153 | + |
| 154 | + @commands.Cog.listener() |
| 155 | + async def on_voice_state_update(self, member, before, after): |
| 156 | + if member.bot or member.id not in self.watched_users: |
| 157 | + return |
| 158 | + |
| 159 | + embed = discord.Embed(color=discord.Color.purple(), timestamp=discord.utils.utcnow()) |
| 160 | + embed.set_author(name=str(member), icon_url=member.display_avatar.url) |
| 161 | + embed.set_footer(text=f"User ID: {member.id}") |
| 162 | + |
| 163 | + if before.channel is None and after.channel is not None: |
| 164 | + embed.title = "Joined Voice Channel" |
| 165 | + embed.add_field(name="Channel", value=after.channel.mention) |
| 166 | + elif before.channel is not None and after.channel is None: |
| 167 | + embed.title = "Left Voice Channel" |
| 168 | + embed.add_field(name="Channel", value=before.channel.mention) |
| 169 | + elif before.channel != after.channel: |
| 170 | + embed.title = "Moved Voice Channel" |
| 171 | + embed.add_field(name="From", value=before.channel.mention) |
| 172 | + embed.add_field(name="To", value=after.channel.mention) |
| 173 | + else: |
| 174 | + return |
| 175 | + |
| 176 | + await self.send_log(embed) |
| 177 | + |
| 178 | + @commands.Cog.listener() |
| 179 | + async def on_raw_reaction_add(self, payload): |
| 180 | + if payload.user_id not in self.watched_users: |
| 181 | + return |
| 182 | + |
| 183 | + guild = self.bot.get_guild(payload.guild_id) |
| 184 | + if not guild: |
| 185 | + return |
| 186 | + |
| 187 | + member = guild.get_member(payload.user_id) |
| 188 | + if not member or member.bot: |
| 189 | + return |
| 190 | + |
| 191 | + channel = guild.get_channel(payload.channel_id) |
| 192 | + message_link = f"https://discord.com/channels/{payload.guild_id}/{payload.channel_id}/{payload.message_id}" |
| 193 | + |
| 194 | + embed = discord.Embed(title="Reaction Added", color=discord.Color.teal(), timestamp=discord.utils.utcnow()) |
| 195 | + embed.set_author(name=str(member), icon_url=member.display_avatar.url) |
| 196 | + embed.add_field(name="Emoji", value=str(payload.emoji)) |
| 197 | + embed.add_field(name="Channel", value=channel.mention if channel else str(payload.channel_id)) |
| 198 | + embed.add_field(name="Message ID", value=str(payload.message_id)) |
| 199 | + embed.add_field(name="Message Link", value=f"[Jump to Message]({message_link})", inline=False) |
| 200 | + embed.set_footer(text=f"User ID: {member.id}") |
| 201 | + |
| 202 | + await self.send_log(embed) |
| 203 | + |
| 204 | + @commands.Cog.listener() |
| 205 | + async def on_member_remove(self, member): |
| 206 | + if member.id not in self.watched_users: |
| 207 | + return |
| 208 | + |
| 209 | + embed = discord.Embed(title="Left Server", color=discord.Color.dark_grey(), timestamp=discord.utils.utcnow()) |
| 210 | + embed.set_author(name=str(member), icon_url=member.display_avatar.url) |
| 211 | + embed.set_footer(text=f"User ID: {member.id}") |
| 212 | + |
| 213 | + await self.send_log(embed) |
| 214 | + |
| 215 | + self.watched_users.discard(member.id) |
| 216 | + async with aiosqlite.connect("botdata.db") as db: |
| 217 | + await db.execute("DELETE FROM chowkidar_tracked WHERE user_id = ?", (member.id,)) |
| 218 | + await db.commit() |
| 219 | + |
| 220 | +async def setup(bot): |
| 221 | + await bot.add_cog(Chowkidar(bot)) |
| 222 | + |
0 commit comments