async def roles(self, ctx): """ Lists all roles in the guild. """ contents = [] content = StringBuilder() logger.info("Listing roles within the guild") for role in ctx.guild.roles: content.writeln( f"- {role.mention} id: `{role.id}`, members: `{len(role.members)}`" ) if len(content) > 1900: # Too long, break into new embed contents.append(str(content)) # Start content over content.clear() if content: contents.append(str(content)) for i, content in enumerate(contents): embed = discord.Embed(description=content, colour=discord.Colour.dark_teal()) embed.set_footer(text=f"Page {i + 1}/{len(contents)}") await ctx.send(embed=embed)
async def reactions(self, ctx, *, message: MessageConv): """ Displays all reactions on a message. """ logger.info("Displaying reactions for message ID %d", message.id) if not message.reactions: embed = discord.Embed(colour=discord.Colour.dark_purple()) embed.description = "This message has no reactions." await ctx.send(embed=embed) return descr = StringBuilder() for reaction in message.reactions: if descr: descr.writeln() descr.write(reaction.emoji) async for user in reaction.users(): descr.write(f" {user.mention}") if len(descr) > 1800: embed = discord.Embed(colour=discord.Colour.teal()) embed.description = str(descr) await ctx.send(embed=embed) descr.clear() descr.write(reaction.emoji) if descr: embed = discord.Embed(colour=discord.Colour.teal()) embed.description = str(descr) await ctx.send(embed=embed)
async def message_violator(): logger.debug("Sending message to user who violated the filter") response = StringBuilder( f"The message you posted in {message.channel.mention} violates a {location_type.value} " f"{filter_type.value} filter disallowing `{escaped_filter_text}`.") if severity >= FilterType.JAIL.level: if roles.jail is not None: response.writeln( "This offense is serious enough to warrant immediate revocation of posting privileges.\n" f"As such, you have been assigned the `{roles.jail.name}` role, until a moderator clears you." ) await message.author.send(content=str(response)) response.clear() if message.content != content: embed_caveat = "(including text from all embeds attached to your message)" else: embed_caveat = "" embed = discord.Embed(description=content) embed.timestamp = discord.utils.snowflake_time(message.id) embed.set_author(name=message.author.display_name, icon_url=message.author.avatar_url) to_send = f"The content of the deleted message {embed_caveat} is:" await message.author.send(content=to_send, embed=embed) response.writeln("or, quoted:") response.writeln("```") response.writeln(escaped_content) response.writeln("```") response.writeln("Contact a moderator if you have questions.") await message.author.send(content=str(response))
async def reapply_show(self, ctx): """ Lists all roles that are reappliable. Reappliable roles, in addition to all punishment and self-assignable roles, are automatically reapplied when the member rejoins the guild. """ reapply_roles = self.bot.sql.settings.get_reapply_roles(ctx.guild) special_roles = self.bot.sql.settings.get_special_roles(ctx.guild) embed = discord.Embed(colour=discord.Colour.dark_teal()) descr = StringBuilder(sep=", ") has_roles = False # Manually set for role in sorted(reapply_roles, key=lambda r: r.name): descr.write(role.mention) if descr: embed.add_field(name="Manually designated", value=str(descr)) has_roles = True else: embed.add_field(name="Manually designated", value="(none)") # Punishment roles descr.clear() if special_roles.mute_role is not None: descr.write(special_roles.mute_role.mention) if special_roles.jail_role is not None: descr.write(special_roles.jail_role.mention) if descr: embed.add_field(name="Punishment roles", value=str(descr)) has_roles = True # Self-assignable roles if "SelfAssignableRoles" in self.bot.cogs: assignable_roles = self.bot.sql.roles.get_assignable_roles( ctx.guild) if assignable_roles: embed.add_field( name="Self-assignable roles", value=", ".join(role.mention for role in sorted(assignable_roles, key=lambda r: r.name)), ) has_roles = True # Send final embed if has_roles: embed.title = "\N{MILITARY MEDAL} Roles which are automatically reapplied" else: embed.colour = discord.Colour.dark_purple() await ctx.send(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 show_content_filter(all_filters, message): if all_filters: contents = [] content = StringBuilder() content.writeln(f"**Filtered SHA1 hashes for {message.guild.name}:**") # Set up filter list filters = {filter_type: [] for filter_type in FilterType} for hashsum, (filter_type, description) in all_filters.items(): filters[filter_type].append((hashsum.hex(), description)) # Iterate through filters for filter_type in FilterType: filter_list = filters[filter_type] filter_list.sort() content.writeln( f"{filter_type.emoji} {filter_type.description} hashes {filter_type.emoji}" ) content.writeln("```") if not filter_list: content.writeln("(none)") content.writeln("```") continue for hexsum, description in filter_list: content.writeln(f"{hexsum} {description}") if len(content) > 1900: content.writeln("```") contents.append(str(content)) content.clear() content.writeln("```") if len(content) > 4: content.writeln("```") else: content.clear() if content: contents.append(str(content)) else: contents = (f"**No filtered SHA1 hashes for {message.guild.name}**", ) for content in contents: await message.author.send(content=content)
def build_regex(text, groups): # Build similar character tree chars = {} pattern = StringBuilder() for group in groups: pattern.write("[") char = group["character"] pattern.write(re.escape(char)) for homoglyph in group["homoglyphs"]: pattern.write(re.escape(homoglyph["c"])) pattern.write("]") chars[char] = str(pattern) pattern.clear() # Create pattern for char in text: pattern.write(chars.get(char, char)) return str(pattern)
async def sha1sum(self, ctx, *urls: str): """ Gives the SHA1 hashes of any files attached to the message. """ # Check all URLs links = [] for url in urls: match = URL_REGEX.match(url) if match is None: raise CommandFailed(content=f"Not a valid url: {url}") links.append(match[1]) links.extend(attach.url for attach in ctx.message.attachments) # Get list of "names" names = list(urls) names.extend(attach.filename for attach in ctx.message.attachments) # Send error if no URLS if not links: raise CommandFailed(content="No URLs listed or files attached.") # Download and check files contents = [] content = StringBuilder("Hashes:\n```") buffers = await download_links(links) for i, binio in enumerate(buffers): if binio is None: hashsum = SHA1_ERROR_MESSAGE else: hashsum = sha1(binio.getbuffer()).hexdigest() content.writeln(f"{hashsum} {names[i]}") if len(content) > 1920: contents.append(content) if i < len(buffers) - 1: content.clear() content.writeln("```") if len(content) > 4: content.writeln("```") contents.append(content) for content in contents: await ctx.send(content=str(content))
async def list_emojis(self, ctx, all_guilds=False): contents = [] content = StringBuilder() if all_guilds: if not mod_perm(ctx): raise ManualCheckFailure( content="Only moderators can do this.") guild_emojis = (guild.emojis for guild in self.bot.guilds) emojis = chain(*guild_emojis) else: emojis = ctx.guild.emojis logger.info("Listing all emojis within the guild") for emoji in emojis: managed = "M" if emoji.managed else "" content.writeln( f"- [{emoji}]({emoji.url}) id: `{emoji.id}`, name: `{emoji.name}` {managed}" ) if len(content) > 1900: # Too long, break into new embed contents.append(str(content)) # Start content over content.clear() if content: contents.append(str(content)) for i, content in enumerate(contents): embed = discord.Embed(description=content, colour=discord.Colour.dark_teal()) embed.set_footer(text=f"Page {i + 1}/{len(contents)}") if i == 0: if all_guilds: embed.set_author(name="Emojis in all guilds") else: embed.set_author(name=f"Emojis within {ctx.guild.name}") await ctx.send(embed=embed)
async def show_filter(all_filters, author, location_name): if all_filters: contents = [] content = StringBuilder(f"**Filtered strings for {location_name}:**\n") filters = defaultdict(list) for filter_text, (_, filter_type) in all_filters.items(): filters[filter_type].append(filter_text) for filter_type, filter_texts in filters.items(): content.writeln( f"{filter_type.emoji} {filter_type.description} strings {filter_type.emoji}" ) content.writeln("```") if not filter_texts: content.writeln("(none)") continue for filter_text in filter_texts: if all(ch in READABLE_CHAR_SET for ch in filter_text): content.writeln(f'- "{filter_text}"') else: content.writeln( f'- {unicode_repr(filter_text)} ["{filter_text}"]') if len(content) > 1900: # Too long, break into new message content.writeln("```") contents.append(str(content)) # Start buffer over content.clear() content.writeln("```") content.writeln("```") contents.append(str(content)) content.clear() else: contents = [f"**No filtered strings for {location_name}**"] for content in contents: await author.send(content=content)
async def log_find(self, ctx, *, condition: str = None): """ List previous journal events that match the given conditions. The condition is a Python expression with no variables but the following attributes: path: str ppath: PurePath guild: discord.Guild content: str attributes: dict """ events, error_embeds = self.log_filter(ctx.guild, condition, 10) if error_embeds: await ctx.send(embed=error_embeds[0]) if events: embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.set_author(name="Matching journal events") descr = StringBuilder() embeds = [] for event in events: descr.writeln(f"Path: `{event.path}`, Content: {event.content}") descr.writeln(f"Attributes: ```py\n{pformat(event.attributes)}\n```\n") if len(descr) > 1400: embed.description = str(descr) embeds.append(embed) descr.clear() embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.set_author(name="Matched journal events") embed.description = str(descr) else: embed = discord.Embed(colour=discord.Colour.dark_purple()) embed.set_author(name="No matching journal entries") embeds = (embed,) for i, embed in enumerate(embeds): embed.set_footer(text=f"Page {i + 1}/{len(embeds)}") await ctx.send(embed=embed)
async def channel_delete(self, ctx, *channels: TextChannelConv): """ Removes the channel(s) from the restricted role channel list. """ logger.info( "Removing channels to be used for role commands in guild '%s' (%d): [%s]", ctx.guild.name, ctx.guild.id, ", ".join(channel.mention for channel in channels), ) if not channels: raise CommandFailed() # Remove channels from database with self.bot.sql.transaction(): for channel in channels: self.bot.sql.roles.remove_role_command_channel( ctx.guild, channel) # Send response embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.set_author(name="Removed channels to be used for adding roles") all_channels = self.bot.sql.roles.get_role_command_channels(ctx.guild) descr = StringBuilder(sep=", ") for channel in channels: descr.write(channel.mention) embed.add_field(name="Removed", value=str(descr)) descr.clear() for channel in all_channels: descr.write(channel.mention) embed.add_field(name="Remaining", value=str(descr) or "(none)") await ctx.send(embed=embed) # Send journal event self.channel_journal(ctx.guild)
async def aliases(self, ctx, *, user: UserConv): """ Gets information about known aliases of the given user. """ logger.info( "Getting and printing alias information for some user '%s' (%d)", user.name, user.id, ) avatars, usernames, nicknames, alt_user_ids = self.bot.sql.alias.get_aliases( ctx.guild, user) # Remove self from chain try: alt_user_ids.remove(user.id) except KeyError: pass embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.set_author(name="Member alias information") if not any((avatars, usernames, nicknames, alt_user_ids)): embed.colour = discord.Colour.dark_purple() embed.description = f"No information found for {user.mention}" await ctx.send(embed=embed) return embed.description = f"{user.mention}\n" content = StringBuilder() files = [] if avatars: for i, (avatar_bin, avatar_ext, timestamp) in enumerate(avatars, 1): time_since = fancy_timedelta(timestamp) content.writeln(f"**{i}.** set {time_since} ago") files.append( discord.File(avatar_bin, filename=f"avatar {time_since}.{avatar_ext}")) embed.add_field(name="Past avatars", value=str(content)) content.clear() if usernames: for username, timestamp in usernames: content.writeln( f"- `{username}` set {fancy_timedelta(timestamp)} ago") embed.add_field(name="Past usernames", value=str(content)) content.clear() if nicknames: for nickname, timestamp in nicknames: content.writeln( f"- `{nickname}` set {fancy_timedelta(timestamp)} ago") embed.add_field(name="Past nicknames", value=str(content)) content.clear() if alt_user_ids: for alt_user_id in alt_user_ids: content.writeln(f"<@!{alt_user_id}>") embed.add_field(name="Possible alts", value=str(content)) await ctx.send(embed=embed) for i, file in enumerate(files, 1): await ctx.send(content=f"#{i}", file=file)