aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/__init__.py2
-rw-r--r--bot/exts/info/information.py40
-rw-r--r--bot/exts/utils/snekbox.py41
-rw-r--r--tests/bot/exts/utils/test_snekbox.py7
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}.'):