diff --git a/readthedocs/misc/v2-migration-guide.rst b/readthedocs/misc/v2-migration-guide.rst index c16ed02f..6ad571a0 100644 --- a/readthedocs/misc/v2-migration-guide.rst +++ b/readthedocs/misc/v2-migration-guide.rst @@ -202,6 +202,12 @@ is decided by Telegram. The ``telethon.errors`` module continues to provide custom errors used by the library such as ``TypeNotFoundError``. +Errors are all now immutable - you can't modify them. This means the library will not correct +weird errors such as FLOOD_WAIT_0 (which was previously modified to FLOOD_WAIT_1). When the +library does automatic flood sleeping, it will still make this correction, but if the +``flood_sleep_threshold`` is in the range [0,1) then you may get FLOOD_WAIT_0 raised to your +application code. This must be handled manually. + // TODO keep RPCError around? eh idk how much it's used // TODO should RpcError subclass ValueError? technically the values used in the request somehow were wrong… // TODO provide a way to see which errors are known in the docs or at tl.telethon.dev diff --git a/telethon/_client/dialogs.py b/telethon/_client/dialogs.py index e3832ee8..8bff83d1 100644 --- a/telethon/_client/dialogs.py +++ b/telethon/_client/dialogs.py @@ -82,7 +82,8 @@ class _DialogsIter(requestiter.RequestIter): cd = _custom.Dialog(self.client, d, entities, message) if cd.dialog.pts: - self.client._channel_pts[cd.id] = cd.dialog.pts + # TODO probably a bad idea to change pts around as a side effect - add a .discard_pending_updates() method? + self.client._channel_pts[cd.entity.id] = cd.dialog.pts if not self.ignore_migrated or getattr( cd.entity, 'migrated_to', None) is None: diff --git a/telethon/_client/messages.py b/telethon/_client/messages.py index 32b540a4..4e824593 100644 --- a/telethon/_client/messages.py +++ b/telethon/_client/messages.py @@ -508,7 +508,7 @@ async def send_message( date=result.date, out=result.out, media=result.media, - entities=result._fmt_entities, + entities=result.entities, reply_markup=request.reply_markup, ttl_period=result.ttl_period ), {}, entity) diff --git a/telethon/_client/telegrambaseclient.py b/telethon/_client/telegrambaseclient.py index 9316668b..182da69e 100644 --- a/telethon/_client/telegrambaseclient.py +++ b/telethon/_client/telegrambaseclient.py @@ -74,6 +74,10 @@ def init( use_ipv6: bool = False, proxy: typing.Union[tuple, dict] = None, local_addr: typing.Union[str, tuple] = None, + default_dc_id: int = None, + default_ipv4_ip: str = None, + default_ipv6_ip: str = None, + default_port: int = None, timeout: int = 10, request_retries: int = 5, connection_retries: int = 5, @@ -180,6 +184,15 @@ def init( '`use_ipv6=True` must only be used with a local IPv6 address.' ) + self._default_dc_id = default_dc_id or DEFAULT_DC_ID + if not isinstance(self._default_dc_id, int): + raise TypeError('`default_dc_id` must be an int or None.') + self._default_ipv4_ip = int(ipaddress.ip_address(default_ipv4_ip or DEFAULT_IPV4_IP)) + self._default_ipv6_ip = int(ipaddress.ip_address(default_ipv6_ip or DEFAULT_IPV6_IP)) + self._default_port = default_port or DEFAULT_PORT + if not isinstance(self._default_port, int): + raise TypeError('`default_port` must be an int or None') + self._raise_last_call_error = raise_last_call_error self._request_retries = request_retries @@ -305,7 +318,7 @@ async def connect(self: 'TelegramClient') -> None: try_fetch_user = False self._session_state = SessionState( user_id=0, - dc_id=DEFAULT_DC_ID, + dc_id=self._default_dc_id, bot=False, pts=0, qts=0, @@ -319,10 +332,10 @@ async def connect(self: 'TelegramClient') -> None: dc = self._all_dcs.get(self._session_state.dc_id) if dc is None: dc = DataCenter( - id=DEFAULT_DC_ID, - ipv4=None if self._use_ipv6 else int(ipaddress.ip_address(DEFAULT_IPV4_IP)), - ipv6=int(ipaddress.ip_address(DEFAULT_IPV6_IP)) if self._use_ipv6 else None, - port=DEFAULT_PORT, + id=self._default_dc_id, + ipv4=None if self._use_ipv6 else self._default_ipv4_ip, + ipv6=self._default_ipv6_ip if self._use_ipv6 else None, + port=self._default_port, auth=b'', ) self._all_dcs[dc.id] = dc diff --git a/telethon/_client/telegramclient.py b/telethon/_client/telegramclient.py index f2dc4ede..fa9ddf69 100644 --- a/telethon/_client/telegramclient.py +++ b/telethon/_client/telegramclient.py @@ -73,6 +73,21 @@ class TelegramClient: You only need to use this if you have multiple network cards and want to use a specific one. + default_dc_id (`int`, optional): + The DC ID to connect to, if not already provided by the session. + + default_ipv4_ip (`str`, optional): + The default IPv4 address to connect to, if not already provided by + the session. + + default_ipv6_ip (`str`, optional): + The default IPv6 address to connect to, if not already provided by + the session. + + default_port (`int`, optional): + The default port to connect to, if not already provided by + the session. This is used whether the connection is IPv4 or IPv6. + timeout (`int` | `float`, optional): The timeout in seconds to be used when connecting. This is **not** the timeout to be used when ``await``'ing for @@ -2681,6 +2696,10 @@ class TelegramClient: use_ipv6: bool = False, proxy: typing.Union[tuple, dict] = None, local_addr: typing.Union[str, tuple] = None, + default_dc_id: int = None, + default_ipv4_ip: str = None, + default_ipv6_ip: str = None, + default_port: int = None, timeout: int = 10, request_retries: int = 5, connection_retries: int = 5, diff --git a/telethon/_client/users.py b/telethon/_client/users.py index d875f471..69009672 100644 --- a/telethon/_client/users.py +++ b/telethon/_client/users.py @@ -102,17 +102,17 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl # SLOWMODE_WAIT is chat-specific, not request-specific if not isinstance(e, errors.SLOWMODE_WAIT): - self._flood_waited_requests\ + # TODO reconsider use of time.time() - maybe monotonic or server time is better. + self._flood_waited_requests \ [request.CONSTRUCTOR_ID] = time.time() + e.seconds # In test servers, FLOOD_WAIT_0 has been observed, and sleeping for # such a short amount will cause retries very fast leading to issues. - if e.seconds == 0: - e.seconds = 1 + seconds = e.seconds or 1 - if e.seconds <= self.flood_sleep_threshold: - self._log[__name__].info(*_fmt_flood(e.seconds, request)) - await asyncio.sleep(e.seconds) + if seconds <= self.flood_sleep_threshold: + self._log[__name__].info(*_fmt_flood(seconds, request)) + await asyncio.sleep(seconds) else: raise except InvalidDcError as e: diff --git a/telethon/_sessions/abstract.py b/telethon/_sessions/abstract.py index 2b28ae76..3e0add81 100644 --- a/telethon/_sessions/abstract.py +++ b/telethon/_sessions/abstract.py @@ -74,7 +74,7 @@ class Session(ABC): For example, if a ``ty`` representing a bot is stored but the asking ``ty`` is a user, the corresponding ``access_hash`` should still be returned. - You may use `types.canonical_entity_type` to find out the canonical type. + You may use `types.get_canonical_entity_type` to find out the canonical type. A ``ty`` with the value of ``None`` should be treated as "any entity with matching ID". """ diff --git a/telethon/_sessions/memory.py b/telethon/_sessions/memory.py index 95a23417..30968faf 100644 --- a/telethon/_sessions/memory.py +++ b/telethon/_sessions/memory.py @@ -1,4 +1,4 @@ -from .types import DataCenter, ChannelState, SessionState, Entity, get_entity_type_group +from .types import DataCenter, ChannelState, SessionState, Entity, get_entity_type_group, get_canonical_entity_type from .abstract import Session from .._misc import utils, tlobject from .. import _tl @@ -34,11 +34,11 @@ class MemorySession(Session): return list(self.channel_states.values()) async def insert_entities(self, entities: List[Entity]): - self.entities.update(((get_peer_canonical_entity_type(e.ty), e.id), e.access_hash) for e in entities) + self.entities.update(((get_canonical_entity_type(e.ty), e.id), e.access_hash) for e in entities) async def get_entity(self, ty: int, id: int) -> Optional[Entity]: try: - access_hash = self.entities[get_peer_canonical_entity_type(ty), id] + access_hash = self.entities[get_canonical_entity_type(ty), id] return Entity(ty, id, access_hash) except KeyError: return None diff --git a/telethon/_sessions/types.py b/telethon/_sessions/types.py index 3b524a8c..fad70ee5 100644 --- a/telethon/_sessions/types.py +++ b/telethon/_sessions/types.py @@ -123,7 +123,7 @@ class Entity: self.access_hash = access_hash -def canonical_entity_type(ty: int, *, _mapping={ +def get_canonical_entity_type(ty: int, *, _mapping={ Entity.USER: Entity.USER, Entity.BOT: Entity.USER, Entity.GROUP: Entity.GROUP, diff --git a/telethon/types/_custom/dialog.py b/telethon/types/_custom/dialog.py index b3b93943..fb519d35 100644 --- a/telethon/types/_custom/dialog.py +++ b/telethon/types/_custom/dialog.py @@ -37,9 +37,6 @@ class Dialog: input_entity (:tl:`InputPeer`): Input version of the entity. - id (`int`): - The marked ID of the entity, which is guaranteed to be unique. - name (`str`): Display name for this dialog. For chats and channels this is their title, and for users it's "First-Name Last-Name". @@ -81,7 +78,6 @@ class Dialog: self.entity = entities[utils.get_peer_id(dialog.peer)] self.input_entity = utils.get_input_peer(self.entity) - self.id = utils.get_peer_id(self.entity) # ^ May be InputPeerSelf() self.name = self.title = utils.get_display_name(self.entity) self.unread_count = dialog.unread_count diff --git a/telethon/types/_custom/message.py b/telethon/types/_custom/message.py index 2b1a44e1..c5c23859 100644 --- a/telethon/types/_custom/message.py +++ b/telethon/types/_custom/message.py @@ -409,14 +409,23 @@ class Message(ChatGetter, SenderGetter): sender_id = None if isinstance(message, _tl.Message): if message.from_id is not None: - sender_id = utils.get_peer_id(message.from_id) + sender_id = message.from_id if sender_id is None and message.peer_id and not isinstance(message, _tl.MessageEmpty): # If the message comes from a Channel, let the sender be it # ...or... # incoming messages in private conversations no longer have from_id # (layer 119+), but the sender can only be the chat we're in. + print("a", message.peer_id, message.out, isinstance(message.peer_id, _tl.PeerUser)) if message.post or (not message.out and isinstance(message.peer_id, _tl.PeerUser)): - sender_id = utils.get_peer_id(message.peer_id) + sender_id = message.peer_id + if message.post: + sender_id = message.peer_id + if isinstance(message.peer_id, _tl.PeerUser): + if message.out: + sender_id = _tl.PeerUser(client._session_state.user_id) + else: + sender_id = message.peer_id + print("b", sender_id) # Note that these calls would reset the client ChatGetter.__init__(self, message.peer_id, broadcast=message.post) @@ -444,8 +453,8 @@ class Message(ChatGetter, SenderGetter): cache = client._entity_cache - self._sender, self._input_sender = utils._get_entity_pair( - self.sender_id, entities, cache) +# self._sender, self._input_sender = utils._get_entity_pair( +# self.sender_id, entities, cache) self._chat, self._input_chat = utils._get_entity_pair( self.chat_id, entities, cache) diff --git a/telethon/types/_custom/sendergetter.py b/telethon/types/_custom/sendergetter.py index 673cab25..ef3a7dba 100644 --- a/telethon/types/_custom/sendergetter.py +++ b/telethon/types/_custom/sendergetter.py @@ -1,4 +1,5 @@ import abc +from ..._misc import utils class SenderGetter(abc.ABC): @@ -8,7 +9,7 @@ class SenderGetter(abc.ABC): methods. """ def __init__(self, sender_id=None, *, sender=None, input_sender=None): - self._sender_id = sender_id + self._sender_id = utils.get_peer(sender_id) self._sender = sender self._input_sender = input_sender self._client = None @@ -84,7 +85,7 @@ class SenderGetter(abc.ABC): @property def sender_id(self): """ - Returns the marked sender integer ID, if present. + Returns the sender peer, if present. If there is a sender in the object, `sender_id` will *always* be set, which is why you should use it instead of `sender.id `.