From bf531350d50c200f9dfa06525c97d6ad1a9f75d1 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 27 Dec 2018 00:59:26 +1000 Subject: Add DM emoji indicator for infr notify success. --- bot/cogs/moderation.py | 89 +++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index 6e958b912..0fc47c9df 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -82,7 +82,7 @@ class Moderation(Scheduler): :param reason: The reason for the warning. """ - await self.notify_infraction( + notified = await self.notify_infraction( user=user, infr_type="Warning", reason=reason @@ -92,12 +92,13 @@ class Moderation(Scheduler): if response_object is None: return + dm_result = ":incoming_envelope: " if notified else "" + action = f"{dm_result}:ok_hand: warned {user.mention}" + if reason is None: - result_message = f":ok_hand: warned {user.mention}." + await ctx.send(f"{action}.") else: - result_message = f":ok_hand: warned {user.mention} ({reason})." - - await ctx.send(result_message) + await ctx.send(f"{action} ({reason}).") @with_role(*MODERATION_ROLES) @command(name="kick") @@ -108,7 +109,7 @@ class Moderation(Scheduler): :param reason: The reason for the kick. """ - await self.notify_infraction( + notified = await self.notify_infraction( user=user, infr_type="Kick", reason=reason @@ -121,12 +122,13 @@ class Moderation(Scheduler): self.mod_log.ignore(Event.member_remove, user.id) await user.kick(reason=reason) + dm_result = ":incoming_envelope: " if notified else "" + action = f"{dm_result}:ok_hand: kicked {user.mention}" + if reason is None: - result_message = f":ok_hand: kicked {user.mention}." + await ctx.send(f"{action}.") else: - result_message = f":ok_hand: kicked {user.mention} ({reason})." - - await ctx.send(result_message) + await ctx.send(f"{action} ({reason}).") # Send a log message to the mod log await self.mod_log.send_log_message( @@ -150,7 +152,7 @@ class Moderation(Scheduler): :param reason: The reason for the ban. """ - await self.notify_infraction( + notified = await self.notify_infraction( user=user, infr_type="Ban", duration="Permanent", @@ -165,12 +167,13 @@ class Moderation(Scheduler): self.mod_log.ignore(Event.member_remove, user.id) await ctx.guild.ban(user, reason=reason, delete_message_days=0) + dm_result = ":incoming_envelope: " if notified else "" + action = f"{dm_result}:ok_hand: permanently banned {user.mention}" + if reason is None: - result_message = f":ok_hand: permanently banned {user.mention}." + await ctx.send(f"{action}.") else: - result_message = f":ok_hand: permanently banned {user.mention} ({reason})." - - await ctx.send(result_message) + await ctx.send(f"{action} ({reason}).") # Send a log message to the mod log await self.mod_log.send_log_message( @@ -194,7 +197,7 @@ class Moderation(Scheduler): :param reason: The reason for the mute. """ - await self.notify_infraction( + notified = await self.notify_infraction( user=user, infr_type="Mute", duration="Permanent", @@ -209,12 +212,13 @@ class Moderation(Scheduler): self.mod_log.ignore(Event.member_update, user.id) await user.add_roles(self._muted_role, reason=reason) + dm_result = ":incoming_envelope: " if notified else "" + action = f"{dm_result}:ok_hand: permanently muted {user.mention}" + if reason is None: - result_message = f":ok_hand: permanently muted {user.mention}." + await ctx.send(f"{action}.") else: - result_message = f":ok_hand: permanently muted {user.mention} ({reason})." - - await ctx.send(result_message) + await ctx.send(f"{action} ({reason}).") # Send a log message to the mod log await self.mod_log.send_log_message( @@ -242,7 +246,7 @@ class Moderation(Scheduler): :param reason: The reason for the temporary mute. """ - await self.notify_infraction( + notified = await self.notify_infraction( user=user, infr_type="Mute", duration=duration, @@ -262,12 +266,13 @@ class Moderation(Scheduler): loop = asyncio.get_event_loop() self.schedule_task(loop, infraction_object["id"], infraction_object) + dm_result = ":incoming_envelope: " if notified else "" + action = f"{dm_result}:ok_hand: muted {user.mention} until {infraction_expiration}" + if reason is None: - result_message = f":ok_hand: muted {user.mention} until {infraction_expiration}." + await ctx.send(f"{action}.") else: - result_message = f":ok_hand: muted {user.mention} until {infraction_expiration} ({reason})." - - await ctx.send(result_message) + await ctx.send(f"{action} ({reason}).") # Send a log message to the mod log await self.mod_log.send_log_message( @@ -294,7 +299,7 @@ class Moderation(Scheduler): :param reason: The reason for the temporary ban. """ - await self.notify_infraction( + notified = await self.notify_infraction( user=user, infr_type="Ban", duration=duration, @@ -316,12 +321,13 @@ class Moderation(Scheduler): loop = asyncio.get_event_loop() self.schedule_task(loop, infraction_object["id"], infraction_object) + dm_result = ":incoming_envelope: " if notified else "" + action = f"{dm_result}:ok_hand: banned {user.mention} until {infraction_expiration}" + if reason is None: - result_message = f":ok_hand: banned {user.mention} until {infraction_expiration}." + await ctx.send(f"{action}.") else: - result_message = f":ok_hand: banned {user.mention} until {infraction_expiration} ({reason})." - - await ctx.send(result_message) + await ctx.send(f"{action} ({reason}).") # Send a log message to the mod log await self.mod_log.send_log_message( @@ -603,7 +609,15 @@ class Moderation(Scheduler): if infraction_object["expires_at"] is not None: self.cancel_expiration(infraction_object["id"]) - await ctx.send(f":ok_hand: Un-muted {user.mention}.") + notified = await self.notify_pardon( + user=user, + title="You have been unmuted.", + content="You may now send messages in the server.", + icon_url=Icons.user_unmute + ) + + dm_result = ":incoming_envelope: " if notified else "" + await ctx.send(f"{dm_result}:ok_hand: Un-muted {user.mention}.") # Send a log message to the mod log await self.mod_log.send_log_message( @@ -617,13 +631,6 @@ class Moderation(Scheduler): Intended expiry: {infraction_object['expires_at']} """) ) - - await self.notify_pardon( - user=user, - title="You have been unmuted.", - content="You may now send messages in the server.", - icon_url=Icons.user_unmute - ) except Exception: log.exception("There was an error removing an infraction.") await ctx.send(":x: There was an error removing the infraction.") @@ -1093,7 +1100,7 @@ class Moderation(Scheduler): embed.title = f"Please review our rules over at {RULES_URL}" embed.url = RULES_URL - await self.send_private_embed(user, embed) + return await self.send_private_embed(user, embed) async def notify_pardon( self, user: Union[User, Member], title: str, content: str, icon_url: str = Icons.user_verified @@ -1114,7 +1121,7 @@ class Moderation(Scheduler): embed.set_author(name=title, icon_url=icon_url) - await self.send_private_embed(user, embed) + return await self.send_private_embed(user, embed) async def send_private_embed(self, user: Union[User, Member], embed: Embed): """ @@ -1129,11 +1136,13 @@ class Moderation(Scheduler): try: await user.send(embed=embed) + return True except (HTTPException, Forbidden): log.debug( f"Infraction-related information could not be sent to user {user} ({user.id}). " "They've probably just disabled private messages." ) + return False # endregion -- cgit v1.2.3 From c8be1f1725f497399c7c75f404f85591d7b189ed Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 27 Dec 2018 04:05:37 +1000 Subject: Add charinfo command. --- bot/cogs/utils.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index b101b8816..d605cd201 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -1,8 +1,9 @@ import logging +import re +import unicodedata from email.parser import HeaderParser from io import StringIO - from discord import Colour, Embed from discord.ext.commands import AutoShardedBot, Context, command @@ -87,6 +88,50 @@ class Utils: await ctx.message.channel.send(embed=pep_embed) + @command() + async def charinfo(self, ctx, *, characters: str): + """ + Shows you information on up to 25 unicode characters. + """ + + match = re.match(r"<(a?):([a-zA-Z0-9\_]+):([0-9]+)>", characters) + if match: + embed = Embed( + title="Non-Character Detected", + description=( + "Only unicode characters can be processed, but a custom Discord emoji " + "was found. Please remove it and try again." + ) + ) + embed.colour = Colour.red() + return await ctx.send(embed=embed) + + if len(characters) > 25: + embed = Embed(title=f"Too many characters ({len(characters)}/25)") + embed.colour = Colour.red() + return await ctx.send(embed=embed) + + def get_info(char): + digit = f"{ord(char):x}" + if len(digit) <= 4: + u_code = f"\\u{digit:>04}" + else: + u_code = f"\\U{digit:>08}" + url = f"https://www.compart.com/en/unicode/U+{digit:>04}" + name = f"[{unicodedata.name(char, '')}]({url})" + info = f"`{u_code.ljust(10)}`: {name} - {char}" + return info, u_code + + charlist, rawlist = zip(*(get_info(c) for c in characters)) + + embed = Embed(description="\n".join(charlist)) + embed.set_author(name="Character Info") + + if len(characters) > 1: + embed.add_field(name='Raw', value=f"`{''.join(rawlist)}`", inline=False) + + await ctx.send(embed=embed) + def setup(bot): bot.add_cog(Utils(bot)) -- cgit v1.2.3 From 6dd7d7d641403d468418eb8b8d1d60d35e5d8e0d Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 27 Dec 2018 04:53:10 +1000 Subject: Add content kwarg to modlog messages. --- bot/cogs/modlog.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 1d1546d5b..905f114c1 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -104,8 +104,9 @@ class ModLog: self._ignored[event].append(item) async def send_log_message( - self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, thumbnail: str = None, - channel_id: int = Channels.modlog, ping_everyone: bool = False, files: List[File] = None + self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, + thumbnail: str = None, channel_id: int = Channels.modlog, ping_everyone: bool = False, + files: List[File] = None, content: str = None ): embed = Embed(description=text) @@ -118,10 +119,11 @@ class ModLog: if thumbnail is not None: embed.set_thumbnail(url=thumbnail) - content = None - if ping_everyone: - content = "@everyone" + if content: + content = f"@everyone\n{content}" + else: + content = "@everyone" await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) -- cgit v1.2.3 From 7a6e289636c131bf724f82c8f00a008b275eb79c Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Thu, 27 Dec 2018 04:53:47 +1000 Subject: Ping mod on infr notify failure in modlog. --- bot/cogs/moderation.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index 0fc47c9df..24b60332f 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -100,6 +100,9 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") + if not notified: + await self.log_notify_failure(user, ctx.author, "warning") + @with_role(*MODERATION_ROLES) @command(name="kick") async def kick(self, ctx: Context, user: Member, *, reason: str = None): @@ -130,6 +133,9 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") + if not notified: + await self.log_notify_failure(user, ctx.author, "kick") + # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.sign_out, @@ -175,6 +181,9 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") + if not notified: + await self.log_notify_failure(user, ctx.author, "ban") + # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_ban, @@ -220,6 +229,9 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") + if not notified: + await self.log_notify_failure(user, ctx.author, "mute") + # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_mute, @@ -274,6 +286,9 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") + if not notified: + await self.log_notify_failure(user, ctx.author, "mute") + # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_mute, @@ -329,6 +344,9 @@ class Moderation(Scheduler): else: await ctx.send(f"{action} ({reason}).") + if not notified: + await self.log_notify_failure(user, ctx.author, "ban") + # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_ban, @@ -619,6 +637,9 @@ class Moderation(Scheduler): dm_result = ":incoming_envelope: " if notified else "" await ctx.send(f"{dm_result}:ok_hand: Un-muted {user.mention}.") + if not notified: + await self.log_notify_failure(user, ctx.author, "unmute") + # Send a log message to the mod log await self.mod_log.send_log_message( icon_url=Icons.user_unmute, @@ -1144,6 +1165,15 @@ class Moderation(Scheduler): ) return False + async def log_notify_failure(self, target: str, actor: Member, infraction_type: str): + await self.mod_log.send_log_message( + icon_url=Icons.token_removed, + content=actor.mention, + colour=Colour(Colours.soft_red), + title="Notification Failed", + text=f"Direct message was unable to be sent.\nUser: {target.mention}\nType: {infraction_type}" + ) + # endregion async def __error(self, ctx, error): -- cgit v1.2.3 From 2587568fa04c8e7511982e1e6b78df850c37b38f Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 27 Dec 2018 11:49:41 -0800 Subject: invite filter: remove redundant channel None check --- bot/cogs/filtering.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index b6ce501fc..0ba1e49c5 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -238,15 +238,13 @@ class Filtering: f"{URLs.discord_invite_api}/{invite}" ) response = await response.json() - if response.get("guild") is None: - # If we have a valid invite which is not a guild invite - # it might be a DM channel invite - if response.get("channel") is not None: - # We don't have whitelisted Group DMs so we can - # go ahead and return a positive for any group DM - return True + guild = response.get("guild") + if guild is None: + # We don't have whitelisted Group DMs so we can + # go ahead and return a positive for any group DM + return True - guild_id = int(response.get("guild").get("id")) + guild_id = int(guild.get("id")) if guild_id not in Filter.guild_invite_whitelist: return True -- cgit v1.2.3 From a6b997fcebe747aa9bbfb92ce17d0725159a08fc Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 28 Dec 2018 08:21:38 +1000 Subject: Simplify regex pattern Co-Authored-By: scragly <29337040+scragly@users.noreply.github.com> --- bot/cogs/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index d605cd201..94f3938e4 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -94,7 +94,7 @@ class Utils: Shows you information on up to 25 unicode characters. """ - match = re.match(r"<(a?):([a-zA-Z0-9\_]+):([0-9]+)>", characters) + match = re.match(r"<(a?):(\w+):(\d+)>", characters) if match: embed = Embed( title="Non-Character Detected", -- cgit v1.2.3 From 81415a1261d2584e017b61609218583621be0e9c Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 28 Dec 2018 23:40:42 +1000 Subject: Expand in_channel check to accept multi-channels, bypass roles. --- bot/cogs/snekbox.py | 26 ++++++-------------------- bot/cogs/utils.py | 16 +++++++++++++--- bot/decorators.py | 44 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/bot/cogs/snekbox.py b/bot/cogs/snekbox.py index 1b51da843..cb0454249 100644 --- a/bot/cogs/snekbox.py +++ b/bot/cogs/snekbox.py @@ -6,12 +6,12 @@ import textwrap from discord import Colour, Embed from discord.ext.commands import ( - Bot, CommandError, Context, MissingPermissions, - NoPrivateMessage, check, command, guild_only + Bot, CommandError, Context, NoPrivateMessage, command, guild_only ) from bot.cogs.rmq import RMQ from bot.constants import Channels, ERROR_REPLIES, NEGATIVE_REPLIES, Roles, URLs +from bot.decorators import InChannelCheckFailure, in_channel from bot.utils.messages import wait_for_deletion @@ -51,22 +51,8 @@ RAW_CODE_REGEX = re.compile( r"\s*$", # any trailing whitespace until the end of the string re.DOTALL # "." also matches newlines ) -BYPASS_ROLES = (Roles.owner, Roles.admin, Roles.moderator, Roles.helpers) -WHITELISTED_CHANNELS = (Channels.bot,) -WHITELISTED_CHANNELS_STRING = ', '.join(f"<#{channel_id}>" for channel_id in WHITELISTED_CHANNELS) - - -async def channel_is_whitelisted_or_author_can_bypass(ctx: Context): - """ - Checks that the author is either helper or above - or the channel is a whitelisted channel. - """ - if ctx.channel.id in WHITELISTED_CHANNELS: - return True - if any(r.id in BYPASS_ROLES for r in ctx.author.roles): - return True - raise MissingPermissions("You are not allowed to do that here.") +BYPASS_ROLES = (Roles.owner, Roles.admin, Roles.moderator, Roles.helpers) class Snekbox: @@ -84,7 +70,7 @@ class Snekbox: @command(name='eval', aliases=('e',)) @guild_only() - @check(channel_is_whitelisted_or_author_can_bypass) + @in_channel(Channels.bot, bypass_roles=BYPASS_ROLES) async def eval_command(self, ctx: Context, *, code: str = None): """ Run some code. get the result back. We've done our best to make this safe, but do let us know if you @@ -205,9 +191,9 @@ class Snekbox: embed.description = "You're not allowed to use this command in private messages." await ctx.send(embed=embed) - elif isinstance(error, MissingPermissions): + elif isinstance(error, InChannelCheckFailure): embed.title = random.choice(NEGATIVE_REPLIES) - embed.description = f"Sorry, but you may only use this command within {WHITELISTED_CHANNELS_STRING}." + embed.description = str(error) await ctx.send(embed=embed) else: diff --git a/bot/cogs/utils.py b/bot/cogs/utils.py index 94f3938e4..65c729414 100644 --- a/bot/cogs/utils.py +++ b/bot/cogs/utils.py @@ -1,4 +1,5 @@ import logging +import random import re import unicodedata from email.parser import HeaderParser @@ -7,11 +8,13 @@ from io import StringIO from discord import Colour, Embed from discord.ext.commands import AutoShardedBot, Context, command -from bot.constants import Roles -from bot.decorators import with_role +from bot.constants import Channels, NEGATIVE_REPLIES, Roles +from bot.decorators import InChannelCheckFailure, in_channel log = logging.getLogger(__name__) +BYPASS_ROLES = (Roles.owner, Roles.admin, Roles.moderator, Roles.helpers) + class Utils: """ @@ -25,7 +28,6 @@ class Utils: self.base_github_pep_url = "https://raw.githubusercontent.com/python/peps/master/pep-" @command(name='pep', aliases=('get_pep', 'p')) - @with_role(Roles.verified) async def pep_command(self, ctx: Context, pep_number: str): """ Fetches information about a PEP and sends it to the channel. @@ -89,6 +91,7 @@ class Utils: await ctx.message.channel.send(embed=pep_embed) @command() + @in_channel(Channels.bot, bypass_roles=BYPASS_ROLES) async def charinfo(self, ctx, *, characters: str): """ Shows you information on up to 25 unicode characters. @@ -132,6 +135,13 @@ class Utils: await ctx.send(embed=embed) + async def __error(self, ctx, error): + embed = Embed(colour=Colour.red()) + if isinstance(error, InChannelCheckFailure): + embed.title = random.choice(NEGATIVE_REPLIES) + embed.description = str(error) + await ctx.send(embed=embed) + def setup(bot): bot.add_cog(Utils(bot)) diff --git a/bot/decorators.py b/bot/decorators.py index fe974cbd3..87877ecbf 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -1,18 +1,51 @@ import logging import random +import typing from asyncio import Lock from functools import wraps from weakref import WeakValueDictionary from discord import Colour, Embed from discord.ext import commands -from discord.ext.commands import Context +from discord.ext.commands import CheckFailure, Context from bot.constants import ERROR_REPLIES log = logging.getLogger(__name__) +class InChannelCheckFailure(CheckFailure): + pass + + +def in_channel(*channels: int, bypass_roles: typing.Container[int] = None): + """ + Checks that the message is in a whitelisted channel or optionally has a bypass role. + """ + def predicate(ctx: Context): + if ctx.channel.id in channels: + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The command was used in a whitelisted channel.") + return True + + if bypass_roles: + if any(r.id in bypass_roles for r in ctx.author.roles): + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The command was not used in a whitelisted channel, " + f"but the author had a role to bypass the in_channel check.") + return True + + log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " + f"The in_channel check failed.") + + channels_str = ', '.join(f"<#{c_id}>" for c_id in channels) + raise InChannelCheckFailure( + f"Sorry, but you may only use this command within {channels_str}." + ) + + return commands.check(predicate) + + def with_role(*role_ids: int): async def predicate(ctx: Context): if not ctx.guild: # Return False in a DM @@ -46,15 +79,6 @@ def without_role(*role_ids: int): return commands.check(predicate) -def in_channel(channel_id): - async def predicate(ctx: Context): - check = ctx.channel.id == channel_id - log.debug(f"{ctx.author} tried to call the '{ctx.command.name}' command. " - f"The result of the in_channel check was {check}.") - return check - return commands.check(predicate) - - def locked(): """ Allows the user to only run one instance of the decorated command at a time. -- cgit v1.2.3 From ea55fd63e432e845008c973949f0304d02da2e79 Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 28 Dec 2018 14:13:29 -0500 Subject: Switch local color constants to PyDis' constants --- bot/cogs/defcon.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index c432d377c..5fb5ef88f 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -5,13 +5,11 @@ from discord import Colour, Embed, Member from discord.ext.commands import Bot, Context, group from bot.cogs.modlog import ModLog -from bot.constants import Channels, Emojis, Icons, Keys, Roles, URLs +from bot.constants import Channels, Colours, Emojis, Icons, Keys, Roles, URLs from bot.decorators import with_role log = logging.getLogger(__name__) -COLOUR_RED = Colour(0xcd6d6d) -COLOUR_GREEN = Colour(0x68c290) REJECTION_MESSAGE = """ Hi, {user} - Thanks for your interest in our server! @@ -93,7 +91,7 @@ class Defcon: message = f"{message}\n\nUnable to send rejection message via DM; they probably have DMs disabled." await self.mod_log.send_log_message( - Icons.defcon_denied, COLOUR_RED, "Entry denied", + Icons.defcon_denied, Colours.soft_red, "Entry denied", message, member.avatar_url_as(static_format="png") ) @@ -134,7 +132,7 @@ class Defcon: ) await self.mod_log.send_log_message( - Icons.defcon_enabled, COLOUR_GREEN, "DEFCON enabled", + Icons.defcon_enabled, Colours.soft_green, "DEFCON enabled", f"**Staffer:** {ctx.author.name}#{ctx.author.discriminator} (`{ctx.author.id}`)\n" f"**Days:** {self.days.days}\n\n" "**There was a problem updating the site** - This setting may be reverted when the bot is " @@ -145,7 +143,7 @@ class Defcon: await ctx.send(f"{Emojis.defcon_enabled} DEFCON enabled.") await self.mod_log.send_log_message( - Icons.defcon_enabled, COLOUR_GREEN, "DEFCON enabled", + Icons.defcon_enabled, Colours.soft_green, "DEFCON enabled", f"**Staffer:** {ctx.author.name}#{ctx.author.discriminator} (`{ctx.author.id}`)\n" f"**Days:** {self.days.days}\n\n" ) @@ -177,7 +175,7 @@ class Defcon: ) await self.mod_log.send_log_message( - Icons.defcon_disabled, COLOUR_RED, "DEFCON disabled", + Icons.defcon_disabled, Colours.soft_red, "DEFCON disabled", f"**Staffer:** {ctx.author.name}#{ctx.author.discriminator} (`{ctx.author.id}`)\n" "**There was a problem updating the site** - This setting may be reverted when the bot is " "restarted.\n\n" @@ -187,7 +185,7 @@ class Defcon: await ctx.send(f"{Emojis.defcon_disabled} DEFCON disabled.") await self.mod_log.send_log_message( - Icons.defcon_disabled, COLOUR_RED, "DEFCON disabled", + Icons.defcon_disabled, Colours.soft_red, "DEFCON disabled", f"**Staffer:** {ctx.author.name}#{ctx.author.discriminator} (`{ctx.author.id}`)" ) -- cgit v1.2.3 From 199222f7b1db2964a68a969f77279af8b5c1a91c Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 28 Dec 2018 14:25:38 -0500 Subject: Add #defcon to channel constants --- bot/constants.py | 1 + config-default.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index 5e7927ed9..b4eca7e1d 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -317,6 +317,7 @@ class Channels(metaclass=YAMLGetter): big_brother_logs: int bot: int checkpoint_test: int + defcon: int devalerts: int devlog: int devtest: int diff --git a/config-default.yml b/config-default.yml index e7145289d..6d301048f 100644 --- a/config-default.yml +++ b/config-default.yml @@ -89,6 +89,7 @@ guild: big_brother_logs: &BBLOGS 468507907357409333 bot: 267659945086812160 checkpoint_test: 422077681434099723 + defcon: 464469101889454091 devalerts: 460181980097675264 devlog: &DEVLOG 409308876241108992 devtest: &DEVTEST 414574275865870337 -- cgit v1.2.3 From 912923418924749af9295232dfae398f0e4dda4f Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 28 Dec 2018 14:42:08 -0500 Subject: Add defcon channel topic updating --- bot/cogs/defcon.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index 5fb5ef88f..d26170444 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -10,7 +10,6 @@ from bot.decorators import with_role log = logging.getLogger(__name__) - REJECTION_MESSAGE = """ Hi, {user} - Thanks for your interest in our server! @@ -22,6 +21,8 @@ will be resolved soon. In the meantime, please feel free to peruse the resources , and have a nice day! """ +BASE_CHANNEL_TOPIC = "Python Discord Defense Mechanism" + class Defcon: """Time-sensitive server defense mechanisms""" @@ -64,6 +65,8 @@ class Defcon: self.days = timedelta(days=0) log.warning(f"DEFCON disabled") + self.update_channel_topic() + async def on_member_join(self, member: Member): if self.enabled and self.days.days > 0: now = datetime.utcnow() @@ -148,6 +151,8 @@ class Defcon: f"**Days:** {self.days.days}\n\n" ) + self.update_channel_topic() + @defcon_group.command(name='disable', aliases=('off', 'd')) @with_role(Roles.admin, Roles.owner) async def disable_command(self, ctx: Context): @@ -189,6 +194,8 @@ class Defcon: f"**Staffer:** {ctx.author.name}#{ctx.author.discriminator} (`{ctx.author.id}`)" ) + self.update_channel_topic() + @defcon_group.command(name='status', aliases=('s',)) @with_role(Roles.admin, Roles.owner) async def status_command(self, ctx: Context): @@ -250,6 +257,21 @@ class Defcon: f"**Days:** {self.days.days}" ) + self.update_channel_topic() + + async def update_channel_topic(self): + """ + Update the #defcon channel topic with the current DEFCON status + """ + + if self.enabled: + new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Enabled, Threshold: {self.days} days)" + else: + new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Disabled)" + + defcon_channel = await self.bot.guild.get_channel(Channels.defcon) + await defcon_channel.edit(topic=new_topic) + def setup(bot: Bot): bot.add_cog(Defcon(bot)) -- cgit v1.2.3 From 32b1ec7aa21d59166458eb2bb1826c628ec8013b Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 28 Dec 2018 14:58:28 -0500 Subject: Fix channel grabbing logic --- bot/cogs/defcon.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index d26170444..a29f4599e 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -265,11 +265,12 @@ class Defcon: """ if self.enabled: - new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Enabled, Threshold: {self.days} days)" + day_str = "days" if self.days > 1 else "day" + new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Enabled, Threshold: {self.days} {day_str})" else: new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Disabled)" - defcon_channel = await self.bot.guild.get_channel(Channels.defcon) + defcon_channel = self.bot.guilds[0].get_channel(Channels.defcon) await defcon_channel.edit(topic=new_topic) -- cgit v1.2.3 From 9bec011dee4ef3f0968aa44d2459a9367d25313c Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 29 Dec 2018 13:10:38 -0500 Subject: Add optional return to modlog post coro Enables generation of a context for AntiSpam to reference for posting infractions --- bot/cogs/modlog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 1d1546d5b..ef4544f81 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -123,7 +123,8 @@ class ModLog: if ping_everyone: content = "@everyone" - await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) + log_message = await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) + return self.bot.get_context(log_message) # Optionally return for use with antispam async def on_guild_channel_create(self, channel: GUILD_CHANNEL): if channel.guild.id != GuildConstant.id: -- cgit v1.2.3 From 9698cf42a22e0637c1afc21dbf3c8282829ccff0 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 29 Dec 2018 13:58:03 -0500 Subject: Add infraction posting for AntiSpam mutes --- bot/cogs/antispam.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index d5b72718c..052fd48b2 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -15,6 +15,7 @@ from bot.constants import ( Colours, DEBUG_MODE, Event, Guild as GuildConfig, Icons, Roles, ) +from bot.utils.moderation import post_infraction from bot.utils.time import humanize_delta @@ -133,7 +134,8 @@ class AntiSpam: mod_alert_message += f"{content}" - await self.mod_log.send_log_message( + # Return the mod log message Context that we can use to post the infraction + mod_log_message = await self.mod_log.send_log_message( icon_url=Icons.filtering, colour=Colour(Colours.soft_red), title=f"Spam detected!", @@ -143,28 +145,30 @@ class AntiSpam: ping_everyone=AntiSpamConfig.ping_everyone ) - await member.add_roles(self.muted_role, reason=reason) + # Post AntiSpam mute as a regular infraction so it can be reversed + ctx = await self.bot.get_context(mod_log_message) + response_object = await post_infraction(ctx, member, type="mute", reason=reason, duration=remove_role_after) + if response_object is None: + return # Appropriate error(s) are already raised by post_infraction + + self.mod_log.ignore(Event.member_update, member.id) + await member.add_roles(self._muted_role, reason=reason) + + loop = asyncio.get_event_loop() + infraction_object = response_object["infraction"] + self.schedule_task(loop, infraction_object["id"], infraction_object) + description = textwrap.dedent(f""" **Channel**: {msg.channel.mention} **User**: {msg.author.mention} (`{msg.author.id}`) **Reason**: {reason} Role will be removed after {human_duration}. """) - await self.mod_log.send_log_message( icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), title="User muted", text=description ) - await asyncio.sleep(remove_role_after) - await member.remove_roles(self.muted_role, reason="AntiSpam mute expired") - - await self.mod_log.send_log_message( - icon_url=Icons.user_mute, colour=Colour(Colours.soft_green), - title="User unmuted", - text=f"Was muted by `AntiSpam` cog for {human_duration}." - ) - async def maybe_delete_messages(self, channel: TextChannel, messages: List[Message]): # Is deletion of offending messages actually enabled? if AntiSpamConfig.clean_offending: -- cgit v1.2.3 From c419a2516d63aba2ada59694e345f27eaa5a0e1a Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 29 Dec 2018 14:27:44 -0500 Subject: Add mod log event for member warn & shadowwarn --- bot/cogs/moderation.py | 26 ++++++++++++++++++++++++++ bot/constants.py | 2 ++ config-default.yml | 2 ++ 3 files changed, 30 insertions(+) diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py index 6e958b912..8add6fdde 100644 --- a/bot/cogs/moderation.py +++ b/bot/cogs/moderation.py @@ -99,6 +99,19 @@ class Moderation(Scheduler): await ctx.send(result_message) + # Send a message to the mod log + await self.mod_log.send_log_message( + icon_url=Icons.user_warn, + colour=Colour(Colours.soft_red), + title="Member warned", + thumbnail=user.avatar_url_as(static_format="png"), + text=textwrap.dedent(f""" + Member: {user.mention} (`{user.id}`) + Actor: {ctx.message.author} + Reason: {reason} + """) + ) + @with_role(*MODERATION_ROLES) @command(name="kick") async def kick(self, ctx: Context, user: Member, *, reason: str = None): @@ -361,6 +374,19 @@ class Moderation(Scheduler): await ctx.send(result_message) + # Send a message to the mod log + await self.mod_log.send_log_message( + icon_url=Icons.user_warn, + colour=Colour(Colours.soft_red), + title="Member shadow warned", + thumbnail=user.avatar_url_as(static_format="png"), + text=textwrap.dedent(f""" + Member: {user.mention} (`{user.id}`) + Actor: {ctx.message.author} + Reason: {reason} + """) + ) + @with_role(*MODERATION_ROLES) @command(name="shadow_kick", hidden=True, aliases=['shadowkick', 'skick']) async def shadow_kick(self, ctx: Context, user: Member, *, reason: str = None): diff --git a/bot/constants.py b/bot/constants.py index 5e7927ed9..99ef98da2 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -292,6 +292,8 @@ class Icons(metaclass=YAMLGetter): user_unmute: str user_verified: str + user_warn: str + pencil: str remind_blurple: str diff --git a/config-default.yml b/config-default.yml index e7145289d..3a1ad8052 100644 --- a/config-default.yml +++ b/config-default.yml @@ -72,6 +72,8 @@ style: user_unmute: "https://cdn.discordapp.com/emojis/472472639206719508.png" user_verified: "https://cdn.discordapp.com/emojis/470326274519334936.png" + user_warn: "https://cdn.discordapp.com/emojis/470326274238447633.png" + pencil: "https://cdn.discordapp.com/emojis/470326272401211415.png" remind_blurple: "https://cdn.discordapp.com/emojis/477907609215827968.png" -- cgit v1.2.3 From 2634865fa0e4f3a3bd2494c8306820c71aee6487 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 29 Dec 2018 21:06:35 -0500 Subject: Remove invite filter deblanking Clarify inline help/comments --- bot/cogs/filtering.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 0ba1e49c5..c21b45648 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -217,16 +217,12 @@ class Filtering: async def _has_invites(self, text: str) -> bool: """ - Returns True if the text contains an invite which - is not on the guild_invite_whitelist in config.yml. + Returns True if the text contains an invite which is not on the guild_invite_whitelist in + config.yml - Also catches a lot of common ways to try to cheat the system. + Attempts to catch some of common ways to try to cheat the system. """ - # Remove spaces to prevent cases like - # d i s c o r d . c o m / i n v i t e / s e x y t e e n s - text = text.replace(" ", "") - # Remove backslashes to prevent escape character aroundfuckery like # discord\.gg/gdudes-pony-farm text = text.replace("\\", "") @@ -240,8 +236,9 @@ class Filtering: response = await response.json() guild = response.get("guild") if guild is None: - # We don't have whitelisted Group DMs so we can - # go ahead and return a positive for any group DM + # Lack of a "guild" key in the JSON response indicates either an group DM invite, an + # expired invite, or an invalid invite. The API does not currently differentiate + # between invalid and expired invites return True guild_id = int(guild.get("id")) -- cgit v1.2.3 From a999fbbead4c86ee77bb727820ccef1bfc6c300e Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 29 Dec 2018 21:08:37 -0500 Subject: Remove the cursed trailing whitespace --- bot/cogs/filtering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index c21b45648..f5811d9d2 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -217,7 +217,7 @@ class Filtering: async def _has_invites(self, text: str) -> bool: """ - Returns True if the text contains an invite which is not on the guild_invite_whitelist in + Returns True if the text contains an invite which is not on the guild_invite_whitelist in config.yml Attempts to catch some of common ways to try to cheat the system. @@ -237,7 +237,7 @@ class Filtering: guild = response.get("guild") if guild is None: # Lack of a "guild" key in the JSON response indicates either an group DM invite, an - # expired invite, or an invalid invite. The API does not currently differentiate + # expired invite, or an invalid invite. The API does not currently differentiate # between invalid and expired invites return True -- cgit v1.2.3 From 905aaab64736738c46e47cdbc52b977225b1ff34 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sun, 30 Dec 2018 08:30:04 -0500 Subject: Add missing awaits oops --- bot/cogs/defcon.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index a29f4599e..def9a70e3 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -65,7 +65,7 @@ class Defcon: self.days = timedelta(days=0) log.warning(f"DEFCON disabled") - self.update_channel_topic() + await self.update_channel_topic() async def on_member_join(self, member: Member): if self.enabled and self.days.days > 0: @@ -151,7 +151,7 @@ class Defcon: f"**Days:** {self.days.days}\n\n" ) - self.update_channel_topic() + await self.update_channel_topic() @defcon_group.command(name='disable', aliases=('off', 'd')) @with_role(Roles.admin, Roles.owner) @@ -194,7 +194,7 @@ class Defcon: f"**Staffer:** {ctx.author.name}#{ctx.author.discriminator} (`{ctx.author.id}`)" ) - self.update_channel_topic() + await self.update_channel_topic() @defcon_group.command(name='status', aliases=('s',)) @with_role(Roles.admin, Roles.owner) @@ -257,7 +257,7 @@ class Defcon: f"**Days:** {self.days.days}" ) - self.update_channel_topic() + await self.update_channel_topic() async def update_channel_topic(self): """ -- cgit v1.2.3 From 0577d11fa18f5556b45160a45939bf0e8ba2f580 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Tue, 1 Jan 2019 19:43:09 +1000 Subject: Cast int to str, correct buffer align, re-add trailing space --- bot/cogs/eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/eval.py b/bot/cogs/eval.py index 9e09b3aa0..8e97a35a2 100644 --- a/bot/cogs/eval.py +++ b/bot/cogs/eval.py @@ -68,7 +68,7 @@ class CodeEval: # then we get the length # and use `str.rjust()` # to indent it. - start = "...:".rjust(len(self.ln) + 2) + start = "...: ".rjust(len(str(self.ln)) + 7) if i == len(lines) - 2: if line.startswith("return"): -- cgit v1.2.3 From b3d1dc26b9c0c5ee8ffac8fad0a9a39bf6fdedfb Mon Sep 17 00:00:00 2001 From: sco1 Date: Tue, 1 Jan 2019 20:24:00 -0500 Subject: Fix timedelta days reference --- bot/cogs/defcon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index def9a70e3..4c043f050 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -265,8 +265,8 @@ class Defcon: """ if self.enabled: - day_str = "days" if self.days > 1 else "day" - new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Enabled, Threshold: {self.days} {day_str})" + day_str = "days" if self.days.days > 1 else "day" + new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Enabled, Threshold: {self.days.days} {day_str})" else: new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Disabled)" -- cgit v1.2.3 From 9e26d3a9a40bd692e1cbf4953376f06a14254c31 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Wed, 2 Jan 2019 21:26:30 +1100 Subject: Ignore defcon title change in modlog. --- bot/cogs/defcon.py | 3 ++- bot/cogs/modlog.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bot/cogs/defcon.py b/bot/cogs/defcon.py index 4c043f050..f07d9df9f 100644 --- a/bot/cogs/defcon.py +++ b/bot/cogs/defcon.py @@ -5,7 +5,7 @@ from discord import Colour, Embed, Member from discord.ext.commands import Bot, Context, group from bot.cogs.modlog import ModLog -from bot.constants import Channels, Colours, Emojis, Icons, Keys, Roles, URLs +from bot.constants import Channels, Colours, Emojis, Event, Icons, Keys, Roles, URLs from bot.decorators import with_role log = logging.getLogger(__name__) @@ -270,6 +270,7 @@ class Defcon: else: new_topic = f"{BASE_CHANNEL_TOPIC}\n(Status: Disabled)" + self.mod_log.ignore(Event.guild_channel_update, Channels.defcon) defcon_channel = self.bot.guilds[0].get_channel(Channels.defcon) await defcon_channel.edit(topic=new_topic) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 905f114c1..0561b5afb 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -176,6 +176,10 @@ class ModLog: if before.guild.id != GuildConstant.id: return + if before.id in self._ignored[Event.guild_channel_update]: + self._ignored[Event.guild_channel_update].remove(before.id) + return + diff = DeepDiff(before, after) changes = [] done = [] -- cgit v1.2.3 From 5b9f265890bb7940fa91b4c1570ede3782f11222 Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 2 Jan 2019 20:27:31 -0500 Subject: Add user notification on invite remove --- bot/cogs/filtering.py | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index f5811d9d2..0c62be77e 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -1,7 +1,8 @@ import logging import re -from discord import Colour, Member, Message +from discord import Colour, Member, Message, TextChannel +import discord.errors from discord.ext.commands import Bot from bot.cogs.modlog import ModLog @@ -42,27 +43,41 @@ class Filtering: "filter_zalgo": { "enabled": Filter.filter_zalgo, "function": self._has_zalgo, - "type": "filter" + "type": "filter", + "user_notification": False, + "notification_msg": "" }, "filter_invites": { "enabled": Filter.filter_invites, "function": self._has_invites, - "type": "filter" + "type": "filter", + "user_notification": True, + "notification_msg": ( + "Per Rule 10, your invite link has been removed. " + "If you believe this was a mistake, please the staff know!\n\n" + r"Our server rules can be found here: " + ) }, "filter_domains": { "enabled": Filter.filter_domains, "function": self._has_urls, - "type": "filter" + "type": "filter", + "user_notification": False, + "notification_msg": "" }, "watch_words": { "enabled": Filter.watch_words, "function": self._has_watchlist_words, - "type": "watchlist" + "type": "watchlist", + "user_notification": False, + "notification_msg": "" }, "watch_tokens": { "enabled": Filter.watch_tokens, "function": self._has_watchlist_tokens, - "type": "watchlist" + "type": "watchlist", + "user_notification": False, + "notification_msg": "" }, } @@ -136,6 +151,10 @@ class Filtering: if _filter["type"] == "filter": await msg.delete() + # Notify the user if the filter specifies + if _filter["user_notification"]: + await self.notify_member(msg.author, _filter["notification_msg"], msg.channel) + break # We don't want multiple filters to trigger @staticmethod @@ -247,6 +266,18 @@ class Filtering: return True return False + async def notify_member(self, filtered_member: Member, reason: str, channel: TextChannel): + """ + Notify filtered_member about a moderation action with the reason str + + First attempts to DM the user, fall back to in-channel notification if user has DMs disabled + """ + + try: + await filtered_member.send(reason) + except discord.errors.Forbidden: + await channel.send(f"{filtered_member.mention} {reason}") + def setup(bot: Bot): bot.add_cog(Filtering(bot)) -- cgit v1.2.3 From 44f35ec35818c8355766a08d586691be5d76d860 Mon Sep 17 00:00:00 2001 From: sco1 Date: Wed, 2 Jan 2019 20:32:59 -0500 Subject: Add identification of invite via DM --- bot/cogs/filtering.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 0c62be77e..8adde6312 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -1,7 +1,7 @@ import logging import re -from discord import Colour, Member, Message, TextChannel +from discord import Colour, DMChannel, Member, Message, TextChannel import discord.errors from discord.ext.commands import Bot @@ -126,10 +126,15 @@ class Filtering: triggered = await _filter["function"](msg.content) if triggered: + if isinstance(msg.channel, DMChannel): + channel_str = "via DM" + else: + channel_str = f"in <#{msg.channel.id}>" + message = ( f"The {filter_name} {_filter['type']} was triggered " f"by **{msg.author.name}#{msg.author.discriminator}** " - f"(`{msg.author.id}`) in <#{msg.channel.id}> with [the " + f"(`{msg.author.id}`) {channel_str} with [the " f"following message]({msg.jump_url}):\n\n" f"{msg.content}" ) -- cgit v1.2.3 From 02892a0978ca9e17a64b547b0a65e60a9c82d868 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 3 Jan 2019 15:34:50 +0100 Subject: Fixing empty LinesPaginator and ImagePaginator with '(nothing to display)' and '(no images to display)' when called with empty container object --- bot/pagination.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bot/pagination.py b/bot/pagination.py index 0d8e8aaa3..7eaad2a6d 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -151,6 +151,10 @@ class LinePaginator(Paginator): paginator = cls(prefix=prefix, suffix=suffix, max_size=max_size, max_lines=max_lines) current_page = 0 + if not lines: + log.debug("No lines to add to paginator, adding empty line") + lines.append("(nothing to display)") + for line in lines: try: paginator.add_line(line, empty=empty) @@ -361,6 +365,10 @@ class ImagePaginator(Paginator): paginator = cls(prefix=prefix, suffix=suffix) current_page = 0 + if not pages: + log.debug("No images to add to paginator, adding empty line") + pages.append(("(no images to display)", "")) + for text, image_url in pages: paginator.add_line(text) paginator.add_image(image_url) -- cgit v1.2.3 From eb9018699eab385310b64cf2127328c65c38f5aa Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 3 Jan 2019 16:21:23 +0100 Subject: Clarifying log.debug messages for empty embeds for both Paginators --- bot/pagination.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/pagination.py b/bot/pagination.py index 7eaad2a6d..97468857c 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -152,7 +152,7 @@ class LinePaginator(Paginator): current_page = 0 if not lines: - log.debug("No lines to add to paginator, adding empty line") + log.debug("No lines to add to paginator, adding '(nothing to display)' message") lines.append("(nothing to display)") for line in lines: @@ -366,7 +366,7 @@ class ImagePaginator(Paginator): current_page = 0 if not pages: - log.debug("No images to add to paginator, adding empty line") + log.debug("No images to add to paginator, adding '(no images to display)' message") pages.append(("(no images to display)", "")) for text, image_url in pages: -- cgit v1.2.3 From 0aa4c1b2690096c1e491446e6a64bed434166abd Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 3 Jan 2019 16:26:13 +0100 Subject: Adding optional kwarg to raise Exception on empty paginator embed instead of sending empty embed --- bot/pagination.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/bot/pagination.py b/bot/pagination.py index 97468857c..72cfd83ef 100644 --- a/bot/pagination.py +++ b/bot/pagination.py @@ -17,6 +17,10 @@ PAGINATION_EMOJI = [FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMO log = logging.getLogger(__name__) +class EmptyPaginatorEmbed(Exception): + pass + + class LinePaginator(Paginator): """ A class that aids in paginating code blocks for Discord messages. @@ -96,7 +100,8 @@ class LinePaginator(Paginator): async def paginate(cls, lines: Iterable[str], ctx: Context, embed: Embed, prefix: str = "", suffix: str = "", max_lines: Optional[int] = None, max_size: int = 500, empty: bool = True, restrict_to_user: User = None, timeout: int = 300, - footer_text: str = None): + footer_text: str = None, + exception_on_empty_embed: bool = False): """ Use a paginator and set of reactions to provide pagination over a set of lines. The reactions are used to switch page, or to finish with pagination. @@ -152,6 +157,10 @@ class LinePaginator(Paginator): current_page = 0 if not lines: + if exception_on_empty_embed: + log.exception(f"Pagination asked for empty lines iterable") + raise EmptyPaginatorEmbed("No lines to paginate") + log.debug("No lines to add to paginator, adding '(nothing to display)' message") lines.append("(nothing to display)") @@ -319,7 +328,8 @@ class ImagePaginator(Paginator): @classmethod async def paginate(cls, pages: List[Tuple[str, str]], ctx: Context, embed: Embed, - prefix: str = "", suffix: str = "", timeout: int = 300): + prefix: str = "", suffix: str = "", timeout: int = 300, + exception_on_empty_embed: bool = False): """ Use a paginator and set of reactions to provide pagination over a set of title/image pairs.The reactions are @@ -366,6 +376,10 @@ class ImagePaginator(Paginator): current_page = 0 if not pages: + if exception_on_empty_embed: + log.exception(f"Pagination asked for empty image list") + raise EmptyPaginatorEmbed("No images to paginate") + log.debug("No images to add to paginator, adding '(no images to display)' message") pages.append(("(no images to display)", "")) -- cgit v1.2.3 From c6d6ffd261d711bdc29674b5dace0f16116cf87d Mon Sep 17 00:00:00 2001 From: sco1 Date: Thu, 3 Jan 2019 11:00:07 -0500 Subject: Move user notification bools to config Intentionally hardcode token notification, this is handled by the token remover cog Signed-off-by: sco1 --- bot/cogs/filtering.py | 10 +++++----- bot/constants.py | 6 ++++++ config-default.yml | 7 +++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 8adde6312..fe4009a32 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -44,14 +44,14 @@ class Filtering: "enabled": Filter.filter_zalgo, "function": self._has_zalgo, "type": "filter", - "user_notification": False, + "user_notification": Filter.notify_user_zalgo, "notification_msg": "" }, "filter_invites": { "enabled": Filter.filter_invites, "function": self._has_invites, "type": "filter", - "user_notification": True, + "user_notification": Filter.notify_user_invites, "notification_msg": ( "Per Rule 10, your invite link has been removed. " "If you believe this was a mistake, please the staff know!\n\n" @@ -62,21 +62,21 @@ class Filtering: "enabled": Filter.filter_domains, "function": self._has_urls, "type": "filter", - "user_notification": False, + "user_notification": Filter.notify_user_domains, "notification_msg": "" }, "watch_words": { "enabled": Filter.watch_words, "function": self._has_watchlist_words, "type": "watchlist", - "user_notification": False, + "user_notification": Filter.notify_user_words, "notification_msg": "" }, "watch_tokens": { "enabled": Filter.watch_tokens, "function": self._has_watchlist_tokens, "type": "watchlist", - "user_notification": False, + "user_notification": False, # Hardcode intentional, already in token remover cog "notification_msg": "" }, } diff --git a/bot/constants.py b/bot/constants.py index 99ef98da2..a3845f43a 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -204,6 +204,12 @@ class Filter(metaclass=YAMLGetter): watch_words: bool watch_tokens: bool + notify_user_zalgo: bool + notify_user_invites: bool + notify_user_domains: bool + notify_user_words: bool + # Token notification intentionally ignored, notification is handled by the token remover cog + ping_everyone: bool guild_invite_whitelist: List[int] domain_blacklist: List[str] diff --git a/config-default.yml b/config-default.yml index 3a1ad8052..f9f1a1d9e 100644 --- a/config-default.yml +++ b/config-default.yml @@ -139,6 +139,13 @@ filter: watch_words: true watch_tokens: true + # Notify user on filter? + notify_user_zalgo: false + notify_user_invites: true + notify_user_domains: false + notify_user_words: false + # Token notification intentionally ignored, notification is handled by the token remover cog + # Filter configuration ping_everyone: true # Ping @everyone when we send a mod-alert? -- cgit v1.2.3 From bf4bef4aba69dd85ec174a14690efbbfdb824515 Mon Sep 17 00:00:00 2001 From: Daniel Brown Date: Thu, 3 Jan 2019 12:55:08 -0600 Subject: Hopefully the final fix to the code block on-edit deletion thing Signed-off-by: Daniel Brown --- bot/cogs/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index b684ad886..b419dad58 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -372,7 +372,7 @@ class Bot: return # Retrieve channel and message objects for use later - channel = self.bot.get_channel(payload.data.get("channel_id")) + channel = self.bot.get_channel(int(payload.data.get("channel_id"))) user_message = await channel.get_message(payload.message_id) # Checks to see if the user has corrected their codeblock. If it's fixed, has_fixed_codeblock will be None -- cgit v1.2.3 From cbf2a50853cf81306fb7254de2b646bfabf90ff7 Mon Sep 17 00:00:00 2001 From: Daniel Brown Date: Thu, 3 Jan 2019 13:28:19 -0600 Subject: Added a log message to go along with messages bot messages being removed. Signed-off-by: Daniel Brown --- bot/cogs/bot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/cogs/bot.py b/bot/cogs/bot.py index b419dad58..a6d9aa278 100644 --- a/bot/cogs/bot.py +++ b/bot/cogs/bot.py @@ -383,6 +383,7 @@ class Bot: bot_message = await channel.get_message(self.codeblock_message_ids[payload.message_id]) await bot_message.delete() del self.codeblock_message_ids[payload.message_id] + log.trace("User's incorrect code block has been fixed. Removing bot formatting message.") def setup(bot): -- cgit v1.2.3 From 0453841a722bedd71392cc79132cfe4c87bee25d Mon Sep 17 00:00:00 2001 From: sco1 Date: Thu, 3 Jan 2019 16:54:03 -0500 Subject: Fix import order --- bot/cogs/filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index fe4009a32..27ec6aad9 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -1,8 +1,8 @@ import logging import re -from discord import Colour, DMChannel, Member, Message, TextChannel import discord.errors +from discord import Colour, DMChannel, Member, Message, TextChannel from discord.ext.commands import Bot from bot.cogs.modlog import ModLog -- cgit v1.2.3 From 92f5466402a3abf9a7771d0c677286306060030d Mon Sep 17 00:00:00 2001 From: sco1 Date: Thu, 3 Jan 2019 17:17:57 -0500 Subject: Add pre-commit dev dependency & flake8 configuration Relock pipenv Add pre-commit pipenv script --- .pre-commit-config.yaml | 5 + Pipfile | 7 +- Pipfile.lock | 419 ++++++++++++++++++++++++++++-------------------- 3 files changed, 255 insertions(+), 176 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..1d75342a2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.0.0 + hooks: + - id: flake8 \ No newline at end of file diff --git a/Pipfile b/Pipfile index 179b317df..3df5ea7c0 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -discord-py = {git = "https://github.com/Rapptz/discord.py.git", extras = ["voice"], ref = "860d6a9ace8248dfeec18b8b159e7b757d9f56bb", editable = true} +discord-py = {git = "https://github.com/Rapptz/discord.py.git",extras = ["voice"],ref = "860d6a9ace8248dfeec18b8b159e7b757d9f56bb",editable = true} dulwich = "*" aiodns = "*" logmatic-python = "*" @@ -29,6 +29,7 @@ requests = "*" "flake8-string-format" = "*" safety = "*" dodgy = "*" +pre-commit = "*" [requires] python_version = "3.6" @@ -36,12 +37,10 @@ python_version = "3.6" [scripts] start = "python -m bot" lint = "python -m flake8" - +precommit = "pre-commit install" build = "docker build -t pythondiscord/bot:latest -f docker/bot.Dockerfile ." push = "docker push pythondiscord/bot:latest" - buildbase = "docker build -t pythondiscord/bot-base:latest -f docker/base.Dockerfile ." pushbase = "docker push pythondiscord/bot-base:latest" - buildci = "docker build -t pythondiscord/bot-ci:latest -f docker/ci.Dockerfile ." pushci = "docker push pythondiscord/bot-ci:latest" diff --git a/Pipfile.lock b/Pipfile.lock index 506b17065..92566c3ed 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "79a3c633f145dbf93ba5b2460d3f49346495328af7302e59be326e9324785cf3" + "sha256": "d53f89c6d3b32ccbc2dadaff1a7e9ee1bdcbd1df9cddab35def36bcceec98b27" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "aio-pika": { "hashes": [ - "sha256:6438e72963e459552f196a07a081a5f6dc54d42a474292b8497bd4a59554fc85", - "sha256:dc15b451dca6d2b1c504ab353e3f2fe7e7e252fdb1c219261b5412e1cafbc72d" + "sha256:c3eb639f7fc5c96355e7a227380989c9e0f342bb6612e6671ea76d188813ba45", + "sha256:ea26efd262d7c4cd4ac00fb968ede89e82c00ad331b47415e3c2353a4b91cbe0" ], "index": "pypi", - "version": "==4.6.3" + "version": "==4.9.1" }, "aiodns": { "hashes": [ @@ -57,6 +57,7 @@ "sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0", "sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07" ], + "index": "pypi", "version": "==3.4.4" }, "alabaster": { @@ -89,18 +90,18 @@ }, "beautifulsoup4": { "hashes": [ - "sha256:194ec62a25438adcb3fdb06378b26559eda1ea8a747367d34c33cef9c7f48d57", - "sha256:90f8e61121d6ae58362ce3bed8cd997efb00c914eae0ff3d363c32f9a9822d10", - "sha256:f0abd31228055d698bb392a826528ea08ebb9959e6bea17c606fd9c9009db938" + "sha256:1ed70a0e99742653953d68462378a1a8eb65dca5f7c8fa44a05a2a0b3545df67", + "sha256:6a7f5e0efc563cd1ffeefba6d528b97aa0d313c02dd126ba6c455e5fe5bd48eb", + "sha256:e394827904cc4923f443e8dd2e9968343669c8e1ad7a8d62d7541e780884acb8" ], - "version": "==4.6.3" + "version": "==4.7.0" }, "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" ], - "version": "==2018.10.15" + "version": "==2018.11.29" }, "cffi": { "hashes": [ @@ -188,17 +189,10 @@ }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" - ], - "version": "==2.7" - }, - "idna-ssl": { - "hashes": [ - "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "markers": "python_version < '3.7'", - "version": "==1.1.0" + "version": "==2.8" }, "imagesize": { "hashes": [ @@ -231,39 +225,39 @@ }, "lxml": { "hashes": [ - "sha256:02bc220d61f46e9b9d5a53c361ef95e9f5e1d27171cd461dddb17677ae2289a5", - "sha256:22f253b542a342755f6cfc047fe4d3a296515cf9b542bc6e261af45a80b8caf6", - "sha256:2f31145c7ff665b330919bfa44aacd3a0211a76ca7e7b441039d2a0b0451e415", - "sha256:36720698c29e7a9626a0dc802ef8885f8f0239bfd1689628ecd459a061f2807f", - "sha256:438a1b0203545521f6616132bfe0f4bca86f8a401364008b30e2b26ec408ce85", - "sha256:4815892904c336bbaf73dafd54f45f69f4021c22b5bad7332176bbf4fb830568", - "sha256:5be031b0f15ad63910d8e5038b489d95a79929513b3634ad4babf77100602588", - "sha256:5c93ae37c3c588e829b037fdfbd64a6e40c901d3f93f7beed6d724c44829a3ad", - "sha256:60842230678674cdac4a1cf0f707ef12d75b9a4fc4a565add4f710b5fcf185d5", - "sha256:62939a8bb6758d1bf923aa1c13f0bcfa9bf5b2fc0f5fa917a6e25db5fe0cfa4e", - "sha256:75830c06a62fe7b8fe3bbb5f269f0b308f19f3949ac81cfd40062f47c1455faf", - "sha256:81992565b74332c7c1aff6a913a3e906771aa81c9d0c68c68113cffcae45bc53", - "sha256:8c892fb0ee52c594d9a7751c7d7356056a9682674b92cc1c4dc968ff0f30c52f", - "sha256:9d862e3cf4fc1f2837dedce9c42269c8c76d027e49820a548ac89fdcee1e361f", - "sha256:a623965c086a6e91bb703d4da62dabe59fe88888e82c4117d544e11fd74835d6", - "sha256:a7783ab7f6a508b0510490cef9f857b763d796ba7476d9703f89722928d1e113", - "sha256:aab09fbe8abfa3b9ce62aaf45aca2d28726b1b9ee44871dbe644050a2fff4940", - "sha256:abf181934ac3ef193832fb973fd7f6149b5c531903c2ec0f1220941d73eee601", - "sha256:ae07fa0c115733fce1e9da96a3ac3fa24801742ca17e917e0c79d63a01eeb843", - "sha256:b9c78242219f674ab645ec571c9a95d70f381319a23911941cd2358a8e0521cf", - "sha256:bccb267678b870d9782c3b44d0cefe3ba0e329f9af8c946d32bf3778e7a4f271", - "sha256:c4df4d27f4c93b2cef74579f00b1d3a31a929c7d8023f870c4b476f03a274db4", - "sha256:caf0e50b546bb60dfa99bb18dfa6748458a83131ecdceaf5c071d74907e7e78a", - "sha256:d3266bd3ac59ac4edcd5fa75165dee80b94a3e5c91049df5f7c057ccf097551c", - "sha256:db0d213987bcd4e6d41710fb4532b22315b0d8fb439ff901782234456556aed1", - "sha256:dbbd5cf7690a40a9f0a9325ab480d0fccf46d16b378eefc08e195d84299bfae1", - "sha256:e16e07a0ec3a75b5ee61f2b1003c35696738f937dc8148fbda9fe2147ccb6e61", - "sha256:e175a006725c7faadbe69e791877d09936c0ef2cf49d01b60a6c1efcb0e8be6f", - "sha256:edd9c13a97f6550f9da2236126bb51c092b3b1ce6187f2bd966533ad794bbb5e", - "sha256:fa39ea60d527fbdd94215b5e5552f1c6a912624521093f1384a491a8ad89ad8b" + "sha256:16cf8bac33ec17049617186d63006ba49da7c5be417042877a49f0ef6d7a195d", + "sha256:18f2d8f14cc61e66e8a45f740d15b6fc683c096f733db1f8d0ee15bcac9843de", + "sha256:260868f69d14a64dd1de9cf92e133d2f71514d288de4906f109bdf48ca9b756a", + "sha256:29b8acd8ecdf772266dbac491f203c71664b0b07ad4309ba2c3bb131306332fc", + "sha256:2b05e5e06f8e8c63595472dc887d0d6e0250af754a35ba690f6a6abf2ef85691", + "sha256:30d6ec05fb607a5b7345549f642c7c7a5b747b634f6d5e935596b910f243f96f", + "sha256:3bf683f0237449ebc1851098f664410e3c99ba3faa8c9cc82c6acfe857df1767", + "sha256:3ce5488121eb15513c4b239dadd67f9e7959511bd766aac6be0c35e80274f298", + "sha256:48be0c375350a5519bb9474b42a9c0e7ab709fb45f11bfcd33de876791137896", + "sha256:49bc343ca3b30cd860845433bb9f62448a54ff87b632175108bacbc5dc63e49e", + "sha256:4cc7531e86a43ea66601763c5914c3d3adb297f32e4284957609b90d41825fca", + "sha256:4e9822fad564d82035f0b6d701a890444560210f8a8648b8f15850f8fe883cd9", + "sha256:51a9a441aefc8c93512bad5efe867d2ff086e7249ce0fc3b47c310644b352936", + "sha256:5bbed9efc8aeb69929140f71a30e655bf496b45b766861513960e1b11168d475", + "sha256:60a5323b2bc893ca1059d283d6695a172d51cc95a70c25b3e587e1aad5459c38", + "sha256:7035d9361f3ceec9ccc1dd3482094d1174580e7e1bf6870b77ea758f7cad15d2", + "sha256:76d62cc048bda0ebf476689ad3eb8e65e6827e43a7521be3b163071020667b8c", + "sha256:78163b578e6d1836012febaa1865e095ccc7fc826964dd69a2dbfe401618a1f7", + "sha256:83b58b2b5904d50de03a47e2f56d24e9da4cf7e3b0d66fb4510b18fca0faf910", + "sha256:a07447e46fffa5bb4d7a0af0a6505c8517e9bd197cfd2aec79e499b6e86cde49", + "sha256:a17d808b3edca4aaf6b295b5a388c844a0b7f79aca2d79eec5acc1461db739e3", + "sha256:a378fd61022cf4d3b492134c3bc48204ac2ff19e0813b23e07c3dd95ae8df0bc", + "sha256:aa7d096a44ae3d475c5ed763e24cf302d32462e78b61bba73ce1ad0efb8f522a", + "sha256:ade8785c93a985956ba6499d5ea6d0a362e24b4a9ba07dd18920fd67cccf63ea", + "sha256:cc039668f91d8af8c4094cfb5a67c7ae733967fdc84c0507fe271db81480d367", + "sha256:d89f1ffe98744c4b5c11f00fb843a4e72f68a6279b5e38168167f1b3c0fdd84c", + "sha256:e691b6ef6e27437860016bd6c32e481bdc2ed3af03289707a38b9ca422105f40", + "sha256:e750da6ac3ca624ae3303df448664012f9b6f9dfbc5d50048ea8a12ce2f8bc29", + "sha256:eca305b200549906ea25648463aeb1b3b220b716415183eaa99c998a846936d9", + "sha256:f52fe795e08858192eea167290033b5ff24f50f51781cb78d989e8d63cfe73d1" ], "index": "pypi", - "version": "==4.2.5" + "version": "==4.2.6" }, "markdownify": { "hashes": [ @@ -307,37 +301,37 @@ }, "multidict": { "hashes": [ - "sha256:013eb6591ab95173fd3deb7667d80951abac80100335b3e97b5fa778c1bb4b91", - "sha256:0bffbbbb48db35f57dfb4733e943ac8178efb31aab5601cb7b303ee228ce96af", - "sha256:1a34aab1dfba492407c757532f665ba3282ec4a40b0d2f678bda828ef422ebb7", - "sha256:1b4b46a33f459a2951b0fd26c2d80639810631eb99b3d846d298b02d28a3e31d", - "sha256:1d616d80c37a388891bf760d64bc50cac7c61dbb7d7013f2373aa4b44936e9f0", - "sha256:225aefa7befbe05bd0116ef87e8cd76cbf4ac39457a66faf7fb5f3c2d7bea19a", - "sha256:2c9b28985ef7c830d5c7ea344d068bcdee22f8b6c251369dea98c3a814713d44", - "sha256:39e0600f8dd72acb011d09960da560ba3451b1eca8de5557c15705afc9d35f0e", - "sha256:3c642c40ea1ca074397698446893a45cd6059d5d071fc3ba3915c430c125320f", - "sha256:42357c90b488fac38852bcd7b31dcd36b1e2325413960304c28b8d98e6ff5fd4", - "sha256:6ac668f27dbdf8a69c31252f501e128a69a60b43a44e43d712fb58ce3e5dfcca", - "sha256:713683da2e3f1dd81a920c995df5dda51f1fff2b3995f5864c3ee782fcdcb96c", - "sha256:73b6e7853b6d3bc0eac795044e700467631dff37a5a33d3230122b03076ac2f9", - "sha256:77534c1b9f4a5d0962392cad3f668d1a04036b807618e3357eb2c50d8b05f7f7", - "sha256:77b579ef57e27457064bb6bb4c8e5ede866af071af60fe3576226136048c6dfa", - "sha256:82cf28f18c935d66c15a6f82fda766a4138d21e78532a1946b8ec603019ba0b8", - "sha256:937e8f12f9edc0d2e351c09fc3e7335a65eefb75406339d488ee46ef241f75d8", - "sha256:985dbf59e92f475573a04598f9a00f92b4fdb64fc41f1df2ea6f33b689319537", - "sha256:9c4fab7599ba8c0dbf829272c48c519625c2b7f5630b49925802f1af3a77f1f4", - "sha256:9e8772be8455b49a85ad6dbf6ce433da7856ba481d6db36f53507ae540823b15", - "sha256:a06d6d88ce3be4b54deabd078810e3c077a8b2e20f0ce541c979b5dd49337031", - "sha256:a1da0cdc3bc45315d313af976dab900888dbb477d812997ee0e6e4ea43d325e5", - "sha256:a6652466a4800e9fde04bf0252e914fff5f05e2a40ee1453db898149624dfe04", - "sha256:a7f23523ea6a01f77e0c6da8aae37ab7943e35630a8d2eda7e49502f36b51b46", - "sha256:a87429da49f4c9fb37a6a171fa38b59a99efdeabffb34b4255a7a849ffd74a20", - "sha256:c26bb81d0d19619367a96593a097baec2d5a7b3a0cfd1e3a9470277505a465c2", - "sha256:d4f4545edb4987f00fde44241cef436bf6471aaac7d21c6bbd497cca6049f613", - "sha256:daabc2766a2b76b3bec2086954c48d5f215f75a335eaee1e89c8357922a3c4d5", - "sha256:f08c1dcac70b558183b3b755b92f1135a76fd1caa04009b89ddea57a815599aa" - ], - "version": "==4.5.1" + "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", + "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", + "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", + "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", + "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", + "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", + "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", + "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", + "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", + "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", + "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", + "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", + "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", + "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", + "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", + "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", + "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", + "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", + "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", + "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", + "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", + "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", + "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", + "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", + "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", + "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", + "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", + "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", + "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b" + ], + "version": "==4.5.2" }, "packaging": { "hashes": [ @@ -348,78 +342,81 @@ }, "pillow": { "hashes": [ - "sha256:00203f406818c3f45d47bb8fe7e67d3feddb8dcbbd45a289a1de7dd789226360", - "sha256:0616f800f348664e694dddb0b0c88d26761dd5e9f34e1ed7b7a7d2da14b40cb7", - "sha256:1f7908aab90c92ad85af9d2fec5fc79456a89b3adcc26314d2cde0e238bd789e", - "sha256:2ea3517cd5779843de8a759c2349a3cd8d3893e03ab47053b66d5ec6f8bc4f93", - "sha256:48a9f0538c91fc136b3a576bee0e7cd174773dc9920b310c21dcb5519722e82c", - "sha256:5280ebc42641a1283b7b1f2c20e5b936692198b9dd9995527c18b794850be1a8", - "sha256:5e34e4b5764af65551647f5cc67cf5198c1d05621781d5173b342e5e55bf023b", - "sha256:63b120421ab85cad909792583f83b6ca3584610c2fe70751e23f606a3c2e87f0", - "sha256:696b5e0109fe368d0057f484e2e91717b49a03f1e310f857f133a4acec9f91dd", - "sha256:870ed021a42b1b02b5fe4a739ea735f671a84128c0a666c705db2cb9abd528eb", - "sha256:916da1c19e4012d06a372127d7140dae894806fad67ef44330e5600d77833581", - "sha256:9303a289fa0811e1c6abd9ddebfc770556d7c3311cb2b32eff72164ddc49bc64", - "sha256:9577888ecc0ad7d06c3746afaba339c94d62b59da16f7a5d1cff9e491f23dace", - "sha256:987e1c94a33c93d9b209315bfda9faa54b8edfce6438a1e93ae866ba20de5956", - "sha256:99a3bbdbb844f4fb5d6dd59fac836a40749781c1fa63c563bc216c27aef63f60", - "sha256:99db8dc3097ceafbcff9cb2bff384b974795edeb11d167d391a02c7bfeeb6e16", - "sha256:a5a96cf49eb580756a44ecf12949e52f211e20bffbf5a95760ac14b1e499cd37", - "sha256:aa6ca3eb56704cdc0d876fc6047ffd5ee960caad52452fbee0f99908a141a0ae", - "sha256:aade5e66795c94e4a2b2624affeea8979648d1b0ae3fcee17e74e2c647fc4a8a", - "sha256:b78905860336c1d292409e3df6ad39cc1f1c7f0964e66844bbc2ebfca434d073", - "sha256:b92f521cdc4e4a3041cc343625b699f20b0b5f976793fb45681aac1efda565f8", - "sha256:bfde84bbd6ae5f782206d454b67b7ee8f7f818c29b99fd02bf022fd33bab14cb", - "sha256:c2b62d3df80e694c0e4a0ed47754c9480521e25642251b3ab1dff050a4e60409", - "sha256:c5e2be6c263b64f6f7656e23e18a4a9980cffc671442795682e8c4e4f815dd9f", - "sha256:c99aa3c63104e0818ec566f8ff3942fb7c7a8f35f9912cb63fd8e12318b214b2", - "sha256:dae06620d3978da346375ebf88b9e2dd7d151335ba668c995aea9ed07af7add4", - "sha256:db5499d0710823fa4fb88206050d46544e8f0e0136a9a5f5570b026584c8fd74", - "sha256:f36baafd82119c4a114b9518202f2a983819101dcc14b26e43fc12cbefdce00e", - "sha256:f52b79c8796d81391ab295b04e520bda6feed54d54931708872e8f9ae9db0ea1", - "sha256:ff8cff01582fa1a7e533cb97f628531c4014af4b5f38e33cdcfe5eec29b6d888" + "sha256:0cd42fe2d99ec6ce23aaf00947a7b7956ad2ed4b1695fd37545c3b8eae06d95a", + "sha256:137bed8972089d65da63fb79b4949b0f2b99e9a58f1b494e82be43ba8b0f4226", + "sha256:14eb2b2e4f2a14f5c89fd0edf55c5af0bf1a40fdf3838d81867f26f131cd557d", + "sha256:1fc43ce8c4fa3754222cd6831d599ad17ca2fc9868d2fb52f4e5362dfbfaf379", + "sha256:26dfeee23a86dad6277a63d18f61f53b957cb2cd3506dbbd74b88ba2fa65b3b1", + "sha256:2e0e582942e025cc58f669499a8e0bffde5bcc8d42b65729f294c1dac54e4672", + "sha256:3bb8dd3ce101dd8b0b37eaae924a5bb93abb6ffdd034bf68a066a808e11768ab", + "sha256:3f07da3874f0b085421f1d4f979785131aa9d497501d8610d82f7378b33858f8", + "sha256:429b2b5ae5f57f8fd9ec2e012c1e7b342ff10f1a8977dc291976b9a3b4c096e1", + "sha256:4a000fdd89d77b6b675de27e1ab91c6fba517c08f19ee83e6716b78930634e04", + "sha256:4ccbe7cce6156391a3ecf447c79a7d4a1a0ecd3de79bdec9ca5e4f7242a306d1", + "sha256:4d08034196db41acb7392e4fccfc0448e7a87192c41d3011ad4093eac2c31ffd", + "sha256:6b202b1cb524bc76ed52a7eb0314f4b0a0497c7cceb9a93539b5a25800e1f2b6", + "sha256:8563b56fa7c34f1606848c2143ea67d27cf225b9726a1b041c3d27cf85e46edc", + "sha256:86d7421e8803d7bae2e594765c378a867b629d46b32fbfe5ed9fd95b30989feb", + "sha256:8d4bddedcb4ab99131d9705a75720efc48b3d006122dae1a4cc329496ac47c9a", + "sha256:a4929c6de9590635c34533609402c9da12b22bfc2feb8c0c4f38c39bab48a9ad", + "sha256:b0736e21798448cee3e663c0df7a6dfa83d805b3f3a45e67f7457a2f019e5fca", + "sha256:b669acba91d47395de84c9ca52a7ad393b487e5ae2e20b9b2790b22a57d479fa", + "sha256:bba993443921f2d077195b425a3283357f52b07807d53704610c1249d20b183a", + "sha256:bdf706a93d00547c9443b2654ae424fd54d5dece4bc4333e7035740aeb7a7cea", + "sha256:c5aa93e55175b9cde95279ccd03c93d218976b376480222d37be41d2c9c54510", + "sha256:cc11fd997d8ad71bb0412e983b711e49639c2ddba9b9dce04d4bdab575fe5f84", + "sha256:d584f1c33995c3dc16a35e30ef43e0881fa0d085f0fef29cebf154ffb5643363", + "sha256:d88f54bdefb7ddccb68efdd710d689aa6a09b875cc3e44b7e81ef54e0751e3a7", + "sha256:de0d323072be72fa4d74f4e013cd594e3f8ee03b2e0eac5876a3249fa076ef7b", + "sha256:f139c963c6679d236b2c45369524338eabd36a853fe23abd39ba246ab0a75aec", + "sha256:f41c0bf667c4c1c30b873eaa8d6bb894f6d721b3e38e9c993bddd1263c02fb1f", + "sha256:fbd0ea468b4ec04270533bf5206f1cd57746fcf226520bb133318fa276de2644", + "sha256:fe2d2850521c467c915ff0a6e27dc64c3c04c2f66612e0072672bd1bd4854b61" ], "index": "pypi", - "version": "==5.3.0" + "version": "==5.4.0" }, "pycares": { "hashes": [ - "sha256:0e81c971236bb0767354f1456e67ab6ae305f248565ce77cd413a311f9572bf5", - "sha256:11c0ff3ccdb5a838cbd59a4e59df35d31355a80a61393bca786ca3b44569ba10", - "sha256:170d62bd300999227e64da4fa85459728cc96e62e44780bbc86a915fdae01f78", - "sha256:36f4c03df57c41a87eb3d642201684eb5a8bc194f4bafaa9f60ee6dc0aef8e40", - "sha256:371ce688776da984c4105c8ca760cc60944b9b49ccf8335c71dc7669335e6173", - "sha256:3a2234516f7db495083d8bba0ccdaabae587e62cfcd1b8154d5d0b09d3a48dfc", - "sha256:3f288586592c697109b2b06e3988b7e17d9765887b5fc367010ee8500cbddc86", - "sha256:40134cee03c8bbfbc644d4c0bc81796e12dd012a5257fb146c5a5417812ee5f7", - "sha256:722f5d2c5f78d47b13b0112f6daff43ce4e08e8152319524d14f1f917cc5125e", - "sha256:7b18fab0ed534a898552df91bc804bd62bb3a2646c11e054baca14d23663e1d6", - "sha256:8a39d03bd99ea191f86b990ef67ecce878d6bf6518c5cde9173fb34fb36beb5e", - "sha256:8ea263de8bf1a30b0d87150b4aa0e3203cf93bc1723ea3e7408a7d25e1299217", - "sha256:943e2dc67ff45ab4c81d628c959837d01561d7e185080ab7a276b8ca67573fb5", - "sha256:9d56a54c93e64b30c0d31f394d9890f175edec029cd846221728f99263cdee82", - "sha256:b95b339c11d824f0bb789d31b91c8534916fcbdce248cccce216fa2630bb8a90", - "sha256:bbfd9aba1e172cd2ab7b7142d49b28cf44d6451c4a66a870aff1dc3cb84849c7", - "sha256:d8637bcc2f901aa61ec1d754abc862f9f145cb0346a0249360df4c159377018e", - "sha256:e2446577eeea79d2179c9469d9d4ce3ab8a07d7985465c3cb91e7d74abc329b6", - "sha256:e72fa163f37ae3b09f143cc6690a36f012d13e905d142e1beed4ec0e593ff657", - "sha256:f32b7c63094749fbc0c1106c9a785666ec8afd49ecfe7002a30bb7c42e62b47c", - "sha256:f50be4dd53f009cfb4b98c3c6b240e18ff9b17e3f1c320bd594bb83eddabfcb2" + "sha256:080ae0f1b1b754be60b6ef31b9ab2915364c210eb1cb4d8e089357c89d7b9819", + "sha256:0eccb76dff0155ddf793a589c6270e1bdbf6975b2824d18d1d23db2075d7fc96", + "sha256:223a03d69e864a18d7bb2e0108bca5ba069ef91e5b048b953ed90ea9f50eb77f", + "sha256:289e49f98adfd7a2ae3656df26e1d62cf49a06bbc03ced63f243c22cd8919adf", + "sha256:292ac442a1d4ff27d41be748ec19f0c4ff47efebfb715064ba336564ea0f2071", + "sha256:34771095123da0e54597fe3c5585a28d3799945257e51b378a20778bf33573b6", + "sha256:34c8865f2d047be4c301ce90a916c7748be597e271c5c7932e8b9a6de85840f4", + "sha256:36af260b215f86ebfe4a5e4aea82fd6036168a5710cbf8aad77019ab52156dda", + "sha256:5e8e2a461717da40482b5fecf1119116234922d29660b3c3e01cbc5ba2cbf4bd", + "sha256:61e77bd75542c56dff49434fedbafb25604997bc57dc0ebf791a5732503cb1bb", + "sha256:691740c332f38a9035b4c6d1f0e6c8af239466ef2373a894d4393f0ea65c815d", + "sha256:6bc0e0fdcb4cdc4ca06aa0b07e6e3560d62b2af79ef0ea4589835fcd2059012b", + "sha256:96db5c93e2fe2e39f519efb7bb9d86aef56f5813fa0b032e47aba329fa925d57", + "sha256:af701b22c91b3e36f65ee9f4b1bc2fe4800c8ed486eb6ef203624acbe53d026d", + "sha256:b25bd21bba9c43d44320b719118c2ce35e4a78031f61d906caeb01316d49dafb", + "sha256:c42f68319f8ea2322ed81c31a86c4e60547e6e90f3ebef479a7a7540bddbf268", + "sha256:cc9a8d35af12bc5f484f3496f9cb3ab5bedfa4dcf3dfff953099453d88b659a7", + "sha256:dfee9d198ba6d6f29aa5bf510bfb2c28a60c3f308116f114c9fd311980d3e870", + "sha256:e1dd02e110a7a97582097ebba6713d9da28583b538c08e8a14bc82169c5d3e10", + "sha256:e48c586c80a139c6c7fb0298b944d1c40752cf839bc8584cc793e42a8971ba6c", + "sha256:f509762dec1a70eac32b86c098f37ac9c5d3d4a8a9098983328377c9e71543b2", + "sha256:f8e0d61733843844f9019c911d5676818d99c4cd2c54b91de58384c7d962862b", + "sha256:fe20280fed496deba60e0f6437b7672bdc83bf45e243bb546af47c60c85bcfbc" ], - "version": "==2.3.0" + "version": "==2.4.0" }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3", + "sha256:db32bd592ba104f8fbb8047c18cd897f0f20d0909ba0ec5dc72a3221f6a82e15" ], "version": "==2.19" }, "pygments": { "hashes": [ - "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a", + "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d" ], - "version": "==2.2.0" + "version": "==2.3.1" }, "pynacl": { "hashes": [ @@ -505,11 +502,11 @@ }, "requests": { "hashes": [ - "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", - "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], "index": "pypi", - "version": "==2.20.1" + "version": "==2.21.0" }, "shortuuid": { "hashes": [ @@ -519,10 +516,10 @@ }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "snowballstemmer": { "hashes": [ @@ -531,13 +528,20 @@ ], "version": "==1.2.1" }, + "soupsieve": { + "hashes": [ + "sha256:057e08f362a255b457a5781675211556799ed3bb8807506eaac3809390bc304b", + "sha256:f7d99b41637be2f249dfcc06ae93c13fcbbdfa7bb68b15308cdd0734e58146f1" + ], + "version": "==1.6.1" + }, "sphinx": { "hashes": [ - "sha256:120732cbddb1b2364471c3d9f8bfd4b0c5b550862f99a65736c77f970b142aea", - "sha256:b348790776490894e0424101af9c8413f2a86831524bd55c5f379d3e3e12ca64" + "sha256:429e3172466df289f0f742471d7e30ba3ee11f3b5aecd9a840480d03f14bcfe5", + "sha256:c4cb17ba44acffae3d3209646b6baec1e215cad3065e852c68cc569d4df1b9f8" ], "index": "pypi", - "version": "==1.8.2" + "version": "==1.8.3" }, "sphinxcontrib-websupport": { "hashes": [ @@ -581,20 +585,29 @@ }, "yarl": { "hashes": [ - "sha256:2556b779125621b311844a072e0ed367e8409a18fa12cbd68eb1258d187820f9", - "sha256:4aec0769f1799a9d4496827292c02a7b1f75c0bab56ab2b60dd94ebb57cbd5ee", - "sha256:55369d95afaacf2fa6b49c84d18b51f1704a6560c432a0f9a1aeb23f7b971308", - "sha256:6c098b85442c8fe3303e708bbb775afd0f6b29f77612e8892627bcab4b939357", - "sha256:9182cd6f93412d32e009020a44d6d170d2093646464a88aeec2aef50592f8c78", - "sha256:c8cbc21bbfa1dd7d5386d48cc814fe3d35b80f60299cdde9279046f399c3b0d8", - "sha256:db6f70a4b09cde813a4807843abaaa60f3b15fb4a2a06f9ae9c311472662daa1", - "sha256:f17495e6fe3d377e3faac68121caef6f974fcb9e046bc075bcff40d8e5cc69a4", - "sha256:f85900b9cca0c67767bb61b2b9bd53208aaa7373dae633dbe25d179b4bf38aa7" - ], - "version": "==1.2.6" + "sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9", + "sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f", + "sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb", + "sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320", + "sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842", + "sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0", + "sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829", + "sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310", + "sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4", + "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", + "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1" + ], + "version": "==1.3.0" } }, "develop": { + "aspy.yaml": { + "hashes": [ + "sha256:04d26279513618f1024e1aba46471db870b3b33aef204c2d09bcf93bea9ba13f", + "sha256:0a77e23fafe7b242068ffc0252cee130d3e509040908fc678d9d1060e7494baa" + ], + "version": "==1.1.1" + }, "attrs": { "hashes": [ "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", @@ -602,12 +615,26 @@ ], "version": "==18.2.0" }, + "cached-property": { + "hashes": [ + "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", + "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504" + ], + "version": "==1.5.1" + }, "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" ], - "version": "==2018.10.15" + "version": "==2018.11.29" + }, + "cfgv": { + "hashes": [ + "sha256:73f48a752bd7aab103c4b882d6596c6360b7aa63b34073dd2c35c7b4b8f93010", + "sha256:d1791caa9ff5c0c7bce80e7ecc1921752a2eb7c2463a08ed9b6c96b85a2f75aa" + ], + "version": "==1.1.0" }, "chardet": { "hashes": [ @@ -684,12 +711,26 @@ "index": "pypi", "version": "==0.7" }, + "identify": { + "hashes": [ + "sha256:08826e68e39e7de53cc2ddd8f6228a4e463b4bacb20565e5301c3ec690e68d27", + "sha256:2364e24a7699fea0dc910e90740adbab43eef3746eeea4e016029c34123ce66d" + ], + "version": "==1.1.8" + }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "importlib-metadata": { + "hashes": [ + "sha256:a17ce1a8c7bff1e8674cb12c992375d8d0800c9190177ecf0ad93e0097224095", + "sha256:b50191ead8c70adfa12495fba19ce6d75f2e0275c14c5a7beb653d6799b512bd" ], - "version": "==2.7" + "version": "==0.8" }, "mccabe": { "hashes": [ @@ -698,6 +739,12 @@ ], "version": "==0.6.1" }, + "nodeenv": { + "hashes": [ + "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a" + ], + "version": "==1.3.3" + }, "packaging": { "hashes": [ "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807", @@ -705,6 +752,14 @@ ], "version": "==18.0" }, + "pre-commit": { + "hashes": [ + "sha256:33bb9bf599c334d458fa9e311bde54e0c306a651473b6a36fdb36a61c8605c89", + "sha256:e233f5cf3230ae9ed9ada132e9cf6890e18cc937adc669353fb64394f6e80c17" + ], + "index": "pypi", + "version": "==1.13.0" + }, "pycodestyle": { "hashes": [ "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", @@ -745,11 +800,11 @@ }, "requests": { "hashes": [ - "sha256:65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", - "sha256:ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], "index": "pypi", - "version": "==2.20.1" + "version": "==2.21.0" }, "safety": { "hashes": [ @@ -761,10 +816,17 @@ }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" }, "urllib3": { "hashes": [ @@ -772,6 +834,19 @@ "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], "version": "==1.24.1" + }, + "virtualenv": { + "hashes": [ + "sha256:34b9ae3742abed2f95d3970acf4d80533261d6061b51160b197f84e5b4c98b4c" + ], + "version": "==16.2.0" + }, + "zipp": { + "hashes": [ + "sha256:55ca87266c38af6658b84db8cfb7343cdb0bf275f93c7afaea0d8e7a209c7478", + "sha256:682b3e1c62b7026afe24eadf6be579fb45fec54c07ea218bded8092af07a68c4" + ], + "version": "==0.3.3" } } } -- cgit v1.2.3 From 069b7373b3b9283a4d7c59a82980e98578cb6120 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 5 Jan 2019 14:39:19 -0500 Subject: Add notification strings for remaining filters Hardcode watch words filter notification to False since it's a watchlist. This logic should be reworked eventually so we ignore user notifications for watchlist type filters. --- bot/cogs/filtering.py | 15 ++++++++++----- bot/constants.py | 2 +- config-default.yml | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 27ec6aad9..701798d18 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -39,13 +39,17 @@ class Filtering: def __init__(self, bot: Bot): self.bot = bot + _staff_mistake_str = "If you believe this was a mistake, please let staff know!" self.filters = { "filter_zalgo": { "enabled": Filter.filter_zalgo, "function": self._has_zalgo, "type": "filter", "user_notification": Filter.notify_user_zalgo, - "notification_msg": "" + "notification_msg": ( + "Your post has been removed for abusing Unicode character rendering (aka Zalgo text). " + f"{_staff_mistake_str}" + ) }, "filter_invites": { "enabled": Filter.filter_invites, @@ -53,8 +57,7 @@ class Filtering: "type": "filter", "user_notification": Filter.notify_user_invites, "notification_msg": ( - "Per Rule 10, your invite link has been removed. " - "If you believe this was a mistake, please the staff know!\n\n" + f"Per Rule 10, your invite link has been removed. {_staff_mistake_str}\n\n" r"Our server rules can be found here: " ) }, @@ -63,13 +66,15 @@ class Filtering: "function": self._has_urls, "type": "filter", "user_notification": Filter.notify_user_domains, - "notification_msg": "" + "notification_msg": ( + f"Your URL has been removed because it matched a blacklisted domain. {_staff_mistake_str}" + ) }, "watch_words": { "enabled": Filter.watch_words, "function": self._has_watchlist_words, "type": "watchlist", - "user_notification": Filter.notify_user_words, + "user_notification": False, # Hardcode intentional for watchlist filter type "notification_msg": "" }, "watch_tokens": { diff --git a/bot/constants.py b/bot/constants.py index f67b7f45f..7014a73b3 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -207,7 +207,7 @@ class Filter(metaclass=YAMLGetter): notify_user_zalgo: bool notify_user_invites: bool notify_user_domains: bool - notify_user_words: bool + # Words watch intentionally ignored since it's a watchlist # Token notification intentionally ignored, notification is handled by the token remover cog ping_everyone: bool diff --git a/config-default.yml b/config-default.yml index 5ea9fc602..f8d8f0c26 100644 --- a/config-default.yml +++ b/config-default.yml @@ -144,7 +144,7 @@ filter: notify_user_zalgo: false notify_user_invites: true notify_user_domains: false - notify_user_words: false + # Words watch intentionally ignored since it's a watchlist # Token notification intentionally ignored, notification is handled by the token remover cog # Filter configuration -- cgit v1.2.3 From 30fcdb91efe9b55448b91a323672ec68b07361cf Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 5 Jan 2019 14:44:45 -0500 Subject: Marginally smarter filter notification logic Only check for a user notification for "filter" type filters --- bot/cogs/filtering.py | 10 +++------- bot/constants.py | 3 +-- config-default.yml | 3 +-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 701798d18..d5f19a7cc 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -74,15 +74,11 @@ class Filtering: "enabled": Filter.watch_words, "function": self._has_watchlist_words, "type": "watchlist", - "user_notification": False, # Hardcode intentional for watchlist filter type - "notification_msg": "" }, "watch_tokens": { "enabled": Filter.watch_tokens, "function": self._has_watchlist_tokens, "type": "watchlist", - "user_notification": False, # Hardcode intentional, already in token remover cog - "notification_msg": "" }, } @@ -161,9 +157,9 @@ class Filtering: if _filter["type"] == "filter": await msg.delete() - # Notify the user if the filter specifies - if _filter["user_notification"]: - await self.notify_member(msg.author, _filter["notification_msg"], msg.channel) + # Notify the user if the filter specifies + if _filter["user_notification"]: + await self.notify_member(msg.author, _filter["notification_msg"], msg.channel) break # We don't want multiple filters to trigger diff --git a/bot/constants.py b/bot/constants.py index 7014a73b3..bbe6c1604 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -204,11 +204,10 @@ class Filter(metaclass=YAMLGetter): watch_words: bool watch_tokens: bool + # Notifications are not expected for "watchlist" type filters notify_user_zalgo: bool notify_user_invites: bool notify_user_domains: bool - # Words watch intentionally ignored since it's a watchlist - # Token notification intentionally ignored, notification is handled by the token remover cog ping_everyone: bool guild_invite_whitelist: List[int] diff --git a/config-default.yml b/config-default.yml index f8d8f0c26..ad87e44ac 100644 --- a/config-default.yml +++ b/config-default.yml @@ -141,11 +141,10 @@ filter: watch_tokens: true # Notify user on filter? + # Notifications are not expected for "watchlist" type filters notify_user_zalgo: false notify_user_invites: true notify_user_domains: false - # Words watch intentionally ignored since it's a watchlist - # Token notification intentionally ignored, notification is handled by the token remover cog # Filter configuration ping_everyone: true # Ping @everyone when we send a mod-alert? -- cgit v1.2.3 From 1c965ac25bceab3d1d9a57aa0be515c02334ff94 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 5 Jan 2019 16:41:29 -0500 Subject: Use explicit channel mention --- bot/cogs/filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index d5f19a7cc..247ee26b8 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -130,7 +130,7 @@ class Filtering: if isinstance(msg.channel, DMChannel): channel_str = "via DM" else: - channel_str = f"in <#{msg.channel.id}>" + channel_str = f"in {msg.channel.mention}" message = ( f"The {filter_name} {_filter['type']} was triggered " -- cgit v1.2.3 From 9e379f23528bef0faf4ffafbdd2470526bd78773 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Sun, 6 Jan 2019 02:14:04 +0100 Subject: Adding 'rich embed' filter to filtering, 'embed'-support to modlog, and 'rich embed' filtering options to config --- bot/cogs/filtering.py | 73 ++++++++++++++++++++++++++++++++++++++++++++------- bot/cogs/modlog.py | 17 ++++++++++++ config-default.yml | 18 +++++++------ 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 247ee26b8..f6a1e7b4d 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -45,6 +45,7 @@ class Filtering: "enabled": Filter.filter_zalgo, "function": self._has_zalgo, "type": "filter", + "content_only": True, "user_notification": Filter.notify_user_zalgo, "notification_msg": ( "Your post has been removed for abusing Unicode character rendering (aka Zalgo text). " @@ -55,6 +56,7 @@ class Filtering: "enabled": Filter.filter_invites, "function": self._has_invites, "type": "filter", + "content_only": True, "user_notification": Filter.notify_user_invites, "notification_msg": ( f"Per Rule 10, your invite link has been removed. {_staff_mistake_str}\n\n" @@ -65,20 +67,36 @@ class Filtering: "enabled": Filter.filter_domains, "function": self._has_urls, "type": "filter", + "content_only": True, "user_notification": Filter.notify_user_domains, "notification_msg": ( f"Your URL has been removed because it matched a blacklisted domain. {_staff_mistake_str}" ) }, + "filter_rich_embeds": { + "enabled": Filter.filter_rich_embeds, + "function": self._has_rich_embed, + "type": "filter", + "content_only": False, + "user_notification": Filter.notify_user_rich_embeds, + "notification_msg": ( + "Your post has been removed because it contained a rich embed. " + "This indicates that you're either using an unofficial discord client or are using a self-bot, " + "both of which violate Discord's Terms of Service.\n\n" + f"Please don't use a self-bot or an unofficial Discord client on our server. {_staff_mistake_str}" + ) + }, "watch_words": { "enabled": Filter.watch_words, "function": self._has_watchlist_words, "type": "watchlist", + "content_only": True, }, "watch_tokens": { "enabled": Filter.watch_tokens, "function": self._has_watchlist_tokens, "type": "watchlist", + "content_only": True, }, } @@ -115,18 +133,46 @@ class Filtering: ) # If we're running the bot locally, ignore role whitelist and only listen to #dev-test + # if DEBUG_MODE: + # filter_message = not msg.author.bot and msg.channel.id == Channels.devtest + if DEBUG_MODE: - filter_message = not msg.author.bot and msg.channel.id == Channels.devtest + filter_message = msg.author.id != 414020331980980234 and msg.channel.id == Channels.devtest # If none of the above, we can start filtering. if filter_message: + for filter_name, _filter in self.filters.items(): # Is this specific filter enabled in the config? if _filter["enabled"]: - triggered = await _filter["function"](msg.content) + # Does the filter only need the message content or the full message? + if _filter["content_only"]: + triggered = await _filter["function"](msg.content) + else: + triggered = await _filter["function"](msg) if triggered: + # If this is a filter (not a watchlist), we should delete the message. + if _filter["type"] == "filter": + try: + # Embeds (can) trigger both the `on_message` and `on_message_edit` + # event handlers, triggering filtering twice for the same message. + # + # If `on_message`-triggered filtering already deleted the message + # then `on_message_edit`-triggered filtering will raise exception + # since the message no longer exists. + # + # In addition, to avoid sending two notifications to the user, the + # logs, and mod_alert, we return if the message no longer exists. + await msg.delete() + except discord.errors.NotFound: + return + + # Notify the user if the filter specifies + if _filter["user_notification"]: + await self.notify_member(msg.author, _filter["notification_msg"], msg.channel) + if isinstance(msg.channel, DMChannel): channel_str = "via DM" else: @@ -153,13 +199,12 @@ class Filtering: ping_everyone=Filter.ping_everyone, ) - # If this is a filter (not a watchlist), we should delete the message. - if _filter["type"] == "filter": - await msg.delete() - - # Notify the user if the filter specifies - if _filter["user_notification"]: - await self.notify_member(msg.author, _filter["notification_msg"], msg.channel) + if filter_name == "filter_rich_embeds": + await self.mod_log.send_log_embeds( + embeds=msg.embeds, + content="The message contained the following embed(s):\n", + channel_id=Channels.mod_alerts, + ) break # We don't want multiple filters to trigger @@ -272,6 +317,16 @@ class Filtering: return True return False + @staticmethod + async def _has_rich_embed(msg: Message): + """ + Returns True if any of the embeds in the message + are of type 'rich', returns False otherwise + """ + if msg.embeds: + return any(embed.type == "rich" for embed in msg.embeds) + return False + async def notify_member(self, filtered_member: Member, reason: str, channel: TextChannel): """ Notify filtered_member about a moderation action with the reason str diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 0561b5afb..a167ba9e8 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -127,6 +127,23 @@ class ModLog: await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) + async def send_log_embeds( + self, embeds: List[Embed], content: Optional[str], + channel_id: int = Channels.modlog, ping_everyone: bool = False, + ): + + if ping_everyone: + if content: + content = f"@everyone\n{content}" + else: + content = "@everyone" + + if content: + await self.bot.get_channel(channel_id).send(content=content) + + for embed in embeds: + await self.bot.get_channel(channel_id).send(embed=embed) + async def on_guild_channel_create(self, channel: GUILD_CHANNEL): if channel.guild.id != GuildConstant.id: return diff --git a/config-default.yml b/config-default.yml index ad87e44ac..ad3487c78 100644 --- a/config-default.yml +++ b/config-default.yml @@ -134,17 +134,19 @@ guild: filter: # What do we filter? - filter_zalgo: false - filter_invites: true - filter_domains: true - watch_words: true - watch_tokens: true + filter_zalgo: false + filter_invites: true + filter_domains: true + filter_rich_embeds: true + watch_words: true + watch_tokens: true # Notify user on filter? # Notifications are not expected for "watchlist" type filters - notify_user_zalgo: false - notify_user_invites: true - notify_user_domains: false + notify_user_zalgo: false + notify_user_invites: true + notify_user_domains: false + notify_user_rich_embeds: true # Filter configuration ping_everyone: true # Ping @everyone when we send a mod-alert? -- cgit v1.2.3 From d9130047e7fcbedd0e033c23c6b82f608cd9039b Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Sun, 6 Jan 2019 02:38:17 +0100 Subject: Restoring DEBUG_MODE filtering condition and adding comment --- bot/cogs/filtering.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index f6a1e7b4d..9324314df 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -133,11 +133,8 @@ class Filtering: ) # If we're running the bot locally, ignore role whitelist and only listen to #dev-test - # if DEBUG_MODE: - # filter_message = not msg.author.bot and msg.channel.id == Channels.devtest - if DEBUG_MODE: - filter_message = msg.author.id != 414020331980980234 and msg.channel.id == Channels.devtest + filter_message = not msg.author.bot and msg.channel.id == Channels.devtest # If none of the above, we can start filtering. if filter_message: @@ -199,6 +196,7 @@ class Filtering: ping_everyone=Filter.ping_everyone, ) + # If filtering rich embeds, also send the removed embeds to mod_alerts if filter_name == "filter_rich_embeds": await self.mod_log.send_log_embeds( embeds=msg.embeds, -- cgit v1.2.3 From 9f8e4774ae6eeac8cbcece8c65c8de390c3300fd Mon Sep 17 00:00:00 2001 From: sco1 Date: Sun, 6 Jan 2019 00:27:49 -0500 Subject: Antispam Infraction Fixes Add muted role object to cog attributes Fix unawaited coroutine in modlog Adjust modlog message ctx variable to be more explicit Fix duration being sent to API as integer instead of string Fix temporary infraction being placed into a nonexistent schedule, now placed into the moderation cog's task schedule --- bot/cogs/antispam.py | 15 +++++++++------ bot/cogs/modlog.py | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index 052fd48b2..cf52d30fa 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -45,7 +45,7 @@ WHITELISTED_ROLES = (Roles.owner, Roles.admin, Roles.moderator, Roles.helpers) class AntiSpam: def __init__(self, bot: Bot): self.bot = bot - self.muted_role = None + self._muted_role = Object(Roles.muted) @property def mod_log(self) -> ModLog: @@ -135,7 +135,7 @@ class AntiSpam: mod_alert_message += f"{content}" # Return the mod log message Context that we can use to post the infraction - mod_log_message = await self.mod_log.send_log_message( + mod_log_ctx = await self.mod_log.send_log_message( icon_url=Icons.filtering, colour=Colour(Colours.soft_red), title=f"Spam detected!", @@ -146,17 +146,20 @@ class AntiSpam: ) # Post AntiSpam mute as a regular infraction so it can be reversed - ctx = await self.bot.get_context(mod_log_message) - response_object = await post_infraction(ctx, member, type="mute", reason=reason, duration=remove_role_after) + response_object = await post_infraction( + mod_log_ctx, member, type="mute", reason=reason, duration=f"{remove_role_after}S" + ) if response_object is None: return # Appropriate error(s) are already raised by post_infraction self.mod_log.ignore(Event.member_update, member.id) await member.add_roles(self._muted_role, reason=reason) - loop = asyncio.get_event_loop() + # Insert ourselves into the moderation infraction loop infraction_object = response_object["infraction"] - self.schedule_task(loop, infraction_object["id"], infraction_object) + loop = asyncio.get_event_loop() + moderation_cog = self.bot.get_cog('Moderation') + moderation_cog.schedule_task(loop, infraction_object["id"], infraction_object) description = textwrap.dedent(f""" **Channel**: {msg.channel.mention} diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index f36c431e6..9d26fa925 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -126,7 +126,7 @@ class ModLog: content = "@everyone" log_message = await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) - return self.bot.get_context(log_message) # Optionally return for use with antispam + return await self.bot.get_context(log_message) # Optionally return for use with antispam async def on_guild_channel_create(self, channel: GUILD_CHANNEL): if channel.guild.id != GuildConstant.id: -- cgit v1.2.3 From 76951b4f1b0ada06b45f38271e6eb8c93ce8e51e Mon Sep 17 00:00:00 2001 From: sco1 Date: Sun, 6 Jan 2019 00:54:27 -0500 Subject: Invoke Moderation tempmute directly --- bot/cogs/antispam.py | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index cf52d30fa..800700a50 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -1,22 +1,18 @@ -import asyncio import logging -import textwrap from datetime import datetime, timedelta from typing import List -from dateutil.relativedelta import relativedelta from discord import Colour, Member, Message, Object, TextChannel from discord.ext.commands import Bot from bot import rules +from bot.cogs.moderation import Moderation from bot.cogs.modlog import ModLog from bot.constants import ( AntiSpam as AntiSpamConfig, Channels, Colours, DEBUG_MODE, Event, Guild as GuildConfig, Icons, Roles, ) -from bot.utils.moderation import post_infraction -from bot.utils.time import humanize_delta log = logging.getLogger(__name__) @@ -111,8 +107,6 @@ class AntiSpam: # Sanity check to ensure we're not lagging behind if self.muted_role not in member.roles: remove_role_after = AntiSpamConfig.punishment['remove_after'] - duration_delta = relativedelta(seconds=remove_role_after) - human_duration = humanize_delta(duration_delta) mod_alert_message = ( f"**Triggered by:** {member.display_name}#{member.discriminator} (`{member.id}`)\n" @@ -145,32 +139,8 @@ class AntiSpam: ping_everyone=AntiSpamConfig.ping_everyone ) - # Post AntiSpam mute as a regular infraction so it can be reversed - response_object = await post_infraction( - mod_log_ctx, member, type="mute", reason=reason, duration=f"{remove_role_after}S" - ) - if response_object is None: - return # Appropriate error(s) are already raised by post_infraction - - self.mod_log.ignore(Event.member_update, member.id) - await member.add_roles(self._muted_role, reason=reason) - - # Insert ourselves into the moderation infraction loop - infraction_object = response_object["infraction"] - loop = asyncio.get_event_loop() - moderation_cog = self.bot.get_cog('Moderation') - moderation_cog.schedule_task(loop, infraction_object["id"], infraction_object) - - description = textwrap.dedent(f""" - **Channel**: {msg.channel.mention} - **User**: {msg.author.mention} (`{msg.author.id}`) - **Reason**: {reason} - Role will be removed after {human_duration}. - """) - await self.mod_log.send_log_message( - icon_url=Icons.user_mute, colour=Colour(Colours.soft_red), - title="User muted", text=description - ) + # Run a tempmute + await mod_log_ctx.invoke(Moderation.tempmute, member, f"{remove_role_after}S", reason=reason) async def maybe_delete_messages(self, channel: TextChannel, messages: List[Message]): # Is deletion of offending messages actually enabled? -- cgit v1.2.3 From 990a901d9a1d00340d401ea9f2c87f648056a144 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Sun, 6 Jan 2019 20:48:20 +0100 Subject: Clarifying comment --- bot/cogs/filtering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 9324314df..67ff9d0bf 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -153,7 +153,7 @@ class Filtering: # If this is a filter (not a watchlist), we should delete the message. if _filter["type"] == "filter": try: - # Embeds (can) trigger both the `on_message` and `on_message_edit` + # Embeds (can?) trigger both the `on_message` and `on_message_edit` # event handlers, triggering filtering twice for the same message. # # If `on_message`-triggered filtering already deleted the message -- cgit v1.2.3 From 23af034e656a82d0d6fe6268a5c41d010cbfaf4c Mon Sep 17 00:00:00 2001 From: Derek Date: Sun, 6 Jan 2019 19:08:34 -0500 Subject: Add free command --- bot/__main__.py | 1 + bot/cogs/free.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 bot/cogs/free.py diff --git a/bot/__main__.py b/bot/__main__.py index 3c40a3243..581fa5c8e 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -75,6 +75,7 @@ bot.load_extension("bot.cogs.tags") bot.load_extension("bot.cogs.token_remover") bot.load_extension("bot.cogs.utils") bot.load_extension("bot.cogs.wolfram") +bot.load_extension("bot.cogs.free") if has_rmq: bot.load_extension("bot.cogs.rmq") diff --git a/bot/cogs/free.py b/bot/cogs/free.py new file mode 100644 index 000000000..e50058106 --- /dev/null +++ b/bot/cogs/free.py @@ -0,0 +1,90 @@ +import logging +from datetime import datetime + +from discord import Colour, Embed, Member, utils +from discord.ext.commands import BucketType, Context, command, cooldown + + +log = logging.getLogger(__name__) + + +class Free: + """Tries to figure out which help channels are free.""" + + PYTHON_HELP_ID = 356013061213126657 + TIME_INACTIVE = 300 + + @command(name="free", aliases=('f',)) + @cooldown(1, 60.0, BucketType.channel) + async def free(self, ctx: Context, user: Member = None, seek: int = 2): + """ + Lists free help channels by likeliness of availability. + :param user: accepts user mention, ID, etc. + :param seek: How far back to check the last active message. + + seek is used only when this command is invoked in a help channel. + + When seek is 2, we are avoiding considering the last active message + in a channel to be the one that invoked this command. + + When seek is 3 or more, a user has been mentioned on the assumption + that they asked if the channel is free or they asked their question + in an active channel, and we want the message before that happened. + """ + free_channels = [] + python_help = utils.get(ctx.guild.categories, id=self.PYTHON_HELP_ID) + + if user is not None and seek == 2: + seek = 3 + elif seek > 10: + seek = 3 + + for channel in python_help.channels: + if channel.id == ctx.channel.id: + messages = await channel.history(limit=seek).flatten() + msg = messages[seek-1] + else: + messages = await channel.history(limit=1).flatten() + msg = messages[0] + + inactive = (datetime.utcnow() - msg.created_at).seconds + if inactive > self.TIME_INACTIVE: + free_channels.append((inactive, channel.id)) + + embed = Embed() + embed.colour = Colour.gold() + embed.title = "**Looking for a free help channel?**" + + if user is not None: + embed.description = f"**Hey <@{user.id}>!**\n\n" + else: + embed.description = "" + + if free_channels: + embed.description += "**The following channel{0} look{1} free:**\n\n**".format( + 's' if len(free_channels) > 1 else '', + '' if len(free_channels) > 1 else 's') + + for i, channel in enumerate(sorted(free_channels, reverse=True), 1): + inactive, ID = channel + minutes, seconds = divmod(inactive, 60) + if minutes > 60: + hours, minutes = divmod(minutes, 60) + embed.description += f'{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n' + else: + embed.description += f'{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n' + + embed.description += ("**\nThese channels aren't guaranteed to be free, " + "so use your best judgement and check for yourself.") + else: + embed.description = ("**Doesn't look like any channels are available to me. " + "You're welcome to check for yourself to be sure. " + "If all channels are truly busy, please be patient " + "as one will likely be available soon.**") + + return await ctx.send(embed=embed) + + +def setup(bot): + bot.add_cog(Free()) + log.info("Cog loaded: Free") -- cgit v1.2.3 From ebad04f068598ddce567c2f855172206d0bb4e21 Mon Sep 17 00:00:00 2001 From: Derek Date: Sun, 6 Jan 2019 19:16:15 -0500 Subject: Change single quotes to double quotes for the sake of consistency --- bot/cogs/free.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index e50058106..73946dec1 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -70,9 +70,9 @@ class Free: minutes, seconds = divmod(inactive, 60) if minutes > 60: hours, minutes = divmod(minutes, 60) - embed.description += f'{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n' + embed.description += f"{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n" else: - embed.description += f'{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n' + embed.description += f"{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n" embed.description += ("**\nThese channels aren't guaranteed to be free, " "so use your best judgement and check for yourself.") -- cgit v1.2.3 From f0f37e657c6ec3c28fcfbdcd1dfbfb7e8d0729f1 Mon Sep 17 00:00:00 2001 From: Derek Date: Sun, 6 Jan 2019 19:48:58 -0500 Subject: Update constraint on seek so it can't be less than 1 --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 73946dec1..697e15f4b 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -36,7 +36,7 @@ class Free: if user is not None and seek == 2: seek = 3 - elif seek > 10: + elif not 0 < seek < 10: seek = 3 for channel in python_help.channels: -- cgit v1.2.3 From 4a78dabeec56a07c4e48ce40bb6fdf09b32110b3 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 03:28:45 -0500 Subject: Remove unnecessary return statement --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 697e15f4b..00e5fb8bb 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -82,7 +82,7 @@ class Free: "If all channels are truly busy, please be patient " "as one will likely be available soon.**") - return await ctx.send(embed=embed) + await ctx.send(embed=embed) def setup(bot): -- cgit v1.2.3 From 5f4e617a98422a588e98e1e0d067e638877c5d65 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 7 Jan 2019 19:45:57 +0100 Subject: Reverting modlog.py to original state --- bot/cogs/modlog.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index a167ba9e8..0561b5afb 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -127,23 +127,6 @@ class ModLog: await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) - async def send_log_embeds( - self, embeds: List[Embed], content: Optional[str], - channel_id: int = Channels.modlog, ping_everyone: bool = False, - ): - - if ping_everyone: - if content: - content = f"@everyone\n{content}" - else: - content = "@everyone" - - if content: - await self.bot.get_channel(channel_id).send(content=content) - - for embed in embeds: - await self.bot.get_channel(channel_id).send(embed=embed) - async def on_guild_channel_create(self, channel: GUILD_CHANNEL): if channel.guild.id != GuildConstant.id: return -- cgit v1.2.3 From fafa80ebcb29e4de5986a276ff85fb84e1d267be Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 7 Jan 2019 19:47:22 +0100 Subject: Modifying 'send_log_message' so it supports sending additional embeds to the specified (log) channel --- bot/cogs/modlog.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 0561b5afb..c96838a54 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -106,7 +106,7 @@ class ModLog: async def send_log_message( self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, thumbnail: str = None, channel_id: int = Channels.modlog, ping_everyone: bool = False, - files: List[File] = None, content: str = None + files: List[File] = None, content: str = None, additional_embeds: List[Embed] = None, ): embed = Embed(description=text) @@ -125,7 +125,14 @@ class ModLog: else: content = "@everyone" - await self.bot.get_channel(channel_id).send(content=content, embed=embed, files=files) + channel = self.bot.get_channel(channel_id) + + await channel.send(content=content, embed=embed, files=files) + + if additional_embeds: + await channel.send("With the following embed(s):") + for additional_embed in additional_embeds: + await channel.send(embed=additional_embed) async def on_guild_channel_create(self, channel: GUILD_CHANNEL): if channel.guild.id != GuildConstant.id: -- cgit v1.2.3 From e3ace591d9dca2d4ab95c7b07d6af3e6fc84b7c3 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 7 Jan 2019 19:51:16 +0100 Subject: Changing the way in which the filter_rich_embed sends the embeds to mod_log.send_log_message --- bot/cogs/filtering.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 67ff9d0bf..5c6ed9c26 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -185,6 +185,8 @@ class Filtering: log.debug(message) + additional_embeds = msg.embeds if filter_name == "filter_rich_embeds" else None + # Send pretty mod log embed to mod-alerts await self.mod_log.send_log_message( icon_url=Icons.filtering, @@ -194,16 +196,10 @@ class Filtering: thumbnail=msg.author.avatar_url_as(static_format="png"), channel_id=Channels.mod_alerts, ping_everyone=Filter.ping_everyone, + additional_embeds=additional_embeds, ) # If filtering rich embeds, also send the removed embeds to mod_alerts - if filter_name == "filter_rich_embeds": - await self.mod_log.send_log_embeds( - embeds=msg.embeds, - content="The message contained the following embed(s):\n", - channel_id=Channels.mod_alerts, - ) - break # We don't want multiple filters to trigger @staticmethod -- cgit v1.2.3 From a78ac6d6e1661d2647dd61ca114ab695b5aa72af Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 7 Jan 2019 20:00:37 +0100 Subject: Including the filter_rich_embeds constants in bot/constants.py --- bot/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/constants.py b/bot/constants.py index bbe6c1604..c1375bb13 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -201,6 +201,7 @@ class Filter(metaclass=YAMLGetter): filter_zalgo: bool filter_invites: bool filter_domains: bool + filter_rich_embeds: bool watch_words: bool watch_tokens: bool @@ -208,6 +209,7 @@ class Filter(metaclass=YAMLGetter): notify_user_zalgo: bool notify_user_invites: bool notify_user_domains: bool + notify_user_rich_embeds: bool ping_everyone: bool guild_invite_whitelist: List[int] -- cgit v1.2.3 From 543b2ae88c82fd5558785cee224680ff0285c7ad Mon Sep 17 00:00:00 2001 From: Daniel Brown Date: Mon, 7 Jan 2019 13:04:09 -0600 Subject: Corrected the delete_reminder() method to use the correct _delete_reminder method(). Signed-off-by: Daniel Brown --- bot/cogs/reminders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/reminders.py b/bot/cogs/reminders.py index f6ed111dc..ddf5cc1f3 100644 --- a/bot/cogs/reminders.py +++ b/bot/cogs/reminders.py @@ -398,7 +398,7 @@ class Reminders(Scheduler): ) if not failed: - self.cancel_reminder(response_data["reminder_id"]) + await self._delete_reminder(response_data["reminder_id"]) def setup(bot: Bot): -- cgit v1.2.3 From 16afa8d6cfa7ff70d73639ea3665bb47c5814586 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 7 Jan 2019 20:44:00 +0100 Subject: Removing orphaned comment and restructuring user notification for filter_rich_embed --- bot/cogs/filtering.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 5c6ed9c26..42424cd05 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -82,8 +82,8 @@ class Filtering: "notification_msg": ( "Your post has been removed because it contained a rich embed. " "This indicates that you're either using an unofficial discord client or are using a self-bot, " - "both of which violate Discord's Terms of Service.\n\n" - f"Please don't use a self-bot or an unofficial Discord client on our server. {_staff_mistake_str}" + f"both of which violate Discord's Terms of Service. {_staff_mistake_str}\n\n" + "Please don't use a self-bot or an unofficial Discord client on our server." ) }, "watch_words": { @@ -140,7 +140,6 @@ class Filtering: if filter_message: for filter_name, _filter in self.filters.items(): - # Is this specific filter enabled in the config? if _filter["enabled"]: # Does the filter only need the message content or the full message? @@ -199,7 +198,6 @@ class Filtering: additional_embeds=additional_embeds, ) - # If filtering rich embeds, also send the removed embeds to mod_alerts break # We don't want multiple filters to trigger @staticmethod -- cgit v1.2.3 From 84437e0ddfed04b87c4d98d47f432e5f7d2e136f Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Mon, 7 Jan 2019 20:45:37 +0100 Subject: Deleting unnecessary additional line --- bot/cogs/filtering.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 42424cd05..570d6549f 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -138,7 +138,6 @@ class Filtering: # If none of the above, we can start filtering. if filter_message: - for filter_name, _filter in self.filters.items(): # Is this specific filter enabled in the config? if _filter["enabled"]: -- cgit v1.2.3 From 71eac59a69b36e21efb0bcf165ad8cd41ccaa1fb Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 7 Jan 2019 17:51:00 -0500 Subject: Add mute infraction check to role restoration --- bot/cogs/events.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index edfc6e579..c604169c0 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -25,6 +25,7 @@ class Events: def __init__(self, bot: Bot): self.bot = bot + self.headers = {"X-API-KEY": Keys.site_api} @property def mod_log(self) -> ModLog: @@ -103,6 +104,29 @@ class Events: resp = await response.json() return resp["data"] + async def has_active_mute(self, user_id: str) -> bool: + """ + Check whether a user has any active mute infractions + """ + response = await self.bot.http_session.get( + URLs.site_infractions_user.format( + user_id=user_id + ), + params={"hidden": "True"}, + headers=self.headers + ) + infraction_list = await response.json() + + # Check for active mute infractions + if len(infraction_list) == 0: + # Short circuit + return False + + muted_check = any( + [infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute"] + ) + return muted_check + async def on_command_error(self, ctx: Context, e: CommandError): command = ctx.command parent = None @@ -236,6 +260,10 @@ class Events: for role in RESTORE_ROLES: if role in old_roles: + # Check for mute roles that were not able to be removed and skip if present + if role == str(Roles.muted) and not await self.has_active_mute(str(member.id)): + continue + new_roles.append(Object(int(role))) for role in new_roles: -- cgit v1.2.3 From d5cb479be6925a10cf0c46e3088518f56a5a91b3 Mon Sep 17 00:00:00 2001 From: sco1 Date: Mon, 7 Jan 2019 19:16:03 -0500 Subject: Add line after docstring --- bot/cogs/events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index c604169c0..77a15733d 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -108,6 +108,7 @@ class Events: """ Check whether a user has any active mute infractions """ + response = await self.bot.http_session.get( URLs.site_infractions_user.format( user_id=user_id -- cgit v1.2.3 From 72150b77659dd130ff80e3797179e75943f5c4a2 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:02:35 -0500 Subject: Add category constant --- bot/cogs/free.py | 4 +++- bot/constants.py | 7 +++++++ config-default.yml | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 00e5fb8bb..4566ade5f 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -4,6 +4,8 @@ from datetime import datetime from discord import Colour, Embed, Member, utils from discord.ext.commands import BucketType, Context, command, cooldown +from bot.constants import Categories + log = logging.getLogger(__name__) @@ -11,7 +13,7 @@ log = logging.getLogger(__name__) class Free: """Tries to figure out which help channels are free.""" - PYTHON_HELP_ID = 356013061213126657 + PYTHON_HELP_ID = Categories.python_help TIME_INACTIVE = 300 @command(name="free", aliases=('f',)) diff --git a/bot/constants.py b/bot/constants.py index bbe6c1604..aae9b05e5 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -315,6 +315,13 @@ class CleanMessages(metaclass=YAMLGetter): message_limit: int +class Categories(metaclass=YAMLGetter): + section = "guild" + subsection = "categories" + + python_help: int + + class Channels(metaclass=YAMLGetter): section = "guild" subsection = "channels" diff --git a/config-default.yml b/config-default.yml index ad87e44ac..7e2a22cc9 100644 --- a/config-default.yml +++ b/config-default.yml @@ -85,6 +85,9 @@ style: guild: id: 267624335836053506 + categories: + python_help: 356013061213126657 + channels: admins: &ADMINS 365960823622991872 announcements: 354619224620138496 -- cgit v1.2.3 From 5e794a0a7ab732ff2ac2ba37473ecd87413603d4 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:12:16 -0500 Subject: Update method for mentioning users and channels to be more idiomatic --- bot/cogs/free.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 4566ade5f..71ee18032 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -51,14 +51,14 @@ class Free: inactive = (datetime.utcnow() - msg.created_at).seconds if inactive > self.TIME_INACTIVE: - free_channels.append((inactive, channel.id)) + free_channels.append((inactive, channel)) embed = Embed() embed.colour = Colour.gold() embed.title = "**Looking for a free help channel?**" if user is not None: - embed.description = f"**Hey <@{user.id}>!**\n\n" + embed.description = f"**Hey {user.mention}!**\n\n" else: embed.description = "" @@ -67,14 +67,13 @@ class Free: 's' if len(free_channels) > 1 else '', '' if len(free_channels) > 1 else 's') - for i, channel in enumerate(sorted(free_channels, reverse=True), 1): - inactive, ID = channel + for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) if minutes > 60: hours, minutes = divmod(minutes, 60) - embed.description += f"{i}. <#{ID}> inactive for {hours}h{minutes}m{seconds}s\n\n" + embed.description += f"{i}. {channel.mention} inactive for {hours}h{minutes}m{seconds}s\n\n" else: - embed.description += f"{i}. <#{ID}> inactive for {minutes}m{seconds}s\n\n" + embed.description += f"{i}. {channel.mention} inactive for {minutes}m{seconds}s\n\n" embed.description += ("**\nThese channels aren't guaranteed to be free, " "so use your best judgement and check for yourself.") -- cgit v1.2.3 From ffbfd4b3467af2becb62f7b7639678136c1b4c1a Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:15:07 -0500 Subject: Fix off by one --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 71ee18032..90caad7ae 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -69,7 +69,7 @@ class Free: for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) - if minutes > 60: + if minutes > 59: hours, minutes = divmod(minutes, 60) embed.description += f"{i}. {channel.mention} inactive for {hours}h{minutes}m{seconds}s\n\n" else: -- cgit v1.2.3 From 3b3f8794cca30b152a7747e3ae7e061328a38ba6 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 7 Jan 2019 20:30:30 -0500 Subject: Update method of obtaining last message to be simpler --- bot/cogs/free.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 90caad7ae..cd04275e2 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -46,8 +46,7 @@ class Free: messages = await channel.history(limit=seek).flatten() msg = messages[seek-1] else: - messages = await channel.history(limit=1).flatten() - msg = messages[0] + msg = await channel.history(limit=1).next() inactive = (datetime.utcnow() - msg.created_at).seconds if inactive > self.TIME_INACTIVE: -- cgit v1.2.3 From 623923780bd5bb2aa1c9624e27faa20229c150a7 Mon Sep 17 00:00:00 2001 From: sco1 Date: Tue, 8 Jan 2019 13:40:04 -0500 Subject: Add RLBot to server whitelist Yay partners! --- config-default.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config-default.yml b/config-default.yml index ad87e44ac..4f4c79fa1 100644 --- a/config-default.yml +++ b/config-default.yml @@ -154,6 +154,7 @@ filter: - 267624335836053506 # Python Discord - 440186186024222721 # Python Discord: ModLog Emojis - 273944235143593984 # STEM + - 348658686962696195 # RLBot domain_blacklist: - pornhub.com -- cgit v1.2.3 From e9134a769b39e2cad58d03193d139760d3840bd7 Mon Sep 17 00:00:00 2001 From: sco1 Date: Tue, 8 Jan 2019 13:59:41 -0500 Subject: Add Pallets to server whitelist Yay partners! --- config-default.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config-default.yml b/config-default.yml index 4f4c79fa1..a51d00778 100644 --- a/config-default.yml +++ b/config-default.yml @@ -155,6 +155,7 @@ filter: - 440186186024222721 # Python Discord: ModLog Emojis - 273944235143593984 # STEM - 348658686962696195 # RLBot + - 531221516914917387 # Pallets domain_blacklist: - pornhub.com -- cgit v1.2.3 From 0b8c5f074c725d739fffabb18076519d33a033a7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Tue, 8 Jan 2019 16:18:43 -0500 Subject: Remove list comprehension since any() works on generators Co-Authored-By: sco1 --- bot/cogs/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index 77a15733d..b44e5871e 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -124,7 +124,7 @@ class Events: return False muted_check = any( - [infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute"] + infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute" ) return muted_check -- cgit v1.2.3 From e8a5db64dc70270488a6b9a43e21470c11936878 Mon Sep 17 00:00:00 2001 From: sco1 Date: Tue, 8 Jan 2019 16:36:54 -0500 Subject: Switch short-circuit logic, add logging --- bot/cogs/events.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index b44e5871e..78878dcb9 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -119,7 +119,7 @@ class Events: infraction_list = await response.json() # Check for active mute infractions - if len(infraction_list) == 0: + if not infraction_list: # Short circuit return False @@ -263,6 +263,10 @@ class Events: if role in old_roles: # Check for mute roles that were not able to be removed and skip if present if role == str(Roles.muted) and not await self.has_active_mute(str(member.id)): + log.debug( + f"User {member.id} has no active mute infraction, " + "their leftover muted role will not be persisted" + ) continue new_roles.append(Object(int(role))) -- cgit v1.2.3 From 01a069c26fa2f45e2ad20ab9fc4c612a503b4d73 Mon Sep 17 00:00:00 2001 From: Derek Date: Tue, 8 Jan 2019 16:45:22 -0500 Subject: Add linter exception --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index cd04275e2..076415ded 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -46,7 +46,7 @@ class Free: messages = await channel.history(limit=seek).flatten() msg = messages[seek-1] else: - msg = await channel.history(limit=1).next() + msg = await channel.history(limit=1).next() # noqa (False positive) inactive = (datetime.utcnow() - msg.created_at).seconds if inactive > self.TIME_INACTIVE: -- cgit v1.2.3 From 25d9b1ea36b03ed431d7262d373124e3f788d408 Mon Sep 17 00:00:00 2001 From: sco1 Date: Tue, 8 Jan 2019 17:34:51 -0500 Subject: From review --- bot/cogs/events.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bot/cogs/events.py b/bot/cogs/events.py index 78878dcb9..f0baecd4b 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -123,10 +123,9 @@ class Events: # Short circuit return False - muted_check = any( + return any( infraction["active"] for infraction in infraction_list if infraction["type"].lower() == "mute" ) - return muted_check async def on_command_error(self, ctx: Context, e: CommandError): command = ctx.command -- cgit v1.2.3 From 3a209226e76b2ef1e542cd5a35e2571b3a1cc418 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 01:45:01 -0500 Subject: Add activity timeout constant --- bot/cogs/free.py | 13 ++++++------- bot/constants.py | 6 ++++++ config-default.yml | 6 ++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 076415ded..7447fd941 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -4,18 +4,17 @@ from datetime import datetime from discord import Colour, Embed, Member, utils from discord.ext.commands import BucketType, Context, command, cooldown -from bot.constants import Categories +from bot.constants import Categories, Free log = logging.getLogger(__name__) +PYTHON_HELP_ID = Categories.python_help +TIMEOUT = Free.activity_timeout + class Free: """Tries to figure out which help channels are free.""" - - PYTHON_HELP_ID = Categories.python_help - TIME_INACTIVE = 300 - @command(name="free", aliases=('f',)) @cooldown(1, 60.0, BucketType.channel) async def free(self, ctx: Context, user: Member = None, seek: int = 2): @@ -34,7 +33,7 @@ class Free: in an active channel, and we want the message before that happened. """ free_channels = [] - python_help = utils.get(ctx.guild.categories, id=self.PYTHON_HELP_ID) + python_help = utils.get(ctx.guild.categories, id=PYTHON_HELP_ID) if user is not None and seek == 2: seek = 3 @@ -49,7 +48,7 @@ class Free: msg = await channel.history(limit=1).next() # noqa (False positive) inactive = (datetime.utcnow() - msg.created_at).seconds - if inactive > self.TIME_INACTIVE: + if inactive > TIMEOUT: free_channels.append((inactive, channel)) embed = Embed() diff --git a/bot/constants.py b/bot/constants.py index aae9b05e5..689590c0c 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -471,6 +471,12 @@ class BigBrother(metaclass=YAMLGetter): header_message_limit: int +class Free(metaclass=YAMLGetter): + section = 'free' + + activity_timeout: int + + # Debug mode DEBUG_MODE = True if 'local' in os.environ.get("SITE_URL", "local") else False diff --git a/config-default.yml b/config-default.yml index 1b4e6f412..504d40ed7 100644 --- a/config-default.yml +++ b/config-default.yml @@ -337,5 +337,11 @@ big_brother: header_message_limit: 15 +free: + # Seconds to elapse for a channel + # to be considered inactive. + activity_timeout: 300 + + config: required_keys: ['bot.token'] -- cgit v1.2.3 From e61a818b394a40185b934d67fe7a2a943edf81fc Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Wed, 9 Jan 2019 19:05:06 +0100 Subject: Patch to make \!help work outside of #bot-commands again. We should look into a proper rewrite later. --- bot/cogs/help.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index d30ff0dfb..c82a25417 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -9,11 +9,13 @@ from discord.ext import commands from fuzzywuzzy import fuzz, process from bot import constants +from bot.decorators import InChannelCheckFailure from bot.pagination import ( DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, ) + REACTIONS = { FIRST_EMOJI: 'first', LEFT_EMOJI: 'back', @@ -427,7 +429,15 @@ class HelpSession: # see if the user can run the command strikeout = '' - can_run = await command.can_run(self._ctx) + + # Patch to make the !help command work outside of #bot-commands again + # This probably needs a proper rewrite, but this will make it work in + # the mean time. + try: + can_run = await command.can_run(self._ctx) + except InChannelCheckFailure: + can_run = False + if not can_run: # skip if we don't show commands they can't run if self._only_can_run: -- cgit v1.2.3 From 2340509a482d2ed3fce2e3ede456ac49b2a97c43 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 17:55:40 -0500 Subject: Add exemption from cooldown for helpers --- bot/cogs/free.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 7447fd941..6b06eaa4c 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -2,9 +2,10 @@ import logging from datetime import datetime from discord import Colour, Embed, Member, utils +from discord.ext import commands from discord.ext.commands import BucketType, Context, command, cooldown -from bot.constants import Categories, Free +from bot.constants import Categories, Free, Roles log = logging.getLogger(__name__) @@ -83,6 +84,26 @@ class Free: await ctx.send(embed=embed) + @free.error + async def free_error(self, ctx: Context, error): + """ + Runs if any error is raised during invocation + of !free command. Any error aside from + CommandOnCooldown is ignored. + + If error raised is CommandOnCooldown, and the + user who invoked has the helper role, reset + the cooldown and reinvoke the command. + """ + helpers = ctx.guild.get_role(Roles.helpers) + + if isinstance(error, commands.CommandOnCooldown): + if helpers in ctx.author.roles: + # reset cooldown so second invocation + # doesn't bring us back here. + ctx.command.reset_cooldown(ctx) + await ctx.invoke(ctx.command) + def setup(bot): bot.add_cog(Free()) -- cgit v1.2.3 From 0f85110040e70376838b380579cac2ccad007914 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:10:03 -0500 Subject: Put channel category id constant back in class --- bot/cogs/free.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 6b06eaa4c..a66a37f1b 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -10,12 +10,14 @@ from bot.constants import Categories, Free, Roles log = logging.getLogger(__name__) -PYTHON_HELP_ID = Categories.python_help TIMEOUT = Free.activity_timeout class Free: """Tries to figure out which help channels are free.""" + + PYTHON_HELP_ID = Categories.python_help + @command(name="free", aliases=('f',)) @cooldown(1, 60.0, BucketType.channel) async def free(self, ctx: Context, user: Member = None, seek: int = 2): @@ -34,7 +36,7 @@ class Free: in an active channel, and we want the message before that happened. """ free_channels = [] - python_help = utils.get(ctx.guild.categories, id=PYTHON_HELP_ID) + python_help = utils.get(ctx.guild.categories, id=self.PYTHON_HELP_ID) if user is not None and seek == 2: seek = 3 -- cgit v1.2.3 From ee0a38a5671597d200c4927d9e900481727cecf1 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:11:39 -0500 Subject: Change awkward wording --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index a66a37f1b..e9c0386fd 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -79,7 +79,7 @@ class Free: embed.description += ("**\nThese channels aren't guaranteed to be free, " "so use your best judgement and check for yourself.") else: - embed.description = ("**Doesn't look like any channels are available to me. " + embed.description = ("**Doesn't look like any channels are available right now. " "You're welcome to check for yourself to be sure. " "If all channels are truly busy, please be patient " "as one will likely be available soon.**") -- cgit v1.2.3 From 61c7b27b3453120771764fbdd8b43fbc504a8882 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:13:19 -0500 Subject: Change embed color to blurple --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index e9c0386fd..4b5f3b3bb 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -55,7 +55,7 @@ class Free: free_channels.append((inactive, channel)) embed = Embed() - embed.colour = Colour.gold() + embed.colour = Colour.blurple() embed.title = "**Looking for a free help channel?**" if user is not None: -- cgit v1.2.3 From 5868516da942e6df4f5caa1fa24b5af13e1f3555 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:19:14 -0500 Subject: Change line to fit prevailing style --- bot/cogs/free.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 4b5f3b3bb..6f0e86330 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -66,7 +66,8 @@ class Free: if free_channels: embed.description += "**The following channel{0} look{1} free:**\n\n**".format( 's' if len(free_channels) > 1 else '', - '' if len(free_channels) > 1 else 's') + '' if len(free_channels) > 1 else 's' + ) for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) -- cgit v1.2.3 From 4313a9c5ed6b052d46f00350b10a8c3d62136c48 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:36:57 -0500 Subject: Add all the block comments! --- bot/cogs/free.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 6f0e86330..0880cdd25 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -43,10 +43,15 @@ class Free: elif not 0 < seek < 10: seek = 3 + # Iterate through all the help channels + # to check latest activity for channel in python_help.channels: + # Seek further back in the help channel + # the command was invoked in if channel.id == ctx.channel.id: messages = await channel.history(limit=seek).flatten() msg = messages[seek-1] + # Otherwise get last message else: msg = await channel.history(limit=1).next() # noqa (False positive) @@ -63,12 +68,17 @@ class Free: else: embed.description = "" + # Display all potentially inactive channels + # in descending order of inactivity if free_channels: embed.description += "**The following channel{0} look{1} free:**\n\n**".format( 's' if len(free_channels) > 1 else '', '' if len(free_channels) > 1 else 's' ) + # Sort channels in descending order by seconds + # Get position in list, inactivity, and channel object + # For each channel, add to embed.description for i, (inactive, channel) in enumerate(sorted(free_channels, reverse=True), 1): minutes, seconds = divmod(inactive, 60) if minutes > 59: -- cgit v1.2.3 From 40d9ba8a08f6ba6586aea331f8574e2aa3dd03b9 Mon Sep 17 00:00:00 2001 From: Derek Date: Wed, 9 Jan 2019 18:38:41 -0500 Subject: Change activity timeout to 10 minutes --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 504d40ed7..7b0468643 100644 --- a/config-default.yml +++ b/config-default.yml @@ -340,7 +340,7 @@ big_brother: free: # Seconds to elapse for a channel # to be considered inactive. - activity_timeout: 300 + activity_timeout: 600 config: -- cgit v1.2.3 From b2929ef2e72d42950169db4a46c064eedc228c4e Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 10 Jan 2019 16:55:55 +0100 Subject: Adding watch reason to the big-brother header embeds --- bot/cogs/bigbrother.py | 65 +++++++++++++++++++++++++++++++++++++++++++------- config-default.yml | 1 + 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py index 29b13f038..26d35a97c 100644 --- a/bot/cogs/bigbrother.py +++ b/bot/cogs/bigbrother.py @@ -2,8 +2,10 @@ import asyncio import logging import re from collections import defaultdict, deque +from time import strptime from typing import List, Union +from aiohttp import ClientError from discord import Color, Embed, Guild, Member, Message, TextChannel, User from discord.ext.commands import Bot, Context, group @@ -26,13 +28,15 @@ class BigBrother: def __init__(self, bot: Bot): self.bot = bot self.watched_users = {} # { user_id: log_channel_id } + self.watch_reasons = {} # { user_id: watch_reason } self.channel_queues = defaultdict(lambda: defaultdict(deque)) # { user_id: { channel_id: queue(messages) } self.last_log = [None, None, 0] # [user_id, channel_id, message_count] self.consuming = False + self.infraction_watch_prefix = "bb watch: " # Please do not change or we won't be able to find old reasons self.bot.loop.create_task(self.get_watched_users()) - def update_cache(self, api_response: List[dict]): + async def update_cache(self, api_response: List[dict]): """ Updates the internal cache of watched users from the given `api_response`. This function will only add (or update) existing keys, it will not delete @@ -54,13 +58,52 @@ class BigBrother: "but the given channel could not be found. Ignoring." ) + watch_reason = await self.get_watch_reason(user_id) + self.watch_reasons[user_id] = watch_reason + async def get_watched_users(self): """Retrieves watched users from the API.""" await self.bot.wait_until_ready() async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: data = await response.json() - self.update_cache(data) + await self.update_cache(data) + + async def get_watch_reason(self, user_id: int) -> str: + """ Fetches and returns the latest watch reason for a user using the infraction API """ + + re_bb_watch = rf"^{self.infraction_watch_prefix}" + user_id = str(user_id) + + try: + response = await self.bot.http_session.get( + URLs.site_infractions_user_type.format( + user_id=user_id, + infraction_type="note", + ), + params={"search": re_bb_watch, "hidden": "True", "active": "False"}, + headers=self.HEADERS + ) + infraction_list = await response.json() + except ClientError: + log.exception(f"Failed to retrieve bb watch reason for {user_id}.") + return "(error retrieving bb reason)" + + if infraction_list: + latest_reason_infraction = max(infraction_list, key=self._parse_time) + latest_reason = latest_reason_infraction['reason'][10:] + log.trace(f"The latest bb watch reason for {user_id}: {latest_reason}") + return latest_reason + + log.trace(f"No bb watch reason found for {user_id}; returning default string") + return "(no reason specified)" + + @staticmethod + def _parse_time(infraction): + """Takes RFC1123 date_time string and returns time object for sorting purposes""" + + date_string = infraction["inserted_at"] + return strptime(date_string, "%a, %d %b %Y %H:%M:%S %Z") async def on_member_ban(self, guild: Guild, user: Union[User, Member]): if guild.id == GuildConfig.id and user.id in self.watched_users: @@ -70,6 +113,7 @@ class BigBrother: async with self.bot.http_session.delete(url, headers=self.HEADERS) as response: del self.watched_users[user.id] del self.channel_queues[user.id] + del self.watch_reasons[user.id] if response.status == 204: await channel.send( f"{Emojis.bb_message}:hammer: {user} got banned, so " @@ -143,6 +187,7 @@ class BigBrother: embed = Embed(description=f"{message.author.mention} in [#{message.channel.name}]({message.jump_url})") embed.set_author(name=message.author.nick or message.author.name, icon_url=message.author.avatar_url) + embed.set_footer(text=f"Watch reason: {self.watch_reasons[message.author.id]}") await destination.send(embed=embed) @staticmethod @@ -201,7 +246,7 @@ class BigBrother: async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: if response.status == 200: data = await response.json() - self.update_cache(data) + await self.update_cache(data) lines = tuple(f"• <@{entry['user_id']}> in <#{entry['channel_id']}>" for entry in data) await LinePaginator.paginate( @@ -246,15 +291,15 @@ class BigBrother: ) else: self.watched_users[user.id] = channel + self.watch_reasons[user.id] = reason + # Add a note (shadow warning) with the reason for watching + reason = f"{self.infraction_watch_prefix}{reason}" + await post_infraction(ctx, user, type="warning", reason=reason, hidden=True) else: data = await response.json() - reason = data.get('error_message', "no message provided") - await ctx.send(f":x: the API returned an error: {reason}") - - # Add a note (shadow warning) with the reason for watching - reason = "bb watch: " + reason # Prepend for situational awareness - await post_infraction(ctx, user, type="warning", reason=reason, hidden=True) + error_reason = data.get('error_message', "no message provided") + await ctx.send(f":x: the API returned an error: {error_reason}") @bigbrother_group.command(name='unwatch', aliases=('uw',)) @with_role(Roles.owner, Roles.admin, Roles.moderator) @@ -270,6 +315,8 @@ class BigBrother: del self.watched_users[user.id] if user.id in self.channel_queues: del self.channel_queues[user.id] + if user.id in self.watch_reasons: + del self.watch_reasons[user.id] else: log.warning(f"user {user.id} was unwatched but was not found in the cache") diff --git a/config-default.yml b/config-default.yml index 21d7f20b9..1a5a63c6a 100644 --- a/config-default.yml +++ b/config-default.yml @@ -240,6 +240,7 @@ urls: site_infractions_type: !JOIN [*SCHEMA, *API, "/bot/infractions/type/{infraction_type}"] site_infractions_by_id: !JOIN [*SCHEMA, *API, "/bot/infractions/id/{infraction_id}"] site_infractions_user_type_current: !JOIN [*SCHEMA, *API, "/bot/infractions/user/{user_id}/{infraction_type}/current"] + site_infractions_user_type: !JOIN [*SCHEMA, *API, "/bot/infractions/user/{user_id}/{infraction_type}"] site_logs_api: !JOIN [*SCHEMA, *API, "/bot/logs"] site_logs_view: !JOIN [*SCHEMA, *DOMAIN, "/bot/logs"] site_names_api: !JOIN [*SCHEMA, *API, "/bot/snake_names"] -- cgit v1.2.3 From f113c2bed932f52439b1185b96a03c6e3ae03c8e Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 10 Jan 2019 17:03:45 +0100 Subject: Adding type annotations --- bot/cogs/bigbrother.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py index 26d35a97c..b325fa5fe 100644 --- a/bot/cogs/bigbrother.py +++ b/bot/cogs/bigbrother.py @@ -2,7 +2,7 @@ import asyncio import logging import re from collections import defaultdict, deque -from time import strptime +from time import strptime, struct_time from typing import List, Union from aiohttp import ClientError @@ -99,7 +99,7 @@ class BigBrother: return "(no reason specified)" @staticmethod - def _parse_time(infraction): + def _parse_time(infraction: str) -> struct_time: """Takes RFC1123 date_time string and returns time object for sorting purposes""" date_string = infraction["inserted_at"] -- cgit v1.2.3 From 66e476f9ca024c5e6e2917679aaa4da03ef69acd Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Thu, 10 Jan 2019 17:37:20 +0100 Subject: Removing default argument for the alias watch to require a reason via the alias as well --- bot/cogs/alias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/alias.py b/bot/cogs/alias.py index 2ce4a51e3..0b848c773 100644 --- a/bot/cogs/alias.py +++ b/bot/cogs/alias.py @@ -71,7 +71,7 @@ class Alias: @command(name="watch", hidden=True) async def bigbrother_watch_alias( - self, ctx, user: User, *, reason: str = None + self, ctx, user: User, *, reason: str ): """ Alias for invoking bigbrother watch user [text_channel]. -- cgit v1.2.3 From 86440dd8ad355d157a0ebf8250d7abe7d56c5982 Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 17:21:43 -0500 Subject: Update error handler to log errors instead of ignore them --- bot/cogs/free.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 0880cdd25..b8e9f65d0 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -115,7 +115,10 @@ class Free: # reset cooldown so second invocation # doesn't bring us back here. ctx.command.reset_cooldown(ctx) - await ctx.invoke(ctx.command) + # return to avoid needlessly logging the error + return await ctx.invoke(ctx.command) + + log.error(error) # Don't ignore other errors def setup(bot): -- cgit v1.2.3 From d46fed40b335060f7d1652cff6eb58912b824e0b Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 17:27:57 -0500 Subject: Add 2nd space to inline comment to obey allmighty linter --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index b8e9f65d0..acd1dc108 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -118,7 +118,7 @@ class Free: # return to avoid needlessly logging the error return await ctx.invoke(ctx.command) - log.error(error) # Don't ignore other errors + log.error(error) # Don't ignore other errors def setup(bot): -- cgit v1.2.3 From c55fa67e21a6a10feb0738b3bacd6b0dba1d4de9 Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 17:58:19 -0500 Subject: Add cooldown constants to config --- bot/cogs/free.py | 4 +++- bot/constants.py | 2 ++ config-default.yml | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index acd1dc108..8d413a69b 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -11,6 +11,8 @@ from bot.constants import Categories, Free, Roles log = logging.getLogger(__name__) TIMEOUT = Free.activity_timeout +RATE = Free.cooldown_rate +PER = Free.cooldown_per class Free: @@ -19,7 +21,7 @@ class Free: PYTHON_HELP_ID = Categories.python_help @command(name="free", aliases=('f',)) - @cooldown(1, 60.0, BucketType.channel) + @cooldown(RATE, PER, BucketType.channel) async def free(self, ctx: Context, user: Member = None, seek: int = 2): """ Lists free help channels by likeliness of availability. diff --git a/bot/constants.py b/bot/constants.py index 1bb602eb2..be713cef2 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -477,6 +477,8 @@ class Free(metaclass=YAMLGetter): section = 'free' activity_timeout: int + cooldown_rate: int + cooldown_per: float # Debug mode diff --git a/config-default.yml b/config-default.yml index 3db7b2025..f462b8199 100644 --- a/config-default.yml +++ b/config-default.yml @@ -343,6 +343,8 @@ free: # Seconds to elapse for a channel # to be considered inactive. activity_timeout: 600 + cooldown_rate: 1 + cooldown_per: 60.0 config: -- cgit v1.2.3 From e1bd2c37693e863738fb5eddcd0f46ae9e484f1f Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 18:04:06 -0500 Subject: Put extra sentence in docstring clarifying arg usage --- bot/cogs/free.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 8d413a69b..0010b4a90 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -29,6 +29,7 @@ class Free: :param seek: How far back to check the last active message. seek is used only when this command is invoked in a help channel. + You cannot override seek without mentioning a user first. When seek is 2, we are avoiding considering the last active message in a channel to be the one that invoked this command. -- cgit v1.2.3 From d8db79581a84c1d8adf0a297d5390c4c3f519aba Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 10 Jan 2019 18:09:14 -0500 Subject: Update error handler docstring to be more accurate --- bot/cogs/free.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 0010b4a90..4be804881 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -103,13 +103,11 @@ class Free: @free.error async def free_error(self, ctx: Context, error): """ - Runs if any error is raised during invocation - of !free command. Any error aside from - CommandOnCooldown is ignored. - If error raised is CommandOnCooldown, and the user who invoked has the helper role, reset the cooldown and reinvoke the command. + + Otherwise log the error. """ helpers = ctx.guild.get_role(Roles.helpers) -- cgit v1.2.3 From a2f8b21adf7880a116ae9f80e95cd7e696e7e135 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 09:24:57 +0100 Subject: Only retrieve/cache watch reason when user becomes active; restore update_cache to synchronous as it was before --- bot/cogs/bigbrother.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/bot/cogs/bigbrother.py b/bot/cogs/bigbrother.py index b325fa5fe..70916cd7b 100644 --- a/bot/cogs/bigbrother.py +++ b/bot/cogs/bigbrother.py @@ -36,7 +36,7 @@ class BigBrother: self.bot.loop.create_task(self.get_watched_users()) - async def update_cache(self, api_response: List[dict]): + def update_cache(self, api_response: List[dict]): """ Updates the internal cache of watched users from the given `api_response`. This function will only add (or update) existing keys, it will not delete @@ -58,16 +58,13 @@ class BigBrother: "but the given channel could not be found. Ignoring." ) - watch_reason = await self.get_watch_reason(user_id) - self.watch_reasons[user_id] = watch_reason - async def get_watched_users(self): """Retrieves watched users from the API.""" await self.bot.wait_until_ready() async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: data = await response.json() - await self.update_cache(data) + self.update_cache(data) async def get_watch_reason(self, user_id: int) -> str: """ Fetches and returns the latest watch reason for a user using the infraction API """ @@ -90,8 +87,8 @@ class BigBrother: return "(error retrieving bb reason)" if infraction_list: - latest_reason_infraction = max(infraction_list, key=self._parse_time) - latest_reason = latest_reason_infraction['reason'][10:] + latest_reason_infraction = max(infraction_list, key=self._parse_infraction_time) + latest_reason = latest_reason_infraction['reason'][len(self.infraction_watch_prefix):] log.trace(f"The latest bb watch reason for {user_id}: {latest_reason}") return latest_reason @@ -99,7 +96,7 @@ class BigBrother: return "(no reason specified)" @staticmethod - def _parse_time(infraction: str) -> struct_time: + def _parse_infraction_time(infraction: str) -> struct_time: """Takes RFC1123 date_time string and returns time object for sorting purposes""" date_string = infraction["inserted_at"] @@ -183,6 +180,12 @@ class BigBrother: # Send header if user/channel are different or if message limit exceeded. if message.author.id != last_user or message.channel.id != last_channel or msg_count > limit: + # Retrieve watch reason from API if it's not already in the cache + if message.author.id not in self.watch_reasons: + log.trace(f"No watch reason for {message.author.id} found in cache; retrieving from API") + user_watch_reason = await self.get_watch_reason(message.author.id) + self.watch_reasons[message.author.id] = user_watch_reason + self.last_log = [message.author.id, message.channel.id, 0] embed = Embed(description=f"{message.author.mention} in [#{message.channel.name}]({message.jump_url})") @@ -246,7 +249,7 @@ class BigBrother: async with self.bot.http_session.get(URLs.site_bigbrother_api, headers=self.HEADERS) as response: if response.status == 200: data = await response.json() - await self.update_cache(data) + self.update_cache(data) lines = tuple(f"• <@{entry['user_id']}> in <#{entry['channel_id']}>" for entry in data) await LinePaginator.paginate( -- cgit v1.2.3 From 5dd611793c598508c5be8868725ca5400ad0c304 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 11:01:53 +0100 Subject: Using stronger language in the message and emphazising the 'strongly recommend' with bold --- bot/cogs/token_remover.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/cogs/token_remover.py b/bot/cogs/token_remover.py index 8277513a7..c1a0e18ba 100644 --- a/bot/cogs/token_remover.py +++ b/bot/cogs/token_remover.py @@ -16,8 +16,9 @@ log = logging.getLogger(__name__) DELETION_MESSAGE_TEMPLATE = ( "Hey {mention}! I noticed you posted a seemingly valid Discord API " - "token in your message and have removed your message to prevent abuse. " - "We recommend regenerating your token regardless, which you can do here: " + "token in your message and have removed your message. " + "We **strongly recommend** regenerating your token as it's probably " + "been compromised. You can do that here: " "\n" "Feel free to re-post it with the token removed. " "If you believe this was a mistake, please let us know!" -- cgit v1.2.3 From 8f30b52fd378bb3546c84d0fdb945a4b492236b8 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 15:06:12 +0100 Subject: Catching the superclass CheckFailure instead of the subclass --- bot/cogs/help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index c82a25417..ded068123 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -6,10 +6,10 @@ from contextlib import suppress from discord import Colour, Embed, HTTPException from discord.ext import commands +from discord.ext.commands import CheckFailure from fuzzywuzzy import fuzz, process from bot import constants -from bot.decorators import InChannelCheckFailure from bot.pagination import ( DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -435,7 +435,7 @@ class HelpSession: # the mean time. try: can_run = await command.can_run(self._ctx) - except InChannelCheckFailure: + except CheckFailure: can_run = False if not can_run: -- cgit v1.2.3 From 0651779383423ad08b860cde79d29c3e00dca677 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Fri, 11 Jan 2019 15:14:43 +0100 Subject: Revert "Catching the superclass CheckFailure instead of the subclass" This reverts commit 8f30b52fd378bb3546c84d0fdb945a4b492236b8. Accidentally pushed to master when I thought I was at a branch --- bot/cogs/help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index ded068123..c82a25417 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -6,10 +6,10 @@ from contextlib import suppress from discord import Colour, Embed, HTTPException from discord.ext import commands -from discord.ext.commands import CheckFailure from fuzzywuzzy import fuzz, process from bot import constants +from bot.decorators import InChannelCheckFailure from bot.pagination import ( DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -435,7 +435,7 @@ class HelpSession: # the mean time. try: can_run = await command.can_run(self._ctx) - except CheckFailure: + except InChannelCheckFailure: can_run = False if not can_run: -- cgit v1.2.3 From 3905e8ff52ae652c94a1ff0372aa044709060774 Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 11 Jan 2019 09:48:50 -0500 Subject: Disable rich embed filter Discord adding the embeds is causing it to trigger. --- config-default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-default.yml b/config-default.yml index 21d7f20b9..866a5b5ab 100644 --- a/config-default.yml +++ b/config-default.yml @@ -137,7 +137,7 @@ filter: filter_zalgo: false filter_invites: true filter_domains: true - filter_rich_embeds: true + filter_rich_embeds: false watch_words: true watch_tokens: true -- cgit v1.2.3 From 4bc92be7c4831fa3b714d7091700d587f8f62373 Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 11 Jan 2019 22:39:15 -0500 Subject: Add edit delta to modlog for multi-edit messages To help with self-bot detection, if a message has been previously edited, generate a human-readable delta between the last edit and the new one Use message timestamp for modlog embeds generated during on_message event Visually separate send_log_message kwargs to make them easier to read --- bot/cogs/modlog.py | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 06f81cb36..bded3baa0 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -104,9 +104,19 @@ class ModLog: self._ignored[event].append(item) async def send_log_message( - self, icon_url: Optional[str], colour: Colour, title: Optional[str], text: str, - thumbnail: str = None, channel_id: int = Channels.modlog, ping_everyone: bool = False, - files: List[File] = None, content: str = None, additional_embeds: List[Embed] = None, + self, + icon_url: Optional[str], + colour: Colour, + title: Optional[str], + text: str, + thumbnail: str = None, + channel_id: int = Channels.modlog, + ping_everyone: bool = False, + files: List[File] = None, + content: str = None, + additional_embeds: List[Embed] = None, + timestamp_override: datetime.datetime = None, + footer_override: str = None, ): embed = Embed(description=text) @@ -114,7 +124,14 @@ class ModLog: embed.set_author(name=title, icon_url=icon_url) embed.colour = colour - embed.timestamp = datetime.datetime.utcnow() + + if timestamp_override: + embed.timestamp = timestamp_override + else: + embed.timestamp = datetime.datetime.utcnow() + + if footer_override: + embed.set_footer(text=footer_override) if thumbnail is not None: embed.set_thumbnail(url=thumbnail) @@ -676,14 +693,28 @@ class ModLog: f"{after.clean_content}" ) + if before.edited_at: + # Message was previously edited, to assist with self-bot detection, use the edited_at + # datetime as the baseline and create a human-readable delta between this edit event + # and the last time the message was edited + timestamp = before.edited_at + delta = humanize_delta(relativedelta(after.edited_at, before.edited_at)) + footer = f"Last edited {delta} ago" + else: + # Message was not previously edited, use the created_at datetime as the baseline, no + # delta calculation needed + timestamp = before.created_at + footer = None + + print(timestamp, footer) await self.send_log_message( - Icons.message_edit, Colour.blurple(), "Message edited (Before)", - before_response, channel_id=Channels.message_log + Icons.message_edit, Colour.blurple(), "Message edited (Before)", before_response, + channel_id=Channels.message_log, timestamp_override=timestamp, footer_override=footer ) await self.send_log_message( - Icons.message_edit, Colour.blurple(), "Message edited (After)", - after_response, channel_id=Channels.message_log + Icons.message_edit, Colour.blurple(), "Message edited (After)", after_response, + channel_id=Channels.message_log, timestamp_override=after.edited_at ) async def on_raw_message_edit(self, event: RawMessageUpdateEvent): -- cgit v1.2.3 From ddfb1f4689c54a8f7adccd90bd9038e69ff67168 Mon Sep 17 00:00:00 2001 From: sco1 Date: Fri, 11 Jan 2019 23:12:07 -0500 Subject: Remove debug print statement --- bot/cogs/modlog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index bded3baa0..76a5eff58 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -706,7 +706,6 @@ class ModLog: timestamp = before.created_at footer = None - print(timestamp, footer) await self.send_log_message( Icons.message_edit, Colour.blurple(), "Message edited (Before)", before_response, channel_id=Channels.message_log, timestamp_override=timestamp, footer_override=footer -- cgit v1.2.3 From 4dc7ba5b54ab8de7f86f5239b956d94d80146c85 Mon Sep 17 00:00:00 2001 From: SebastiaanZ <33516116+SebastiaanZ@users.noreply.github.com> Date: Sat, 12 Jan 2019 15:04:17 +0100 Subject: Changing check-specific exception to superclass exception so help doesn't break when new check exceptions are added --- bot/cogs/help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/cogs/help.py b/bot/cogs/help.py index c82a25417..ded068123 100644 --- a/bot/cogs/help.py +++ b/bot/cogs/help.py @@ -6,10 +6,10 @@ from contextlib import suppress from discord import Colour, Embed, HTTPException from discord.ext import commands +from discord.ext.commands import CheckFailure from fuzzywuzzy import fuzz, process from bot import constants -from bot.decorators import InChannelCheckFailure from bot.pagination import ( DELETE_EMOJI, FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, @@ -435,7 +435,7 @@ class HelpSession: # the mean time. try: can_run = await command.can_run(self._ctx) - except InChannelCheckFailure: + except CheckFailure: can_run = False if not can_run: -- cgit v1.2.3 From e4a707157fe9cadef5debef67779a5443b48d11e Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 12 Jan 2019 13:16:58 -0500 Subject: Add Optional type hints where appropriate --- bot/cogs/modlog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 76a5eff58..589052b1e 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -109,14 +109,14 @@ class ModLog: colour: Colour, title: Optional[str], text: str, - thumbnail: str = None, + thumbnail: Optional[str] = None, channel_id: int = Channels.modlog, ping_everyone: bool = False, - files: List[File] = None, - content: str = None, - additional_embeds: List[Embed] = None, - timestamp_override: datetime.datetime = None, - footer_override: str = None, + files: Optional[List[File]] = None, + content: Optional[str] = None, + additional_embeds: Optional[List[Embed]] = None, + timestamp_override: Optional[datetime.datetime] = None, + footer_override: Optional[str] = None, ): embed = Embed(description=text) -- cgit v1.2.3 From 3fc94a97dc88504c29985fff735b346e2122c4a2 Mon Sep 17 00:00:00 2001 From: sco1 Date: Sat, 12 Jan 2019 13:22:11 -0500 Subject: Condense logic --- bot/cogs/modlog.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 589052b1e..55611c5e4 100644 --- a/bot/cogs/modlog.py +++ b/bot/cogs/modlog.py @@ -125,15 +125,12 @@ class ModLog: embed.colour = colour - if timestamp_override: - embed.timestamp = timestamp_override - else: - embed.timestamp = datetime.datetime.utcnow() + embed.timestamp = timestamp_override or datetime.datetime.utcnow() if footer_override: embed.set_footer(text=footer_override) - if thumbnail is not None: + if thumbnail: embed.set_thumbnail(url=thumbnail) if ping_everyone: -- cgit v1.2.3 From c94189cbfae4b1788773af3bb7ec32ba32caeb12 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Sat, 12 Jan 2019 16:36:04 -0500 Subject: Change ctx.invoke to ctx.reinvoke to conserve passed arguments Co-Authored-By: fiskenslakt --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index 4be804881..e310d9cc7 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -117,7 +117,7 @@ class Free: # doesn't bring us back here. ctx.command.reset_cooldown(ctx) # return to avoid needlessly logging the error - return await ctx.invoke(ctx.command) + return await ctx.reinvoke() log.error(error) # Don't ignore other errors -- cgit v1.2.3 From 240b33719d48716762461ee8eddbeb28ff460a03 Mon Sep 17 00:00:00 2001 From: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> Date: Sat, 12 Jan 2019 16:46:14 -0500 Subject: Change log.error to log.exception to avoid hiding traceback Co-Authored-By: fiskenslakt --- bot/cogs/free.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/cogs/free.py b/bot/cogs/free.py index e310d9cc7..620449f7e 100644 --- a/bot/cogs/free.py +++ b/bot/cogs/free.py @@ -119,7 +119,7 @@ class Free: # return to avoid needlessly logging the error return await ctx.reinvoke() - log.error(error) # Don't ignore other errors + log.exception(error) # Don't ignore other errors def setup(bot): -- cgit v1.2.3