diff options
Diffstat (limited to '')
| -rw-r--r-- | Pipfile.lock | 59 | ||||
| -rw-r--r-- | bot/cogs/error_handler.py | 43 | ||||
| -rw-r--r-- | bot/cogs/tags.py | 66 | 
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) | 
