async def unban(self, ctx, user: UserConv, *, reason: str): """ Unbans the id from the guild with a reason. If guild has moderation logging enabled, it is logged """ try: embed = discord.Embed(description="Done! User Unbanned") embed.add_field(name="Reason", value=reason) mod = user_discrim(ctx.author) unbanned = user_discrim(user) clean_reason = escape_backticks(reason) content = f"{mod} unbanned {user.mention} ({unbanned}) with reason: `{clean_reason}`" await ctx.guild.unban(user, reason=f"{reason} - {mod}") await ctx.send(embed=embed) self.journal.send("member/unban", ctx.guild, content, icon="unban", user=user) except discord.errors.Forbidden: raise ManualCheckFailure( content="I don't have permission to unban this user")
async def member_join(self, member): logger.info( "Member %s (%d) joined '%s' (%d) %s", user_discrim(member), member.id, member.guild.name, member.guild.id, "(BOT)" if member.bot else "", ) if member.bot: # Since bots have to be manually invited by an admin, # let them handle all the roles and stuff. return welcome = self.bot.sql.welcome.get_welcome(member.guild) roles = self.bot.sql.settings.get_special_roles(member.guild) # Delay to let Discord API catch up # Without this, some users won't receive the guest role await asyncio.sleep(2) if welcome.welcome_message and welcome.channel: self.send_welcome_message(self.bot, member, welcome.welcome_message, welcome.channel) if roles.guest: logger.info("Adding role %s (%d) to new guest", roles.guest.name, roles.guest.id) await member.add_roles(roles.guest, reason="New user joined")
async def handle(self, _path, guild, _content, attributes): """ Handle the incoming leave event, and output a kick or ban journal event as appropriate. """ # We want to ignore two arguments # pylint: disable=arguments-differ leaver = attributes["member"] cause = attributes["cause"] logger.debug("Received member leave event: %s (%d)", leaver.name, leaver.id) if cause.type == MemberLeaveType.KICKED: action = "kicked" path = "member/kick" icon = "kick" elif cause.type == MemberLeaveType.BANNED: action = "banned" path = "member/ban" icon = "ban" else: # We don't care about this event! return # Get formatted reason if cause.reason: reason = f"with reason: `{escape_backticks(cause.reason)}`" else: reason = "" # Build journal event content mod = user_discrim(cause.cause) leaver_discrim = user_discrim(leaver) logger.info("Sending journal event %s for %s (%d)", path, leaver.name, leaver.id) content = f"{mod} {action} {leaver.mention} ({leaver_discrim}) {reason}" self.broadcaster.send(path, guild, content, icon=icon)
async def cleanup_text(self, ctx, text: str, count: int, channel: discord.TextChannel = None): """ Deletes the last <count> messages with the given text. """ await self.check_count(ctx, count) if channel is None: channel = ctx.channel # Deletes the messages with the text text = normalize_caseless(text) deleted = _Counter() def check(message): if deleted < count: if text in normalize_caseless(message.content): deleted.incr() return True return False messages = await channel.purge(limit=count * 2, check=check, before=ctx.message, bulk=True) # Send journal events text = escape_backticks(text) causer = user_discrim(ctx.author) content = f"{causer} deleted {len(messages)} messages in {channel.mention} matching `{text}`" self.journal.send( "text", ctx.guild, content, icon="delete", count=count, channel=channel, messags=messages, text=text, cause=ctx.author, ) obj, file = self.dump_messages(messages) content = f"Cleanup by {causer} in {channel.mention} of `{text}` deleted these messages:" self.dump.send("text", ctx.guild, content, icon="delete", messages=obj, file=file)
async def avatar(self, ctx, *, name: str = None): """ Displays the given user's avatar and its URL. If no argument is passed, the caller is checked instead. """ user = await self.get_user(ctx, name) logger.info("Displaying avatar on '%s' (%d)", user.name, user.id) embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.set_author(name=user_discrim(user)) embed.set_image(url=user.avatar_url) await ctx.send(content=user.avatar_url, embed=embed)
async def uinfo(self, ctx, *, name: str = None): """ Fetch information about a user, whether they are in the guild or not. If no argument is passed, the caller is checked instead. """ user = await self.get_user(ctx, name) usernames, nicknames = self.bot.sql.alias.get_alias_names( ctx.guild, user) logger.info("Running uinfo on '%s' (%d)", user.name, user.id) # Status content = StringBuilder() if getattr(user, "status", None): status = ("do not disturb" if user.status == discord.Status.dnd else user.status) content.writeln(f"{user.mention}, {status}") else: content.writeln(user.mention) embed = discord.Embed() embed.timestamp = user.created_at embed.set_author(name=user_discrim(user)) embed.set_thumbnail(url=user.avatar_url) # User colour if hasattr(user, "colour"): embed.colour = user.colour embed.add_field(name="ID", value=f"`{user.id}`") self.uinfo_add_roles(embed, user) self.uinfo_add_activity(embed, user, content) embed.description = str(content) content.clear() self.uinfo_add_voice(embed, user) self.uinfo_add_aliases(embed, content, usernames, nicknames) # Guild join date if hasattr(user, "joined_at"): embed.add_field(name="Member for", value=fancy_timedelta(user.joined_at)) # Discord join date embed.add_field(name="Account age", value=fancy_timedelta(user.created_at)) # Send them await ctx.send(embed=embed)
async def cleanup_user(self, ctx, user: discord.User, count: int, channel: discord.TextChannel = None): """ Deletes the last <count> messages created by the given user. """ await self.check_count(ctx, count) if channel is None: channel = ctx.channel # Deletes the messages by the user deleted = _Counter() def check(message): if deleted < count: if user == message.author: deleted.incr() return True return False messages = await channel.purge(limit=count * 2, check=check, before=ctx.message, bulk=True) # Send journal events causer = user_discrim(ctx.author) content = f"{causer} deleted {len(messages)} messages in {channel.mention} by {user.mention}" self.journal.send( "user", ctx.guild, content, icon="delete", count=count, channel=channel, messages=messages, user=user, cause=ctx.author, ) obj, file = self.dump_messages(messages) content = f"Cleanup by {causer} of {user.mention} in {channel.mention} deleted these messages:" self.dump.send("user", ctx.guild, content, icon="delete", messages=obj, file=file)
async def nick(self, ctx, member: MemberConv, nick: str = None): """ Changes or reset a member's nickname. """ logger.info("Setting the nickname of user '%s' (%d) to %r", member.name, member.id, nick) if member.top_role >= ctx.me.top_role: raise ManualCheckFailure( "I don't have permission to nick this user") mod = user_discrim(ctx.author) await member.edit( nick=nick, reason=f"{mod} {'un' if nick is None else ''}set nickname")
async def softban(self, ctx, user: UserConv, *, reason: str): """ Soft-bans the user from the guild with a reason. If guild has moderation logging enabled, it is logged Soft-ban is a kick that cleans up the chat """ try: embed = discord.Embed(description="Done! User Soft-banned") embed.add_field(name="Reason", value=reason) mod = user_discrim(ctx.author) banned = user_discrim(user) clean_reason = escape_backticks(reason) content = f"{mod} soft-banned {user.mention} ({banned}) with reason: `{clean_reason}`" await ctx.guild.ban(user, reason=f"{reason} - {mod}", delete_message_days=1) await asyncio.sleep(0.1) await ctx.guild.unban(user, reason=f"{reason} - {mod}") await ctx.send(embed=embed) self.journal.send( "member/softban", ctx.guild, content, icon="soft", user=user, reason=reason, cause=ctx.author, ) except discord.errors.Forbidden: raise ManualCheckFailure( content="I don't have permission to soft-ban this user")
def format_message(welcome_message, ctx): user = ctx.author channel = ctx.channel guild = ctx.guild return welcome_message.format( mention=user.mention, user=user.name, discrim=user.discriminator, user_discrim=user_discrim(user), user_id=user.id, channel=channel.mention, channel_name=channel.name, channel_id=channel.id, server=guild.name, server_id=guild.id, guild=guild.name, guild_id=guild.id, )
async def member_leave(self, member): logger.info( "Member %s (%d) left '%s' (%d)", user_discrim(member), member.id, member.guild.name, member.guild.id, ) welcome = self.bot.sql.welcome.get_welcome(member.guild) if welcome.goodbye_message and welcome.channel: self.send_welcome_message(self.bot, member, welcome.goodbye_message, welcome.channel) # Remove from recently_joined, they need to re-agree! try: self.recently_joined.remove(member) except ValueError: pass
async def cleanup(self, ctx, count: int, channel: discord.TextChannel = None): """ Deletes the last <count> messages, not including this command. """ await self.check_count(ctx, count) if channel is None: channel = ctx.channel # Delete the messages messages = await channel.purge(limit=count, before=ctx.message, bulk=True) # Send journal events causer = user_discrim(ctx.author) content = f"{causer} deleted {len(messages)} messages in {channel.mention}" self.journal.send( "count", ctx.guild, content, icon="delete", count=count, channel=channel, messages=messages, cause=ctx.author, ) obj, file = self.dump_messages(messages) content = f"Cleanup by {causer} in {channel.mention} deleted these messages:" self.dump.send("count", ctx.guild, content, icon="delete", messages=obj, file=file)
async def cleanup_id(self, ctx, message_id: int, channel: discord.TextChannel = None): """ Deletes all messages from the passed message ID to the present. """ if channel is None: channel = ctx.channel # Make sure it's an ID if not is_discord_id(message_id): embed = discord.Embed(colour=discord.Colour.red()) embed.set_author(name="Won't delete to message ID") embed.description = ( f"The given number `{message_id}` doesn't look like a Discord ID." ) raise CommandFailed(embed=embed) # Make sure it's not actually a user ID try: user = await self.bot.fetch_user(message_id) except discord.NotFound: pass else: embed = discord.Embed(colour=discord.Colour.red()) embed.description = ( f"The passed ID is for user {user.mention}. Did you copy the message ID or the user ID?\n\n" f"Not deleting. If you'd like to delete this far, specify the message count directly instead." ) raise CommandFailed(embed=embed) # Delete the messages before the message ID max_count = self.bot.sql.settings.get_max_delete_messages(ctx.guild) messages = await channel.purge( limit=max_count, check=lambda message: message.id >= message_id, before=ctx.message, bulk=True, ) if len(messages) == max_count and messages[0].id != message_id: embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.description = ( f"This guild only allows `{max_count}` messages to be deleted at a time. " f"Because of this limitation, message ID `{message_id}` was not actually deleted." ) await ctx.send(embed=embed) # Send journal events causer = user_discrim(ctx.author) content = (f"{causer} deleted {len(messages)} messages in " f"{channel.mention} until message ID {message_id}") self.journal.send( "id", ctx.guild, content, icon="delete", message_id=message_id, messages=messages, cause=ctx.author, ) obj, file = self.dump_messages(messages) content = (f"Cleanup by {causer} until message ID {message_id} in " f"{channel.mention} deleted these messages") self.dump.send("id", ctx.guild, content, icon="delete", messages=obj, file=file)