diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/__init__.py | 2 | ||||
| -rw-r--r-- | bot/exts/info/information.py | 40 | ||||
| -rw-r--r-- | bot/exts/utils/snekbox.py | 41 | ||||
| -rw-r--r-- | tests/bot/exts/utils/test_snekbox.py | 7 | 
4 files changed, 56 insertions, 34 deletions
| diff --git a/bot/__init__.py b/bot/__init__.py index 3ee70c4e9..4fce04532 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -64,7 +64,7 @@ coloredlogs.install(logger=root_log, stream=sys.stdout)  logging.getLogger("discord").setLevel(logging.WARNING)  logging.getLogger("websockets").setLevel(logging.WARNING)  logging.getLogger("chardet").setLevel(logging.WARNING) -logging.getLogger(__name__) +logging.getLogger("async_rediscache").setLevel(logging.WARNING)  # On Windows, the selector event loop is required for aiodns. diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 2d9cab94b..5aaf85e5a 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -6,12 +6,13 @@ from collections import Counter, defaultdict  from string import Template  from typing import Any, Mapping, Optional, Tuple, Union -from discord import ChannelType, Colour, Embed, Guild, Member, Message, Role, Status, utils +from discord import ChannelType, Colour, Embed, Guild, Message, Role, Status, utils  from discord.abc import GuildChannel  from discord.ext.commands import BucketType, Cog, Context, Paginator, command, group, has_any_role  from bot import constants  from bot.bot import Bot +from bot.converters import FetchedMember  from bot.decorators import in_whitelist  from bot.pagination import LinePaginator  from bot.utils.channel import is_mod_channel @@ -192,7 +193,7 @@ class Information(Cog):          await ctx.send(embed=embed)      @command(name="user", aliases=["user_info", "member", "member_info"]) -    async def user_info(self, ctx: Context, user: Member = None) -> None: +    async def user_info(self, ctx: Context, user: FetchedMember = None) -> None:          """Returns info about a user."""          if user is None:              user = ctx.author @@ -207,12 +208,14 @@ class Information(Cog):              embed = await self.create_user_embed(ctx, user)              await ctx.send(embed=embed) -    async def create_user_embed(self, ctx: Context, user: Member) -> Embed: +    async def create_user_embed(self, ctx: Context, user: FetchedMember) -> Embed:          """Creates an embed containing information on the `user`.""" +        on_server = bool(ctx.guild.get_member(user.id)) +          created = time_since(user.created_at, max_units=3)          name = str(user) -        if user.nick: +        if on_server and user.nick:              name = f"{user.nick} ({name})"          badges = [] @@ -221,8 +224,16 @@ class Information(Cog):              if is_set and (emoji := getattr(constants.Emojis, f"badge_{badge}", None)):                  badges.append(emoji) -        joined = time_since(user.joined_at, max_units=3) -        roles = ", ".join(role.mention for role in user.roles[1:]) +        if on_server: +            joined = time_since(user.joined_at, max_units=3) +            roles = ", ".join(role.mention for role in user.roles[1:]) +            membership = textwrap.dedent(f""" +                             Joined: {joined} +                             Roles: {roles or None} +                         """).strip() +        else: +            roles = None +            membership = "The user is not a member of the server"          fields = [              ( @@ -235,10 +246,7 @@ class Information(Cog):              ),              (                  "Member information", -                textwrap.dedent(f""" -                    Joined: {joined} -                    Roles: {roles or None} -                """).strip() +                membership              ),          ] @@ -263,13 +271,13 @@ class Information(Cog):          return embed -    async def basic_user_infraction_counts(self, member: Member) -> Tuple[str, str]: +    async def basic_user_infraction_counts(self, user: FetchedMember) -> Tuple[str, str]:          """Gets the total and active infraction counts for the given `member`."""          infractions = await self.bot.api_client.get(              'bot/infractions',              params={                  'hidden': 'False', -                'user__id': str(member.id) +                'user__id': str(user.id)              }          ) @@ -280,7 +288,7 @@ class Information(Cog):          return "Infractions", infraction_output -    async def expanded_user_infraction_counts(self, member: Member) -> Tuple[str, str]: +    async def expanded_user_infraction_counts(self, user: FetchedMember) -> Tuple[str, str]:          """          Gets expanded infraction counts for the given `member`. @@ -290,7 +298,7 @@ class Information(Cog):          infractions = await self.bot.api_client.get(              'bot/infractions',              params={ -                'user__id': str(member.id) +                'user__id': str(user.id)              }          ) @@ -321,12 +329,12 @@ class Information(Cog):          return "Infractions", "\n".join(infraction_output) -    async def user_nomination_counts(self, member: Member) -> Tuple[str, str]: +    async def user_nomination_counts(self, user: FetchedMember) -> Tuple[str, str]:          """Gets the active and historical nomination counts for the given `member`."""          nominations = await self.bot.api_client.get(              'bot/nominations',              params={ -                'user__id': str(member.id) +                'user__id': str(user.id)              }          ) diff --git a/bot/exts/utils/snekbox.py b/bot/exts/utils/snekbox.py index 213d57365..41cb00541 100644 --- a/bot/exts/utils/snekbox.py +++ b/bot/exts/utils/snekbox.py @@ -21,14 +21,12 @@ log = logging.getLogger(__name__)  ESCAPE_REGEX = re.compile("[`\u202E\u200B]{3,}")  FORMATTED_CODE_REGEX = re.compile( -    r"^\s*"                                 # any leading whitespace from the beginning of the string      r"(?P<delim>(?P<block>```)|``?)"        # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block      r"(?(block)(?:(?P<lang>[a-z]+)\n)?)"    # if we're in a block, match optional language (only letters plus newline)      r"(?:[ \t]*\n)*"                        # any blank (empty or tabs/spaces only) lines before the code      r"(?P<code>.*?)"                        # extract all code inside the markup      r"\s*"                                  # any more whitespace before the end of the code markup -    r"(?P=delim)"                           # match the exact same delimiter from the start again -    r"\s*$",                                # any trailing whitespace until the end of the string +    r"(?P=delim)",                          # match the exact same delimiter from the start again      re.DOTALL | re.IGNORECASE               # "." also matches newlines, case insensitive  )  RAW_CODE_REGEX = re.compile( @@ -76,23 +74,32 @@ class Snekbox(Cog):      @staticmethod      def prepare_input(code: str) -> str: -        """Extract code from the Markdown, format it, and insert it into the code template.""" -        match = FORMATTED_CODE_REGEX.fullmatch(code) -        if match: -            code, block, lang, delim = match.group("code", "block", "lang", "delim") -            code = textwrap.dedent(code) -            if block: -                info = (f"'{lang}' highlighted" if lang else "plain") + " code block" +        """ +        Extract code from the Markdown, format it, and insert it into the code template. + +        If there is any code block, ignore text outside the code block. +        Use the first code block, but prefer a fenced code block. +        If there are several fenced code blocks, concatenate only the fenced code blocks. +        """ +        if match := list(FORMATTED_CODE_REGEX.finditer(code)): +            blocks = [block for block in match if block.group("block")] + +            if len(blocks) > 1: +                code = '\n'.join(block.group("code") for block in blocks) +                info = "several code blocks"              else: -                info = f"{delim}-enclosed inline code" -            log.trace(f"Extracted {info} for evaluation:\n{code}") +                match = match[0] if len(blocks) == 0 else blocks[0] +                code, block, lang, delim = match.group("code", "block", "lang", "delim") +                if block: +                    info = (f"'{lang}' highlighted" if lang else "plain") + " code block" +                else: +                    info = f"{delim}-enclosed inline code"          else: -            code = textwrap.dedent(RAW_CODE_REGEX.fullmatch(code).group("code")) -            log.trace( -                f"Eval message contains unformatted or badly formatted code, " -                f"stripping whitespace only:\n{code}" -            ) +            code = RAW_CODE_REGEX.fullmatch(code).group("code") +            info = "unformatted or badly formatted code" +        code = textwrap.dedent(code) +        log.trace(f"Extracted {info} for evaluation:\n{code}")          return code      @staticmethod diff --git a/tests/bot/exts/utils/test_snekbox.py b/tests/bot/exts/utils/test_snekbox.py index 6601fad2c..9a42d0610 100644 --- a/tests/bot/exts/utils/test_snekbox.py +++ b/tests/bot/exts/utils/test_snekbox.py @@ -52,6 +52,13 @@ class SnekboxTests(unittest.IsolatedAsyncioTestCase):              ('`print("Hello world!")`', 'print("Hello world!")', 'one line code block'),              ('```\nprint("Hello world!")```', 'print("Hello world!")', 'multiline code block'),              ('```py\nprint("Hello world!")```', 'print("Hello world!")', 'multiline python code block'), +            ('text```print("Hello world!")```text', 'print("Hello world!")', 'code block surrounded by text'), +            ('```print("Hello world!")```\ntext\n```py\nprint("Hello world!")```', +             'print("Hello world!")\nprint("Hello world!")', 'two code blocks with text in-between'), +            ('`print("Hello world!")`\ntext\n```print("How\'s it going?")```', +             'print("How\'s it going?")', 'code block preceded by inline code'), +            ('`print("Hello world!")`\ntext\n`print("Hello world!")`', +             'print("Hello world!")', 'one inline code block of two')          )          for case, expected, testname in cases:              with self.subTest(msg=f'Extract code from {testname}.'): | 
