Skip to content

Instantly share code, notes, and snippets.

@LeoCx1000
Last active April 1, 2024 20:01
Show Gist options
  • Save LeoCx1000/021dc52981299b95ea7790416e4f5ca4 to your computer and use it in GitHub Desktop.
Save LeoCx1000/021dc52981299b95ea7790416e4f5ca4 to your computer and use it in GitHub Desktop.
Mentionable CommandTree implementation to allow mentioning slash commands in discord.py

discord.py MentionableTree implementation

My implementation of a CommandTree subclass which allows for finding a mention for a given app command.

Changelog

  • 2023-08-14: Turn find_mention_for into a coroutine, and make it auto-fetch commands.
  • 2024-03-12: Make find_mention_for respect fallback_to_global.
  • 2024-03-24: Fix this issue.
from .mentionable_tree import *
# fmt: off
from typing import Optional, List
import discord
from discord import app_commands
__all__ = ('MentionableTree',)
class MentionableTree(app_commands.CommandTree):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.application_commands: dict[Optional[int], List[app_commands.AppCommand]] = {}
async def sync(self, *, guild: Optional[discord.abc.Snowflake] = None):
"""Method overwritten to store the commands."""
ret = await super().sync(guild=guild)
self.application_commands[guild.id if guild else None] = ret
return ret
async def fetch_commands(self, *, guild: Optional[discord.abc.Snowflake] = None):
"""Method overwritten to store the commands."""
ret = await super().fetch_commands(guild=guild)
self.application_commands[guild.id if guild else None] = ret
return ret
async def find_mention_for(
self,
command: app_commands.Command | app_commands.Group | str,
*,
guild: Optional[discord.abc.Snowflake] = None,
) -> Optional[str]:
"""Retrieves the mention of an AppCommand given a specific command name, and optionally, a guild.
Parameters
----------
name: Union[:class:`app_commands.Command`, :class:`app_commands.Group`, str]
The command which it's mention we will attempt to retrieve.
guild: Optional[:class:`discord.abc.Snowflake`]
The scope (guild) from which to retrieve the commands from. If None is given or not passed,
only the global scope will be searched, however the global scope will also be searched if
a guild is passed.
"""
check_global = self.fallback_to_global is True or guild is not None
if isinstance(command, str):
# Try and find a command by that name. discord.py does not return children from tree.get_command, but
# using walk_commands and utils.get is a simple way around that.
_command = discord.utils.get(self.walk_commands(guild=guild), qualified_name=command)
if check_global and not _command:
_command = discord.utils.get(self.walk_commands(), qualified_name=command)
else:
_command = command
if not _command:
return None
if guild:
try:
local_commands = self.application_commands[guild.id]
except KeyError:
local_commands = await self.fetch_commands(guild=guild)
app_command_found = discord.utils.get(local_commands, name=(_command.root_parent or _command).name)
else:
app_command_found = None
if check_global and not app_command_found:
try:
global_commands = self.application_commands[None]
except KeyError:
global_commands = await self.fetch_commands()
app_command_found = discord.utils.get(global_commands, name=(_command.root_parent or _command).name)
if not app_command_found:
return None
return f"</{_command.qualified_name}:{app_command_found.id}>"
@Sachaa-Thanasius
Copy link

Minor bug:

check_global = self.fallback_to_global is True and guild is not None

This should be an or condition and not an and one, based on the description in the docstring.

@LeoCx1000
Copy link
Author

LeoCx1000 commented Mar 24, 2024

You're right, I suppose this could cause issues if you're looking for a global command's mention in a guild, but you have fallback_to_global set to False. I hadn't thought of this edge case, thanks.

Oh! I see the other logic issue now, oops.

@Sachaa-Thanasius
Copy link

Sachaa-Thanasius commented Mar 25, 2024

There may be more to it, but I didn’t look closer beyond the first supposed mismatch in logic vs. docs. Glad it was helpful. Thanks for the great snippet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment