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 

Changed files:
tests/bot/test_converters.py, bot/cogs/error_handler.py, bot/converters.py

diff --git a/bot/cogs/error_handler.py b/bot/cogs/error_handler.py
index 86445013..261769ef 100644
--- a/bot/cogs/error_handler.py
+++ b/bot/cogs/error_handler.py
@@ -8,6 +8,7 @@ from sentry_sdk import push_scope
 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 @@ class ErrorHandler(Cog):
             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
index cca57a02..1945e1da 100644
--- 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 @@ class TagContentConverter(Converter):
 
         # 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
diff --git a/tests/bot/test_converters.py b/tests/bot/test_converters.py
index b2b78d9d..1e5ca62a 100644
--- a/tests/bot/test_converters.py
+++ b/tests/bot/test_converters.py
@@ -68,7 +68,7 @@ class ConverterTests(unittest.TestCase):
             ('👋', "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!"),
         )