aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/converters.py4
-rw-r--r--bot/exts/backend/error_handler.py47
-rw-r--r--bot/exts/filters/filtering.py2
-rw-r--r--bot/exts/help_channels/_cog.py19
-rw-r--r--bot/exts/info/doc/_batch_parser.py22
-rw-r--r--bot/exts/info/doc/_cog.py1
-rw-r--r--bot/exts/info/doc/_redis_cache.py43
-rw-r--r--bot/exts/info/help.py6
-rw-r--r--bot/exts/info/information.py2
-rw-r--r--bot/exts/moderation/incidents.py2
-rw-r--r--bot/exts/moderation/infraction/_utils.py20
-rw-r--r--bot/exts/moderation/modlog.py12
-rw-r--r--bot/exts/utils/internal.py7
-rw-r--r--bot/utils/regex.py18
-rw-r--r--tests/bot/exts/moderation/infraction/test_utils.py20
15 files changed, 127 insertions, 98 deletions
diff --git a/bot/converters.py b/bot/converters.py
index 4a4d3b544..dd02f6ae6 100644
--- a/bot/converters.py
+++ b/bot/converters.py
@@ -71,10 +71,10 @@ class ValidDiscordServerInvite(Converter):
async def convert(self, ctx: Context, server_invite: str) -> dict:
"""Check whether the string is a valid Discord server invite."""
- invite_code = INVITE_RE.search(server_invite)
+ invite_code = INVITE_RE.match(server_invite)
if invite_code:
response = await ctx.bot.http_session.get(
- f"{URLs.discord_invite_api}/{invite_code[1]}"
+ f"{URLs.discord_invite_api}/{invite_code.group('invite')}"
)
if response.status != 404:
invite_data = await response.json()
diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py
index 7644b93ae..6ab6634a6 100644
--- a/bot/exts/backend/error_handler.py
+++ b/bot/exts/backend/error_handler.py
@@ -59,17 +59,23 @@ class ErrorHandler(Cog):
log.trace(f"Command {command} had its error already handled locally; ignoring.")
return
+ debug_message = (
+ f"Command {command} invoked by {ctx.message.author} with error "
+ f"{e.__class__.__name__}: {e}"
+ )
+
if isinstance(e, errors.CommandNotFound) and not getattr(ctx, "invoked_from_error_handler", False):
if await self.try_silence(ctx):
return
- # Try to look for a tag with the command's name
- await self.try_get_tag(ctx)
- return # Exit early to avoid logging.
+ await self.try_get_tag(ctx) # Try to look for a tag with the command's name
elif isinstance(e, errors.UserInputError):
+ log.debug(debug_message)
await self.handle_user_input_error(ctx, e)
elif isinstance(e, errors.CheckFailure):
+ log.debug(debug_message)
await self.handle_check_failure(ctx, e)
elif isinstance(e, errors.CommandOnCooldown):
+ log.debug(debug_message)
await ctx.send(e)
elif isinstance(e, errors.CommandInvokeError):
if isinstance(e.original, ResponseCodeError):
@@ -80,22 +86,16 @@ class ErrorHandler(Cog):
await ctx.send(f"Cannot infract that user. {e.original.reason}")
else:
await self.handle_unexpected_error(ctx, e.original)
- return # Exit early to avoid logging.
elif isinstance(e, errors.ConversionError):
if isinstance(e.original, ResponseCodeError):
await self.handle_api_error(ctx, e.original)
else:
await self.handle_unexpected_error(ctx, e.original)
- return # Exit early to avoid logging.
- elif not isinstance(e, errors.DisabledCommand):
+ elif isinstance(e, errors.DisabledCommand):
+ log.debug(debug_message)
+ else:
# MaxConcurrencyReached, ExtensionError
await self.handle_unexpected_error(ctx, e)
- return # Exit early to avoid logging.
-
- log.debug(
- f"Command {command} invoked by {ctx.message.author} with error "
- f"{e.__class__.__name__}: {e}"
- )
@staticmethod
def get_help_command(ctx: Context) -> t.Coroutine:
@@ -188,9 +188,6 @@ class ErrorHandler(Cog):
if not any(role.id in MODERATION_ROLES for role in ctx.author.roles):
await self.send_command_suggestion(ctx, ctx.invoked_with)
- # Return to not raise the exception
- return
-
async def send_command_suggestion(self, ctx: Context, command_name: str) -> None:
"""Sends user similar commands if any can be found."""
# No similar tag found, or tag on cooldown -
@@ -235,38 +232,32 @@ class ErrorHandler(Cog):
"""
if isinstance(e, errors.MissingRequiredArgument):
embed = self._get_error_embed("Missing required argument", e.param.name)
- await ctx.send(embed=embed)
- await self.get_help_command(ctx)
self.bot.stats.incr("errors.missing_required_argument")
elif isinstance(e, errors.TooManyArguments):
embed = self._get_error_embed("Too many arguments", str(e))
- await ctx.send(embed=embed)
- await self.get_help_command(ctx)
self.bot.stats.incr("errors.too_many_arguments")
elif isinstance(e, errors.BadArgument):
embed = self._get_error_embed("Bad argument", str(e))
- await ctx.send(embed=embed)
- await self.get_help_command(ctx)
self.bot.stats.incr("errors.bad_argument")
elif isinstance(e, errors.BadUnionArgument):
embed = self._get_error_embed("Bad argument", f"{e}\n{e.errors[-1]}")
- await ctx.send(embed=embed)
- await self.get_help_command(ctx)
self.bot.stats.incr("errors.bad_union_argument")
elif isinstance(e, errors.ArgumentParsingError):
embed = self._get_error_embed("Argument parsing error", str(e))
await ctx.send(embed=embed)
self.get_help_command(ctx).close()
self.bot.stats.incr("errors.argument_parsing_error")
+ return
else:
embed = self._get_error_embed(
"Input error",
"Something about your input seems off. Check the arguments and try again."
)
- await ctx.send(embed=embed)
- await self.get_help_command(ctx)
self.bot.stats.incr("errors.other_user_input_error")
+ await ctx.send(embed=embed)
+ await self.get_help_command(ctx)
+
@staticmethod
async def handle_check_failure(ctx: Context, e: errors.CheckFailure) -> None:
"""
@@ -299,8 +290,8 @@ class ErrorHandler(Cog):
async def handle_api_error(ctx: Context, e: ResponseCodeError) -> None:
"""Send an error message in `ctx` for ResponseCodeError and log it."""
if e.status == 404:
- await ctx.send("There does not seem to be anything matching your query.")
log.debug(f"API responded with 404 for command {ctx.command}")
+ await ctx.send("There does not seem to be anything matching your query.")
ctx.bot.stats.incr("errors.api_error_404")
elif e.status == 400:
content = await e.response.json()
@@ -308,12 +299,12 @@ class ErrorHandler(Cog):
await ctx.send("According to the API, your request is malformed.")
ctx.bot.stats.incr("errors.api_error_400")
elif 500 <= e.status < 600:
- await ctx.send("Sorry, there seems to be an internal issue with the API.")
log.warning(f"API responded with {e.status} for command {ctx.command}")
+ await ctx.send("Sorry, there seems to be an internal issue with the API.")
ctx.bot.stats.incr("errors.api_internal_server_error")
else:
- await ctx.send(f"Got an unexpected status code from the API (`{e.status}`).")
log.warning(f"Unexpected API response for command {ctx.command}: {e.status}")
+ await ctx.send(f"Got an unexpected status code from the API (`{e.status}`).")
ctx.bot.stats.incr(f"errors.api_error_{e.status}")
@staticmethod
diff --git a/bot/exts/filters/filtering.py b/bot/exts/filters/filtering.py
index 7faf063b9..a151db1f0 100644
--- a/bot/exts/filters/filtering.py
+++ b/bot/exts/filters/filtering.py
@@ -507,7 +507,7 @@ class Filtering(Cog):
# discord\.gg/gdudes-pony-farm
text = text.replace("\\", "")
- invites = INVITE_RE.findall(text)
+ invites = [m.group("invite") for m in INVITE_RE.finditer(text)]
invite_data = dict()
for invite in invites:
if invite in invite_data:
diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py
index 498305b47..3c6cf7f26 100644
--- a/bot/exts/help_channels/_cog.py
+++ b/bot/exts/help_channels/_cog.py
@@ -125,14 +125,21 @@ class HelpChannels(commands.Cog):
"""
log.info(f"Channel #{message.channel} was claimed by `{message.author.id}`.")
await self.move_to_in_use(message.channel)
- await self._handle_role_change(message.author, message.author.add_roles)
- await _message.pin(message)
+ # Handle odd edge case of `message.author` not being a `discord.Member` (see bot#1839)
+ if not isinstance(message.author, discord.Member):
+ log.warning(
+ f"{message.author} ({message.author.id}) isn't a member. Not giving cooldown role or sending DM."
+ )
+ else:
+ await self._handle_role_change(message.author, message.author.add_roles)
- try:
- await _message.dm_on_open(message)
- except Exception as e:
- log.warning("Error occurred while sending DM:", exc_info=e)
+ try:
+ await _message.dm_on_open(message)
+ except Exception as e:
+ log.warning("Error occurred while sending DM:", exc_info=e)
+
+ await _message.pin(message)
# Add user with channel for dormant check.
await _caches.claimants.set(message.channel.id, message.author.id)
diff --git a/bot/exts/info/doc/_batch_parser.py b/bot/exts/info/doc/_batch_parser.py
index 92f814c9d..c27f28eac 100644
--- a/bot/exts/info/doc/_batch_parser.py
+++ b/bot/exts/info/doc/_batch_parser.py
@@ -17,6 +17,7 @@ from bot.utils import scheduling
from . import _cog, doc_cache
from ._parsing import get_symbol_markdown
+from ._redis_cache import StaleItemCounter
log = get_logger(__name__)
@@ -24,6 +25,8 @@ log = get_logger(__name__)
class StaleInventoryNotifier:
"""Handle sending notifications about stale inventories through `DocItem`s to dev log."""
+ symbol_counter = StaleItemCounter()
+
def __init__(self):
self._init_task = scheduling.create_task(
self._init_channel(),
@@ -40,13 +43,16 @@ class StaleInventoryNotifier:
async def send_warning(self, doc_item: _cog.DocItem) -> None:
"""Send a warning to dev log if one wasn't already sent for `item`'s url."""
if doc_item.url not in self._warned_urls:
- self._warned_urls.add(doc_item.url)
- await self._init_task
- embed = discord.Embed(
- description=f"Doc item `{doc_item.symbol_id=}` present in loaded documentation inventories "
- f"not found on [site]({doc_item.url}), inventories may need to be refreshed."
- )
- await self._dev_log.send(embed=embed)
+ # Only warn if the item got less than 3 warnings
+ # or if it has been more than 3 weeks since the last warning
+ if await self.symbol_counter.increment_for(doc_item) < 3:
+ self._warned_urls.add(doc_item.url)
+ await self._init_task
+ embed = discord.Embed(
+ description=f"Doc item `{doc_item.symbol_id=}` present in loaded documentation inventories "
+ f"not found on [site]({doc_item.url}), inventories may need to be refreshed."
+ )
+ await self._dev_log.send(embed=embed)
class QueueItem(NamedTuple):
@@ -103,7 +109,7 @@ class BatchParser:
if doc_item not in self._item_futures and doc_item not in self._queue:
self._item_futures[doc_item].user_requested = True
- async with bot.instance.http_session.get(doc_item.url) as response:
+ async with bot.instance.http_session.get(doc_item.url, raise_for_status=True) as response:
soup = await bot.instance.loop.run_in_executor(
None,
BeautifulSoup,
diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py
index fbbcd4a10..ebf5f5932 100644
--- a/bot/exts/info/doc/_cog.py
+++ b/bot/exts/info/doc/_cog.py
@@ -464,6 +464,7 @@ class DocCog(commands.Cog):
) -> None:
"""Clear the persistent redis cache for `package`."""
if await doc_cache.delete(package_name):
+ await self.item_fetcher.stale_inventory_notifier.symbol_counter.delete()
await ctx.send(f"Successfully cleared the cache for `{package_name}`.")
else:
await ctx.send("No keys matching the package found.")
diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py
index 79648893a..107f2344f 100644
--- a/bot/exts/info/doc/_redis_cache.py
+++ b/bot/exts/info/doc/_redis_cache.py
@@ -25,8 +25,7 @@ class DocRedisCache(RedisObject):
All keys from a single page are stored together, expiring a week after the first set.
"""
- url_key = remove_suffix(item.relative_url_path, ".html")
- redis_key = f"{self.namespace}:{item.package}:{url_key}"
+ redis_key = f"{self.namespace}:{item_key(item)}"
needs_expire = False
with await self._get_pool_connection() as connection:
@@ -44,10 +43,36 @@ class DocRedisCache(RedisObject):
@namespace_lock
async def get(self, item: DocItem) -> Optional[str]:
"""Return the Markdown content of the symbol `item` if it exists."""
- url_key = remove_suffix(item.relative_url_path, ".html")
+ with await self._get_pool_connection() as connection:
+ return await connection.hget(f"{self.namespace}:{item_key(item)}", item.symbol_id, encoding="utf8")
+ @namespace_lock
+ async def delete(self, package: str) -> bool:
+ """Remove all values for `package`; return True if at least one key was deleted, False otherwise."""
+ with await self._get_pool_connection() as connection:
+ package_keys = [
+ package_key async for package_key in connection.iscan(match=f"{self.namespace}:{package}:*")
+ ]
+ if package_keys:
+ await connection.delete(*package_keys)
+ return True
+ return False
+
+
+class StaleItemCounter(RedisObject):
+ """Manage increment counters for stale `DocItem`s."""
+
+ @namespace_lock
+ async def increment_for(self, item: DocItem) -> int:
+ """
+ Increment the counter for `item` by 1, set it to expire in 3 weeks and return the new value.
+
+ If the counter didn't exist, initialize it with 1.
+ """
+ key = f"{self.namespace}:{item_key(item)}:{item.symbol_id}"
with await self._get_pool_connection() as connection:
- return await connection.hget(f"{self.namespace}:{item.package}:{url_key}", item.symbol_id, encoding="utf8")
+ await connection.expire(key, WEEK_SECONDS * 3)
+ return int(await connection.incr(key))
@namespace_lock
async def delete(self, package: str) -> bool:
@@ -62,10 +87,6 @@ class DocRedisCache(RedisObject):
return False
-def remove_suffix(string: str, suffix: str) -> str:
- """Remove `suffix` from end of `string`."""
- # TODO replace usages with str.removesuffix on 3.9
- if string.endswith(suffix):
- return string[:-len(suffix)]
- else:
- return string
+def item_key(item: DocItem) -> str:
+ """Get the redis redis key string from `item`."""
+ return f"{item.package}:{item.relative_url_path.removesuffix('.html')}"
diff --git a/bot/exts/info/help.py b/bot/exts/info/help.py
index f413caded..743dfdd3f 100644
--- a/bot/exts/info/help.py
+++ b/bot/exts/info/help.py
@@ -1,4 +1,5 @@
import itertools
+import re
from collections import namedtuple
from contextlib import suppress
from typing import List, Union
@@ -179,7 +180,10 @@ class CustomHelpCommand(HelpCommand):
except CommandError:
command_details += NOT_ALLOWED_TO_RUN_MESSAGE
- command_details += f"*{command.help or 'No details provided.'}*\n"
+ # Remove line breaks from docstrings, if not used to separate paragraphs.
+ # Allow overriding this behaviour via putting \u2003 at the start of a line.
+ formatted_doc = re.sub("(?<!\n)\n(?![\n\u2003])", " ", command.help)
+ command_details += f"*{formatted_doc or 'No details provided.'}*\n"
embed.description = command_details
return embed
diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py
index 1b3e28e79..0dcb8de11 100644
--- a/bot/exts/info/information.py
+++ b/bot/exts/info/information.py
@@ -200,7 +200,7 @@ class Information(Cog):
f"\nRoles: {num_roles}"
f"\nMember status: {member_status}"
)
- embed.set_thumbnail(url=ctx.guild.icon_url)
+ embed.set_thumbnail(url=ctx.guild.icon.url)
# Members
total_members = f"{ctx.guild.member_count:,}"
diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py
index 20e73ccf5..c531f4902 100644
--- a/bot/exts/moderation/incidents.py
+++ b/bot/exts/moderation/incidents.py
@@ -113,7 +113,7 @@ async def make_embed(incident: discord.Message, outcome: Signal, actioned_by: di
else:
embed.set_author(name="[Failed to relay attachment]", url=attachment.proxy_url) # Embed links the file
else:
- file = None
+ file = discord.utils.MISSING
return embed, file
diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py
index 89718c857..c0ef80e3d 100644
--- a/bot/exts/moderation/infraction/_utils.py
+++ b/bot/exts/moderation/infraction/_utils.py
@@ -27,16 +27,18 @@ RULES_URL = "https://pythondiscord.com/pages/rules"
# Type aliases
Infraction = t.Dict[str, t.Union[str, int, bool]]
-APPEAL_EMAIL = "[email protected]"
+APPEAL_SERVER_INVITE = "https://discord.gg/WXrCJxWBnm"
INFRACTION_TITLE = "Please review our rules"
-INFRACTION_APPEAL_EMAIL_FOOTER = f"To appeal this infraction, send an e-mail to {APPEAL_EMAIL}"
+INFRACTION_APPEAL_SERVER_FOOTER = f"\n\nTo appeal this infraction, join our [appeals server]({APPEAL_SERVER_INVITE})."
INFRACTION_APPEAL_MODMAIL_FOOTER = (
- 'If you would like to discuss or appeal this infraction, '
- 'send a message to the ModMail bot'
+ '\n\nIf you would like to discuss or appeal this infraction, '
+ 'send a message to the ModMail bot.'
)
INFRACTION_AUTHOR_NAME = "Infraction information"
+LONGEST_EXTRAS = max(len(INFRACTION_APPEAL_SERVER_FOOTER), len(INFRACTION_APPEAL_MODMAIL_FOOTER))
+
INFRACTION_DESCRIPTION_TEMPLATE = (
"**Type:** {type}\n"
"**Expires:** {expires}\n"
@@ -170,8 +172,10 @@ async def notify_infraction(
)
# For case when other fields than reason is too long and this reach limit, then force-shorten string
- if len(text) > 4096:
- text = f"{text[:4093]}..."
+ if len(text) > 4096 - LONGEST_EXTRAS:
+ text = f"{text[:4093-LONGEST_EXTRAS]}..."
+
+ text += INFRACTION_APPEAL_SERVER_FOOTER if infr_type.lower() == 'ban' else INFRACTION_APPEAL_MODMAIL_FOOTER
embed = discord.Embed(
description=text,
@@ -182,10 +186,6 @@ async def notify_infraction(
embed.title = INFRACTION_TITLE
embed.url = RULES_URL
- embed.set_footer(
- text=INFRACTION_APPEAL_EMAIL_FOOTER if infr_type == 'Ban' else INFRACTION_APPEAL_MODMAIL_FOOTER
- )
-
return await send_private_embed(user, embed)
diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py
index b09eb2d14..9d1ae6853 100644
--- a/bot/exts/moderation/modlog.py
+++ b/bot/exts/moderation/modlog.py
@@ -378,7 +378,7 @@ class ModLog(Cog, name="ModLog"):
await self.send_log_message(
Icons.guild_update, Colour.blurple(),
"Guild updated", message,
- thumbnail=after.icon_url_as(format="png")
+ thumbnail=after.icon.with_static_format("png")
)
@Cog.listener()
@@ -539,7 +539,7 @@ class ModLog(Cog, name="ModLog"):
channel = self.bot.get_channel(channel_id)
# Ignore not found channels, DMs, and messages outside of the main guild.
- if not channel or channel.guild and channel.guild.id != GuildConstant.id:
+ if not channel or not hasattr(channel, "guild") or channel.guild.id != GuildConstant.id:
return True
# Look at the parent channel of a thread.
@@ -786,11 +786,11 @@ class ModLog(Cog, name="ModLog"):
return
if not before.archived and after.archived:
- colour = Colour.red()
+ colour = Colours.soft_red
action = "archived"
icon = Icons.hash_red
elif before.archived and not after.archived:
- colour = Colour.green()
+ colour = Colours.soft_green
action = "un-archived"
icon = Icons.hash_green
else:
@@ -808,7 +808,7 @@ class ModLog(Cog, name="ModLog"):
"""Log thread deletion."""
await self.send_log_message(
Icons.hash_red,
- Colour.red(),
+ Colours.soft_red,
"Thread deleted",
f"Thread {thread.mention} (`{thread.id}`) from {thread.parent.mention} (`{thread.parent.id}`) deleted"
)
@@ -823,7 +823,7 @@ class ModLog(Cog, name="ModLog"):
await self.send_log_message(
Icons.hash_green,
- Colour.green(),
+ Colours.soft_green,
"Thread created",
f"Thread {thread.mention} (`{thread.id}`) from {thread.parent.mention} (`{thread.parent.id}`) created"
)
diff --git a/bot/exts/utils/internal.py b/bot/exts/utils/internal.py
index 879735945..96664929b 100644
--- a/bot/exts/utils/internal.py
+++ b/bot/exts/utils/internal.py
@@ -37,11 +37,10 @@ class Internal(Cog):
self.eval.add_check(is_owner().predicate)
@Cog.listener()
- async def on_socket_response(self, msg: dict) -> None:
+ async def on_socket_event_type(self, event_type: str) -> None:
"""When a websocket event is received, increase our counters."""
- if event_type := msg.get("t"):
- self.socket_event_total += 1
- self.socket_events[event_type] += 1
+ self.socket_event_total += 1
+ self.socket_events[event_type] += 1
def _format(self, inp: str, out: Any) -> Tuple[str, Optional[discord.Embed]]:
"""Format the eval output into a string & attempt to format it into an Embed."""
diff --git a/bot/utils/regex.py b/bot/utils/regex.py
index 7bad1e627..d77f5950b 100644
--- a/bot/utils/regex.py
+++ b/bot/utils/regex.py
@@ -1,14 +1,14 @@
import re
INVITE_RE = re.compile(
- r"(?:discord(?:[\.,]|dot)gg|" # Could be discord.gg/
- r"discord(?:[\.,]|dot)com(?:\/|slash)invite|" # or discord.com/invite/
- r"discordapp(?:[\.,]|dot)com(?:\/|slash)invite|" # or discordapp.com/invite/
- r"discord(?:[\.,]|dot)me|" # or discord.me
- r"discord(?:[\.,]|dot)li|" # or discord.li
- r"discord(?:[\.,]|dot)io|" # or discord.io.
- r"(?:[\.,]|dot)gg" # or .gg/
- r")(?:[\/]|slash)" # / or 'slash'
- r"([a-zA-Z0-9\-]+)", # the invite code itself
+ r"(discord([\.,]|dot)gg|" # Could be discord.gg/
+ r"discord([\.,]|dot)com(\/|slash)invite|" # or discord.com/invite/
+ r"discordapp([\.,]|dot)com(\/|slash)invite|" # or discordapp.com/invite/
+ r"discord([\.,]|dot)me|" # or discord.me
+ r"discord([\.,]|dot)li|" # or discord.li
+ r"discord([\.,]|dot)io|" # or discord.io.
+ r"((?<!\w)([\.,]|dot))gg" # or .gg/
+ r")([\/]|slash)" # / or 'slash'
+ r"(?P<invite>[a-zA-Z0-9\-]+)", # the invite code itself
flags=re.IGNORECASE
)
diff --git a/tests/bot/exts/moderation/infraction/test_utils.py b/tests/bot/exts/moderation/infraction/test_utils.py
index eb256f1fd..72eebb254 100644
--- a/tests/bot/exts/moderation/infraction/test_utils.py
+++ b/tests/bot/exts/moderation/infraction/test_utils.py
@@ -139,14 +139,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Ban",
expires="2020-02-26 09:20 (23 hours and 59 minutes)",
reason="No reason provided."
- ),
+ ) + utils.INFRACTION_APPEAL_SERVER_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.token_removed
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": True
},
{
@@ -157,14 +157,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Warning",
expires="N/A",
reason="Test reason."
- ),
+ ) + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.token_removed
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": False
},
# Note that this test case asserts that the DM that *would* get sent to the user is formatted
@@ -177,14 +177,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Note",
expires="N/A",
reason="No reason provided."
- ),
+ ) + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.defcon_denied
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": False
},
{
@@ -195,14 +195,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Mute",
expires="2020-02-26 09:20 (23 hours and 59 minutes)",
reason="Test"
- ),
+ ) + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.defcon_denied
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": False
},
{
@@ -213,14 +213,14 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
type="Mute",
expires="N/A",
reason="foo bar" * 4000
- )[:4093] + "...",
+ )[:4093-utils.LONGEST_EXTRAS] + "..." + utils.INFRACTION_APPEAL_MODMAIL_FOOTER,
colour=Colours.soft_red,
url=utils.RULES_URL
).set_author(
name=utils.INFRACTION_AUTHOR_NAME,
url=utils.RULES_URL,
icon_url=Icons.defcon_denied
- ).set_footer(text=utils.INFRACTION_APPEAL_MODMAIL_FOOTER),
+ ),
"send_result": True
}
]