def test_calculate_ratios_does_nothing(self, create_core): """Tests the loop does nothing on different messages""" member = Member(1, 1, messages=[Message(1, 2, 3, 4, "Hello I am the world")]) create_core._calculate_ratios(Message(2, 2, 3, 4, "My name is Ethan!"), member, Guild(1))
def test_calculate_ratios_raises(self, create_core): member = Member( 1, 1, messages=[Message(1, 2, 3, 4, "One"), Message(2, 2, 3, 4, "Two")]) m = Message(1, 2, 3, 4, "One") m.creation_time = member.messages[0].creation_time with pytest.raises(DuplicateObject): create_core._calculate_ratios(m, member, Guild(1))
async def test_delete_member_messages(self, create_dpy_lib_handler): member = Member(1, 2) member.messages = [ Message(1, 2, 3, 4, "First"), Message(2, 2, 3, 4, "second", is_duplicate=True), Message(3, 2, 3, 4, "third", is_duplicate=True), ] with patch("antispam.libs.dpy.DPY.delete_message", new_callable=AsyncMock) as delete_call: await create_dpy_lib_handler.delete_member_messages(member) assert delete_call.call_count == 2
def test_calculate_ratios(self, create_core): member = Member(1, 1) member.messages = [ Message(1, 1, 1, 1, "Hello world", datetime.datetime.now()) ] message = Message(2, 1, 1, 1, "Hello world", datetime.datetime.now()) assert member.duplicate_counter == 1 create_core._calculate_ratios(message, member, Guild(1)) assert member.duplicate_counter == 2 assert message.is_duplicate is True
def test_calculate_ratios_per_channel(self, create_core): member = Member(1, 1) member.messages = [ Message(1, 1, 1, 1, "Hello world", datetime.datetime.now()) ] message = Message(2, 2, 1, 1, "Hello world", datetime.datetime.now()) guild = Guild(1, options=Options(per_channel_spam=True)) assert member.duplicate_counter == 1 create_core._calculate_ratios(message, member, guild) assert member.duplicate_counter == 1 assert message.is_duplicate is False
def create_message_from_dict(message_data) -> Message: message = Message( id=message_data["id"], content=message_data["content"], guild_id=message_data["guild_id"], author_id=message_data["author_id"], channel_id=message_data["channel_id"], ) message.is_duplicate = message_data["is_duplicate"] message.creation_time = datetime.datetime.strptime( message_data["creation_time"], "%f:%S:%M:%H:%d:%m:%Y") log.info("Created Message(id=%s) from dict", message.id) return message
def test_create_message(self): test_dict = { "id": 1, "content": "Hello World!", "guild_id": 2, "author_id": 3, "channel_id": 4, "is_duplicate": True, "creation_time": "225596:21:8:3:12:5:2021", } time = datetime.datetime.strptime("225596:21:8:3:12:5:2021", "%f:%S:%M:%H:%d:%m:%Y") message = Message( id=1, content="Hello World!", guild_id=2, author_id=3, channel_id=4, is_duplicate=True, creation_time=time, ) test_message = FactoryBuilder.create_message_from_dict(test_dict) assert test_message == message
async def test_add_message_filled(self, create_memory_cache): """Test with a pre-filled member""" await create_memory_cache.set_member(Member(4, 3)) await create_memory_cache.add_message(Message(1, 2, 3, 4, "Content")) assert len(create_memory_cache.cache[3].members) == 1 assert len(create_memory_cache.cache[3].members[4].messages) == 1
async def test_add_message_raw(self, create_redis_cache): """Test without a pre-filled cache""" await create_redis_cache.add_message(Message(1, 2, 3, 4, "Content")) r_1 = await create_redis_cache.get_guild(3) assert len(r_1.members) == 1 assert len(r_1.members[4].messages) == 1
async def create_message(self, message: messages.Message) -> Message: log.debug( "Attempting to create a new message for author(id=%s) in Guild(%s)", message.author.id, message.guild_id, ) if not bool(message.content and message.content.strip()): if not message.embeds: raise LogicError content = "" for embed in message.embeds: if not isinstance(embed, embeds.Embed): raise LogicError content += await self.embed_to_string(embed) else: content = message.content if self.handler.options.delete_zero_width_chars: content = (content.replace("u200B", "").replace( "u200C", "").replace("u200D", "").replace("u200E", "").replace("u200F", "").replace("uFEFF", "")) return Message( id=message.id, channel_id=message.channel_id, guild_id=message.guild_id, author_id=message.author.id, content=content, )
async def test_add_message_filled_guild(self, create_redis_cache): """Test with an existing guild""" await create_redis_cache.set_guild(Guild(3, Options())) await create_redis_cache.add_message(Message(1, 2, 3, 4, "Content")) r_1 = await create_redis_cache.get_member(4, 3) r_2 = await create_redis_cache.get_guild(3) assert len(r_2.members) == 1 assert len(r_1.messages) == 1
async def create_message(self, message: "UserMessage") -> Message: log.debug( "Attempting to create a new message for author(id=%s) in Guild(%s)", message.author.id, message.guild_id, ) # TODO Reimplement once #424 is resolved # if message.type not in { # MessageType.DEFAULT, # MessageType.REPLY, # MessageType.APPLICATION_COMMAND, # MessageType.THREAD_STARTER_MESSAGE, # }: # raise InvalidMessage( # "Message is a system one, we don't check against those." # ) content = "" if message.sticker_items: # 'sticker' names should be unique.. all_stickers = "|".join(s.name for s in message.sticker_items) content += all_stickers elif not bool(message.content and message.content.strip()): if not message.embeds and not message.attachments: raise LogicError if not message.embeds: # We don't check against attachments raise InvalidMessage for embed in message.embeds: if not isinstance(embed, objects.Embed): raise LogicError content += await self.embed_to_string(embed) else: content += message.content if self.handler.options.delete_zero_width_chars: content = (content.replace("u200B", "").replace( "u200C", "").replace("u200D", "").replace("u200E", "").replace("u200F", "").replace("uFEFF", "")) return Message( id=message.id, channel_id=message.channel_id, guild_id=message.guild_id, author_id=message.author.id, content=content, )
async def test_set_member(self, create_mongo_cache): """Tests set member with an existing guild""" with pytest.raises(MemberNotFound): await create_mongo_cache.get_member(3, 1) await create_mongo_cache.set_member( Member( 3, 1, messages=[ Message(1, 1, 1, 1, "Hello world"), Message(2, 1, 1, 1, "foo bar"), ], ) ) r_1 = await create_mongo_cache.get_member(3, 1) assert isinstance(r_1, Member) assert len(r_1.messages) == 2 assert r_1.messages[0].content == "Hello world"
async def test_add_message(self, create_mongo_cache): r_1 = await create_mongo_cache.get_member(1, 1) assert isinstance(r_1, Member) assert len(r_1.messages) == 3 msg = Message(4, 1, 1, 1, "Hello world") await create_mongo_cache.add_message(msg) r_2 = await create_mongo_cache.get_member(1, 1) assert isinstance(r_2, Member) assert len(r_2.messages) == 4 assert r_2.messages[3].content == "Hello world"
def create_mongo_cache(create_handler) -> MockedMongoCache: guild_data: List[Dict[str, Any]] = [asdict(Guild(1))] member_data: List[Dict[str, Any]] = [ asdict( Member( 1, 1, warn_count=2, kick_count=1, messages=[ Message(1, 1, 1, 1, content="Foo"), Message(2, 1, 1, 1, content="Bar"), Message(3, 1, 1, 1, content="Baz"), ], ), recurse=True, ), asdict(Member(2, 1)), ] return MockedMongoCache(create_handler, member_data, guild_data)
def test_calculate_ratio_edge_case(self, create_core): """Tests calculate ratios correctly marks an edge case as spam See: https://mystb.in/HarderTournamentsPrimary.properties Previously it would not mark all "Spam tho" as spam when it should have """ member = Member(1, 1) member.messages = [ Message(1, 1, 1, 1, "This is a test", datetime.datetime.now()), Message(2, 1, 1, 1, "Heres another message", datetime.datetime.now()), Message(3, 1, 1, 1, "Spam tho", datetime.datetime.now()), Message(4, 1, 1, 1, "Spam tho", datetime.datetime.now()), Message(5, 1, 1, 1, "Spam tho", datetime.datetime.now()), ] message = Message(6, 1, 1, 1, "Spam tho", datetime.datetime.now()) create_core._calculate_ratios(message, member, Guild(1)) assert message.is_duplicate is True assert member.messages[0].is_duplicate is False assert member.messages[1].is_duplicate is False assert member.messages[2].is_duplicate is True assert member.messages[3].is_duplicate is True assert member.messages[4].is_duplicate is True
async def get_guild(self, guild_id: int) -> Guild: log.debug("Attempting to return cached Guild(id=%s)", guild_id) guild: Guild = await self.guilds.find({"id": guild_id}) # This is a dict here actually if not guild: raise GuildNotFound guild.options = Options(**guild.options) # type: ignore members: List[Member] = await self.members.find_many_by_custom( {"guild_id": guild_id}) for member in members: messages: List[Message] = [] for dict_message in member.messages: dict_message: dict = dict_message msg = Message(**dict_message) msg.creation_time = msg.creation_time.replace(tzinfo=pytz.UTC) messages.append(msg) member.messages = messages guild.members[member.id] = member return guild
async def get_member(self, member_id: int, guild_id: int) -> Member: log.debug( "Attempting to return a cached Member(id=%s) for Guild(id=%s)", member_id, guild_id, ) member: Member = await self.members.find({ "id": member_id, "guild_id": guild_id }) if not member: raise MemberNotFound messages: List[Message] = [] for dict_message in member.messages: dict_message: dict = dict_message msg = Message(**dict_message) msg.creation_time = msg.creation_time.replace(tzinfo=pytz.UTC) messages.append(msg) member.messages = messages return member
def test_clean_old_messages(self): member = Member( 1, 2, messages=[ Message(1, 2, 3, 4, "Hello"), Message( 2, 2, 3, 4, "World", creation_time=get_aware_time() - datetime.timedelta(seconds=45), ), ], ) assert len(member.messages) == 2 FactoryBuilder.clean_old_messages(member, get_aware_time(), Options()) assert len(member.messages) == 1
async def create_message(self, message: discord.Message) -> Message: log.debug( "Attempting to create a new message for author(id=%s) in Guild(%s)", message.author.id, message.guild.id, ) if message.is_system(): raise InvalidMessage( "Message is a system one, we don't check against those.") if message.stickers: # 'sticker' urls should be unique.. all_stickers = "|".join(s.url for s in message.stickers) content = all_stickers elif not bool(message.content and message.content.strip()): if not message.embeds and not message.attachments: # System message? Like on join trip these raise LogicError if not message.embeds: # We don't check agaisn't attachments raise InvalidMessage content = "" for embed in message.embeds: if not isinstance(embed, discord.Embed): raise LogicError if embed.type.lower() != "rich": raise LogicError content += await self.embed_to_string(embed) else: content = message.clean_content if self.handler.options.delete_zero_width_chars: content = (content.replace("u200B", "").replace( "u200C", "").replace("u200D", "").replace("u200E", "").replace("u200F", "").replace("uFEFF", "")) return Message( id=message.id, channel_id=message.channel.id, guild_id=message.guild.id, author_id=message.author.id, content=content, )
async def test_clean_up_duplicate_count(self, create_core): member = Member(1, 1) messages = [ Message( 1, 1, 1, 1, "First message", datetime.datetime.now() - datetime.timedelta(minutes=1), ), Message( 2, 1, 1, 1, "Second message", datetime.datetime.now() - datetime.timedelta(minutes=1), ), Message( 3, 1, 1, 1, "Third message", datetime.datetime.now() - datetime.timedelta(minutes=1), is_duplicate=True, ), Message(4, 1, 1, 1, "Fourth message", datetime.datetime.now()), ] member.messages = messages member.duplicate_counter = 1 await create_core.clean_up(member, datetime.datetime.now(), 1, Guild(1)) assert member.duplicate_counter == 0
async def test_clean_up(self, create_core): member = Member(1, 1) messages = [ Message( 1, 1, 1, 1, "First message", datetime.datetime.now() - datetime.timedelta(minutes=1), ), Message( 2, 1, 1, 1, "Second message", datetime.datetime.now() - datetime.timedelta(minutes=1), ), Message( 3, 1, 1, 1, "Third message", datetime.datetime.now() - datetime.timedelta(minutes=1), ), Message(4, 1, 1, 1, "Fourth message", datetime.datetime.now()), ] member.messages = messages assert len(member.messages) == 4 await create_core.clean_up(member, datetime.datetime.now(), 1, Guild(1)) assert len(member.messages) == 1
async def test_clean_cache_strict_member(self, create_handler): """Tests clean_cache on members with strict mode""" await create_handler.cache.set_member(Member(1, 1)) assert bool(create_handler.cache.cache) await create_handler.clean_cache(strict=True) assert not bool(create_handler.cache.cache) await create_handler.cache.set_member( Member(1, 1, messages=[Message(1, 2, 3, 4, "Hello")]) ) assert bool(create_handler.cache.cache) await create_handler.clean_cache(strict=True) assert bool(create_handler.cache.cache)
async def test_add_message_no_exist(self, create_mongo_cache): """Tests add_message when the member doesnt exist""" with pytest.raises(MemberNotFound): await create_mongo_cache.get_member(3, 3) with pytest.raises(GuildNotFound): await create_mongo_cache.get_guild(3) await create_mongo_cache.add_message(Message(1, 1, 3, 3, "Foo bar")) r_1 = await create_mongo_cache.get_member(3, 3) assert isinstance(r_1, Member) assert len(r_1.messages) == 1 assert r_1.messages[0].content == "Foo bar" r_2 = await create_mongo_cache.get_guild(3) assert isinstance(r_2, Guild) assert r_1 in list(r_2.members.values())
async def create_message(self, message: messages.Message) -> Message: log.debug( "Attempting to create a new message for author(id=%s) in Guild(%s)", message.author.id, message.guild_id, ) content = "" if message.stickers: # 'sticker' names should be unique.. all_stickers = "|".join(s.name for s in message.stickers) content += all_stickers if not bool(message.content and message.content.strip()): if not message.embeds and not message.attachments: raise LogicError if not message.embeds: # We dont check attachments lol raise InvalidMessage for embed in message.embeds: if not isinstance(embed, embeds.Embed): raise LogicError content += await self.embed_to_string(embed) else: content = message.content if self.handler.options.delete_zero_width_chars: content = (content.replace("u200B", "").replace( "u200C", "").replace("u200D", "").replace("u200E", "").replace("u200F", "").replace("uFEFF", "")) return Message( id=message.id, channel_id=message.channel_id, guild_id=message.guild_id, author_id=message.author.id, content=content, )
async def get_member(self, member_id: int, guild_id: int) -> Member: log.debug( "Attempting to return a cached Member(id=%s) for Guild(id=%s)", member_id, guild_id, ) resp = await self.redis.get(f"MEMBER:{guild_id}:{member_id}") if not resp: raise MemberNotFound as_json = json.loads(resp.decode("utf-8")) member: Member = Member(**as_json) messages: List[Message] = [] for message in member.messages: messages.append(Message(**message)) # type: ignore member.messages = messages return member
async def test_set_member_no_guild(self, create_mongo_cache): """Tests set member with no existing cached guild""" with pytest.raises(MemberNotFound): await create_mongo_cache.get_member(2, 2) with pytest.raises(GuildNotFound): await create_mongo_cache.get_guild(2) await create_mongo_cache.set_member( Member(2, 2, messages=[Message(1, 1, 1, 1, "Hello world")]) ) r_1 = await create_mongo_cache.get_guild(2) assert isinstance(r_1, Guild) assert len(r_1.members) == 1 r_2 = await create_mongo_cache.get_member(2, 2) assert isinstance(r_2, Member) assert len(r_2.messages) == 1 assert r_2.messages[0].content == "Hello world"
def _calculate_ratios( self, message: Message, member: Member, ) -> None: """ Calculates a messages relation to other messages """ for message_obj in member.messages: # This calculates the relation to each other if message == message_obj: raise DuplicateObject elif ( self.options.per_channel_spam and message.channel_id != message_obj.channel_id ): # This user's spam should only be counted per channel # and these messages are in different channel continue elif ( fuzz.token_sort_ratio(message.content, message_obj.content) >= self.options.message_duplicate_accuracy ): """ The handler works off an internal message duplicate counter so just increment that and then let our logic process it later """ self._increment_duplicate_count(member, channel_id=message.channel_id) message.is_duplicate = True message_obj.is_duplicate = True if ( self._get_duplicate_count( member, channel_id=message.channel_id, ) >= self.options.message_duplicate_count ): break
async def test_add_message_filled_guild(self, create_memory_cache): """Test with an existing guild""" await create_memory_cache.set_guild(Guild(3, Options())) await create_memory_cache.add_message(Message(1, 2, 3, 4, "Content")) assert len(create_memory_cache.cache[3].members) == 1 assert len(create_memory_cache.cache[3].members[4].messages) == 1
def test_create_member(self): test_data = { "id": 1, "guild_id": 2, "is_in_guild": True, "warn_count": 5, "kick_count": 6, "duplicate_count": 7, "duplicate_channel_counter_dict": {}, "messages": [], } test_member = FactoryBuilder.create_member_from_dict(test_data) member = Member( id=1, guild_id=2, in_guild=True, warn_count=5, kick_count=6, duplicate_counter=7, duplicate_channel_counter_dict={}, messages=[], ) assert test_member == member test_data_two = { "id": 1, "guild_id": 2, "is_in_guild": True, "warn_count": 5, "kick_count": 6, "duplicate_count": 7, "duplicate_channel_counter_dict": {}, "messages": [{ "id": 1, "content": "Hello World!", "guild_id": 2, "author_id": 3, "channel_id": 4, "is_duplicate": True, "creation_time": "225596:21:8:3:12:5:2021", }], } test_member_two = FactoryBuilder.create_member_from_dict(test_data_two) time = datetime.datetime.strptime("225596:21:8:3:12:5:2021", "%f:%S:%M:%H:%d:%m:%Y") message = Message( id=1, content="Hello World!", guild_id=2, author_id=3, channel_id=4, is_duplicate=True, creation_time=time, ) member_two = Member( id=1, guild_id=2, in_guild=True, warn_count=5, kick_count=6, duplicate_counter=7, duplicate_channel_counter_dict={}, messages=[message], ) assert member_two == test_member_two