aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/cogs/error_handler.py31
-rw-r--r--bot/cogs/moderation/scheduler.py18
-rw-r--r--bot/cogs/tags.py38
-rw-r--r--bot/utils/time.py36
4 files changed, 80 insertions, 43 deletions
diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py
index 52893b2ee..13221799b 100644
--- a/bot/cogs/error_handler.py
+++ b/bot/cogs/error_handler.py
@@ -1,4 +1,5 @@
-import contextlib
+# import contextlib
+import difflib
import logging
from discord.ext.commands import (
@@ -75,7 +76,7 @@ class ErrorHandler(Cog):
if not ctx.channel.id == Channels.verification:
tags_get_command = self.bot.get_command("tags get")
ctx.invoked_from_error_handler = True
-
+ command_name = ctx.invoked_with
log_msg = "Cancelling attempt to fall back to a tag due to failed checks."
try:
if not await tags_get_command.can_run(ctx):
@@ -87,9 +88,31 @@ class ErrorHandler(Cog):
return
# Return to not raise the exception
- with contextlib.suppress(ResponseCodeError):
- await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with)
+ log.debug("Calling...")
+ tags_cog = self.bot.get_cog("Tags")
+ sent = await tags_cog._get_command(ctx, command_name)
+ # sent = await tags_get_command.callback(tags_get_command.cog, ctx, ctx.invoked_with)
+ if sent:
+ log.debug("Found")
return
+ # No similar tag found, or tag on cooldown -
+ # searching for a similar command
+ log.debug("Not Found")
+ raw_commands = [
+ (cmd.name, *cmd.aliases)
+ for cmd in self.bot.walk_commands()
+ if not cmd.hidden
+ ]
+ raw_commands = [c for data in raw_commands for c in data]
+ similar_command_data = difflib.get_close_matches(command_name, raw_commands, 1)
+ log.debug(similar_command_data)
+ similar_command = self.bot.get_command(similar_command_data[0])
+ if similar_command.can_run(ctx):
+ misspelled_content = ctx.message.content
+ await ctx.send(
+ f"Did you mean:\n**{misspelled_content.replace(command_name, similar_command.name)}**"
+ )
+
elif isinstance(e, BadArgument):
await ctx.send(f"Bad argument: {e}\n")
await ctx.invoke(*help_command)
diff --git a/bot/cogs/moderation/scheduler.py b/bot/cogs/moderation/scheduler.py
index e14c302cb..c0de0e4da 100644
--- a/bot/cogs/moderation/scheduler.py
+++ b/bot/cogs/moderation/scheduler.py
@@ -309,16 +309,23 @@ class InfractionScheduler(Scheduler):
guild = self.bot.get_guild(constants.Guild.id)
mod_role = guild.get_role(constants.Roles.moderator)
user_id = infraction["user"]
+ actor = infraction["actor"]
type_ = infraction["type"]
id_ = infraction["id"]
+ inserted_at = infraction["inserted_at"]
+ expiry = infraction["expires_at"]
log.info(f"Marking infraction #{id_} as inactive (expired).")
+ expiry = dateutil.parser.isoparse(expiry).replace(tzinfo=None) if expiry else None
+ created = time.format_infraction_with_duration(inserted_at, expiry)
+
log_content = None
log_text = {
- "Member": str(user_id),
- "Actor": str(self.bot.user),
- "Reason": infraction["reason"]
+ "Member": f"<@{user_id}>",
+ "Actor": str(self.bot.get_user(actor) or actor),
+ "Reason": infraction["reason"],
+ "Created": created,
}
try:
@@ -384,14 +391,19 @@ class InfractionScheduler(Scheduler):
if send_log:
log_title = f"expiration failed" if "Failure" in log_text else "expired"
+ user = self.bot.get_user(user_id)
+ avatar = user.avatar_url_as(static_format="png") if user else None
+
log.trace(f"Sending deactivation mod log for infraction #{id_}.")
await self.mod_log.send_log_message(
icon_url=utils.INFRACTION_ICONS[type_][1],
colour=Colours.soft_green,
title=f"Infraction {log_title}: {type_}",
+ thumbnail=avatar,
text="\n".join(f"{k}: {v}" for k, v in log_text.items()),
footer=f"ID: {id_}",
content=log_content,
+
)
return log_text
diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py
index 5f7c810b8..61cf55d65 100644
--- a/bot/cogs/tags.py
+++ b/bot/cogs/tags.py
@@ -12,8 +12,6 @@ from bot.converters import TagContentConverter, TagNameConverter
from bot.decorators import with_role
from bot.pagination import LinePaginator
-from fuzzywuzzy import process
-
log = logging.getLogger(__name__)
TEST_CHANNELS = (
@@ -94,9 +92,9 @@ class Tags(Cog):
"""Show all known tags, a single tag, or run a subcommand."""
await ctx.invoke(self.get_command, tag_name=tag_name)
- @tags_group.command(name='get', aliases=('show', 'g'))
- async def get_command(self, ctx: Context, *, tag_name: TagNameConverter = None) -> None:
- """Get a specified tag, or a list of all tags if no tag is specified."""
+ async def _get_command(self, ctx: Context, *, tag_name: TagNameConverter = None) -> None:
+ log.debug(self, ctx, tag_name)
+
def _command_on_cooldown(tag_name: str) -> bool:
"""
Check if the command is currently on cooldown, on a per-tag, per-channel basis.
@@ -120,7 +118,7 @@ class Tags(Cog):
time_left = Cooldowns.tags - (time.time() - self.tag_cooldowns[tag_name]["time"])
log.warning(f"{ctx.author} tried to get the '{tag_name}' tag, but the tag is on cooldown. "
f"Cooldown ends in {time_left:.1f} seconds.")
- return
+ return False
await self._get_tags()
@@ -135,28 +133,13 @@ class Tags(Cog):
"channel": ctx.channel.id
}
await ctx.send(embed=Embed.from_dict(tag['embed']))
+ return True
elif founds and len(tag_name) >= 3:
await ctx.send(embed=Embed(
title='Did you mean ...',
description='\n'.join(tag['title'] for tag in founds[:10])
))
- else:
- # No similar tag found, searching for a similar command
- command_name = ctx.invoked_with
- raw_commands = [cmd.name for cmd in self.bot.walk_commands()]
- similar_command_data = process.extractOne(command_name, raw_commands)
- # The match is not very similar, no need to suggest it
- log.debug(similar_command_data)
- if similar_command_data[1] < 65:
- log.debug("No similar commands found")
- return
- similar_command = self.bot.get_command(similar_command_data[0])
- if similar_command.can_run(ctx):
- misspelled_content = ctx.message.content
- await ctx.send(
- f"Did you mean:\n**{misspelled_content.replace(command_name, similar_command.name)}**"
- )
- return
+ return True
else:
tags = self._cache.values()
@@ -165,6 +148,7 @@ class Tags(Cog):
description="**There are no tags in the database!**",
colour=Colour.red()
))
+ return True
else:
embed: Embed = Embed(title="**Current tags**")
await LinePaginator.paginate(
@@ -175,6 +159,14 @@ class Tags(Cog):
empty=False,
max_lines=15
)
+ return True
+
+ return False
+
+ @tags_group.command(name='get', aliases=('show', 'g'))
+ async def get_command(self, ctx: Context, *, tag_name: TagNameConverter = None) -> None:
+ """Get a specified tag, or a list of all tags if no tag is specified."""
+ await self._get_command(ctx, tag_name)
@tags_group.command(name='set', aliases=('add', 's'))
@with_role(*MODERATION_ROLES)
diff --git a/bot/utils/time.py b/bot/utils/time.py
index 7416f36e0..77060143c 100644
--- a/bot/utils/time.py
+++ b/bot/utils/time.py
@@ -114,30 +114,40 @@ def format_infraction(timestamp: str) -> str:
def format_infraction_with_duration(
- expiry: Optional[str],
+ date_to: Optional[str],
date_from: Optional[datetime.datetime] = None,
- max_units: int = 2
+ max_units: int = 2,
+ absolute: bool = True
) -> Optional[str]:
"""
- Format an infraction timestamp to a more readable ISO 8601 format WITH the duration.
+ Return `date_to` formatted as a readable ISO-8601 with the humanized duration since `date_from`.
- Returns a human-readable version of the duration between datetime.utcnow() and an expiry.
- Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.
- `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
- By default, max_units is 2.
+ `date_from` must be an ISO-8601 formatted timestamp. The duration is calculated as from
+ `date_from` until `date_to` with a precision of seconds. If `date_from` is unspecified, the
+ current time is used.
+
+ `max_units` specifies the maximum number of units of time to include in the duration. For
+ example, a value of 1 may include days but not hours.
+
+ If `absolute` is True, the absolute value of the duration delta is used. This prevents negative
+ values in the case that `date_to` is in the past relative to `date_from`.
"""
- if not expiry:
+ if not date_to:
return None
+ date_to_formatted = format_infraction(date_to)
+
date_from = date_from or datetime.datetime.utcnow()
- date_to = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)
+ date_to = dateutil.parser.isoparse(date_to).replace(tzinfo=None, microsecond=0)
- expiry_formatted = format_infraction(expiry)
+ delta = relativedelta(date_to, date_from)
+ if absolute:
+ delta = abs(delta)
- duration = humanize_delta(relativedelta(date_to, date_from), max_units=max_units)
- duration_formatted = f" ({duration})" if duration else ''
+ duration = humanize_delta(delta, max_units=max_units)
+ duration_formatted = f" ({duration})" if duration else ""
- return f"{expiry_formatted}{duration_formatted}"
+ return f"{date_to_formatted}{duration_formatted}"
def until_expiration(