Repository: Cog-Creators/Red-DiscordBot

Issue:
[Core] Fuzzy command search - commands show up multiple times if they have aliases
# Other bugs

#### What were you trying to do?

Misspell a command with aliases (as in, coded aliases, not the ones from alias cog) with fuzzy command search enabled.

#### What were you expecting to happen?

Fuzzy command search to show proper command once.

#### What actually happened?

Fuzzy command search showed the proper command twice.

#### How can we reproduce this issue?

Use these commands with fuzzy command search enabled (misspell in second command intended)
```
[p]load general
[p]stopwatdc
```
Get this embed as a response:
![image](https://user-images.githubusercontent.com/6032823/60015441-d29e9080-9683-11e9-8573-9b5bc8f456ff.png)



Discussion:
Ha, a #2450 regression? Or maybe it was never fixed :eyes:
wasnt ever fixed. the fact that help's fuzzy search was deduplicated was merely a side effect of how commands are filtered there, the normal on none found doesnt have that
Anyway, this should be a pretty easy fix. I'll try get it done now

Resolving patch:
diff --git a/redbot/core/commands/help.py b/redbot/core/commands/help.py
--- a/redbot/core/commands/help.py
+++ b/redbot/core/commands/help.py
@@ -417,8 +417,9 @@ def width_maker(cmds):
             pages = [box(p) for p in pagify(to_page)]
             await self.send_pages(ctx, pages, embed=False)
 
+    @staticmethod
     async def help_filter_func(
-        self, ctx, objects: Iterable[SupportsCanSee], bypass_hidden=False
+        ctx, objects: Iterable[SupportsCanSee], bypass_hidden=False
     ) -> AsyncIterator[SupportsCanSee]:
         """
         This does most of actual filtering.
@@ -450,7 +451,7 @@ async def command_not_found(self, ctx, help_for):
         """
         Sends an error, fuzzy help, or stays quiet based on settings
         """
-        coms = [c async for c in self.help_filter_func(ctx, ctx.bot.walk_commands())]
+        coms = {c async for c in self.help_filter_func(ctx, ctx.bot.walk_commands())}
         fuzzy_commands = await fuzzy_command_search(ctx, help_for, commands=coms, min_score=75)
         use_embeds = await ctx.embed_requested()
         if fuzzy_commands:
diff --git a/redbot/core/events.py b/redbot/core/events.py
--- a/redbot/core/events.py
+++ b/redbot/core/events.py
@@ -13,6 +13,7 @@
 from colorama import Fore, Style, init
 from pkg_resources import DistributionNotFound
 
+from redbot.core.commands import RedHelpFormatter
 from .. import __version__ as red_version, version_info as red_version_info, VersionInfo
 from . import commands
 from .config import get_latest_confs
@@ -201,7 +202,12 @@ async def on_command_error(ctx, error, unhandled_by_cog=False):
             bot._last_exception = exception_log
             await ctx.send(inline(message))
         elif isinstance(error, commands.CommandNotFound):
-            fuzzy_commands = await fuzzy_command_search(ctx)
+            fuzzy_commands = await fuzzy_command_search(
+                ctx,
+                commands={
+                    c async for c in RedHelpFormatter.help_filter_func(ctx, bot.walk_commands())
+                },
+            )
             if not fuzzy_commands:
                 pass
             elif await ctx.embed_requested():
diff --git a/redbot/core/utils/__init__.py b/redbot/core/utils/__init__.py
--- a/redbot/core/utils/__init__.py
+++ b/redbot/core/utils/__init__.py
@@ -19,14 +19,18 @@
     Tuple,
     TypeVar,
     Union,
+    Set,
+    TYPE_CHECKING,
 )
 
 import discord
 from fuzzywuzzy import fuzz, process
-from redbot.core import commands
 
 from .chat_formatting import box
 
+if TYPE_CHECKING:
+    from ..commands import Command, Context
+
 __all__ = [
     "bounded_gather",
     "safe_delete",
@@ -180,12 +184,12 @@ async def async_enumerate(
 
 
 async def fuzzy_command_search(
-    ctx: commands.Context,
+    ctx: "Context",
     term: Optional[str] = None,
     *,
-    commands: Optional[list] = None,
+    commands: Optional[Set["Command"]] = None,
     min_score: int = 80,
-) -> Optional[List[commands.Command]]:
+) -> Optional[List["Command"]]:
     """Search for commands which are similar in name to the one invoked.
 
     Returns a maximum of 5 commands which must all be at least matched
@@ -196,8 +200,11 @@ async def fuzzy_command_search(
     ctx : `commands.Context <redbot.core.commands.Context>`
         The command invocation context.
     term : Optional[str]
-        The name of the invoked command. If ``None``, `Context.invoked_with`
-        will be used instead.
+        The name of the invoked command. If ``None``,
+        `Context.invoked_with` will be used instead.
+    commands : Optional[Set[commands.Command]]
+        The commands available to choose from when doing a fuzzy match.
+        When omitted, `Bot.walk_commands` will be used instead.
     min_score : int
         The minimum score for matched commands to reach. Defaults to 80.
 
@@ -239,7 +246,7 @@ async def fuzzy_command_search(
 
     # Do the scoring. `extracted` is a list of tuples in the form `(command, score)`
     extracted = process.extract(
-        term, (commands or ctx.bot.walk_commands()), limit=5, scorer=fuzz.QRatio
+        term, (commands or set(ctx.bot.walk_commands())), limit=5, scorer=fuzz.QRatio
     )
     if not extracted:
         return
@@ -257,10 +264,7 @@ async def fuzzy_command_search(
 
 
 async def format_fuzzy_results(
-    ctx: commands.Context,
-    matched_commands: List[commands.Command],
-    *,
-    embed: Optional[bool] = None,
+    ctx: "Context", matched_commands: List["Command"], *, embed: Optional[bool] = None
 ) -> Union[str, discord.Embed]:
     """Format the result of a fuzzy command search.
 


Test patch: