Repository: python-discord/bot

Issue:
ZeroDivisionError in tag fuzzy search
Don't know exactly what triggered it cause I noticed it way up in my bot's log history.

```
Ignoring exception in on_command_error
Traceback (most recent call last):
  File "/home/mark/repos/python/bot-pydis/.venv/lib/python3.7/site-packages/discord/client.py", line 312, in _run_event
    await coro(*args, **kwargs)
  File "/home/mark/repos/python/bot-pydis/bot/cogs/error_handler.py", line 92, in on_command_error
    await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with)
  File "/home/mark/repos/python/bot-pydis/.venv/lib/python3.7/site-packages/discord/ext/commands/context.py", line 132, in invoke
    ret = await command.callback(*arguments, **kwargs)
  File "/home/mark/repos/python/bot-pydis/bot/cogs/tags.py", line 128, in get_command
    founds = await self._get_tag(tag_name)
  File "/home/mark/repos/python/bot-pydis/bot/cogs/tags.py", line 87, in _get_tag
    return self._get_suggestions(tag_name)
  File "/home/mark/repos/python/bot-pydis/bot/cogs/tags.py", line 66, in _get_suggestions
    for tag_title, tag in self._cache.items()
  File "/home/mark/repos/python/bot-pydis/bot/cogs/tags.py", line 66, in <dictcomp>
    for tag_title, tag in self._cache.items()
  File "/home/mark/repos/python/bot-pydis/bot/cogs/tags.py", line 60, in _fuzzy_search
    return current / len(_search) * 100
ZeroDivisionError: division by zero
```

CC @ikuyarihS 


Discussion:
Seems like no match (REGEX_NON_ALPHABET) was found, Is returning (unreachable value?) an option?
Looks like the error handler tags don't go through `TagNameConverter` which discards invalid tag names, can look at it more if assigned.
Sure @Numerlor, I've assigned you.
This will also get triggered when going through the tag commands, but passing a tag name without any letters. Do we want to allow those or should they also be rejected in the converter?
Without letters? As in A-Z? Or do you mean without any characters? Cause I think tag names with, for example, digits only, should be allowed (kind of weird but why not).
Tag names with only numbers already get rejected
https://github.com/python-discord/bot/blob/1ccc0f11fd4511d13c7cdf59070dca86465fe59f/bot/converters.py#L166-L170
But other ascii characters go through and fail at the alpha regex e.g. ----- or $+-(
In that case, follow in its footsteps and also ignore symbol only characters. Probably can merge the number and symbol check together by checking that at least one letter exists. 

I don't know what exactly the regex problem is. If the above does not fix it, then the regex should support symbols too (if it's feasible). If it's too much trouble then I suppose the symbols should be completely blacklisted.

Resolving patch:
diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py
--- a/bot/cogs/error_handler.py
+++ b/bot/cogs/error_handler.py
@@ -8,6 +8,7 @@
 from bot.api import ResponseCodeError
 from bot.bot import Bot
 from bot.constants import Channels
+from bot.converters import TagNameConverter
 from bot.decorators import InChannelCheckFailure
 
 log = logging.getLogger(__name__)
@@ -109,10 +110,18 @@ async def try_get_tag(self, ctx: Context) -> None:
             await self.on_command_error(ctx, tag_error)
             return
 
+        try:
+            tag_name = await TagNameConverter.convert(ctx, ctx.invoked_with)
+        except errors.BadArgument:
+            log.debug(
+                f"{ctx.author} tried to use an invalid command "
+                f"and the fallback tag failed validation in TagNameConverter."
+            )
+        else:
+            with contextlib.suppress(ResponseCodeError):
+                await ctx.invoke(tags_get_command, tag_name=tag_name)
         # Return to not raise the exception
-        with contextlib.suppress(ResponseCodeError):
-            await ctx.invoke(tags_get_command, tag_name=ctx.invoked_with)
-            return
+        return
 
     async def handle_user_input_error(self, ctx: Context, e: errors.UserInputError) -> None:
         """
diff --git a/bot/converters.py b/bot/converters.py
--- a/bot/converters.py
+++ b/bot/converters.py
@@ -141,40 +141,24 @@ class TagNameConverter(Converter):
     @staticmethod
     async def convert(ctx: Context, tag_name: str) -> str:
         """Lowercase & strip whitespace from proposed tag_name & ensure it's valid."""
-        def is_number(value: str) -> bool:
-            """Check to see if the input string is numeric."""
-            try:
-                float(value)
-            except ValueError:
-                return False
-            return True
-
         tag_name = tag_name.lower().strip()
 
         # The tag name has at least one invalid character.
         if ascii(tag_name)[1:-1] != tag_name:
-            log.warning(f"{ctx.author} tried to put an invalid character in a tag name. "
-                        "Rejecting the request.")
             raise BadArgument("Don't be ridiculous, you can't use that character!")
 
         # The tag name is either empty, or consists of nothing but whitespace.
         elif not tag_name:
-            log.warning(f"{ctx.author} tried to create a tag with a name consisting only of whitespace. "
-                        "Rejecting the request.")
             raise BadArgument("Tag names should not be empty, or filled with whitespace.")
 
-        # The tag name is a number of some kind, we don't allow that.
-        elif is_number(tag_name):
-            log.warning(f"{ctx.author} tried to create a tag with a digit as its name. "
-                        "Rejecting the request.")
-            raise BadArgument("Tag names can't be numbers.")
-
         # The tag name is longer than 127 characters.
         elif len(tag_name) > 127:
-            log.warning(f"{ctx.author} tried to request a tag name with over 127 characters. "
-                        "Rejecting the request.")
             raise BadArgument("Are you insane? That's way too long!")
 
+        # The tag name is ascii but does not contain any letters.
+        elif not any(character.isalpha() for character in tag_name):
+            raise BadArgument("Tag names must contain at least one letter.")
+
         return tag_name
 
 
@@ -192,8 +176,6 @@ async def convert(ctx: Context, tag_content: str) -> str:
 
         # The tag contents should not be empty, or filled with whitespace.
         if not tag_content:
-            log.warning(f"{ctx.author} tried to create a tag containing only whitespace. "
-                        "Rejecting the request.")
             raise BadArgument("Tag contents should not be empty, or filled with whitespace.")
 
         return tag_content


Test patch:
diff --git a/tests/bot/test_converters.py b/tests/bot/test_converters.py
--- a/tests/bot/test_converters.py
+++ b/tests/bot/test_converters.py
@@ -68,7 +68,7 @@ def test_tag_name_converter_for_invalid(self):
             ('👋', "Don't be ridiculous, you can't use that character!"),
             ('', "Tag names should not be empty, or filled with whitespace."),
             ('  ', "Tag names should not be empty, or filled with whitespace."),
-            ('42', "Tag names can't be numbers."),
+            ('42', "Tag names must contain at least one letter."),
             ('x' * 128, "Are you insane? That's way too long!"),
         )