mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[V3 Fuzzy search] fix several issues with this feature (#1788)
* [V3 Fuzzy search] fix several issues with this feature * Make it check if parent commands are hidden * Check if compiler available in setup.py * Let's just compile a dummy C file to check compiler availability * Add a missing import + remove unneeded code
This commit is contained in:
parent
db5d4d5158
commit
0b78664792
@ -50,6 +50,7 @@ class RedBase(BotBase):
|
|||||||
locale="en",
|
locale="en",
|
||||||
embeds=True,
|
embeds=True,
|
||||||
color=15158332,
|
color=15158332,
|
||||||
|
fuzzy=False,
|
||||||
help__page_char_limit=1000,
|
help__page_char_limit=1000,
|
||||||
help__max_pages_in_guild=2,
|
help__max_pages_in_guild=2,
|
||||||
help__tagline="",
|
help__tagline="",
|
||||||
@ -63,6 +64,7 @@ class RedBase(BotBase):
|
|||||||
mod_role=None,
|
mod_role=None,
|
||||||
embeds=None,
|
embeds=None,
|
||||||
use_bot_color=False,
|
use_bot_color=False,
|
||||||
|
fuzzy=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db.register_user(embeds=None)
|
self.db.register_user(embeds=None)
|
||||||
|
|||||||
@ -48,6 +48,21 @@ class Command(commands.Command):
|
|||||||
# We don't want our help property to be overwritten, namely by super()
|
# We don't want our help property to be overwritten, namely by super()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parents(self):
|
||||||
|
"""
|
||||||
|
Returns all parent commands of this command.
|
||||||
|
|
||||||
|
This is a list, sorted by the length of :attr:`.qualified_name` from highest to lowest.
|
||||||
|
If the command has no parents, this will be an empty list.
|
||||||
|
"""
|
||||||
|
cmd = self.parent
|
||||||
|
entries = []
|
||||||
|
while cmd is not None:
|
||||||
|
entries.append(cmd)
|
||||||
|
cmd = cmd.parent
|
||||||
|
return sorted(entries, key=lambda x: len(x.qualified_name), reverse=True)
|
||||||
|
|
||||||
def command(self, cls=None, *args, **kwargs):
|
def command(self, cls=None, *args, **kwargs):
|
||||||
"""A shortcut decorator that invokes :func:`.command` and adds it to
|
"""A shortcut decorator that invokes :func:`.command` and adds it to
|
||||||
the internal command list via :meth:`~.GroupMixin.add_command`.
|
the internal command list via :meth:`~.GroupMixin.add_command`.
|
||||||
|
|||||||
@ -653,6 +653,39 @@ class Core(CoreLogic):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@_set.command()
|
||||||
|
@checks.guildowner()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def serverfuzzy(self, ctx):
|
||||||
|
"""
|
||||||
|
Toggle whether to enable fuzzy command search for the server.
|
||||||
|
|
||||||
|
Default is for fuzzy command search to be disabled.
|
||||||
|
"""
|
||||||
|
current_setting = await ctx.bot.db.guild(ctx.guild).fuzzy()
|
||||||
|
await ctx.bot.db.guild(ctx.guild).fuzzy.set(not current_setting)
|
||||||
|
await ctx.send(
|
||||||
|
_("Fuzzy command search has been {} for this server.").format(
|
||||||
|
_("disabled") if current_setting else _("enabled")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@_set.command()
|
||||||
|
@checks.is_owner()
|
||||||
|
async def fuzzy(self, ctx):
|
||||||
|
"""
|
||||||
|
Toggle whether to enable fuzzy command search in DMs.
|
||||||
|
|
||||||
|
Default is for fuzzy command search to be disabled.
|
||||||
|
"""
|
||||||
|
current_setting = await ctx.bot.db.fuzzy()
|
||||||
|
await ctx.bot.db.fuzzy.set(not current_setting)
|
||||||
|
await ctx.send(
|
||||||
|
_("Fuzzy command search has been {} in DMs.").format(
|
||||||
|
_("disabled") if current_setting else _("enabled")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@_set.command(aliases=["color"])
|
@_set.command(aliases=["color"])
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def colour(self, ctx, *, colour: discord.Colour = None):
|
async def colour(self, ctx, *, colour: discord.Colour = None):
|
||||||
|
|||||||
@ -225,7 +225,9 @@ def init_events(bot, cli_flags):
|
|||||||
term = ctx.invoked_with + " "
|
term = ctx.invoked_with + " "
|
||||||
if len(ctx.args) > 1:
|
if len(ctx.args) > 1:
|
||||||
term += " ".join(ctx.args[1:])
|
term += " ".join(ctx.args[1:])
|
||||||
await ctx.maybe_send_embed(fuzzy_command_search(ctx, ctx.invoked_with))
|
fuzzy_result = await fuzzy_command_search(ctx, ctx.invoked_with)
|
||||||
|
if fuzzy_result is not None:
|
||||||
|
await ctx.maybe_send_embed(fuzzy_result)
|
||||||
elif isinstance(error, commands.CheckFailure):
|
elif isinstance(error, commands.CheckFailure):
|
||||||
pass
|
pass
|
||||||
elif isinstance(error, commands.NoPrivateMessage):
|
elif isinstance(error, commands.NoPrivateMessage):
|
||||||
|
|||||||
@ -281,11 +281,10 @@ class Help(formatter.HelpFormatter):
|
|||||||
embed.set_author(**self.author)
|
embed.set_author(**self.author)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
async def cmd_not_found(self, ctx, cmd, color=None):
|
async def cmd_not_found(self, ctx, cmd, description=None, color=None):
|
||||||
# Shortcut for a shortcut. Sue me
|
# Shortcut for a shortcut. Sue me
|
||||||
out = fuzzy_command_search(ctx, " ".join(ctx.args[1:]))
|
|
||||||
embed = await self.simple_embed(
|
embed = await self.simple_embed(
|
||||||
ctx, title="Command {} not found.".format(cmd), description=out, color=color
|
ctx, title="Command {} not found.".format(cmd), description=description, color=color
|
||||||
)
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
@ -326,11 +325,19 @@ async def help(ctx, *cmds: str):
|
|||||||
command = ctx.bot.all_commands.get(name)
|
command = ctx.bot.all_commands.get(name)
|
||||||
if command is None:
|
if command is None:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
await destination.send(embed=await ctx.bot.formatter.cmd_not_found(ctx, name))
|
fuzzy_result = await fuzzy_command_search(ctx, name)
|
||||||
|
if fuzzy_result is not None:
|
||||||
|
await destination.send(
|
||||||
|
embed=await ctx.bot.formatter.cmd_not_found(
|
||||||
|
ctx, name, description=fuzzy_result
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await destination.send(
|
fuzzy_result = await fuzzy_command_search(ctx, name)
|
||||||
ctx.bot.command_not_found.format(name, fuzzy_command_search(ctx, name))
|
if fuzzy_result is not None:
|
||||||
)
|
await destination.send(
|
||||||
|
ctx.bot.command_not_found.format(name, fuzzy_result)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
embeds = await ctx.bot.formatter.format_help_for(ctx, command)
|
embeds = await ctx.bot.formatter.format_help_for(ctx, command)
|
||||||
@ -341,11 +348,17 @@ async def help(ctx, *cmds: str):
|
|||||||
command = ctx.bot.all_commands.get(name)
|
command = ctx.bot.all_commands.get(name)
|
||||||
if command is None:
|
if command is None:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
await destination.send(embed=await ctx.bot.formatter.cmd_not_found(ctx, name))
|
fuzzy_result = await fuzzy_command_search(ctx, name)
|
||||||
|
if fuzzy_result is not None:
|
||||||
|
await destination.send(
|
||||||
|
embed=await ctx.bot.formatter.cmd_not_found(
|
||||||
|
ctx, name, description=fuzzy_result
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await destination.send(
|
fuzzy_result = await fuzzy_command_search(ctx, name)
|
||||||
ctx.bot.command_not_found.format(name, fuzzy_command_search(ctx, name))
|
if fuzzy_result is not None:
|
||||||
)
|
await destination.send(ctx.bot.command_not_found.format(name, fuzzy_result))
|
||||||
return
|
return
|
||||||
|
|
||||||
for key in cmds[1:]:
|
for key in cmds[1:]:
|
||||||
@ -354,13 +367,19 @@ async def help(ctx, *cmds: str):
|
|||||||
command = command.all_commands.get(key)
|
command = command.all_commands.get(key)
|
||||||
if command is None:
|
if command is None:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
await destination.send(
|
fuzzy_result = await fuzzy_command_search(ctx, name)
|
||||||
embed=await ctx.bot.formatter.cmd_not_found(ctx, key)
|
if fuzzy_result is not None:
|
||||||
)
|
await destination.send(
|
||||||
|
embed=await ctx.bot.formatter.cmd_not_found(
|
||||||
|
ctx, name, description=fuzzy_result
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await destination.send(
|
fuzzy_result = await fuzzy_command_search(ctx, name)
|
||||||
ctx.bot.command_not_found.format(key, fuzzy_command_search(ctx, name))
|
if fuzzy_result is not None:
|
||||||
)
|
await destination.send(
|
||||||
|
ctx.bot.command_not_found.format(name, fuzzy_result)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if use_embeds:
|
if use_embeds:
|
||||||
|
|||||||
@ -3,11 +3,19 @@ __all__ = ["safe_delete", "fuzzy_command_search"]
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import logging
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from fuzzywuzzy import process
|
from fuzzywuzzy import process
|
||||||
from .chat_formatting import box
|
from .chat_formatting import box
|
||||||
|
|
||||||
|
|
||||||
|
def fuzzy_filter(record):
|
||||||
|
return record.funcName != "extractWithoutOrder"
|
||||||
|
|
||||||
|
|
||||||
|
logging.getLogger().addFilter(fuzzy_filter)
|
||||||
|
|
||||||
|
|
||||||
def safe_delete(pth: Path):
|
def safe_delete(pth: Path):
|
||||||
if pth.exists():
|
if pth.exists():
|
||||||
for root, dirs, files in os.walk(str(pth)):
|
for root, dirs, files in os.walk(str(pth)):
|
||||||
@ -19,9 +27,49 @@ def safe_delete(pth: Path):
|
|||||||
shutil.rmtree(str(pth), ignore_errors=True)
|
shutil.rmtree(str(pth), ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def fuzzy_command_search(ctx: commands.Context, term: str):
|
async def filter_commands(ctx: commands.Context, extracted: list):
|
||||||
|
return [
|
||||||
|
i
|
||||||
|
for i in extracted
|
||||||
|
if i[1] >= 90
|
||||||
|
and not i[0].hidden
|
||||||
|
and await i[0].can_run(ctx)
|
||||||
|
and all([await p.can_run(ctx) for p in i[0].parents])
|
||||||
|
and not any([p.hidden for p in i[0].parents])
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def fuzzy_command_search(ctx: commands.Context, term: str):
|
||||||
out = ""
|
out = ""
|
||||||
for pos, extracted in enumerate(process.extract(term, ctx.bot.walk_commands(), limit=5), 1):
|
if ctx.guild is not None:
|
||||||
|
enabled = await ctx.bot.db.guild(ctx.guild).fuzzy()
|
||||||
|
else:
|
||||||
|
enabled = await ctx.bot.db.fuzzy()
|
||||||
|
if not enabled:
|
||||||
|
return None
|
||||||
|
alias_cog = ctx.bot.get_cog("Alias")
|
||||||
|
if alias_cog is not None:
|
||||||
|
is_alias, alias = await alias_cog.is_alias(ctx.guild, term)
|
||||||
|
if is_alias:
|
||||||
|
return None
|
||||||
|
|
||||||
|
customcom_cog = ctx.bot.get_cog("CustomCommands")
|
||||||
|
if customcom_cog is not None:
|
||||||
|
cmd_obj = customcom_cog.commandobj
|
||||||
|
try:
|
||||||
|
ccinfo = await cmd_obj.get(ctx.message, term)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
extracted_cmds = await filter_commands(
|
||||||
|
ctx, process.extract(term, ctx.bot.walk_commands(), limit=5)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not extracted_cmds:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for pos, extracted in enumerate(extracted_cmds, 1):
|
||||||
out += "{0}. {1.prefix}{2.qualified_name}{3}\n".format(
|
out += "{0}. {1.prefix}{2.qualified_name}{3}\n".format(
|
||||||
pos,
|
pos,
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
21
setup.py
21
setup.py
@ -1,6 +1,10 @@
|
|||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
from distutils import ccompiler
|
||||||
|
from distutils.errors import CCompilerError
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
import importlib
|
||||||
|
import tempfile
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -21,6 +25,20 @@ def get_package_list():
|
|||||||
return core
|
return core
|
||||||
|
|
||||||
|
|
||||||
|
def check_compiler_available():
|
||||||
|
m = ccompiler.new_compiler()
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
|
with tempfile.NamedTemporaryFile(prefix="dummy", suffix=".c", dir=tdir) as tfile:
|
||||||
|
tfile.write(b"int main(int argc, char** argv) {return 0;}")
|
||||||
|
tfile.seek(0)
|
||||||
|
try:
|
||||||
|
m.compile([tfile.name])
|
||||||
|
except CCompilerError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_requirements():
|
def get_requirements():
|
||||||
with open("requirements.txt") as f:
|
with open("requirements.txt") as f:
|
||||||
requirements = f.read().splitlines()
|
requirements = f.read().splitlines()
|
||||||
@ -31,6 +49,9 @@ def get_requirements():
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if not check_compiler_available(): # Can't compile python-Levensthein, so drop extra
|
||||||
|
requirements.remove("fuzzywuzzy[speedup]<=0.16.0")
|
||||||
|
requirements.append("fuzzywuzzy<=0.16.0")
|
||||||
if IS_DEPLOYING or not (IS_TRAVIS or IS_RTD):
|
if IS_DEPLOYING or not (IS_TRAVIS or IS_RTD):
|
||||||
requirements.append("discord.py>=1.0.0a0")
|
requirements.append("discord.py>=1.0.0a0")
|
||||||
if sys.platform.startswith("linux"):
|
if sys.platform.startswith("linux"):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user