diff options
| -rw-r--r-- | bot/cogs/filtering.py | 83 | ||||
| -rw-r--r-- | bot/constants.py | 1 | ||||
| -rw-r--r-- | config-default.yml | 3 | 
3 files changed, 80 insertions, 7 deletions
| diff --git a/bot/cogs/filtering.py b/bot/cogs/filtering.py index 1e7521054..61c8f389b 100644 --- a/bot/cogs/filtering.py +++ b/bot/cogs/filtering.py @@ -1,10 +1,12 @@ +import asyncio +import datetime  import logging  import re -from typing import Optional, Union +from typing import Mapping, Optional, Union  import discord.errors  from dateutil.relativedelta import relativedelta -from discord import Colour, DMChannel, Member, Message, TextChannel +from discord import Colour, DMChannel, Member, Message, NotFound, TextChannel  from discord.ext.commands import Bot, Cog  from bot.cogs.moderation import ModLog @@ -12,6 +14,8 @@ from bot.constants import (      Channels, Colours,      Filter, Icons, URLs  ) +from bot.utils.scheduling import Scheduler +from bot.utils.time import wait_until  log = logging.getLogger(__name__) @@ -36,12 +40,15 @@ TOKEN_WATCHLIST_PATTERNS = [      re.compile(fr'{expression}', flags=re.IGNORECASE) for expression in Filter.token_watchlist  ] +OFFENSIVE_MSG_DELETE_TIME = datetime.timedelta(days=Filter.offensive_msg_delete_time) -class Filtering(Cog): + +class Filtering(Cog, Scheduler):      """Filtering out invites, blacklisting domains, and warning us of certain regular expressions."""      def __init__(self, bot: Bot):          self.bot = bot +        super().__init__()          staff_mistake_str = "If you believe this was a mistake, please let staff know!"          self.filters = { @@ -54,7 +61,8 @@ class Filtering(Cog):                  "notification_msg": (                      "Your post has been removed for abusing Unicode character rendering (aka Zalgo text). "                      f"{staff_mistake_str}" -                ) +                ), +                "schedule_deletion": False              },              "filter_invites": {                  "enabled": Filter.filter_invites, @@ -65,7 +73,8 @@ class Filtering(Cog):                  "notification_msg": (                      f"Per Rule 6, your invite link has been removed. {staff_mistake_str}\n\n"                      r"Our server rules can be found here: <https://pythondiscord.com/pages/rules>" -                ) +                ), +                "schedule_deletion": False              },              "filter_domains": {                  "enabled": Filter.filter_domains, @@ -75,28 +84,35 @@ class Filtering(Cog):                  "user_notification": Filter.notify_user_domains,                  "notification_msg": (                      f"Your URL has been removed because it matched a blacklisted domain. {staff_mistake_str}" -                ) +                ), +                "schedule_deletion": False              },              "watch_rich_embeds": {                  "enabled": Filter.watch_rich_embeds,                  "function": self._has_rich_embed,                  "type": "watchlist",                  "content_only": False, +                "schedule_deletion": False              },              "watch_words": {                  "enabled": Filter.watch_words,                  "function": self._has_watchlist_words,                  "type": "watchlist",                  "content_only": True, +                "schedule_deletion": True              },              "watch_tokens": {                  "enabled": Filter.watch_tokens,                  "function": self._has_watchlist_tokens,                  "type": "watchlist",                  "content_only": True, +                "schedule_deletion": True              },          } +        self.deletion_task = None +        self.bot.loop.create_task(self.reschedule_offensive_msg_deletion()) +      @property      def mod_log(self) -> ModLog:          """Get currently loaded ModLog cog instance.""" @@ -175,6 +191,21 @@ class Filtering(Cog):                              if _filter["user_notification"]:                                  await self.notify_member(msg.author, _filter["notification_msg"], msg.channel) +                        # If the message is classed as offensive, we store it in the site db and +                        # it will be deleted it after one week. +                        if _filter["schedule_deletion"]: +                            delete_date = msg.created_at + OFFENSIVE_MSG_DELETE_TIME +                            await self.bot.api_client.post( +                                'bot/offensive-message', +                                json={ +                                    'id': msg.id, +                                    'channel_id': msg.channel.id, +                                    'delete_date': delete_date.isoformat()[:-1] +                                } +                            ) +                            log.trace(f"Offensive message will be deleted on " +                                      f"{delete_date.isoformat()}") +                          if isinstance(msg.channel, DMChannel):                              channel_str = "via DM"                          else: @@ -368,6 +399,46 @@ class Filtering(Cog):          except discord.errors.Forbidden:              await channel.send(f"{filtered_member.mention} {reason}") +    async def _scheduled_task(self, msg: dict) -> None: +        """A coroutine which delete the offensive message once the delete date is reached.""" +        delete_at = datetime.datetime.fromisoformat(msg['delete_date'][:-1]) + +        await wait_until(delete_at) +        await self.delete_offensive_msg(msg) + +        self.cancel_task(msg['id']) + +    async def reschedule_offensive_msg_deletion(self) -> None: +        """Get all the pending message deletion from the API and reschedule them.""" +        await self.bot.wait_until_ready() +        response = await self.bot.api_client.get( +            'bot/offensive-message', +        ) + +        now = datetime.datetime.utcnow() +        loop = asyncio.get_event_loop() + +        for msg in response: +            delete_at = datetime.datetime.fromisoformat(msg['delete_date'][:-1]) + +            if delete_at < now: +                await self.delete_offensive_msg(msg) +            else: +                self.schedule_task(loop, msg['id'], msg) + +    async def delete_offensive_msg(self, msg: Mapping[str, str]) -> None: +        """Delete an offensive message, and then delete it from the db.""" +        try: +            channel = self.bot.get_channel(msg['channel_id']) +            if channel: +                msg_obj = await channel.fetch_message(msg['id']) +                await msg_obj.delete() +        except NotFound: +            log.info(f"Tried to delete message {msg['id']}, but the message can't be found " +                     f"(it has been probably already deleted).") +        await self.bot.api_client.delete(f'bot/offensive-message/{msg["id"]}') +        log.info(f"Deleted the offensive message with id {msg['id']}.") +  def setup(bot: Bot) -> None:      """Filtering cog load.""" diff --git a/bot/constants.py b/bot/constants.py index 45f42cf81..e906312db 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -211,6 +211,7 @@ class Filter(metaclass=YAMLGetter):      notify_user_domains: bool      ping_everyone: bool +    offensive_msg_delete_time: int      guild_invite_whitelist: List[int]      domain_blacklist: List[str]      word_watchlist: List[str] diff --git a/config-default.yml b/config-default.yml index ee9f8a06b..b8fc94f5d 100644 --- a/config-default.yml +++ b/config-default.yml @@ -174,7 +174,8 @@ filter:      notify_user_domains:     false      # Filter configuration -    ping_everyone: true  # Ping @everyone when we send a mod-alert? +    ping_everyone:             true  # Ping @everyone when we send a mod-alert? +    offensive_msg_delete_time: 7     # How many days before deleting an offensive message?      guild_invite_whitelist:          - 280033776820813825  # Functional Programming | 
