31
31
from base64 import b64encode
32
32
from bisect import bisect_left
33
33
import datetime
34
+ import typing
35
+ from .enums import TimestampStyle
34
36
import functools
35
37
from inspect import isawaitable as _isawaitable , signature as _signature
36
38
from operator import attrgetter
43
45
DISCORD_EPOCH = 1420070400000
44
46
MAX_ASYNCIO_SECONDS = 3456000
45
47
48
+
46
49
class cached_property :
47
50
def __init__ (self , function ):
48
51
self .function = function
@@ -57,6 +60,7 @@ def __get__(self, instance, owner):
57
60
58
61
return value
59
62
63
+
60
64
class CachedSlotProperty :
61
65
def __init__ (self , name , function ):
62
66
self .name = name
@@ -74,11 +78,14 @@ def __get__(self, instance, owner):
74
78
setattr (instance , self .name , value )
75
79
return value
76
80
81
+
77
82
def cached_slot_property (name ):
78
83
def decorator (func ):
79
84
return CachedSlotProperty (name , func )
85
+
80
86
return decorator
81
87
88
+
82
89
class SequenceProxy (collections .abc .Sequence ):
83
90
"""Read-only proxy of a Sequence."""
84
91
def __init__ (self , proxied ):
@@ -105,34 +112,39 @@ def index(self, value, *args, **kwargs):
105
112
def count (self , value ):
106
113
return self .__proxied .count (value )
107
114
115
+
108
116
def parse_time (timestamp ):
109
117
if timestamp :
118
+
110
119
return datetime .datetime (* map (int , re .split (r'[^\d]' , timestamp .replace ('+00:00' , '' ))))
111
120
return None
112
121
122
+
113
123
def copy_doc (original ):
114
124
def decorator (overriden ):
115
125
overriden .__doc__ = original .__doc__
116
126
overriden .__signature__ = _signature (original )
117
127
return overriden
118
128
return decorator
119
129
130
+
120
131
def deprecated (instead = None ):
121
132
def actual_decorator (func ):
122
133
@functools .wraps (func )
123
134
def decorated (* args , ** kwargs ):
124
- warnings .simplefilter ('always' , DeprecationWarning ) # turn off filter
135
+ warnings .simplefilter ('always' , DeprecationWarning ) # turn off filter
125
136
if instead :
126
137
fmt = "{0.__name__} is deprecated, use {1} instead."
127
138
else :
128
139
fmt = '{0.__name__} is deprecated.'
129
140
130
141
warnings .warn (fmt .format (func , instead ), stacklevel = 3 , category = DeprecationWarning )
131
- warnings .simplefilter ('default' , DeprecationWarning ) # reset filter
142
+ warnings .simplefilter ('default' , DeprecationWarning ) # reset filter
132
143
return func (* args , ** kwargs )
133
144
return decorated
134
145
return actual_decorator
135
146
147
+
136
148
def oauth_url (client_id , permissions = None , guild = None , redirect_uri = None , scopes = None ):
137
149
"""A helper function that returns the OAuth2 URL for inviting the bot
138
150
into guilds.
@@ -183,6 +195,7 @@ def snowflake_time(id):
183
195
The creation date in UTC of a Discord snowflake ID."""
184
196
return datetime .datetime .utcfromtimestamp (((id >> 22 ) + DISCORD_EPOCH ) / 1000 )
185
197
198
+
186
199
def time_snowflake (datetime_obj , high = False ):
187
200
"""Returns a numeric snowflake pretending to be created at the given date.
188
201
@@ -201,6 +214,7 @@ def time_snowflake(datetime_obj, high=False):
201
214
202
215
return (discord_millis << 22 ) + (2 ** 22 - 1 if high else 0 )
203
216
217
+
204
218
def find (predicate , seq ):
205
219
"""A helper to return the first element found in the sequence
206
220
that meets the predicate. For example: ::
@@ -226,6 +240,7 @@ def find(predicate, seq):
226
240
return element
227
241
return None
228
242
243
+
229
244
def get (iterable , ** attrs ):
230
245
r"""A helper that returns the first element in the iterable that meets
231
246
all the traits passed in ``attrs``. This is an alternative for
@@ -291,13 +306,24 @@ def get(iterable, **attrs):
291
306
for elem in iterable :
292
307
if _all (pred (elem ) == value for pred , value in converted ):
293
308
return elem
309
+
294
310
return None
295
311
312
+
313
+ def styled_timestamp (timestamp : typing .Union [datetime .datetime , int ], style : typing .Union [TimestampStyle , str ] = TimestampStyle .short ):
314
+ unix_timestamp = int (timestamp .timestamp ()) if isinstance (timestamp , datetime .datetime ) else timestamp
315
+ style = TimestampStyle .from_value (style ) if isinstance (style , str ) else style
316
+ if not isinstance (style , TimestampStyle ):
317
+ raise AttributeError ('style has to be a discord.TimestampStyle' )
318
+ return f'<t:{ unix_timestamp } :{ str (style )} >'
319
+
320
+
296
321
def _unique (iterable ):
297
322
seen = set ()
298
323
adder = seen .add
299
324
return [x for x in iterable if not (x in seen or adder (x ))]
300
325
326
+
301
327
def _get_as_snowflake (data , key ):
302
328
try :
303
329
value = data [key ]
@@ -306,6 +332,7 @@ def _get_as_snowflake(data, key):
306
332
else :
307
333
return value and int (value )
308
334
335
+
309
336
def _get_mime_type_for_image (data ):
310
337
if data .startswith (b'\x89 \x50 \x4E \x47 \x0D \x0A \x1A \x0A ' ):
311
338
return 'image/png'
@@ -318,15 +345,18 @@ def _get_mime_type_for_image(data):
318
345
else :
319
346
raise InvalidArgument ('Unsupported image type given' )
320
347
348
+
321
349
def _bytes_to_base64_data (data ):
322
350
fmt = 'data:{mime};base64,{data}'
323
351
mime = _get_mime_type_for_image (data )
324
352
b64 = b64encode (data ).decode ('ascii' )
325
353
return fmt .format (mime = mime , data = b64 )
326
354
355
+
327
356
def to_json (obj ):
328
357
return json .dumps (obj , separators = (',' , ':' ), ensure_ascii = True )
329
358
359
+
330
360
def _parse_ratelimit_header (request , * , use_clock = False ):
331
361
reset_after = request .headers .get ('X-Ratelimit-Reset-After' )
332
362
if use_clock or not reset_after :
@@ -337,13 +367,15 @@ def _parse_ratelimit_header(request, *, use_clock=False):
337
367
else :
338
368
return float (reset_after )
339
369
370
+
340
371
async def maybe_coroutine (f , * args , ** kwargs ):
341
372
value = f (* args , ** kwargs )
342
373
if _isawaitable (value ):
343
374
return await value
344
375
else :
345
376
return value
346
377
378
+
347
379
async def async_all (gen , * , check = _isawaitable ):
348
380
for elem in gen :
349
381
if check (elem ):
@@ -389,10 +421,12 @@ async def sleep_until(when, result=None):
389
421
delta -= MAX_ASYNCIO_SECONDS
390
422
return await asyncio .sleep (max (delta , 0 ), result )
391
423
424
+
392
425
def valid_icon_size (size ):
393
426
"""Icons must be power of 2 within [16, 4096]."""
394
427
return not size & (size - 1 ) and size in range (16 , 4097 )
395
428
429
+
396
430
class SnowflakeList (array .array ):
397
431
"""Internal data storage class to efficiently store a list of snowflakes.
398
432
@@ -422,8 +456,10 @@ def has(self, element):
422
456
i = bisect_left (self , element )
423
457
return i != len (self ) and self [i ] == element
424
458
459
+
425
460
_IS_ASCII = re .compile (r'^[\x00-\x7f]+$' )
426
461
462
+
427
463
def _string_width (string , * , _IS_ASCII = _IS_ASCII ):
428
464
"""Returns string's width."""
429
465
match = _IS_ASCII .match (string )
@@ -434,6 +470,7 @@ def _string_width(string, *, _IS_ASCII=_IS_ASCII):
434
470
func = unicodedata .east_asian_width
435
471
return sum (2 if func (char ) in UNICODE_WIDE_CHAR_TYPE else 1 for char in string )
436
472
473
+
437
474
def resolve_invite (invite ):
438
475
"""
439
476
Resolves an invite from a :class:`~discord.Invite`, URL or code.
@@ -458,6 +495,7 @@ def resolve_invite(invite):
458
495
return m .group (1 )
459
496
return invite
460
497
498
+
461
499
def resolve_template (code ):
462
500
"""
463
501
Resolves a template code from a :class:`~discord.Template`, URL or code.
@@ -474,7 +512,7 @@ def resolve_template(code):
474
512
:class:`str`
475
513
The template code.
476
514
"""
477
- from .template import Template # circular import
515
+ from .template import Template # circular import
478
516
if isinstance (code , Template ):
479
517
return code .code
480
518
else :
@@ -484,6 +522,7 @@ def resolve_template(code):
484
522
return m .group (1 )
485
523
return code
486
524
525
+
487
526
_MARKDOWN_ESCAPE_SUBREGEX = '|' .join (r'\{0}(?=([\s\S]*((?<!\{0})\{0})))' .format (c )
488
527
for c in ('*' , '`' , '_' , '~' , '|' ))
489
528
@@ -495,6 +534,7 @@ def resolve_template(code):
495
534
496
535
_MARKDOWN_STOCK_REGEX = r'(?P<markdown>[_\\~|\*`]|%s)' % _MARKDOWN_ESCAPE_COMMON
497
536
537
+
498
538
def remove_markdown (text , * , ignore_links = True ):
499
539
"""A helper function that removes markdown characters.
500
540
@@ -528,6 +568,7 @@ def replacement(match):
528
568
regex = '(?:%s|%s)' % (_URL_REGEX , regex )
529
569
return re .sub (regex , replacement , text , 0 , re .MULTILINE )
530
570
571
+
531
572
def escape_markdown (text , * , as_needed = False , ignore_links = True ):
532
573
r"""A helper function that escapes Discord's markdown.
533
574
@@ -569,6 +610,7 @@ def replacement(match):
569
610
text = re .sub (r'\\' , r'\\\\' , text )
570
611
return _MARKDOWN_ESCAPE_REGEX .sub (r'\\\1' , text )
571
612
613
+
572
614
def escape_mentions (text ):
573
615
"""A helper function that escapes everyone, here, role, and user mentions.
574
616
0 commit comments