Repository: Cog-Creators/Red-DiscordBot

Issue:
[Filter] [V3] Removes words not added to the filter.
# Other bugs

<!-- 
Did you find a bug with something other than a command? Fill out the following:
-->

#### What were you trying to do?

we added certain words to the filter to be filtered on the discord server.
and we want the words that consist of the bad word to remain on the channel.

#### What were you expecting to happen?

for the word consisting of the bad. so not being the bad word.
For example: ass is on the filter but if someone then types assist or assistant it should leave those words in the channel, because they are not bad words.

#### What actually happened?

Someone mentioned that he needs assistance with a specific problem and the bot removed the word assistance because it has the word ass in it that is indeed on the filter

the word has been added with: !filter add ass

#### How can we reproduce this issue?

1. add a word to the the filter by typing for example: !filter add ass
2. type a word that has the filtered word in it like: assistant
3. The bot will remove the word assistant, event though the word itself isn't filtered but the word ass is.

I hope i did this reporting right, i've made an account with github just to report this issue.
Thanks for the great bot!



Discussion:

Resolving patch:
diff --git a/redbot/cogs/filter/filter.py b/redbot/cogs/filter/filter.py
--- a/redbot/cogs/filter/filter.py
+++ b/redbot/cogs/filter/filter.py
@@ -1,11 +1,13 @@
 import discord
-from typing import Union
+import re
+from typing import Union, Set
 
 from redbot.core import checks, Config, modlog, commands
 from redbot.core.bot import Red
 from redbot.core.i18n import Translator, cog_i18n
 from redbot.core.utils.chat_formatting import pagify
 
+RE_WORD_SPLIT = re.compile(r"[^\w]")
 _ = Translator("Filter", __file__)
 
 
@@ -317,17 +319,38 @@ async def remove_from_filter(
 
         return removed
 
+    async def filter_hits(
+        self, text: str, server_or_channel: Union[discord.Guild, discord.TextChannel]
+    ) -> Set[str]:
+        if isinstance(server_or_channel, discord.Guild):
+            word_list = set(await self.settings.guild(server_or_channel).filter())
+        elif isinstance(server_or_channel, discord.TextChannel):
+            word_list = set(
+                await self.settings.guild(server_or_channel.guild).filter()
+                + await self.settings.channel(server_or_channel).filter()
+            )
+        else:
+            raise TypeError("%r should be Guild or TextChannel" % server_or_channel)
+
+        content = text.lower()
+        msg_words = set(RE_WORD_SPLIT.split(content))
+
+        filtered_phrases = {x for x in word_list if len(RE_WORD_SPLIT.split(x)) > 1}
+        filtered_words = word_list - filtered_phrases
+
+        hits = {p for p in filtered_phrases if p in content}
+        hits |= filtered_words & msg_words
+        return hits
+
     async def check_filter(self, message: discord.Message):
         server = message.guild
         author = message.author
-        word_list = set(
-            await self.settings.guild(server).filter()
-            + await self.settings.channel(message.channel).filter()
-        )
+
         filter_count = await self.settings.guild(server).filterban_count()
         filter_time = await self.settings.guild(server).filterban_time()
         user_count = await self.settings.member(author).filter_count()
         next_reset_time = await self.settings.member(author).next_reset_time()
+
         if filter_count > 0 and filter_time > 0:
             if message.created_at.timestamp() >= next_reset_time:
                 next_reset_time = message.created_at.timestamp() + filter_time
@@ -336,36 +359,36 @@ async def check_filter(self, message: discord.Message):
                     user_count = 0
                     await self.settings.member(author).filter_count.set(user_count)
 
-        if word_list:
-            for w in word_list:
-                if w in message.content.lower():
-                    try:
-                        await message.delete()
-                    except discord.HTTPException:
-                        pass
-                    else:
-                        if filter_count > 0 and filter_time > 0:
-                            user_count += 1
-                            await self.settings.member(author).filter_count.set(user_count)
-                            if (
-                                user_count >= filter_count
-                                and message.created_at.timestamp() < next_reset_time
-                            ):
-                                reason = _("Autoban (too many filtered messages.)")
-                                try:
-                                    await server.ban(author, reason=reason)
-                                except discord.HTTPException:
-                                    pass
-                                else:
-                                    await modlog.create_case(
-                                        self.bot,
-                                        server,
-                                        message.created_at,
-                                        "filterban",
-                                        author,
-                                        server.me,
-                                        reason,
-                                    )
+        hits = await self.filter_hits(message.content, message.channel)
+
+        if hits:
+            try:
+                await message.delete()
+            except discord.HTTPException:
+                pass
+            else:
+                if filter_count > 0 and filter_time > 0:
+                    user_count += 1
+                    await self.settings.member(author).filter_count.set(user_count)
+                    if (
+                        user_count >= filter_count
+                        and message.created_at.timestamp() < next_reset_time
+                    ):
+                        reason = _("Autoban (too many filtered messages.)")
+                        try:
+                            await server.ban(author, reason=reason)
+                        except discord.HTTPException:
+                            pass
+                        else:
+                            await modlog.create_case(
+                                self.bot,
+                                server,
+                                message.created_at,
+                                "filterban",
+                                author,
+                                server.me,
+                                reason,
+                            )
 
     async def on_message(self, message: discord.Message):
         if isinstance(message.channel, discord.abc.PrivateChannel):


Test patch: