Ruby-Cogs/onetrueslash/commands.py
2025-04-02 22:57:51 -04:00

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),
)