diff options
| author | 2019-01-11 14:38:05 +0100 | |
|---|---|---|
| committer | 2019-01-11 14:38:05 +0100 | |
| commit | f24d135af0cf8577b557fbfd8d1310e1db5cca60 (patch) | |
| tree | f3cc92e5a7b5e53ddbd1cc95c9445726c7337685 | |
| parent | From review (diff) | |
| parent | Merge pull request #267 from python-discord/token-detection-warning-strong-la... (diff) | |
Merge branch 'master' into accidental-permamute
| -rw-r--r-- | bot/cogs/antispam.py | 35 | ||||
| -rw-r--r-- | bot/cogs/filtering.py | 66 | ||||
| -rw-r--r-- | bot/cogs/help.py | 12 | ||||
| -rw-r--r-- | bot/cogs/modlog.py | 12 | ||||
| -rw-r--r-- | bot/cogs/reminders.py | 2 | ||||
| -rw-r--r-- | bot/cogs/token_remover.py | 5 | ||||
| -rw-r--r-- | bot/constants.py | 2 | ||||
| -rw-r--r-- | config-default.yml | 20 |
8 files changed, 101 insertions, 53 deletions
diff --git a/bot/cogs/antispam.py b/bot/cogs/antispam.py index d5b72718c..800700a50 100644 --- a/bot/cogs/antispam.py +++ b/bot/cogs/antispam.py @@ -1,21 +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.time import humanize_delta log = logging.getLogger(__name__) @@ -44,7 +41,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: @@ -110,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" @@ -133,7 +128,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_ctx = await self.mod_log.send_log_message( icon_url=Icons.filtering, colour=Colour(Colours.soft_red), title=f"Spam detected!", @@ -143,27 +139,8 @@ class AntiSpam: ping_everyone=AntiSpamConfig.ping_everyone ) - await member.add_roles(self.muted_role, reason=reason) - 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}." - ) + # 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? diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 247ee26b8..570d6549f 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, " + 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": { "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, }, } @@ -121,12 +139,35 @@ 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"]: - 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: @@ -142,6 +183,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, @@ -151,16 +194,9 @@ 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 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) - break # We don't want multiple filters to trigger @staticmethod @@ -272,6 +308,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/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: diff --git a/bot/cogs/modlog.py b/bot/cogs/modlog.py index 0561b5afb..06f81cb36 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,15 @@ 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) + log_message = 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) + + 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: 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): 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: " "<https://discordapp.com/developers/applications/me>\n" "Feel free to re-post it with the token removed. " "If you believe this was a mistake, please let us know!" 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] diff --git a/config-default.yml b/config-default.yml index ad87e44ac..21d7f20b9 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? @@ -154,6 +156,8 @@ filter: - 267624335836053506 # Python Discord - 440186186024222721 # Python Discord: ModLog Emojis - 273944235143593984 # STEM + - 348658686962696195 # RLBot + - 531221516914917387 # Pallets domain_blacklist: - pornhub.com |