Skip to content

Commit 4957542

Browse files
Change context and http to reflect upcoming changes
1 parent 0c7df22 commit 4957542

File tree

2 files changed

+87
-75
lines changed

2 files changed

+87
-75
lines changed

discord_slash/context.py

Lines changed: 46 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ class SlashContext:
2020
:ivar name: Name of the command.
2121
:ivar subcommand_name: Subcommand of the command.
2222
:ivar subcommand_group: Subcommand group of the command.
23-
:ivar interaction_id: Interaction ID of the command message.
23+
:ivar _interaction_id: Interaction ID of the command message.
2424
:ivar command_id: ID of the command.
2525
:ivar _http: :class:`.http.SlashCommandRequest` of the client.
2626
:ivar bot: discord.py client.
27-
:ivar logger: Logger instance.
28-
:ivar sent: Whether you sent the initial response.
27+
:ivar _logger: Logger instance.
28+
:ivar _deffered: Whether the command is current deffered (loading state)
29+
:ivar _deffered_hidden: Internal var to check that state stays the same
30+
:ivar _sent: Whether you sent the initial response.
2931
:ivar guild_id: Guild ID of the command message. If the command was invoked in DM, then it is ``None``
3032
:ivar author_id: User ID representing author of the command message.
3133
:ivar channel_id: Channel ID representing channel of the command message.
@@ -42,12 +44,14 @@ def __init__(self,
4244
self.name = self.command = self.invoked_with = _json["data"]["name"]
4345
self.subcommand_name = self.invoked_subcommand = self.subcommand_passed = None
4446
self.subcommand_group = self.invoked_subcommand_group = self.subcommand_group_passed = None
45-
self.interaction_id = _json["id"]
47+
self._interaction_id = _json["id"]
4648
self.command_id = _json["data"]["id"]
4749
self._http = _http
4850
self.bot = _discord
49-
self.logger = logger
50-
self.sent = False
51+
self._logger = logger
52+
self._deffered = False
53+
self._sent = False
54+
self._deffered_hidden = False # To check if the patch to the deffered response matches
5155
self.guild_id = int(_json["guild_id"]) if "guild_id" in _json.keys() else None
5256
self.author_id = int(_json["member"]["user"]["id"] if "member" in _json.keys() else _json["user"]["id"])
5357
self.channel_id = int(_json["channel_id"])
@@ -76,39 +80,18 @@ def channel(self) -> typing.Optional[typing.Union[discord.abc.GuildChannel, disc
7680
"""
7781
return self.bot.get_channel(self.channel_id)
7882

79-
async def respond(self, eat: bool = False):
83+
async def defer(self, hidden: bool = False):
8084
"""
81-
Sends command invoke response.\n
82-
You should call this first.
85+
'Deferes' the response, showing a loading state to the user
8386
84-
.. note::
85-
- If `eat` is ``False``, there is a chance that ``message`` variable is present.
86-
- While it is recommended to be manually called, this will still be automatically called
87-
if this isn't called but :meth:`.send()` is called.
88-
89-
:param eat: Whether to eat user's input. Default ``False``.
87+
:param hidden: Whether the deffered response should be ephemeral . Default ``False``.
9088
"""
91-
base = {"type": 2 if eat else 5}
92-
_task = self.bot.loop.create_task(self._http.post(base, self.interaction_id, self.__token, True))
93-
self.sent = True
94-
if not eat and (not self.guild_id or (self.channel and self.channel.permissions_for(self.guild.me).view_channel)):
95-
with suppress(asyncio.TimeoutError):
96-
def check(message: discord.Message):
97-
user_id = self.author_id
98-
is_author = message.author.id == user_id
99-
channel_id = self.channel_id
100-
is_channel = channel_id == message.channel.id
101-
is_user_input = message.type == 20
102-
is_correct_command = message.content.startswith(f"</{self.name}:{self.command_id}>")
103-
return is_author and is_channel and is_user_input and is_correct_command
104-
105-
self.message = await self.bot.wait_for("message", timeout=3, check=check)
106-
await _task
107-
108-
@property
109-
def ack(self):
110-
"""Alias of :meth:`.respond`."""
111-
return self.respond
89+
base = {"type": 5}
90+
if hidden:
91+
base["data"] = {"flags": 64}
92+
self._deffered_hidden = True
93+
await self._http.post_initial_response(base, self._interaction_id, self.__token)
94+
self._deffered = True
11295

11396
async def send(self,
11497
content: str = "", *,
@@ -129,6 +112,7 @@ async def send(self,
129112
.. warning::
130113
- Since Release 1.0.9, this is completely changed. If you are migrating from older version, please make sure to fix the usage.
131114
- You can't use both ``embed`` and ``embeds`` at the same time, also applies to ``file`` and ``files``.
115+
- You cannot send files in the initial response
132116
133117
:param content: Content of the response.
134118
:type content: str
@@ -152,13 +136,6 @@ async def send(self,
152136
"""
153137
if isinstance(content, int) and 2 <= content <= 5:
154138
raise error.IncorrectFormat("`.send` Method is rewritten at Release 1.0.9. Please read the docs and fix all the usages.")
155-
if not self.sent:
156-
self.logger.info(f"At command `{self.name}`: It is recommended to call `.respond()` first!")
157-
await self.respond(eat=hidden)
158-
if hidden:
159-
if embeds or embed or files or file:
160-
self.logger.warning("Embed/File is not supported for `hidden`!")
161-
return await self.send_hidden(content)
162139
if embed and embeds:
163140
raise error.IncorrectFormat("You can't use both `embed` and `embeds`!")
164141
if embed:
@@ -180,27 +157,38 @@ async def send(self,
180157
"allowed_mentions": allowed_mentions.to_dict() if allowed_mentions
181158
else self.bot.allowed_mentions.to_dict() if self.bot.allowed_mentions else {}
182159
}
160+
if hidden:
161+
if embeds or files:
162+
self._logger.warning("Embed/File is not supported for `hidden`!")
163+
base["flags"] = 64
164+
165+
initial_message = False
166+
if not self._sent:
167+
initial_message = True
168+
if files:
169+
raise error.IncorrectFormat("You cannot send files in the initial response!")
170+
if self._deffered:
171+
if self._deffered_hidden != hidden:
172+
self._logger.warning(
173+
"Deffered response might not be what you set it to! (hidden / visible) "
174+
"This is because it was deffered in a different state"
175+
)
176+
resp = await self._http.edit(base, self.__token)
177+
self._deffered = False
178+
else:
179+
base["type"] = 4
180+
resp = await self._http.post_initial_response(base, self._interaction_id, self.__token)
181+
self._sent = True
182+
else:
183+
resp = await self._http.post_followup(base, self.__token, files=files)
183184

184-
resp = await self._http.post(base, self.interaction_id, self.__token, files=files)
185185
smsg = model.SlashMessage(state=self.bot._connection,
186186
data=resp,
187187
channel=self.channel or discord.Object(id=self.channel_id),
188188
_http=self._http,
189189
interaction_token=self.__token)
190190
if delete_after:
191191
self.bot.loop.create_task(smsg.delete(delay=delete_after))
192+
if initial_message:
193+
self.message = smsg
192194
return smsg
193-
194-
def send_hidden(self, content: str = ""):
195-
"""
196-
Sends hidden response.\n
197-
This is automatically used if you pass ``hidden=True`` at :meth:`.send`.
198-
199-
:param content: Message content.
200-
:return: Coroutine
201-
"""
202-
base = {
203-
"content": content,
204-
"flags": 64
205-
}
206-
return self._http.post(base, self.interaction_id, self.__token)

discord_slash/http.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import aiohttp
44
import discord
55
from discord.http import Route
6+
from . import error
67

78

89
class CustomRoute(Route):
@@ -78,49 +79,73 @@ def command_request(self, method, guild_id, url_ending="", **kwargs):
7879
route = CustomRoute(method, url)
7980
return self._discord.http.request(route, **kwargs)
8081

81-
def post(self, _resp, interaction_id, token, initial=False, files: typing.List[discord.File] = None):
82+
def post_followup(self, _resp, token, files: typing.List[discord.File] = None):
8283
"""
83-
Sends command response POST request to Discord API.
84+
Sends command followup response POST request to Discord API.
8485
8586
:param _resp: Command response.
8687
:type _resp: dict
87-
:param interaction_id: Interaction ID.
8888
:param token: Command message token.
89-
:param initial: Whether this request is initial. Default ``False``
9089
:param files: Files to send. Default ``None``
9190
:type files: List[discord.File]
9291
:return: Coroutine
9392
"""
9493
if files:
9594
return self.post_with_files(_resp, files, token)
96-
req_url = f"/interactions/{interaction_id}/{token}/callback" if initial else f"/webhooks/{self._discord.user.id}/{token}"
97-
route = CustomRoute("POST", req_url)
98-
return self._discord.http.request(route, json=_resp)
95+
return self.command_response(token, True, "POST", json=_resp)
96+
97+
def post_initial_response(self, _resp, interaction_id, token):
98+
"""
99+
Sends an initial "POST" response to the Discord API.
100+
101+
:param _resp: Command response.
102+
:type _resp: dict
103+
:param interaction_id: Interaction ID.
104+
:param token: Command message token.
105+
:return: Coroutine
106+
"""
107+
return self.command_response(token, False, "POST", interaction_id, json=_resp)
108+
109+
def command_response(self, token, use_webhook, method, interaction_id= None, url_ending = "", **kwargs):
110+
"""
111+
Sends a command response to discord (POST, PATCH, DELETE)
112+
113+
:param token: Interaction token
114+
:param use_webhook: Whether to use webhooks
115+
:param method: The HTTP request to use
116+
:param interaction_id: The id of the interaction
117+
:param url_ending: String to append onto the end of the url.
118+
:param \**kwargs: Kwargs to pass into discord.py's `request function <https://github.com/Rapptz/discord.py/blob/master/discord/http.py#L134>`_
119+
:return: Coroutine
120+
"""
121+
if not use_webhook and not interaction_id:
122+
raise error.IncorrectFormat("Internal Error! interaction_id must be set if use_webhook is False")
123+
req_url = f"/webhooks/{self._discord.user.id}/{token}" if use_webhook else f"/interactions/{interaction_id}/{token}/callback"
124+
req_url += url_ending
125+
route = CustomRoute(method, req_url)
126+
return self._discord.http.request(route, **kwargs)
99127

100128
def post_with_files(self, _resp, files: typing.List[discord.File], token):
101-
req_url = f"/webhooks/{self._discord.user.id}/{token}"
102-
route = CustomRoute("POST", req_url)
103129
form = aiohttp.FormData()
104130
form.add_field("payload_json", json.dumps(_resp))
105131
for x in range(len(files)):
106132
name = f"file{x if len(files) > 1 else ''}"
107133
sel = files[x]
108134
form.add_field(name, sel.fp, filename=sel.filename, content_type="application/octet-stream")
109-
return self._discord.http.request(route, data=form, files=files)
135+
return self.command_response(token, True, "POST", data=form, files=files)
110136

111137
def edit(self, _resp, token, message_id="@original"):
112138
"""
113-
Sends edit command response POST request to Discord API.
139+
Sends edit command response PATCH request to Discord API.
114140
115141
:param _resp: Edited response.
116142
:type _resp: dict
117143
:param token: Command message token.
118144
:param message_id: Message ID to edit. Default initial message.
119145
:return: Coroutine
120146
"""
121-
req_url = f"/webhooks/{self._discord.user.id}/{token}/messages/{message_id}"
122-
route = CustomRoute("PATCH", req_url)
123-
return self._discord.http.request(route, json=_resp)
147+
req_url = f"/messages/{message_id}"
148+
return self.command_response(token, True, "PATCH", url_ending = req_url, json=_resp)
124149

125150
def delete(self, token, message_id="@original"):
126151
"""
@@ -130,6 +155,5 @@ def delete(self, token, message_id="@original"):
130155
:param message_id: Message ID to delete. Default initial message.
131156
:return: Coroutine
132157
"""
133-
req_url = f"/webhooks/{self._discord.user.id}/{token}/messages/{message_id}"
134-
route = CustomRoute("DELETE", req_url)
135-
return self._discord.http.request(route)
158+
req_url = f"/messages/{message_id}"
159+
return self.command_response(token, True, "DELETE", url_ending = req_url)

0 commit comments

Comments
 (0)