141 lines
5.3 KiB
Python
141 lines
5.3 KiB
Python
import asyncio
|
|
import functools
|
|
import heapq
|
|
import operator
|
|
from copy import copy
|
|
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, cast
|
|
|
|
import discord
|
|
from rapidfuzz import fuzz
|
|
from redbot.core import app_commands, commands
|
|
from redbot.core.bot import Red
|
|
from redbot.core.commands.help import HelpSettings
|
|
from redbot.core.i18n import set_contextual_locale
|
|
|
|
from .context import InterContext
|
|
from .utils import walk_aliases
|
|
|
|
|
|
@app_commands.command(extras={"red_force_enable": True})
|
|
async def onetrueslash(
|
|
interaction: discord.Interaction,
|
|
command: str,
|
|
arguments: Optional[str] = None,
|
|
attachment: Optional[discord.Attachment] = None,
|
|
) -> None:
|
|
"""
|
|
The one true slash command.
|
|
|
|
Parameters
|
|
-----------
|
|
command: str
|
|
The text-based command to run.
|
|
arguments: Optional[str]
|
|
The arguments to provide to the command, if any.
|
|
attachment: Optional[Attachment]
|
|
The attached file to provide to the command, if any.
|
|
"""
|
|
assert isinstance(interaction.client, Red)
|
|
set_contextual_locale(str(interaction.guild_locale or interaction.locale))
|
|
actual = interaction.client.get_command(command)
|
|
ctx = await InterContext.from_interaction(interaction, recreate_message=True)
|
|
error = None
|
|
if command == "help":
|
|
ctx._deferring = True
|
|
# Moving ctx._interaction can cause check errors with some hybrid commands
|
|
# see https://github.com/Zephyrkul/FluffyCogs/issues/75 for details
|
|
# ctx.interaction = interaction
|
|
await interaction.response.defer(ephemeral=True)
|
|
actual = None
|
|
if arguments:
|
|
actual = interaction.client.get_command(arguments)
|
|
if actual and (signature := actual.signature):
|
|
actual = copy(actual)
|
|
actual.usage = f"arguments:{signature}"
|
|
await interaction.client.send_help_for(
|
|
ctx, actual or interaction.client, from_help_command=True
|
|
)
|
|
else:
|
|
ferror: asyncio.Task[Tuple[InterContext, commands.CommandError]] = asyncio.create_task(
|
|
interaction.client.wait_for("command_error", check=lambda c, _: c is ctx)
|
|
)
|
|
ferror.add_done_callback(lambda _: setattr(ctx, "interaction", interaction))
|
|
await interaction.client.invoke(ctx)
|
|
if not interaction.response.is_done():
|
|
ctx._deferring = True
|
|
await interaction.response.defer(ephemeral=True)
|
|
if ferror.done():
|
|
error = ferror.exception() or ferror.result()[1]
|
|
ferror.cancel()
|
|
if ctx._deferring and not interaction.is_expired():
|
|
if error is None:
|
|
if ctx._ticked:
|
|
await interaction.followup.send(ctx._ticked, ephemeral=True)
|
|
else:
|
|
await interaction.delete_original_response()
|
|
elif isinstance(error, commands.CommandNotFound):
|
|
await interaction.followup.send(
|
|
f"❌ Command `{command}` was not found.", ephemeral=True
|
|
)
|
|
elif isinstance(error, commands.CheckFailure):
|
|
await interaction.followup.send(
|
|
f"❌ You don't have permission to run `{command}`.", ephemeral=True
|
|
)
|
|
|
|
|
|
@onetrueslash.autocomplete("command")
|
|
async def onetrueslash_command_autocomplete(
|
|
interaction: discord.Interaction, current: str
|
|
) -> List[app_commands.Choice[str]]:
|
|
assert isinstance(interaction.client, Red)
|
|
|
|
if not await interaction.client.allowed_by_whitelist_blacklist(interaction.user):
|
|
return []
|
|
|
|
ctx = await InterContext.from_interaction(interaction)
|
|
if not await interaction.client.message_eligible_as_command(ctx.message):
|
|
return []
|
|
|
|
help_settings = await HelpSettings.from_context(ctx)
|
|
if current:
|
|
extracted = cast(
|
|
List[str],
|
|
await asyncio.get_event_loop().run_in_executor(
|
|
None,
|
|
heapq.nlargest,
|
|
6,
|
|
walk_aliases(interaction.client, show_hidden=help_settings.show_hidden),
|
|
functools.partial(fuzz.token_sort_ratio, current),
|
|
),
|
|
)
|
|
extracted.append("help")
|
|
else:
|
|
extracted = ["help"]
|
|
_filter: Callable[[commands.Command], Awaitable[bool]] = operator.methodcaller(
|
|
"can_run" if help_settings.show_hidden else "can_see", ctx
|
|
)
|
|
matches: Dict[commands.Command, str] = {}
|
|
for name in extracted:
|
|
command = interaction.client.get_command(name)
|
|
if not command or command in matches:
|
|
continue
|
|
try:
|
|
if name == "help" and await command.can_run(ctx) or await _filter(command):
|
|
if len(name) > 100:
|
|
name = name[:99] + "\N{HORIZONTAL ELLIPSIS}"
|
|
matches[command] = name
|
|
except commands.CommandError:
|
|
pass
|
|
return [app_commands.Choice(name=name, value=name) for name in matches.values()]
|
|
|
|
|
|
@onetrueslash.error
|
|
async def onetrueslash_error(interaction: discord.Interaction, error: Exception):
|
|
assert isinstance(interaction.client, Red)
|
|
if isinstance(error, app_commands.CommandInvokeError):
|
|
error = error.original
|
|
error = getattr(error, "original", error)
|
|
await interaction.client.on_command_error(
|
|
await InterContext.from_interaction(interaction, recreate_message=True),
|
|
commands.CommandInvokeError(error),
|
|
)
|