23
23
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24
24
DEALINGS IN THE SOFTWARE.
25
25
"""
26
+ from __future__ import annotations
27
+
28
+ from typing import (
29
+ Union ,
30
+ Optional ,
31
+ List ,
32
+ Dict ,
33
+ Any ,
34
+ TYPE_CHECKING ,
35
+ Coroutine ,
36
+ Awaitable
37
+ )
26
38
39
+ from typing_extensions import Literal
27
40
28
41
import re
29
42
import copy
33
46
from types import FunctionType
34
47
35
48
from .utils import async_all , find , get , snowflake_time
36
- from typing_extensions import Literal
37
49
from .abc import GuildChannel
38
- from typing import Union , Optional , List , Dict , Any , TYPE_CHECKING , Coroutine , Awaitable
39
50
from .enums import ApplicationCommandType , InteractionType , ChannelType , OptionType , Locale , try_enum
40
51
from .permissions import Permissions
41
52
42
53
if TYPE_CHECKING :
43
- from .ext .commands import Cog , Converter
44
54
from datetime import datetime
45
55
from .guild import Guild
56
+ from .ext .commands import Cog , Greedy , Converter
46
57
from .interactions import BaseInteraction
47
58
48
59
__all__ = (
64
75
api_docs = 'https://discord.com/developers/docs'
65
76
66
77
67
- # TODO: Add a (optional) feature for auto generated name_localizations & description_localizations by a translator
78
+ # TODO: Add a (optional) feature for auto generated localizations by a translator
68
79
69
80
class Localizations :
70
81
"""
@@ -160,7 +171,7 @@ def __getitem__(self, item) -> Optional[str]:
160
171
raise KeyError (f'There is no locale value set for { locale .name } .' )
161
172
162
173
163
- def __setitem__ (self , key , value ):
174
+ def __setitem__ (self , key , value ) -> None :
164
175
self .__languages_dict__ [Locale [key ].value ] = value
165
176
166
177
def __bool__ (self ) -> bool :
@@ -174,10 +185,11 @@ def from_dict(cls, data: Dict[str, str]) -> 'Localizations':
174
185
data = data or {}
175
186
return cls (** {try_enum (Locale , key ): value for key , value in data .items ()})
176
187
177
- def update (self , __m : 'Localizations' ):
188
+ def update (self , __m : 'Localizations' ) -> None :
189
+ """Similar to :meth:`dict.update`"""
178
190
self .__languages_dict__ .update (__m .__languages_dict__ )
179
191
180
- def from_target (self , target : Union [' Guild' , ' BaseInteraction' ], * , default : Any = None ):
192
+ def from_target (self , target : Union [Guild , BaseInteraction ], * , default : Any = None ):
181
193
"""
182
194
Returns the value for the local of the object (if it's set), or :attr:`default`(:class:`None`)
183
195
@@ -190,17 +202,16 @@ def from_target(self, target: Union['Guild', 'BaseInteraction'], *, default: Any
190
202
The value or an object to return by default if there is no value for the locale of :attr:`target` set.
191
203
Default to :class:`None` or :class:`~discord.Locale.english_US`/:class:`~discord.Locale.english_GB`
192
204
193
- Return
194
- ------
205
+ Returns
206
+ -------
195
207
Union[:class:`str`, None]
196
- The value of the locale or :class :`None` if there is no value for the locale set.
208
+ The value of the locale or :obj :`None` if there is no value for the locale set.
197
209
198
210
Raises
199
211
------
200
212
:exc:`TypeError`
201
213
If :attr:`target` is of the wrong type.
202
214
"""
203
- return_default = False
204
215
if hasattr (target , 'preferred_locale' ):
205
216
try :
206
217
return self [target .preferred_locale .value ]
@@ -255,7 +266,7 @@ def __init__(self, type: int, *args, **kwargs):
255
266
self .name_localizations : Localizations = kwargs .get ('name_localizations' , Localizations ())
256
267
self .description_localizations : Localizations = kwargs .get ('description_localizations' , Localizations ())
257
268
258
- def __getitem__ (self , item ):
269
+ def __getitem__ (self , item ) -> Any :
259
270
return getattr (self , item )
260
271
261
272
@property
@@ -268,22 +279,23 @@ def _state(self, value):
268
279
269
280
@property
270
281
def cog (self ) -> Optional ['Cog' ]:
282
+ """Optional[:class:`~discord.ext.commands.Cog`]: The cog associated with this command if any."""
271
283
return getattr (self , '_cog' , None )
272
284
273
285
@cog .setter
274
- def cog (self , __cog : 'Cog' ):
286
+ def cog (self , __cog : 'Cog' ) -> None :
275
287
setattr (self , '_cog' , __cog )
276
288
277
- def _set_cog (self , cog : 'Cog' , recursive : bool = False ):
289
+ def _set_cog (self , cog : 'Cog' , recursive : bool = False ) -> None :
278
290
self .cog = cog
279
291
280
292
def __call__ (self , * args , ** kwargs ):
281
293
return super ().__init__ (self , * args , ** kwargs )
282
294
283
- def __repr__ (self ):
295
+ def __repr__ (self ) -> str :
284
296
return '<%s name=%s, id=%s, disabled=%s>' % (self .__class__ .__name__ , self .name , self .id , self .disabled )
285
297
286
- def __eq__ (self , other ):
298
+ def __eq__ (self , other ) -> bool :
287
299
if isinstance (other , self .__class__ ):
288
300
other = other .to_dict ()
289
301
if isinstance (other , dict ):
@@ -335,19 +347,19 @@ def check_options(_options: list, _other: list):
335
347
and check_options (options , other .get ('options' , [])))
336
348
return False
337
349
338
- def __ne__ (self , other ):
350
+ def __ne__ (self , other ) -> bool :
339
351
return not self .__eq__ (other )
340
352
341
- def _fill_data (self , data ):
353
+ def _fill_data (self , data ) -> ApplicationCommand :
342
354
self ._id = int (data .get ('id' , 0 ))
343
355
self .application_id = int (data .get ('application_id' , 0 ))
344
356
self ._guild_id = int (data .get ('guild_id' , 0 ))
345
357
self ._permissions = data .get ('permissions' , {})
346
358
return self
347
359
348
- async def can_run (self , * args , ** kwargs ):
360
+ async def can_run (self , * args , ** kwargs ) -> bool :
349
361
check_func = kwargs .pop ('__func' , self )
350
- checks = getattr (check_func , '__command_checks__ ' , getattr (self .func , '__command_checks__ ' , None ))
362
+ checks = getattr (check_func , '__commands_checks__ ' , getattr (self .func , '__commands_checks__ ' , None ))
351
363
if not checks :
352
364
return True
353
365
@@ -370,13 +382,14 @@ async def invoke(self, interaction, *args, **kwargs):
370
382
else :
371
383
self ._state .dispatch ('application_command_error' , self , interaction , exc )
372
384
373
- def error (self , coro ):
385
+ def error (self , coro ) -> Coroutine :
386
+ """A decorator to set an error handler for this command similar to :func:`on_application_command_error` but only for this command"""
374
387
if not asyncio .iscoroutinefunction (coro ):
375
388
raise TypeError ('The error handler must be a coroutine.' )
376
389
self .on_error = coro
377
390
return coro
378
391
379
- def to_dict (self ):
392
+ def to_dict (self ) -> dict :
380
393
base = {
381
394
'type' : int (self .type ),
382
395
'name' : str (self .name ),
@@ -394,22 +407,24 @@ def to_dict(self):
394
407
return base
395
408
396
409
@property
397
- def id (self ):
410
+ def id (self ) -> Optional [ int ] :
398
411
"""Optional[:class:`int`]: The id of the command, only set if the bot is running"""
399
412
return getattr (self , '_id' , None )
400
413
401
414
@property
402
- def created_at (self ) -> Optional [' datetime' ]:
415
+ def created_at (self ) -> Optional [datetime ]:
403
416
"""Optional[:class:`datetime.datetime`]: The creation time of the command in UTC, only set if the bot is running"""
404
417
if self .id :
405
418
return snowflake_time (self .id )
406
419
407
420
@property
408
421
def type (self ) -> ApplicationCommandType :
422
+ """:class:`ApplicationCommandType`: The type of the command"""
409
423
return try_enum (ApplicationCommandType , self ._type )
410
424
411
425
@property
412
- def guild_id (self ):
426
+ def guild_id (self ) -> Optional [int ]:
427
+ """Optional[:class:`int`]: Th id this command belongs to, if any"""
413
428
return self ._guild_id
414
429
415
430
@property
@@ -443,7 +458,7 @@ def _sorted_by_type(cls, commands):
443
458
sorted_dict [predicate ].append (cmd )
444
459
return sorted_dict
445
460
446
- async def delete (self ):
461
+ async def delete (self ) -> None :
447
462
"""|coro|
448
463
449
464
Deletes the application command
@@ -463,19 +478,23 @@ class SlashCommandOptionChoice:
463
478
464
479
Parameters
465
480
-----------
466
- name: :class:`str`
481
+ name: Union[ :class:`str`, :class:`int`, :class:`float`]
467
482
The 1-100 characters long name that will show in the client.
468
- value: Union[:class:`str`, :class:`int`, :class:`float`]
483
+ value: Union[:class:`str`, :class:`int`, :class:`float`, :obj:`None` ]
469
484
The value that will send as the options value.
470
- Must be of the type the option is of (:class:`str`, :class:`int` or :class:`float`).
485
+ Must be of the type the :class:`SlashCommandOption` is of (:class:`str`, :class:`int` or :class:`float`).
486
+
487
+ .. note::
488
+ If this is left empty it takes the :attr:`~SlashCommandOption.name` as value.
489
+
471
490
name_localizations: Optional[:class:`Localizations`]
472
491
Localized names for the choice.
473
492
"""
474
- def __init__ (self , name : str , value : Union [str , int , float ] = None , name_localizations : Optional [Localizations ] = Localizations ()):
493
+ def __init__ (self , name : Union [ str , int , float ] , value : Union [str , int , float ] = None , name_localizations : Optional [Localizations ] = Localizations ()):
475
494
476
- if 100 < len (name ) < 1 :
495
+ if 100 < len (str ( name ) ) < 1 :
477
496
raise ValueError ('The name of a choice must bee between 1 and 100 characters long, got %s.' % len (name ))
478
- self .name = name
497
+ self .name = str ( name )
479
498
self .value = value if value is not None else name
480
499
self .name_localizations = name_localizations
481
500
@@ -519,19 +538,24 @@ class SlashCommandOption:
519
538
required: Optional[:class:`bool`]
520
539
Weather this option must be provided by the user, default ``True``.
521
540
If ``False``, the parameter of the slash-command that takes this option needs a default value.
522
- choices: Optional[List[:class:`SlashCommandOptionChoice`]]
541
+ choices: Optional[List[Union[ :class:`SlashCommandOptionChoice`, :class:`str`, :class:`int`, :class:`float`] ]]
523
542
A list of up to 25 choices the user could select. Only valid if the :attr:`option_type` is one of
524
- :class:`OptionType.string`, :class:`OptionType.integer` or :class:`OptionType.number`.
525
- The :attr:`value`'s of the choices must be of the :attr:`~SlashCommandOption.option_type` of this option
543
+ :attr:`~OptionType.string`, :attr:`~OptionType.integer` or :attr:`~OptionType.number`.
544
+
545
+ .. note::
546
+ If you want to have values that are not the same as their name, you can use :class:`SlashCommandOptionChoice`
547
+
548
+ The :attr:`~SlashCommandOptionChoice.value`'s of the choices must be of the :attr:`~SlashCommandOption.option_type` of this option
526
549
(e.g. :class:`str`, :class:`int` or :class:`float`).
527
550
If choices are set they are the only options a user could pass.
528
551
autocomplete: Optional[:class:`bool`]
529
552
Whether to enable
530
553
`autocomplete <https://discord.com/developers/docs/interactions/application-commands#autocomplete>`_
531
554
interactions for this option, default ``False``.
532
555
With autocomplete, you can check the user's input and send matching choices to the client.
533
- **Autocomplete can only be used with options of the type** ``string``, ``integer`` or ``number``.
534
- **If autocomplete is activated, the option cannot have** :attr:`~SlashCommandOption.choices` **.**
556
+ .. note::
557
+ Autocomplete can only be used with options of the type :attr:`~OptionType.string`, :attr:`~OptionType.integer` or :attr:`~OptionType.number`.
558
+ **If autocomplete is activated, the option cannot have** :attr:`~SlashCommandOption.choices` **.**
535
559
min_value: Optional[Union[:class:`int`, :class:`float`]]
536
560
If the :attr:`~SlashCommandOption.option_type` is one of :attr:`~OptionType.integer` or :attr:`~OptionType.number`
537
561
this is the minimum value the users input must be of.
@@ -558,7 +582,7 @@ def __init__(self,
558
582
name_localizations : Optional [Localizations ] = Localizations (),
559
583
description_localizations : Optional [Localizations ] = Localizations (),
560
584
required : bool = True ,
561
- choices : Optional [List [SlashCommandOptionChoice ]] = [],
585
+ choices : Optional [List [Union [ SlashCommandOptionChoice , str , int , float ] ]] = [],
562
586
autocomplete : bool = False ,
563
587
min_value : Optional [Union [int , float ]] = None ,
564
588
max_value : Optional [Union [int , float ]] = None ,
@@ -585,27 +609,30 @@ def __init__(self,
585
609
f'{ api_docs } /interactions/application-commands#application-command-object-application-command-naming.'
586
610
f'Got "{ name } " with length { len (name )} .'
587
611
)
588
- self .name = name
612
+ self .name : str = name
589
613
self .name_localizations : Localizations = name_localizations
590
614
if 100 < len (description ) < 1 :
591
615
raise ValueError ('The description must be between 1 and 100 characters long, got %s.' % len (description ))
592
- self .description = description
616
+ self .description : str = description
593
617
self .description_localizations : Localizations = description_localizations
594
- self .required = required
618
+ self .required : bool = required
595
619
options = kwargs .get ('__options' , [])
596
620
if self .type == 2 and (not options ):
597
621
raise ValueError ('You need to pass __options if the option_type is subcommand_group.' )
598
622
self ._options = options
599
623
self .autocomplete : bool = autocomplete
600
624
self .min_value : Optional [Union [int , float ]] = min_value
601
625
self .max_value : Optional [Union [int , float ]] = max_value
602
- self .choices : Optional [List [SlashCommandOptionChoice ]] = choices
626
+ for index , choice in enumerate (choices ): # TODO: find a more efficient way to do this
627
+ if not isinstance (choice , SlashCommandOptionChoice ):
628
+ choices [index ] = SlashCommandOptionChoice (choice )
629
+ self .choices : List [SlashCommandOptionChoice ] = choices
603
630
self .channel_types : Optional [List [Union [GuildChannel , ChannelType , int ]]] = channel_types
604
- self .default = default
605
- self .converter = converter
606
- self .ignore_conversion_failures = ignore_conversion_failures
631
+ self .default : Any = default
632
+ self .converter : Union [ Greedy , Converter ] = converter
633
+ self .ignore_conversion_failures : bool = ignore_conversion_failures
607
634
608
- def __repr__ (self ):
635
+ def __repr__ (self ) -> str :
609
636
return '<SlashCommandOption type=%s, name=%s, description=%s, required=%s, choices=%s>' \
610
637
% (self .type ,
611
638
self .name ,
@@ -744,7 +771,7 @@ def to_dict(self) -> dict:
744
771
return base
745
772
746
773
@classmethod
747
- def from_dict (cls , data ):
774
+ def from_dict (cls , data ) -> SlashCommandOption :
748
775
option_type : OptionType = try_enum (OptionType , data ['type' ])
749
776
if option_type .sub_command_group :
750
777
return SubCommandGroup .from_dict (data )
@@ -815,7 +842,8 @@ def to_dict(self):
815
842
async def can_run (self , * args , ** kwargs ):
816
843
if self .cog is not None :
817
844
args = (self .cog , * args )
818
- checks = getattr (self , '__command_checks__' , [])
845
+ check_func = kwargs .pop ('__func' , self )
846
+ checks = getattr (check_func , '__commands_checks__' , getattr (self .func , '__commands_checks__' , None ))
819
847
if not checks :
820
848
return True
821
849
0 commit comments