11import typing
22import asyncio
3+ from warnings import warn
4+
35import discord
46from contextlib import suppress
57from discord .ext import commands
@@ -18,14 +20,18 @@ class SlashContext:
1820
1921 :ivar message: Message that invoked the slash command.
2022 :ivar name: Name of the command.
23+ :ivar args: List of processed arguments invoked with the command.
24+ :ivar kwargs: Dictionary of processed arguments invoked with the command.
2125 :ivar subcommand_name: Subcommand of the command.
2226 :ivar subcommand_group: Subcommand group of the command.
2327 :ivar interaction_id: Interaction ID of the command message.
2428 :ivar command_id: ID of the command.
25- :ivar _http: :class:`.http.SlashCommandRequest` of the client.
2629 :ivar bot: discord.py client.
27- :ivar logger: Logger instance.
28- :ivar sent: Whether you sent the initial response.
30+ :ivar _http: :class:`.http.SlashCommandRequest` of the client.
31+ :ivar _logger: Logger instance.
32+ :ivar deferred: Whether the command is current deferred (loading state)
33+ :ivar _deferred_hidden: Internal var to check that state stays the same
34+ :ivar responded: Whether you have responded with a message to the interaction.
2935 :ivar guild_id: Guild ID of the command message. If the command was invoked in DM, then it is ``None``
3036 :ivar author_id: User ID representing author of the command message.
3137 :ivar channel_id: Channel ID representing channel of the command message.
@@ -40,14 +46,18 @@ def __init__(self,
4046 self .__token = _json ["token" ]
4147 self .message = None # Should be set later.
4248 self .name = self .command = self .invoked_with = _json ["data" ]["name" ]
49+ self .args = []
50+ self .kwargs = {}
4351 self .subcommand_name = self .invoked_subcommand = self .subcommand_passed = None
4452 self .subcommand_group = self .invoked_subcommand_group = self .subcommand_group_passed = None
4553 self .interaction_id = _json ["id" ]
4654 self .command_id = _json ["data" ]["id" ]
4755 self ._http = _http
4856 self .bot = _discord
49- self .logger = logger
50- self .sent = False
57+ self ._logger = logger
58+ self .deferred = False
59+ self .responded = False
60+ self ._deferred_hidden = False # To check if the patch to the deferred response matches
5161 self .guild_id = int (_json ["guild_id" ]) if "guild_id" in _json .keys () else None
5262 self .author_id = int (_json ["member" ]["user" ]["id" ] if "member" in _json .keys () else _json ["user" ]["id" ])
5363 self .channel_id = int (_json ["channel_id" ])
@@ -58,6 +68,26 @@ def __init__(self,
5868 else :
5969 self .author = discord .User (data = _json ["user" ], state = self .bot ._connection )
6070
71+ @property
72+ def _deffered_hidden (self ):
73+ warn ("`_deffered_hidden` as been renamed to `_deferred_hidden`." , DeprecationWarning , stacklevel = 2 )
74+ return self ._deferred_hidden
75+
76+ @_deffered_hidden .setter
77+ def _deffered_hidden (self , value ):
78+ warn ("`_deffered_hidden` as been renamed to `_deferred_hidden`." , DeprecationWarning , stacklevel = 2 )
79+ self ._deferred_hidden = value
80+
81+ @property
82+ def deffered (self ):
83+ warn ("`deffered` as been renamed to `deferred`." , DeprecationWarning , stacklevel = 2 )
84+ return self .deferred
85+
86+ @deffered .setter
87+ def deffered (self , value ):
88+ warn ("`deffered` as been renamed to `deferred`." , DeprecationWarning , stacklevel = 2 )
89+ self .deferred = value
90+
6191 @property
6292 def guild (self ) -> typing .Optional [discord .Guild ]:
6393 """
@@ -76,39 +106,20 @@ def channel(self) -> typing.Optional[typing.Union[discord.TextChannel, discord.D
76106 """
77107 return self .bot .get_channel (self .channel_id )
78108
79- async def respond (self , eat : bool = False ):
109+ async def defer (self , hidden : bool = False ):
80110 """
81- Sends command invoke response.\n
82- You should call this first.
83-
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.
111+ 'Defers' the response, showing a loading state to the user
88112
89- :param eat : Whether to eat user's input . Default ``False``.
113+ :param hidden : Whether the deferred response should be ephemeral . Default ``False``.
90114 """
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
115+ if self .deferred or self .responded :
116+ raise error .AlreadyResponded ("You have already responded to this command!" )
117+ base = {"type" : 5 }
118+ if hidden :
119+ base ["data" ] = {"flags" : 64 }
120+ self ._deferred_hidden = True
121+ await self ._http .post_initial_response (base , self .interaction_id , self .__token )
122+ self .deferred = True
112123
113124 async def send (self ,
114125 content : str = "" , * ,
@@ -129,6 +140,7 @@ async def send(self,
129140 .. warning::
130141 - Since Release 1.0.9, this is completely changed. If you are migrating from older version, please make sure to fix the usage.
131142 - You can't use both ``embed`` and ``embeds`` at the same time, also applies to ``file`` and ``files``.
143+ - You cannot send files in the initial response
132144
133145 :param content: Content of the response.
134146 :type content: str
@@ -150,15 +162,6 @@ async def send(self,
150162 :type delete_after: float
151163 :return: Union[discord.Message, dict]
152164 """
153- if isinstance (content , int ) and 2 <= content <= 5 :
154- 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 )
162165 if embed and embeds :
163166 raise error .IncorrectFormat ("You can't use both `embed` and `embeds`!" )
164167 if embed :
@@ -172,6 +175,8 @@ async def send(self,
172175 raise error .IncorrectFormat ("You can't use both `file` and `files`!" )
173176 if file :
174177 files = [file ]
178+ if delete_after and hidden :
179+ raise error .IncorrectFormat ("You can't delete a hidden message!" )
175180
176181 base = {
177182 "content" : content ,
@@ -180,30 +185,47 @@ async def send(self,
180185 "allowed_mentions" : allowed_mentions .to_dict () if allowed_mentions
181186 else self .bot .allowed_mentions .to_dict () if self .bot .allowed_mentions else {}
182187 }
183-
184- resp = await self ._http .post (base , self .interaction_id , self .__token , files = files )
185- smsg = model .SlashMessage (state = self .bot ._connection ,
186- data = resp ,
187- channel = self .channel or discord .Object (id = self .channel_id ),
188- _http = self ._http ,
189- interaction_token = self .__token )
190- if delete_after :
191- self .bot .loop .create_task (smsg .delete (delay = delete_after ))
192- 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- .. note::
200- This is not intended to be manually called. Please use :meth:`.send` instead.
201-
202- :param content: Message content.
203- :return: Coroutine
204- """
205- base = {
206- "content" : content ,
207- "flags" : 64
208- }
209- return self ._http .post (base , self .interaction_id , self .__token )
188+ if hidden :
189+ if embeds or files :
190+ self ._logger .warning ("Embed/File is not supported for `hidden`!" )
191+ base ["flags" ] = 64
192+
193+ initial_message = False
194+ if not self .responded :
195+ initial_message = True
196+ if files :
197+ raise error .IncorrectFormat ("You cannot send files in the initial response!" )
198+ if self .deferred :
199+ if self ._deferred_hidden != hidden :
200+ self ._logger .warning (
201+ "deferred response might not be what you set it to! (hidden / visible) "
202+ "This is because it was deferred in a different state"
203+ )
204+ resp = await self ._http .edit (base , self .__token )
205+ self .deferred = False
206+ else :
207+ json_data = {
208+ "type" : 4 ,
209+ "data" : base
210+ }
211+ await self ._http .post_initial_response (json_data , self .interaction_id , self .__token )
212+ if not hidden :
213+ resp = await self ._http .edit ({}, self .__token )
214+ else :
215+ resp = {}
216+ self .responded = True
217+ else :
218+ resp = await self ._http .post_followup (base , self .__token , files = files )
219+ if not hidden :
220+ smsg = model .SlashMessage (state = self .bot ._connection ,
221+ data = resp ,
222+ channel = self .channel or discord .Object (id = self .channel_id ),
223+ _http = self ._http ,
224+ interaction_token = self .__token )
225+ if delete_after :
226+ self .bot .loop .create_task (smsg .delete (delay = delete_after ))
227+ if initial_message :
228+ self .message = smsg
229+ return smsg
230+ else :
231+ return resp
0 commit comments