aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Pipfile.lock59
-rw-r--r--bot/cogs/error_handler.py43
-rw-r--r--bot/cogs/tags.py66
3 files changed, 109 insertions, 59 deletions
diff --git a/Pipfile.lock b/Pipfile.lock
index fa29bf995..6a8a982a4 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -279,25 +279,25 @@
},
"multidict": {
"hashes": [
- "sha256:13f3ebdb5693944f52faa7b2065b751cb7e578b8dd0a5bb8e4ab05ad0188b85e",
- "sha256:26502cefa86d79b86752e96639352c7247846515c864d7c2eb85d036752b643c",
- "sha256:4fba5204d32d5c52439f88437d33ad14b5f228e25072a192453f658bddfe45a7",
- "sha256:527124ef435f39a37b279653ad0238ff606b58328ca7989a6df372fd75d7fe26",
- "sha256:5414f388ffd78c57e77bd253cf829373721f450613de53dc85a08e34d806e8eb",
- "sha256:5eee66f882ab35674944dfa0d28b57fa51e160b4dce0ce19e47f495fdae70703",
- "sha256:63810343ea07f5cd86ba66ab66706243a6f5af075eea50c01e39b4ad6bc3c57a",
- "sha256:6bd10adf9f0d6a98ccc792ab6f83d18674775986ba9bacd376b643fe35633357",
- "sha256:83c6ddf0add57c6b8a7de0bc7e2d656be3eefeff7c922af9a9aae7e49f225625",
- "sha256:93166e0f5379cf6cd29746989f8a594fa7204dcae2e9335ddba39c870a287e1c",
- "sha256:9a7b115ee0b9b92d10ebc246811d8f55d0c57e82dbb6a26b23c9a9a6ad40ce0c",
- "sha256:a38baa3046cce174a07a59952c9f876ae8875ef3559709639c17fdf21f7b30dd",
- "sha256:a6d219f49821f4b2c85c6d426346a5d84dab6daa6f85ca3da6c00ed05b54022d",
- "sha256:a8ed33e8f9b67e3b592c56567135bb42e7e0e97417a4b6a771e60898dfd5182b",
- "sha256:d7d428488c67b09b26928950a395e41cc72bb9c3d5abfe9f0521940ee4f796d4",
- "sha256:dcfed56aa085b89d644af17442cdc2debaa73388feba4b8026446d168ca8dad7",
- "sha256:f29b885e4903bd57a7789f09fe9d60b6475a6c1a4c0eca874d8558f00f9d4b51"
- ],
- "version": "==4.7.4"
+ "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1",
+ "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35",
+ "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928",
+ "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969",
+ "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e",
+ "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78",
+ "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1",
+ "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136",
+ "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8",
+ "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2",
+ "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e",
+ "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4",
+ "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5",
+ "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd",
+ "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab",
+ "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20",
+ "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3"
+ ],
+ "version": "==4.7.5"
},
"ordered-set": {
"hashes": [
@@ -444,11 +444,11 @@
},
"sphinx": {
"hashes": [
- "sha256:525527074f2e0c2585f68f73c99b4dc257c34bbe308b27f5f8c7a6e20642742f",
- "sha256:543d39db5f82d83a5c1aa0c10c88f2b6cff2da3e711aa849b2c627b4b403bbd9"
+ "sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88",
+ "sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709"
],
"index": "pypi",
- "version": "==2.4.2"
+ "version": "==2.4.3"
},
"sphinxcontrib-applehelp": {
"hashes": [
@@ -466,10 +466,10 @@
},
"sphinxcontrib-htmlhelp": {
"hashes": [
- "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422",
- "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"
+ "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
+ "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
],
- "version": "==1.0.2"
+ "version": "==1.0.3"
},
"sphinxcontrib-jsmath": {
"hashes": [
@@ -640,7 +640,8 @@
},
"distlib": {
"hashes": [
- "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"
+ "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21",
+ "sha256:3db50260f17a3479465fe376211c0816e6b0d1503a6c71caebe80360cab04828"
],
"version": "==0.3.0"
},
@@ -913,10 +914,10 @@
},
"virtualenv": {
"hashes": [
- "sha256:08f3623597ce73b85d6854fb26608a6f39ee9d055c81178dc6583803797f8994",
- "sha256:de2cbdd5926c48d7b84e0300dea9e8f276f61d186e8e49223d71d91250fbaebd"
+ "sha256:531b142e300d405bb9faedad4adbeb82b4098b918e35209af2adef3129274aae",
+ "sha256:5dd42a9f56307542bddc446cfd10ef6576f11910366a07609fe8d0d88fa8fb7e"
],
- "version": "==20.0.4"
+ "version": "==20.0.5"
},
"zipp": {
"hashes": [
diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py
index 0abb7e521..5af5974cb 100644
--- a/bot/cogs/error_handler.py
+++ b/bot/cogs/error_handler.py
@@ -1,6 +1,7 @@
-import contextlib
+import difflib
import logging
+from discord import Embed
from discord.ext.commands import (
BadArgument,
BotMissingPermissions,
@@ -19,7 +20,7 @@ from sentry_sdk import push_scope
from bot.api import ResponseCodeError
from bot.bot import Bot
-from bot.constants import Channels
+from bot.constants import Channels, Icons
from bot.decorators import InChannelCheckFailure
log = logging.getLogger(__name__)
@@ -74,9 +75,13 @@ class ErrorHandler(Cog):
# Try to look for a tag with the command's name if the command isn't found.
if isinstance(e, CommandNotFound) and not hasattr(ctx, "invoked_from_error_handler"):
if not ctx.channel.id == Channels.verification:
+ tags_cog = self.bot.get_cog("Tags")
tags_get_command = self.bot.get_command("tags get")
- ctx.invoked_from_error_handler = True
+ if not tags_cog and not tags_get_command:
+ return
+ 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,10 +92,36 @@ class ErrorHandler(Cog):
await self.on_command_error(ctx, tag_error)
return
- # Return to not raise the exception
- with contextlib.suppress(ResponseCodeError):
- await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with)
+ sent = await tags_cog.display_tag(ctx, command_name)
+ if sent:
return
+
+ # No similar tag found, or tag on cooldown -
+ # searching for a similar command
+ raw_commands = []
+ for cmd in self.bot.walk_commands():
+ if not cmd.hidden:
+ raw_commands += (cmd.name, *cmd.aliases)
+ similar_command_data = difflib.get_close_matches(command_name, raw_commands, 1)
+ similar_command_name = similar_command_data[0]
+ similar_command = self.bot.get_command(similar_command_name)
+
+ log_msg = "Cancelling attempt to suggest a command due to failed checks."
+ try:
+ if not await similar_command.can_run(ctx):
+ log.debug(log_msg)
+ return
+ except CommandError as cmd_error:
+ log.debug(log_msg)
+ await self.on_command_error(ctx, cmd_error)
+ return
+
+ misspelled_content = ctx.message.content
+ e = Embed()
+ e.set_author(name="Did you mean:", icon_url=Icons.questionmark)
+ e.description = f"{misspelled_content.replace(command_name, similar_command_name, 1)}"
+ await ctx.send(embed=e, delete_after=7.0)
+
elif isinstance(e, BadArgument):
await ctx.send(f"Bad argument: {e}\n")
await ctx.invoke(*help_command)
diff --git a/bot/cogs/tags.py b/bot/cogs/tags.py
index 54a51921c..c3bc9861f 100644
--- a/bot/cogs/tags.py
+++ b/bot/cogs/tags.py
@@ -92,33 +92,40 @@ 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."""
- def _command_on_cooldown(tag_name: str) -> bool:
- """
- Check if the command is currently on cooldown, on a per-tag, per-channel basis.
-
- The cooldown duration is set in constants.py.
- """
- now = time.time()
-
- cooldown_conditions = (
- tag_name
- and tag_name in self.tag_cooldowns
- and (now - self.tag_cooldowns[tag_name]["time"]) < Cooldowns.tags
- and self.tag_cooldowns[tag_name]["channel"] == ctx.channel.id
- )
-
- if cooldown_conditions:
- return True
- return False
-
- if _command_on_cooldown(tag_name):
+ def command_on_cooldown(self, ctx: Context, tag_name: str) -> bool:
+ """
+ Check if the command is currently on cooldown, on a per-tag, per-channel basis.
+
+ The cooldown duration is set in constants.py.
+ """
+ now = time.time()
+
+ cooldown_conditions = (
+ tag_name
+ and tag_name in self.tag_cooldowns
+ and (now - self.tag_cooldowns[tag_name]["time"]) < Cooldowns.tags
+ and self.tag_cooldowns[tag_name]["channel"] == ctx.channel.id
+ )
+
+ if cooldown_conditions:
+ return True
+ return False
+
+ async def display_tag(self, ctx: Context, tag_name: str = None) -> bool:
+ """
+ Show contents of the tag `tag_name` in `ctx` and return True if something is shown.
+
+ If a tag is not found, display similar tag names as suggestions. If a tag is not specified,
+ display a paginated embed of all tags.
+
+ Tags are on cooldowns on a per-tag, per-channel basis. If a tag is on cooldown, display
+ nothing and return False.
+ """
+ if self.command_on_cooldown(ctx, tag_name):
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()
@@ -133,11 +140,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])
))
+ return False
else:
tags = self._cache.values()
@@ -146,6 +155,7 @@ class Tags(Cog):
description="**There are no tags in the database!**",
colour=Colour.red()
))
+ return False
else:
embed: Embed = Embed(title="**Current tags**")
await LinePaginator.paginate(
@@ -156,6 +166,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.display_tag(ctx, tag_name)
@tags_group.command(name='set', aliases=('add', 's'))
@with_role(*MODERATION_ROLES)