3131from base64 import b64encode
3232from bisect import bisect_left
3333import datetime
34+ import typing
35+ from .enums import TimestampStyle
3436import functools
3537from inspect import isawaitable as _isawaitable , signature as _signature
3638from operator import attrgetter
4345DISCORD_EPOCH = 1420070400000
4446MAX_ASYNCIO_SECONDS = 3456000
4547
48+
4649class cached_property :
4750 def __init__ (self , function ):
4851 self .function = function
@@ -57,6 +60,7 @@ def __get__(self, instance, owner):
5760
5861 return value
5962
63+
6064class CachedSlotProperty :
6165 def __init__ (self , name , function ):
6266 self .name = name
@@ -74,11 +78,14 @@ def __get__(self, instance, owner):
7478 setattr (instance , self .name , value )
7579 return value
7680
81+
7782def cached_slot_property (name ):
7883 def decorator (func ):
7984 return CachedSlotProperty (name , func )
85+
8086 return decorator
8187
88+
8289class SequenceProxy (collections .abc .Sequence ):
8390 """Read-only proxy of a Sequence."""
8491 def __init__ (self , proxied ):
@@ -105,34 +112,39 @@ def index(self, value, *args, **kwargs):
105112 def count (self , value ):
106113 return self .__proxied .count (value )
107114
115+
108116def parse_time (timestamp ):
109117 if timestamp :
118+
110119 return datetime .datetime (* map (int , re .split (r'[^\d]' , timestamp .replace ('+00:00' , '' ))))
111120 return None
112121
122+
113123def copy_doc (original ):
114124 def decorator (overriden ):
115125 overriden .__doc__ = original .__doc__
116126 overriden .__signature__ = _signature (original )
117127 return overriden
118128 return decorator
119129
130+
120131def deprecated (instead = None ):
121132 def actual_decorator (func ):
122133 @functools .wraps (func )
123134 def decorated (* args , ** kwargs ):
124- warnings .simplefilter ('always' , DeprecationWarning ) # turn off filter
135+ warnings .simplefilter ('always' , DeprecationWarning ) # turn off filter
125136 if instead :
126137 fmt = "{0.__name__} is deprecated, use {1} instead."
127138 else :
128139 fmt = '{0.__name__} is deprecated.'
129140
130141 warnings .warn (fmt .format (func , instead ), stacklevel = 3 , category = DeprecationWarning )
131- warnings .simplefilter ('default' , DeprecationWarning ) # reset filter
142+ warnings .simplefilter ('default' , DeprecationWarning ) # reset filter
132143 return func (* args , ** kwargs )
133144 return decorated
134145 return actual_decorator
135146
147+
136148def oauth_url (client_id , permissions = None , guild = None , redirect_uri = None , scopes = None ):
137149 """A helper function that returns the OAuth2 URL for inviting the bot
138150 into guilds.
@@ -183,6 +195,7 @@ def snowflake_time(id):
183195 The creation date in UTC of a Discord snowflake ID."""
184196 return datetime .datetime .utcfromtimestamp (((id >> 22 ) + DISCORD_EPOCH ) / 1000 )
185197
198+
186199def time_snowflake (datetime_obj , high = False ):
187200 """Returns a numeric snowflake pretending to be created at the given date.
188201
@@ -201,6 +214,7 @@ def time_snowflake(datetime_obj, high=False):
201214
202215 return (discord_millis << 22 ) + (2 ** 22 - 1 if high else 0 )
203216
217+
204218def find (predicate , seq ):
205219 """A helper to return the first element found in the sequence
206220 that meets the predicate. For example: ::
@@ -226,6 +240,7 @@ def find(predicate, seq):
226240 return element
227241 return None
228242
243+
229244def get (iterable , ** attrs ):
230245 r"""A helper that returns the first element in the iterable that meets
231246 all the traits passed in ``attrs``. This is an alternative for
@@ -291,13 +306,24 @@ def get(iterable, **attrs):
291306 for elem in iterable :
292307 if _all (pred (elem ) == value for pred , value in converted ):
293308 return elem
309+
294310 return None
295311
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+
296321def _unique (iterable ):
297322 seen = set ()
298323 adder = seen .add
299324 return [x for x in iterable if not (x in seen or adder (x ))]
300325
326+
301327def _get_as_snowflake (data , key ):
302328 try :
303329 value = data [key ]
@@ -306,6 +332,7 @@ def _get_as_snowflake(data, key):
306332 else :
307333 return value and int (value )
308334
335+
309336def _get_mime_type_for_image (data ):
310337 if data .startswith (b'\x89 \x50 \x4E \x47 \x0D \x0A \x1A \x0A ' ):
311338 return 'image/png'
@@ -318,15 +345,18 @@ def _get_mime_type_for_image(data):
318345 else :
319346 raise InvalidArgument ('Unsupported image type given' )
320347
348+
321349def _bytes_to_base64_data (data ):
322350 fmt = 'data:{mime};base64,{data}'
323351 mime = _get_mime_type_for_image (data )
324352 b64 = b64encode (data ).decode ('ascii' )
325353 return fmt .format (mime = mime , data = b64 )
326354
355+
327356def to_json (obj ):
328357 return json .dumps (obj , separators = (',' , ':' ), ensure_ascii = True )
329358
359+
330360def _parse_ratelimit_header (request , * , use_clock = False ):
331361 reset_after = request .headers .get ('X-Ratelimit-Reset-After' )
332362 if use_clock or not reset_after :
@@ -337,13 +367,15 @@ def _parse_ratelimit_header(request, *, use_clock=False):
337367 else :
338368 return float (reset_after )
339369
370+
340371async def maybe_coroutine (f , * args , ** kwargs ):
341372 value = f (* args , ** kwargs )
342373 if _isawaitable (value ):
343374 return await value
344375 else :
345376 return value
346377
378+
347379async def async_all (gen , * , check = _isawaitable ):
348380 for elem in gen :
349381 if check (elem ):
@@ -389,10 +421,12 @@ async def sleep_until(when, result=None):
389421 delta -= MAX_ASYNCIO_SECONDS
390422 return await asyncio .sleep (max (delta , 0 ), result )
391423
424+
392425def valid_icon_size (size ):
393426 """Icons must be power of 2 within [16, 4096]."""
394427 return not size & (size - 1 ) and size in range (16 , 4097 )
395428
429+
396430class SnowflakeList (array .array ):
397431 """Internal data storage class to efficiently store a list of snowflakes.
398432
@@ -422,8 +456,10 @@ def has(self, element):
422456 i = bisect_left (self , element )
423457 return i != len (self ) and self [i ] == element
424458
459+
425460_IS_ASCII = re .compile (r'^[\x00-\x7f]+$' )
426461
462+
427463def _string_width (string , * , _IS_ASCII = _IS_ASCII ):
428464 """Returns string's width."""
429465 match = _IS_ASCII .match (string )
@@ -434,6 +470,7 @@ def _string_width(string, *, _IS_ASCII=_IS_ASCII):
434470 func = unicodedata .east_asian_width
435471 return sum (2 if func (char ) in UNICODE_WIDE_CHAR_TYPE else 1 for char in string )
436472
473+
437474def resolve_invite (invite ):
438475 """
439476 Resolves an invite from a :class:`~discord.Invite`, URL or code.
@@ -458,6 +495,7 @@ def resolve_invite(invite):
458495 return m .group (1 )
459496 return invite
460497
498+
461499def resolve_template (code ):
462500 """
463501 Resolves a template code from a :class:`~discord.Template`, URL or code.
@@ -474,7 +512,7 @@ def resolve_template(code):
474512 :class:`str`
475513 The template code.
476514 """
477- from .template import Template # circular import
515+ from .template import Template # circular import
478516 if isinstance (code , Template ):
479517 return code .code
480518 else :
@@ -484,6 +522,7 @@ def resolve_template(code):
484522 return m .group (1 )
485523 return code
486524
525+
487526_MARKDOWN_ESCAPE_SUBREGEX = '|' .join (r'\{0}(?=([\s\S]*((?<!\{0})\{0})))' .format (c )
488527 for c in ('*' , '`' , '_' , '~' , '|' ))
489528
@@ -495,6 +534,7 @@ def resolve_template(code):
495534
496535_MARKDOWN_STOCK_REGEX = r'(?P<markdown>[_\\~|\*`]|%s)' % _MARKDOWN_ESCAPE_COMMON
497536
537+
498538def remove_markdown (text , * , ignore_links = True ):
499539 """A helper function that removes markdown characters.
500540
@@ -528,6 +568,7 @@ def replacement(match):
528568 regex = '(?:%s|%s)' % (_URL_REGEX , regex )
529569 return re .sub (regex , replacement , text , 0 , re .MULTILINE )
530570
571+
531572def escape_markdown (text , * , as_needed = False , ignore_links = True ):
532573 r"""A helper function that escapes Discord's markdown.
533574
@@ -569,6 +610,7 @@ def replacement(match):
569610 text = re .sub (r'\\' , r'\\\\' , text )
570611 return _MARKDOWN_ESCAPE_REGEX .sub (r'\\\1' , text )
571612
613+
572614def escape_mentions (text ):
573615 """A helper function that escapes everyone, here, role, and user mentions.
574616
0 commit comments