def test_find_token_no_matches(self, token_re): """None should be returned if the regex matches no tokens in a message.""" token_re.finditer.return_value = () return_value = TokenRemover.find_token_in_message(self.msg) self.assertIsNone(return_value) token_re.finditer.assert_called_once_with(self.msg.content)
def test_find_token_ignores_bot_messages(self, token_re): """The token finder should ignore messages authored by bots.""" self.msg.author.bot = True return_value = TokenRemover.find_token_in_message(self.msg) self.assertIsNone(return_value) token_re.finditer.assert_not_called()
def test_find_token_invalid_matches(self, token_re, token_cls, is_valid_id, is_valid_timestamp): """None should be returned if no matches have valid user IDs or timestamps.""" token_re.finditer.return_value = [ mock.create_autospec(Match, spec_set=True, instance=True) ] token_cls.return_value = mock.create_autospec(Token, spec_set=True, instance=True) is_valid_id.return_value = False is_valid_timestamp.return_value = False return_value = TokenRemover.find_token_in_message(self.msg) self.assertIsNone(return_value) token_re.finditer.assert_called_once_with(self.msg.content)
def test_find_token_valid_match(self, token_re, token_cls, is_valid_id, is_valid_timestamp): """The first match with a valid user ID and timestamp should be returned as a `Token`.""" matches = [ mock.create_autospec(Match, spec_set=True, instance=True), mock.create_autospec(Match, spec_set=True, instance=True), ] tokens = [ mock.create_autospec(Token, spec_set=True, instance=True), mock.create_autospec(Token, spec_set=True, instance=True), ] token_re.finditer.return_value = matches token_cls.side_effect = tokens is_valid_id.side_effect = ( False, True) # The 1st match will be invalid, 2nd one valid. is_valid_timestamp.return_value = True return_value = TokenRemover.find_token_in_message(self.msg) self.assertEqual(tokens[1], return_value) token_re.finditer.assert_called_once_with(self.msg.content)
async def relay_message(self, msg: Message) -> None: """Relays the message to the relevant watch channel.""" limit = BigBrotherConfig.header_message_limit if (msg.author.id != self.message_history.last_author or msg.channel.id != self.message_history.last_channel or self.message_history.message_count >= limit): self.message_history = MessageHistory(last_author=msg.author.id, last_channel=msg.channel.id) await self.send_header(msg) if TokenRemover.find_token_in_message(msg) or WEBHOOK_URL_RE.search( msg.content): cleaned_content = "Content is censored because it contains a bot or webhook token." elif cleaned_content := msg.clean_content: # Put all non-media URLs in a code block to prevent embeds media_urls = { embed.url for embed in msg.embeds if embed.type in ("image", "video") } for url in URL_RE.findall(cleaned_content): if url not in media_urls: cleaned_content = cleaned_content.replace(url, f"`{url}`")
async def on_message(self, msg: Message) -> None: """ Detect poorly formatted Python code in new messages. If poorly formatted code is detected, send the user a helpful message explaining how to do properly formatted Python syntax highlighting codeblocks. """ is_help_channel = (getattr(msg.channel, "category", None) and msg.channel.category.id in (Categories.help_available, Categories.help_in_use)) parse_codeblock = ((is_help_channel or msg.channel.id in self.channel_cooldowns or msg.channel.id in self.channel_whitelist) and not msg.author.bot and len(msg.content.splitlines()) > 3 and not TokenRemover.find_token_in_message(msg)) if parse_codeblock: # no token in the msg on_cooldown = (time.time() - self.channel_cooldowns.get(msg.channel.id, 0)) < 300 if not on_cooldown or DEBUG_MODE: try: if self.has_bad_ticks(msg): ticks = msg.content[:3] content = self.codeblock_stripping( f"```{msg.content[3:-3]}```", True) if content is None: return content, repl_code = content if len(content) == 2: content = content[1] else: content = content[0] space_left = 204 if len(content) >= space_left: current_length = 0 lines_walked = 0 for line in content.splitlines(keepends=True): if current_length + len( line ) > space_left or lines_walked == 10: break current_length += len(line) lines_walked += 1 content = content[:current_length] + "#..." content_escaped_markdown = RE_MARKDOWN.sub( r'\\\1', content) howto = ( "It looks like you are trying to paste code into this channel.\n\n" "You seem to be using the wrong symbols to indicate where the codeblock should start. " f"The correct symbols would be \\`\\`\\`, not `{ticks}`.\n\n" "**Here is an example of how it should look:**\n" f"\\`\\`\\`python\n{content_escaped_markdown}\n\\`\\`\\`\n\n" "**This will result in the following:**\n" f"```python\n{content}\n```") else: howto = "" content = self.codeblock_stripping(msg.content, False) if content is None: return content, repl_code = content # Attempts to parse the message into an AST node. # Invalid Python code will raise a SyntaxError. tree = ast.parse(content[0]) # Multiple lines of single words could be interpreted as expressions. # This check is to avoid all nodes being parsed as expressions. # (e.g. words over multiple lines) if not all( isinstance(node, ast.Expr) for node in tree.body) or repl_code: # Shorten the code to 10 lines and/or 204 characters. space_left = 204 if content and repl_code: content = content[1] else: content = content[0] if len(content) >= space_left: current_length = 0 lines_walked = 0 for line in content.splitlines(keepends=True): if current_length + len( line ) > space_left or lines_walked == 10: break current_length += len(line) lines_walked += 1 content = content[:current_length] + "#..." content_escaped_markdown = RE_MARKDOWN.sub( r'\\\1', content) howto += ( "It looks like you're trying to paste code into this channel.\n\n" "Discord has support for Markdown, which allows you to post code with full " "syntax highlighting. Please use these whenever you paste code, as this " "helps improve the legibility and makes it easier for us to help you.\n\n" f"**To do this, use the following method:**\n" f"\\`\\`\\`python\n{content_escaped_markdown}\n\\`\\`\\`\n\n" "**This will result in the following:**\n" f"```python\n{content}\n```") log.debug( f"{msg.author} posted something that needed to be put inside python code " "blocks. Sending the user some instructions.") else: log.trace( "The code consists only of expressions, not sending instructions" ) if howto != "": howto_embed = Embed(description=howto) bot_message = await msg.channel.send( f"Hey {msg.author.mention}!", embed=howto_embed) self.codeblock_message_ids[msg.id] = bot_message.id self.bot.loop.create_task( wait_for_deletion(bot_message, user_ids=(msg.author.id, ), client=self.bot)) else: return if msg.channel.id not in self.channel_whitelist: self.channel_cooldowns[msg.channel.id] = time.time() except SyntaxError: log.trace( f"{msg.author} posted in a help channel, and when we tried to parse it as Python code, " "ast.parse raised a SyntaxError. This probably just means it wasn't Python code. " f"The message that was posted was:\n\n{msg.content}\n\n" )