From 09cad58f433f385b5a328a39585152dcc7b1229c Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Wed, 30 Jun 2021 21:55:17 +0200 Subject: [PATCH] Don't snipe steam codes, fixes #78 (bot commands can be sniped) --- cogs/events.py | 6 +++--- data/regexes.py | 10 ++++++++++ data/snipe.py | 19 +++++++++++++++++-- tests/run_tests.py | 6 ++++++ tests/test_data/__init__.py | 0 tests/test_data/test_regexes.py | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 data/regexes.py create mode 100644 tests/run_tests.py create mode 100644 tests/test_data/__init__.py create mode 100644 tests/test_data/test_regexes.py diff --git a/cogs/events.py b/cogs/events.py index b1ff4b6..9069c3b 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -1,5 +1,5 @@ from data import constants -from data.snipe import Snipe, Action +from data.snipe import Snipe, Action, should_snipe import datetime import discord from discord.ext import commands @@ -276,13 +276,13 @@ class Events(commands.Cog): if not checks.freeGamesCheck(after): return await self.failedChecksCog.freeGames(after) - if before.guild is not None and not before.author.bot: + if should_snipe(before): self.client.snipe[before.channel.id] = Snipe(before.author.id, before.channel.id, before.guild.id, Action.Edit, before.content, after.content) @commands.Cog.listener() async def on_message_delete(self, message: discord.Message): - if message.guild is not None and not message.author.bot: + if should_snipe(message): self.client.snipe[message.channel.id] = Snipe(message.author.id, message.channel.id, message.guild.id, Action.Remove, message.content) diff --git a/data/regexes.py b/data/regexes.py new file mode 100644 index 0000000..e0475af --- /dev/null +++ b/data/regexes.py @@ -0,0 +1,10 @@ +import re + +STEAM_CODE = {"pattern": "[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}", "flags": re.IGNORECASE} + + +def contains(text: str, pattern: dict) -> bool: + if "flags" in pattern: + return re.search(pattern["pattern"], text, pattern["flags"]) is not None + else: + return re.search(pattern["pattern"], text) is not None diff --git a/data/snipe.py b/data/snipe.py index 41b7b22..0078b60 100644 --- a/data/snipe.py +++ b/data/snipe.py @@ -1,6 +1,7 @@ -from enum import Enum - from attr import dataclass +from data import regexes +import discord +from enum import Enum class Action(Enum): @@ -22,3 +23,17 @@ class Snipe: action: Action old: str new: str = None + + +def should_snipe(message: discord.Message) -> bool: + """ + Check if a message should be sniped or not + This could be a oneliner but that makes it unreadable + """ + if message.guild is None: + return False + + if message.author.bot: + return False + + return not regexes.contains(message.content, regexes.STEAM_CODE) diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100644 index 0000000..4b64cea --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,6 @@ +import unittest + + +if __name__ == "__main__": + suite = unittest.TestLoader().discover('.', pattern="test_*.py") + unittest.TextTestRunner(verbosity=3).run(suite) diff --git a/tests/test_data/__init__.py b/tests/test_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_data/test_regexes.py b/tests/test_data/test_regexes.py new file mode 100644 index 0000000..02bbfd6 --- /dev/null +++ b/tests/test_data/test_regexes.py @@ -0,0 +1,32 @@ +from data import regexes +import re +import unittest + + +class TestRegexes(unittest.TestCase): + def test_contains(self): + pattern = {"pattern": "ABC123"} + + self.assertTrue(regexes.contains("ABC123TESTSTRING", pattern)) # Beginning + self.assertTrue(regexes.contains("TESTABC123STRING", pattern)) # Middle + self.assertTrue(regexes.contains("TESTSTRINGABC123", pattern)) # End + self.assertTrue(regexes.contains("ABC123", pattern)) # Entire string + + self.assertFalse(regexes.contains("aBC123TESTSTRING", pattern)) # Wrong casing + self.assertFalse(regexes.contains("SOMETHING ELSE", pattern)) # No match + + # Add case insensitive flag + pattern["flags"] = re.IGNORECASE + self.assertTrue(regexes.contains("aBC123TESTSTRING", pattern)) # Incorrect casing should now pass + + def test_steam_codes(self): + self.assertTrue(regexes.contains("AAAAA-BBBBB-CCCCC", regexes.STEAM_CODE)) # Only letters + self.assertTrue(regexes.contains("11111-22222-33333", regexes.STEAM_CODE)) # Only numbers + self.assertTrue(regexes.contains("ABC12-34DEF-GHI56", regexes.STEAM_CODE)) # Both + self.assertTrue(regexes.contains("abcde-fghij-lmnop", regexes.STEAM_CODE)) # Case insensitive flag + self.assertTrue(regexes.contains("AAAAAA-BBBBB-CCCCC", regexes.STEAM_CODE)) # Extra characters can be in front + + self.assertFalse(regexes.contains("A-BBBBB-CCCCC", regexes.STEAM_CODE)) # First group is too small + self.assertFalse(regexes.contains("AAAAA-BBBBBB-CCCCC", regexes.STEAM_CODE)) # Second group is too big + self.assertFalse(regexes.contains("AA??A-#ù$B6-!ÈCMa", regexes.STEAM_CODE)) # Invalid characters + self.assertFalse(regexes.contains("Something something communism", regexes.STEAM_CODE)) # Random string