This commit is contained in:
Hackintosh 5 2021-12-28 17:50:39 +00:00
parent 1d4bd39307
commit 34fc07b1fe
12 changed files with 73 additions and 28 deletions

View File

@ -202,6 +202,12 @@ is decided by Telegram.
The ``telethon.errors`` module continues to provide custom errors used by the library such as The ``telethon.errors`` module continues to provide custom errors used by the library such as
``TypeNotFoundError``. ``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 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 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 // TODO provide a way to see which errors are known in the docs or at tl.telethon.dev

View File

@ -82,7 +82,8 @@ class _DialogsIter(requestiter.RequestIter):
cd = _custom.Dialog(self.client, d, entities, message) cd = _custom.Dialog(self.client, d, entities, message)
if cd.dialog.pts: 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( if not self.ignore_migrated or getattr(
cd.entity, 'migrated_to', None) is None: cd.entity, 'migrated_to', None) is None:

View File

@ -508,7 +508,7 @@ async def send_message(
date=result.date, date=result.date,
out=result.out, out=result.out,
media=result.media, media=result.media,
entities=result._fmt_entities, entities=result.entities,
reply_markup=request.reply_markup, reply_markup=request.reply_markup,
ttl_period=result.ttl_period ttl_period=result.ttl_period
), {}, entity) ), {}, entity)

View File

@ -74,6 +74,10 @@ def init(
use_ipv6: bool = False, use_ipv6: bool = False,
proxy: typing.Union[tuple, dict] = None, proxy: typing.Union[tuple, dict] = None,
local_addr: typing.Union[str, tuple] = 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, timeout: int = 10,
request_retries: int = 5, request_retries: int = 5,
connection_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.' '`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._raise_last_call_error = raise_last_call_error
self._request_retries = request_retries self._request_retries = request_retries
@ -305,7 +318,7 @@ async def connect(self: 'TelegramClient') -> None:
try_fetch_user = False try_fetch_user = False
self._session_state = SessionState( self._session_state = SessionState(
user_id=0, user_id=0,
dc_id=DEFAULT_DC_ID, dc_id=self._default_dc_id,
bot=False, bot=False,
pts=0, pts=0,
qts=0, qts=0,
@ -319,10 +332,10 @@ async def connect(self: 'TelegramClient') -> None:
dc = self._all_dcs.get(self._session_state.dc_id) dc = self._all_dcs.get(self._session_state.dc_id)
if dc is None: if dc is None:
dc = DataCenter( dc = DataCenter(
id=DEFAULT_DC_ID, id=self._default_dc_id,
ipv4=None if self._use_ipv6 else int(ipaddress.ip_address(DEFAULT_IPV4_IP)), ipv4=None if self._use_ipv6 else self._default_ipv4_ip,
ipv6=int(ipaddress.ip_address(DEFAULT_IPV6_IP)) if self._use_ipv6 else None, ipv6=self._default_ipv6_ip if self._use_ipv6 else None,
port=DEFAULT_PORT, port=self._default_port,
auth=b'', auth=b'',
) )
self._all_dcs[dc.id] = dc self._all_dcs[dc.id] = dc

View File

@ -73,6 +73,21 @@ class TelegramClient:
You only need to use this if you have multiple network cards and You only need to use this if you have multiple network cards and
want to use a specific one. 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): timeout (`int` | `float`, optional):
The timeout in seconds to be used when connecting. The timeout in seconds to be used when connecting.
This is **not** the timeout to be used when ``await``'ing for This is **not** the timeout to be used when ``await``'ing for
@ -2681,6 +2696,10 @@ class TelegramClient:
use_ipv6: bool = False, use_ipv6: bool = False,
proxy: typing.Union[tuple, dict] = None, proxy: typing.Union[tuple, dict] = None,
local_addr: typing.Union[str, tuple] = 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, timeout: int = 10,
request_retries: int = 5, request_retries: int = 5,
connection_retries: int = 5, connection_retries: int = 5,

View File

@ -102,17 +102,17 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
# SLOWMODE_WAIT is chat-specific, not request-specific # SLOWMODE_WAIT is chat-specific, not request-specific
if not isinstance(e, errors.SLOWMODE_WAIT): 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 [request.CONSTRUCTOR_ID] = time.time() + e.seconds
# In test servers, FLOOD_WAIT_0 has been observed, and sleeping for # In test servers, FLOOD_WAIT_0 has been observed, and sleeping for
# such a short amount will cause retries very fast leading to issues. # such a short amount will cause retries very fast leading to issues.
if e.seconds == 0: seconds = e.seconds or 1
e.seconds = 1
if e.seconds <= self.flood_sleep_threshold: if seconds <= self.flood_sleep_threshold:
self._log[__name__].info(*_fmt_flood(e.seconds, request)) self._log[__name__].info(*_fmt_flood(seconds, request))
await asyncio.sleep(e.seconds) await asyncio.sleep(seconds)
else: else:
raise raise
except InvalidDcError as e: except InvalidDcError as e:

View File

@ -74,7 +74,7 @@ class Session(ABC):
For example, if a ``ty`` representing a bot is stored but the asking ``ty`` is a user, 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. 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". A ``ty`` with the value of ``None`` should be treated as "any entity with matching ID".
""" """

View File

@ -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 .abstract import Session
from .._misc import utils, tlobject from .._misc import utils, tlobject
from .. import _tl from .. import _tl
@ -34,11 +34,11 @@ class MemorySession(Session):
return list(self.channel_states.values()) return list(self.channel_states.values())
async def insert_entities(self, entities: List[Entity]): 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]: async def get_entity(self, ty: int, id: int) -> Optional[Entity]:
try: 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) return Entity(ty, id, access_hash)
except KeyError: except KeyError:
return None return None

View File

@ -123,7 +123,7 @@ class Entity:
self.access_hash = access_hash 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.USER: Entity.USER,
Entity.BOT: Entity.USER, Entity.BOT: Entity.USER,
Entity.GROUP: Entity.GROUP, Entity.GROUP: Entity.GROUP,

View File

@ -37,9 +37,6 @@ class Dialog:
input_entity (:tl:`InputPeer`): input_entity (:tl:`InputPeer`):
Input version of the entity. Input version of the entity.
id (`int`):
The marked ID of the entity, which is guaranteed to be unique.
name (`str`): name (`str`):
Display name for this dialog. For chats and channels this is Display name for this dialog. For chats and channels this is
their title, and for users it's "First-Name Last-Name". 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.entity = entities[utils.get_peer_id(dialog.peer)]
self.input_entity = utils.get_input_peer(self.entity) 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.name = self.title = utils.get_display_name(self.entity)
self.unread_count = dialog.unread_count self.unread_count = dialog.unread_count

View File

@ -409,14 +409,23 @@ class Message(ChatGetter, SenderGetter):
sender_id = None sender_id = None
if isinstance(message, _tl.Message): if isinstance(message, _tl.Message):
if message.from_id is not None: 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 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 # If the message comes from a Channel, let the sender be it
# ...or... # ...or...
# incoming messages in private conversations no longer have from_id # incoming messages in private conversations no longer have from_id
# (layer 119+), but the sender can only be the chat we're in. # (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)): 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 # Note that these calls would reset the client
ChatGetter.__init__(self, message.peer_id, broadcast=message.post) ChatGetter.__init__(self, message.peer_id, broadcast=message.post)
@ -444,8 +453,8 @@ class Message(ChatGetter, SenderGetter):
cache = client._entity_cache cache = client._entity_cache
self._sender, self._input_sender = utils._get_entity_pair( # self._sender, self._input_sender = utils._get_entity_pair(
self.sender_id, entities, cache) # self.sender_id, entities, cache)
self._chat, self._input_chat = utils._get_entity_pair( self._chat, self._input_chat = utils._get_entity_pair(
self.chat_id, entities, cache) self.chat_id, entities, cache)

View File

@ -1,4 +1,5 @@
import abc import abc
from ..._misc import utils
class SenderGetter(abc.ABC): class SenderGetter(abc.ABC):
@ -8,7 +9,7 @@ class SenderGetter(abc.ABC):
methods. methods.
""" """
def __init__(self, sender_id=None, *, sender=None, input_sender=None): 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._sender = sender
self._input_sender = input_sender self._input_sender = input_sender
self._client = None self._client = None
@ -84,7 +85,7 @@ class SenderGetter(abc.ABC):
@property @property
def sender_id(self): 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, 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 <sender>`. which is why you should use it instead of `sender.id <sender>`.