Skip to content

Commit

Permalink
Include bot in interaction context
Browse files Browse the repository at this point in the history
Currently incoming application commands have no direct reference to the
bot instance. This change includes the bot instance in our interaction
context, much like how we do it in the commands extension.

Having a reference to the bot is pretty handy, and fits well with the
existing design of the library.

A reference to the bot will also allow us to more easily reference a
potential before_invoke or after_invoke hook from within
discord.app.commands.

Finally, by exposing a public get_context method, we open up the ability
to define a custom context, a feature currently supported by the
commands extension.
  • Loading branch information
jgayfer committed Sep 1, 2021
1 parent 28b3f81 commit 4140060
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 34 deletions.
45 changes: 20 additions & 25 deletions discord/app/commands.py
Expand Up @@ -30,7 +30,6 @@
from typing import Callable, Dict, List, Optional, Union

from ..enums import SlashCommandOptionType
from ..interactions import Interaction
from ..member import Member
from ..user import User
from ..message import Message
Expand Down Expand Up @@ -138,12 +137,10 @@ def __eq__(self, other) -> bool:
and other.description == self.description
)

async def invoke(self, interaction) -> None:
async def invoke(self, ctx: InteractionContext) -> None:
# TODO: Parse the args better, apply custom converters etc.
ctx = InteractionContext(interaction)

kwargs = {}
for arg in interaction.data.get("options", []):
for arg in ctx.interaction.data.get("options", []):
op = find(lambda x: x.name == arg["name"], self.options)
arg = arg["value"]

Expand Down Expand Up @@ -257,11 +254,11 @@ def command_group(self, name, description) -> SubCommandGroup:
self.subcommands.append(sub_command_group)
return sub_command_group

async def invoke(self, interaction: Interaction) -> None:
option = interaction.data["options"][0]
async def invoke(self, ctx: InteractionContext) -> None:
option = ctx.interaction.data["options"][0]
command = find(lambda x: x.name == option["name"], self.subcommands)
interaction.data = option
await command.invoke(interaction)
ctx.interaction.data = option
await command.invoke(ctx)


class UserCommand(ApplicationCommand):
Expand Down Expand Up @@ -290,29 +287,28 @@ def __init__(self, func: Callable, *args, **kwargs) -> None:
def to_dict(self) -> Dict[str, Union[str, int]]:
return {"name": self.name, "description": self.description, "type": self.type}

async def invoke(self, interaction: Interaction) -> None:
if "members" not in interaction.data["resolved"]:
_data = interaction.data["resolved"]["users"]
async def invoke(self, ctx: InteractionContext) -> None:
if "members" not in ctx.interaction.data["resolved"]:
_data = ctx.interaction.data["resolved"]["users"]
for i, v in _data.items():
v["id"] = int(i)
user = v
target = User(state=interaction._state, data=user)
target = User(state=ctx.interaction._state, data=user)
else:
_data = interaction.data["resolved"]["members"]
_data = ctx.interaction.data["resolved"]["members"]
for i, v in _data.items():
v["id"] = int(i)
member = v
_data = interaction.data["resolved"]["users"]
_data = ctx.interaction.data["resolved"]["users"]
for i, v in _data.items():
v["id"] = int(i)
user = v
member["user"] = user
target = Member(
data=member,
guild=interaction._state._get_guild(interaction.guild_id),
state=interaction._state,
guild=ctx.interaction._state._get_guild(ctx.interaction.guild_id),
state=ctx.interaction._state,
)
ctx = InteractionContext(interaction)
await self.callback(ctx, target)


Expand Down Expand Up @@ -340,18 +336,17 @@ def __init__(self, func, *args, **kwargs):
def to_dict(self):
return {"name": self.name, "description": self.description, "type": self.type}

async def invoke(self, interaction):
_data = interaction.data["resolved"]["messages"]
async def invoke(self, ctx: InteractionContext):
_data = ctx.interaction.data["resolved"]["messages"]
for i, v in _data.items():
v["id"] = int(i)
message = v
channel = interaction._state.get_channel(int(message["channel_id"]))
channel = ctx.interaction._state.get_channel(int(message["channel_id"]))
if channel is None:
data = await interaction._state.http.start_private_message(
data = await ctx.interaction._state.http.start_private_message(
int(message["author"]["id"])
)
channel = interaction._state.add_dm_channel(data)
channel = ctx.interaction._state.add_dm_channel(data)

target = Message(state=interaction._state, channel=channel, data=message)
ctx = InteractionContext(interaction)
target = Message(state=ctx.interaction._state, channel=channel, data=message)
await self.callback(ctx, target)
3 changes: 2 additions & 1 deletion discord/app/context.py
Expand Up @@ -35,7 +35,8 @@ class InteractionContext:
.. versionadded:: 2.0
"""

def __init__(self, interaction: Interaction):
def __init__(self, bot, interaction: Interaction):
self.bot = bot
self.interaction = interaction

@cached_property
Expand Down
54 changes: 46 additions & 8 deletions discord/bot.py
Expand Up @@ -38,6 +38,7 @@
MessageCommand,
UserCommand,
ApplicationCommand,
InteractionContext,
)
from .errors import Forbidden
from .interactions import Interaction
Expand Down Expand Up @@ -117,7 +118,9 @@ def add_application_command(self, command: ApplicationCommand) -> None:
"""
self.to_register.append(command)

def remove_application_command(self, command: ApplicationCommand) -> Optional[ApplicationCommand]:
def remove_application_command(
self, command: ApplicationCommand
) -> Optional[ApplicationCommand]:
"""Remove a :class:`.ApplicationCommand` from the internal list
of commands.
Expand Down Expand Up @@ -195,10 +198,16 @@ async def register_commands(self) -> None:
raised_guilds = []
for guild_id in update_guild_commands:
try:
cmds = await self.http.bulk_upsert_guild_commands(self.user.id, guild_id,
update_guild_commands[guild_id])
cmds = await self.http.bulk_upsert_guild_commands(
self.user.id, guild_id, update_guild_commands[guild_id]
)
for i in cmds:
cmd = get(self.to_register, name=i["name"], description=i["description"], type=i['type'])
cmd = get(
self.to_register,
name=i["name"],
description=i["description"],
type=i["type"],
)
self.app_commands[i["id"]] = cmd
except Forbidden as e:
raised_error = e
Expand All @@ -207,8 +216,10 @@ async def register_commands(self) -> None:
try: # TODO: this is awful
raise raised_error
except Forbidden:
print(f'Ignoring exception running bulk_upsert_guild_commands on guilds {raised_guilds}',
file=sys.stderr)
print(
f"Ignoring exception running bulk_upsert_guild_commands on guilds {raised_guilds}",
file=sys.stderr,
)
traceback.print_exc()

cmds = await self.http.bulk_upsert_global_commands(self.user.id, commands)
Expand Down Expand Up @@ -249,7 +260,8 @@ async def handle_interaction(self, interaction: Interaction) -> None:
except KeyError:
self.dispatch("unknown_command", interaction)
else:
await command.invoke(interaction)
context = await self.get_application_context(interaction)
await command.invoke(context)

def slash_command(self, **kwargs) -> SlashCommand:
"""A shortcut decorator that invokes :func:`.ApplicationCommandMixin.command` and adds it to
Expand Down Expand Up @@ -334,12 +346,38 @@ def command(self, **kwargs):
"""
return self.application_command(**kwargs)

def command_group(self, name: str, description: str, guild_ids=None) -> SubCommandGroup:
def command_group(
self, name: str, description: str, guild_ids=None
) -> SubCommandGroup:
# TODO: Write documentation for this. I'm not familiar enough with what this function does to do it myself.
group = SubCommandGroup(name, description, guild_ids)
self.add_application_command(group)
return group

async def get_application_context(
self, interaction: Interaction, cls=None
) -> InteractionContext:
r"""|coro|
Returns the invocation context from the interaction.
This is a more low-level counter-part for :meth:`.handle_interaction`
to allow users more fine grained control over the processing.
Parameters
-----------
interaction: :class:`discord.Interaction`
The interaction to get the invocation context from.
Returns
--------
:class:`.InteractionContext`
The invocation context.
"""
if cls == None:
cls = InteractionContext
return cls(self, interaction)


class BotBase(ApplicationCommandMixin): # To Insert: CogMixin
# TODO I think
Expand Down

0 comments on commit 4140060

Please sign in to comment.