async def message_violator(): logger.debug("Sending message to user who violated the filter") response = StringBuilder() response.write( f"The message you posted in {message.channel.mention} contains or links to a file " ) response.writeln( f"that violates a {filter_type.value} content filter: `{hashsum.hex()}`." ) response.writeln(f"Your original link: <{url}>") if reupload: response.writeln( "The filtered file has been attached to this message.") 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." ) kwargs = {} if reupload: response.writeln( "In case the link is broken, the file has been attached below:" ) filename = os.path.basename(urlparse(url).path) kwargs["file"] = discord.File(binio.getbuffer(), filename=filename) kwargs["content"] = str(response) await message.author.send(**kwargs)
async def ufind(self, ctx, *, name: str): """ Perform a fuzzy search to find users who match the given name. They are listed with the closest matches first. Users not in this guild are marked with a \N{GLOBE WITH MERIDIANS}. """ logger.info("Running ufind on '%s'", name) users = await similar_users(self.bot, name) users_not_in_guild = (set( member.id for member in ctx.guild.members) if ctx.guild else set()) descr = StringBuilder() for user in users: extra = "\N{GLOBE WITH MERIDIANS}" if user in users_not_in_guild else "" descr.writeln( f"- {user.mention} {user.name}#{user.discriminator} {extra}") if users: embed = discord.Embed(description=str(descr), colour=discord.Colour.teal()) else: embed = discord.Embed(description="**No users found!**", colour=discord.Colour.red()) await ctx.send(embed=embed)
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 log_show(self, ctx, *channels: TextChannelConv): """ Displays current settings for this guild. If channels are provided, then only outputs for those channels are fetched. """ if not channels: channels = ctx.guild.channels outputs = self.bot.sql.journal.get_journals_on_channels(*channels) outputs = sorted(outputs, key=lambda out: out.path) attributes = [] descr = StringBuilder() for output in outputs: if not output.settings.recursive: attributes.append("exact path") attr_str = f'({", ".join(attributes)})' if attributes else "" descr.writeln( f"- `{output.path}` mounted at {output.sink.mention} {attr_str}" ) attributes.clear() if outputs: embed = discord.Embed(colour=discord.Colour.teal(), description=str(descr)) embed.set_author(name="Current journal outputs") else: embed = discord.Embed(colour=discord.Colour.dark_purple()) embed.set_author(name="No journal outputs!") await ctx.send(embed=embed)
async def log_dm_show(self, ctx, user: UserConv = None): """ Displays current journal mounts for a user. """ if user is None: user = self.bot.get_user(ctx.author.id) else: user = self.bot.get_user(user.id) outputs = self.bot.sql.journal.get_journals_on_user(user) outputs = sorted(outputs, key=lambda out: out.path) attributes = [] descr = StringBuilder(f"{user.mention}:\n\n") for output in outputs: if not output.settings.recursive: attributes.append("exact path") attr_str = f'({", ".join(attributes)})' if attributes else "" descr.writeln(f"- `{output.path}` {attr_str}") attributes.clear() if outputs: embed = discord.Embed(colour=discord.Colour.teal(), description=str(descr)) embed.set_author(name="Current journal outputs") else: embed = discord.Embed(colour=discord.Colour.dark_purple()) embed.set_author(name="No journal outputs!") 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 listcogs(self, ctx): """ Lists all currently loaded cogs. """ content = StringBuilder("```\n") extensions = defaultdict(list) for cog_name, cog in self.bot.cogs.items(): ext_name = cog.__module__.rsplit(".", 1)[0] ext_name = ext_name.rsplit(".", 1)[1] extensions[ext_name].append(cog_name) # I hate this but tree-format uses lists and tuples for some reason # So this takes the nice dictionary and converts it to that extensions = [(ext, [(cog_name, []) for cog_name in cog]) for ext, cog in extensions.items()] tree = ("futaba", [("cogs", extensions)]) content.writeln( format_tree(tree, format_node=itemgetter(0), get_children=itemgetter(1))) content.writeln("```") await ctx.send(content=str(content))
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 message_count(self, ctx, user: UserConv, *exclude: TextChannelConv): """ Gets the total number of messages this user has sent in the guild. Text channels to exclude from the search can be added as optional arguments. """ message_count, edited_count, deleted_count = self.sql.message_count( ctx.guild, user, exclude ) descr = StringBuilder() descr.writeln( f"Found `{message_count}` message{plural(message_count)} from {user.mention}." ) if message_count: descr.writeln( f"Of those, `{edited_count}` (or `{edited_count / message_count * 100:.2f}%`) are edited,\n" f"and `{deleted_count}` (or `{deleted_count / message_count * 100:.2f}%`) are deleted." ) if hasattr(user, "joined_at"): descr.writeln() descr.writeln( f"They have been a member for {fancy_timedelta(user.joined_at)}." ) embed = discord.Embed(colour=discord.Colour.teal()) embed.description = str(descr) await ctx.send(embed=embed)
async def alert_add(self, ctx, attribute: str, relationship: str, *, amount: str): """ Adds a join alert with the given condition. Possible attributes: id, created, name, discrim, avatar, status Possible relationships: > >= = != < <= ~ """ logging.info( "Got request to add new join alert for '%s' (%d)", ctx.guild.name, ctx.guild.id, ) try: key = JoinAlertKey.parse(attribute) except ValueError: raise ManualCheckFailure(content=f"Invalid attribute: {attribute}") try: op = ValueRelationship(relationship) except ValueError: raise ManualCheckFailure( content=f"Invalid relationship: {relationship}") try: value = key.parse_value(amount) except ValueError as error: raise ManualCheckFailure(content=str(error)) alert = JoinAlert(ctx.guild, None, key, op, value) logging.info("Adding join alert: %s", alert) with self.bot.sql.transaction(): self.bot.sql.welcome.add_alert(ctx.guild, alert) self.alerts[alert.id] = alert # Notify the user embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.set_author(name="Successfully added join alert") descr = StringBuilder() descr.writeln(f"ID: #`{alert.id:05}`, Condition: `{alert}`") descr.writeln( "To get these notifications in a channel, add a logger for path `/welcome/alert`" ) embed.description = str(descr) await ctx.send(embed=embed)
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 message_violator(jailed): response = StringBuilder( f"The {name_type.value} you just set violates a {filter_type.value} text filter " f"disallowing `{escaped_filter_text}`.\n" ) if jailed: if roles.jail is not None: response.writeln( f"In the mean time, you have been assigned the `{roles.jail.name}` role, " "revoking your posting privileges until a moderator clears you." ) else: response.writeln( "Your name has been manually cleared. Please do not set your name to " "anything offensive in the future." ) await member.send(content=str(response))
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 remind_list(self, ctx): """ Lists all reminders for the current user. """ reminders = self.bot.sql.navi.get_reminders(ctx.author) if reminders: descr = StringBuilder() for reminder in reminders: until = fancy_timedelta(reminder.timestamp) message = escape_backticks(reminder.message) descr.writeln( f"ID: #`{reminder.id:05}`: in `{until}` with message: `{message}`" ) embed = discord.Embed(colour=discord.Colour.dark_teal()) embed.set_author(name=f"Reminders for {ctx.author.display_name}") embed.description = str(descr) else: embed = discord.Embed(colour=discord.Colour.dark_purple()) embed.description = f"No reminders for {ctx.author.mention}" await ctx.send(embed=embed)
def elevated_role_embed(guild, role, level): """ Takes the result of elevated_role_perms() and produces an embed listing the permissions. The parameter level must be 'warning' or 'error'. """ elevated = elevated_role_perms(guild, role) if not elevated: return None if level == "warning": colour = discord.Colour.gold() icon = "\N{WARNING SIGN}" elif level == "error": colour = discord.Colour.red() icon = "\N{NO ENTRY}" else: raise ValueError(f"Unknown severity level: '{level}'") embed = discord.Embed() embed.colour = colour embed.title = f"{icon} Role gives elevated permissions" descr = StringBuilder() for location, perm in elevated: perm = perm.replace("_", " ").title() if isinstance(location, discord.Guild): descr.writeln(f"- {perm}") elif isinstance(location, discord.TextChannel): descr.writeln(f"- {perm} in {location.mention}") else: descr.writeln(f"- {perm} in {location.name}") embed.description = str(descr) return embed
async def alert_show(self, ctx): """ Lists all join alerts for this guild. """ logging.info("Showing all join alerts for '%s' (%d)", ctx.guild.name, ctx.guild.id) embed = discord.Embed() embed.set_author(name="Join alerts") descr = StringBuilder() descr.writeln("__Alert ID__ | __Condition__") for alert in self.alerts.values(): assert alert.id is not None, "Alert was not given an ID" descr.writeln(f"#**`{alert.id:05}`** | `{alert}`") if self.alerts: embed.colour = discord.Colour.dark_teal() embed.description = str(descr) else: embed.colour = discord.Colour.dark_purple() embed.description = "No alerts for this guild" await ctx.send(embed=embed)
async def check_text_filter(cog, message): # Also check embed content content = StringBuilder(message.content) for embed in message.embeds: embed_dict = embed.to_dict() content.writeln(embed_dict.get("description", "")) content.writeln(embed_dict.get("title", "")) for field in embed_dict.get("fields", []): content.writeln(field.get("name", "")) content.writeln(field.get("value", "")) # This is the string we will validate against to_check = str(content) logger.debug("Content to check: %r", to_check) # Iterate through all guild filters triggered = None filter_groups = ( (LocationType.GUILD, cog.filters[message.guild]), (LocationType.CHANNEL, cog.filters[message.channel]), ) for location_type, all_filters in filter_groups: for filter_text, (filter, filter_type) in all_filters.items(): if filter.matches(to_check): if triggered is None or filter_type.value > triggered.filter_type.value: triggered = FoundTextViolation( bot=cog.bot, journal=cog.journal, message=message, content=to_check, location_type=location_type, filter_type=filter_type, filter_text=filter_text, ) if triggered is not None: roles = cog.bot.sql.settings.get_special_roles(message.guild) await found_text_violation(triggered, roles)
async def rinfo(self, ctx, *, name: str = None): """ Fetches and prints information about a particular role in the current guild. If no role is specified, it displays information about the default role. """ if name is None: role = ctx.guild.default_role else: conv = RoleConv() try: role = await conv.convert(ctx, name) except commands.BadArgument: embed = discord.Embed(colour=discord.Colour.red()) embed.description = ( f"No role found in this guild for `{escape_backticks(name)}`." ) raise CommandFailed(embed=embed) logger.info("Running rinfo on '%s' (%d)", role.name, role.id) embed = discord.Embed(colour=role.colour) embed.timestamp = role.created_at embed.add_field(name="ID", value=str(role.id)) embed.add_field(name="Position", value=str(role.position)) descr = StringBuilder(f"{role.mention}\n") if role.mentionable: descr.writeln("Mentionable") if role.hoist: descr.writeln("Hoisted") if role.managed: descr.writeln("Managed") embed.description = str(descr) if role.members: max_members = 10 members = ", ".join( map(lambda m: m.mention, islice(role.members, 0, max_members))) if len(role.members) > max_members: diff = len(role.members) - max_members members += f" (and {diff} other{plural(diff)})" else: members = "(none)" embed.add_field(name="Members", value=members) await ctx.send(embed=embed)
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 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 alert_match(self, ctx, id: int): """ Lists all members currently joined who matches the conditions given by the join alert ID. """ logging.info( "Showing all members matching join alert %d in guild '%s' (%d)", id, ctx.guild.name, ctx.guild.id, ) try: alert = self.alerts[id] except KeyError: embed = discord.Embed(colour=discord.Colour.red()) embed.set_author(name="Alert check failed") embed.description = f"No such join alert id: `{id}`" raise CommandFailed(embed=embed) embed = discord.Embed() embed.set_author(name="Matching members") descr = StringBuilder() descr.writeln(f"Join alert: `{alert}`") matching_members = list(filter(alert.matches, ctx.guild.members)) if matching_members: embed.colour = discord.Colour.dark_teal() descr.writeln(f"Found {len(matching_members)} matching members:") descr.writeln() max_members = 8 for member in islice(matching_members, 0, max_members): descr.writeln(f"- {member.mention}") if len(matching_members) > max_members: descr.writeln( f"... and {len(matching_members) - max_members} more") else: embed.colour = discord.Colour.dark_purple() descr.writeln("**No members matching this condition**") embed.description = str(descr) await ctx.send(embed=embed)
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)
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)
async def ginfo(self, ctx): """ Gets information about the current guild. """ embed = discord.Embed() embed.timestamp = ctx.guild.created_at embed.set_author(name=ctx.guild.name) embed.set_thumbnail(url=ctx.guild.icon_url) descr = StringBuilder() descr.writeln(f"\N{MAN} **Members:** {len(ctx.guild.members):,}") descr.writeln( f"\N{MILITARY MEDAL} **Roles:** {len(ctx.guild.roles):,}") descr.writeln( f"\N{BAR CHART} **Channel categories:** {len(ctx.guild.categories):,}" ) descr.writeln( f"\N{MEMO} **Text Channels:** {len(ctx.guild.text_channels):,}") descr.writeln( f"\N{STUDIO MICROPHONE} **Voice Channels:** {len(ctx.guild.voice_channels):,}" ) descr.writeln( f"\N{CLOCK FACE TWO OCLOCK} **Age:** {fancy_timedelta(ctx.guild.created_at)}" ) descr.writeln() moderators = 0 admins = 0 bots = 0 # Do a single loop instead of generator expressions for member in ctx.guild.members: if member.bot: bots += 1 perms = member.permissions_in(ctx.channel) if perms.administrator: admins += 1 elif perms.manage_messages: moderators += 1 if bots: descr.writeln(f"\N{ROBOT FACE} **Bots:** {bots:,}") if moderators: descr.writeln( f"\N{CONSTRUCTION WORKER} **Moderators:** {moderators:,}") if admins: descr.writeln(f"\N{POLICE OFFICER} **Administrators:** {admins:,}") descr.writeln(f"\N{CROWN} **Owner:** {ctx.guild.owner.mention}") embed.description = str(descr) 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 reapply_add(self, ctx, *roles: RoleConv): """ Designate a collection of roles as "reappliable". Reappliable roles, in addition to all punishment and self-assignable roles, are automatically reapplied when the member rejoins the guild. """ warning = StringBuilder() roles = set(roles) # Filter out roles that shouldn't be reassignable special_roles = self.bot.sql.settings.get_special_roles(ctx.guild) if ctx.guild.default_role in roles: warning.writeln("You should not make @everyone reappliable.") roles.remove(ctx.guild.default_role) if special_roles.guest_role in roles: warning.writeln( f"You should not make {special_roles.guest_role.mention} reappliable." ) if special_roles.member_role in roles: warning.writeln( f"You should not make {special_roles.member_role.mention} reappliable." ) # Warn on roles that are already reappliable if special_roles.mute_role in roles: warning.writeln( f"The {special_roles.mute_role.mention} is always reappliable." ) if special_roles.jail_role in roles: warning.writeln( f"The {special_roles.jail_role.mention} is always reappliable." ) if "SelfAssignableRoles" in self.bot.cogs: assignable_roles = self.bot.sql.roles.get_assignable_roles( ctx.guild) else: assignable_roles = () for role in roles: if role in assignable_roles: warning.writeln( f"The {role.mention} is already reappliable since it is self-assignable." ) if warning: embed = discord.Embed(colour=discord.Colour.dark_purple()) embed.description = str(warning) await ctx.send(embed=embed) logger.info( "Setting roles as 'reappliable': [%s]", ", ".join(role.name for role in roles), ) with self.bot.sql.transaction(): self.bot.sql.settings.update_reapply_roles(ctx.guild, roles, True)