diff options
| -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  |