2018-12-27 22:15:35 +03:00
|
|
|
import base64
|
|
|
|
import json
|
2017-12-28 14:32:16 +03:00
|
|
|
import struct
|
2019-08-01 19:47:38 +03:00
|
|
|
from datetime import datetime, date, timedelta, timezone
|
|
|
|
import time
|
2021-10-16 12:53:29 +03:00
|
|
|
from .helpers import pretty_print
|
2019-08-01 19:47:38 +03:00
|
|
|
|
|
|
|
_EPOCH_NAIVE = datetime(*time.gmtime(0)[:6])
|
|
|
|
_EPOCH_NAIVE_LOCAL = datetime(*time.localtime(0)[:6])
|
|
|
|
_EPOCH = _EPOCH_NAIVE.replace(tzinfo=timezone.utc)
|
|
|
|
|
|
|
|
|
|
|
|
def _datetime_to_timestamp(dt):
|
|
|
|
# If no timezone is specified, it is assumed to be in utc zone
|
|
|
|
if dt.tzinfo is None:
|
|
|
|
dt = dt.replace(tzinfo=timezone.utc)
|
2021-09-26 18:52:16 +03:00
|
|
|
# We use .total_seconds() method instead of simply dt.timestamp(),
|
2019-08-01 19:47:38 +03:00
|
|
|
# because on Windows the latter raises OSError on datetimes ~< datetime(1970,1,1)
|
2020-11-12 17:24:33 +03:00
|
|
|
secs = int((dt - _EPOCH).total_seconds())
|
|
|
|
# Make sure it's a valid signed 32 bit integer, as used by Telegram.
|
|
|
|
# This does make very large dates wrap around, but it's the best we
|
|
|
|
# can do with Telegram's limitations.
|
|
|
|
return struct.unpack('i', struct.pack('I', secs & 0xffffffff))[0]
|
2016-08-26 13:58:53 +03:00
|
|
|
|
|
|
|
|
2018-12-27 22:15:35 +03:00
|
|
|
def _json_default(value):
|
|
|
|
if isinstance(value, bytes):
|
|
|
|
return base64.b64encode(value).decode('ascii')
|
|
|
|
elif isinstance(value, datetime):
|
|
|
|
return value.isoformat()
|
|
|
|
else:
|
|
|
|
return repr(value)
|
|
|
|
|
|
|
|
|
2018-07-22 20:26:34 +03:00
|
|
|
class TLObject:
|
2021-09-26 18:52:16 +03:00
|
|
|
__slots__ = ()
|
2018-07-21 13:25:20 +03:00
|
|
|
CONSTRUCTOR_ID = None
|
|
|
|
SUBCLASS_OF_ID = None
|
|
|
|
|
2017-09-26 15:36:02 +03:00
|
|
|
@staticmethod
|
|
|
|
def serialize_bytes(data):
|
|
|
|
"""Write bytes by using Telegram guidelines"""
|
2017-10-25 13:43:57 +03:00
|
|
|
if not isinstance(data, bytes):
|
|
|
|
if isinstance(data, str):
|
|
|
|
data = data.encode('utf-8')
|
|
|
|
else:
|
2017-12-28 02:22:28 +03:00
|
|
|
raise TypeError(
|
|
|
|
'bytes or str expected, not {}'.format(type(data)))
|
2017-09-28 10:55:29 +03:00
|
|
|
|
2017-09-26 15:36:02 +03:00
|
|
|
r = []
|
|
|
|
if len(data) < 254:
|
|
|
|
padding = (len(data) + 1) % 4
|
|
|
|
if padding != 0:
|
|
|
|
padding = 4 - padding
|
|
|
|
|
|
|
|
r.append(bytes([len(data)]))
|
|
|
|
r.append(data)
|
|
|
|
|
|
|
|
else:
|
|
|
|
padding = len(data) % 4
|
|
|
|
if padding != 0:
|
|
|
|
padding = 4 - padding
|
|
|
|
|
2017-09-26 15:41:11 +03:00
|
|
|
r.append(bytes([
|
|
|
|
254,
|
|
|
|
len(data) % 256,
|
|
|
|
(len(data) >> 8) % 256,
|
|
|
|
(len(data) >> 16) % 256
|
|
|
|
]))
|
2017-09-26 15:36:02 +03:00
|
|
|
r.append(data)
|
|
|
|
|
|
|
|
r.append(bytes(padding))
|
|
|
|
return b''.join(r)
|
|
|
|
|
2017-12-28 14:32:16 +03:00
|
|
|
@staticmethod
|
|
|
|
def serialize_datetime(dt):
|
2019-08-01 19:47:38 +03:00
|
|
|
if not dt and not isinstance(dt, timedelta):
|
2017-12-28 14:32:16 +03:00
|
|
|
return b'\0\0\0\0'
|
|
|
|
|
|
|
|
if isinstance(dt, datetime):
|
2019-08-01 19:47:38 +03:00
|
|
|
dt = _datetime_to_timestamp(dt)
|
2017-12-28 14:32:16 +03:00
|
|
|
elif isinstance(dt, date):
|
2019-08-01 19:47:38 +03:00
|
|
|
dt = _datetime_to_timestamp(datetime(dt.year, dt.month, dt.day))
|
2017-12-28 14:32:16 +03:00
|
|
|
elif isinstance(dt, float):
|
|
|
|
dt = int(dt)
|
2018-07-09 14:36:52 +03:00
|
|
|
elif isinstance(dt, timedelta):
|
2019-08-01 19:47:38 +03:00
|
|
|
# Timezones are tricky. datetime.utcnow() + ... timestamp() works
|
|
|
|
dt = _datetime_to_timestamp(datetime.utcnow() + dt)
|
2017-12-28 14:32:16 +03:00
|
|
|
|
|
|
|
if isinstance(dt, int):
|
2019-08-01 19:47:38 +03:00
|
|
|
return struct.pack('<i', dt)
|
2017-12-28 14:32:16 +03:00
|
|
|
|
|
|
|
raise TypeError('Cannot interpret "{}" as a date.'.format(dt))
|
|
|
|
|
2018-02-01 14:10:03 +03:00
|
|
|
def __eq__(self, o):
|
|
|
|
return isinstance(o, type(self)) and self.to_dict() == o.to_dict()
|
|
|
|
|
|
|
|
def __ne__(self, o):
|
|
|
|
return not isinstance(o, type(self)) or self.to_dict() != o.to_dict()
|
|
|
|
|
2021-10-16 12:53:29 +03:00
|
|
|
def __repr__(self):
|
|
|
|
return pretty_print(self)
|
|
|
|
|
2018-01-19 14:12:52 +03:00
|
|
|
def __str__(self):
|
2021-10-16 12:53:29 +03:00
|
|
|
return pretty_print(self, max_depth=2)
|
2018-01-19 14:12:52 +03:00
|
|
|
|
|
|
|
def stringify(self):
|
2021-10-16 12:53:29 +03:00
|
|
|
return pretty_print(self, indent=0)
|
2018-01-19 14:12:52 +03:00
|
|
|
|
2018-01-25 11:44:07 +03:00
|
|
|
def to_dict(self):
|
2021-09-26 19:30:08 +03:00
|
|
|
res = {}
|
|
|
|
pre = ('', 'fn.')[isinstance(self, TLRequest)]
|
|
|
|
mod = self.__class__.__module__[self.__class__.__module__.rfind('.') + 1:]
|
|
|
|
if mod in ('_tl', 'fn'):
|
|
|
|
res['_'] = f'{pre}{self.__class__.__name__}'
|
|
|
|
else:
|
|
|
|
res['_'] = f'{pre}{mod}.{self.__class__.__name__}'
|
|
|
|
|
|
|
|
for slot in self.__slots__:
|
|
|
|
attr = getattr(self, slot)
|
|
|
|
if isinstance(attr, list):
|
|
|
|
res[slot] = [val.to_dict() if hasattr(val, 'to_dict') else val for val in attr]
|
|
|
|
else:
|
|
|
|
res[slot] = attr.to_dict() if hasattr(attr, 'to_dict') else attr
|
|
|
|
|
|
|
|
return res
|
2017-07-04 22:15:47 +03:00
|
|
|
|
2017-10-17 20:54:24 +03:00
|
|
|
def __bytes__(self):
|
2020-02-28 12:42:23 +03:00
|
|
|
try:
|
|
|
|
return self._bytes()
|
|
|
|
except AttributeError:
|
|
|
|
# If a type is wrong (e.g. expected `TLObject` but `int` was
|
|
|
|
# provided) it will try to access `._bytes()`, which will fail
|
|
|
|
# with `AttributeError`. This occurs in fact because the type
|
|
|
|
# was wrong, so raise the correct error type.
|
|
|
|
raise TypeError('a TLObject was expected but found something else')
|
|
|
|
|
|
|
|
# Custom objects will call `(...)._bytes()` and not `bytes(...)` so that
|
|
|
|
# if the wrong type is used (e.g. `int`) we won't try allocating a huge
|
|
|
|
# amount of data, which would cause a `MemoryError`.
|
|
|
|
def _bytes(self):
|
2018-06-12 21:05:05 +03:00
|
|
|
raise NotImplementedError
|
2016-08-26 13:58:53 +03:00
|
|
|
|
2018-04-14 21:28:25 +03:00
|
|
|
@classmethod
|
|
|
|
def from_reader(cls, reader):
|
2018-06-12 21:05:05 +03:00
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
2018-07-22 20:26:34 +03:00
|
|
|
class TLRequest(TLObject):
|
2018-06-12 21:05:05 +03:00
|
|
|
"""
|
|
|
|
Represents a content-related `TLObject` (a request that can be sent).
|
|
|
|
"""
|
|
|
|
@staticmethod
|
|
|
|
def read_result(reader):
|
|
|
|
return reader.tgread_object()
|
|
|
|
|
|
|
|
async def resolve(self, client, utils):
|
|
|
|
pass
|