mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-06-17 18:46:40 +00:00
Implemnet chat hash cache and adapting updates
This commit is contained in:
parent
7c112d8b0f
commit
c77c10b48f
0
client/src/telethon/_impl/session/__init__.py
Normal file
0
client/src/telethon/_impl/session/__init__.py
Normal file
4
client/src/telethon/_impl/session/chat/__init__.py
Normal file
4
client/src/telethon/_impl/session/chat/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from .hash_cache import ChatHashCache
|
||||||
|
from .packed import PackedChat, PackedType
|
||||||
|
|
||||||
|
__all__ = ["ChatHashCache", "PackedChat", "PackedType"]
|
314
client/src/telethon/_impl/session/chat/hash_cache.py
Normal file
314
client/src/telethon/_impl/session/chat/hash_cache.py
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from ...tl import abcs, types
|
||||||
|
from .packed import PackedChat, PackedType
|
||||||
|
|
||||||
|
|
||||||
|
class ChatHashCache:
|
||||||
|
__slots__ = ("_hash_map", "_self_id", "_self_bot")
|
||||||
|
|
||||||
|
def __init__(self, self_user: Optional[Tuple[int, bool]]):
|
||||||
|
self._hash_map: Dict[int, Tuple[int, PackedType]] = {}
|
||||||
|
self._self_id = self_user[0] if self_user else None
|
||||||
|
self._self_bot = self_user[1] if self_user else False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def self_id(self) -> int:
|
||||||
|
assert self._self_id is not None
|
||||||
|
return self._self_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_self_bot(self) -> bool:
|
||||||
|
return self._self_bot
|
||||||
|
|
||||||
|
def set_self_user(self, user: PackedChat) -> None:
|
||||||
|
assert user.ty in (PackedType.USER, PackedType.BOT)
|
||||||
|
self._self_bot = user.ty == PackedType.BOT
|
||||||
|
self._self_id = user.id
|
||||||
|
|
||||||
|
def get(self, id: int) -> Optional[PackedChat]:
|
||||||
|
if (entry := self._hash_map.get(id)) is not None:
|
||||||
|
hash, ty = entry
|
||||||
|
return PackedChat(ty, id, hash)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _has(self, id: int) -> bool:
|
||||||
|
return id in self._hash_map
|
||||||
|
|
||||||
|
def _has_peer(self, peer: abcs.Peer) -> bool:
|
||||||
|
if isinstance(peer, types.PeerUser):
|
||||||
|
return self._has(peer.user_id)
|
||||||
|
elif isinstance(peer, types.PeerChat):
|
||||||
|
return True # no hash needed, so we always have it
|
||||||
|
elif isinstance(peer, types.PeerChannel):
|
||||||
|
return self._has(peer.channel_id)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def _has_dialog_peer(self, peer: abcs.DialogPeer) -> bool:
|
||||||
|
if isinstance(peer, types.DialogPeer):
|
||||||
|
return self._has_peer(peer.peer)
|
||||||
|
elif isinstance(peer, types.DialogPeerFolder):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def _has_notify_peer(self, peer: abcs.NotifyPeer) -> bool:
|
||||||
|
if isinstance(peer, types.NotifyPeer):
|
||||||
|
return self._has_peer(peer.peer)
|
||||||
|
elif isinstance(peer, types.NotifyForumTopic):
|
||||||
|
return self._has_peer(peer.peer)
|
||||||
|
elif isinstance(
|
||||||
|
peer, (types.NotifyUsers, types.NotifyChats, types.NotifyBroadcasts)
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def _has_button(self, button: abcs.KeyboardButton) -> bool:
|
||||||
|
if isinstance(button, types.InputKeyboardButtonUrlAuth):
|
||||||
|
return self._has_user(button.bot)
|
||||||
|
elif isinstance(button, types.InputKeyboardButtonUserProfile):
|
||||||
|
return self._has_user(button.user_id)
|
||||||
|
elif isinstance(button, types.KeyboardButtonUserProfile):
|
||||||
|
return self._has(button.user_id)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _has_entity(self, entity: abcs.MessageEntity) -> bool:
|
||||||
|
if isinstance(entity, types.MessageEntityMentionName):
|
||||||
|
return self._has(entity.user_id)
|
||||||
|
elif isinstance(entity, types.InputMessageEntityMentionName):
|
||||||
|
return self._has_user(entity.user_id)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _has_user(self, peer: abcs.InputUser) -> bool:
|
||||||
|
if isinstance(peer, (types.InputUserEmpty, types.InputUserSelf)):
|
||||||
|
return True
|
||||||
|
elif isinstance(peer, types.InputUser):
|
||||||
|
return self._has(peer.user_id)
|
||||||
|
elif isinstance(peer, types.InputUserFromMessage):
|
||||||
|
return self._has(peer.user_id)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def _has_participant(self, participant: abcs.ChatParticipant) -> bool:
|
||||||
|
if isinstance(participant, types.ChatParticipant):
|
||||||
|
return self._has(participant.user_id) and self._has(participant.inviter_id)
|
||||||
|
elif isinstance(participant, types.ChatParticipantCreator):
|
||||||
|
return self._has(participant.user_id)
|
||||||
|
elif isinstance(participant, types.ChatParticipantAdmin):
|
||||||
|
return self._has(participant.user_id) and self._has(participant.inviter_id)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def _has_channel_participant(self, participant: abcs.ChannelParticipant) -> bool:
|
||||||
|
if isinstance(participant, types.ChannelParticipant):
|
||||||
|
return self._has(participant.user_id)
|
||||||
|
elif isinstance(participant, types.ChannelParticipantSelf):
|
||||||
|
return self._has(participant.user_id) and self._has(participant.inviter_id)
|
||||||
|
elif isinstance(participant, types.ChannelParticipantCreator):
|
||||||
|
return self._has(participant.user_id)
|
||||||
|
elif isinstance(participant, types.ChannelParticipantAdmin):
|
||||||
|
return (
|
||||||
|
self._has(participant.user_id)
|
||||||
|
and (
|
||||||
|
participant.inviter_id is None or self._has(participant.inviter_id)
|
||||||
|
)
|
||||||
|
and self._has(participant.promoted_by)
|
||||||
|
)
|
||||||
|
elif isinstance(participant, types.ChannelParticipantBanned):
|
||||||
|
return self._has_peer(participant.peer) and self._has(participant.kicked_by)
|
||||||
|
elif isinstance(participant, types.ChannelParticipantLeft):
|
||||||
|
return self._has_peer(participant.peer)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def extend(self, users: List[abcs.User], chats: List[abcs.Chat]) -> bool:
|
||||||
|
# See https://core.telegram.org/api/min for "issues" with "min constructors".
|
||||||
|
success = True
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
if isinstance(user, types.UserEmpty):
|
||||||
|
pass
|
||||||
|
elif isinstance(user, types.User):
|
||||||
|
if not user.min and user.access_hash is not None:
|
||||||
|
ty = PackedType.BOT if user.bot else PackedType.USER
|
||||||
|
self._hash_map[user.id] = (user.access_hash, ty)
|
||||||
|
else:
|
||||||
|
success &= user.id in self._hash_map
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
for chat in chats:
|
||||||
|
if isinstance(chat, (types.ChatEmpty, types.Chat, types.ChatForbidden)):
|
||||||
|
pass
|
||||||
|
elif isinstance(chat, types.Channel):
|
||||||
|
if not chat.min and chat.access_hash is not None:
|
||||||
|
if chat.megagroup:
|
||||||
|
ty = PackedType.MEGAGROUP
|
||||||
|
elif chat.gigagroup:
|
||||||
|
ty = PackedType.GIGAGROUP
|
||||||
|
else:
|
||||||
|
ty = PackedType.BROADCAST
|
||||||
|
self._hash_map[chat.id] = (chat.access_hash, ty)
|
||||||
|
else:
|
||||||
|
success &= chat.id in self._hash_map
|
||||||
|
elif isinstance(chat, types.ChannelForbidden):
|
||||||
|
ty = PackedType.MEGAGROUP if chat.megagroup else PackedType.BROADCAST
|
||||||
|
self._hash_map[chat.id] = (chat.access_hash, ty)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def extend_from_updates(self, updates: abcs.Updates) -> bool:
|
||||||
|
if isinstance(updates, types.UpdatesTooLong):
|
||||||
|
return True
|
||||||
|
elif isinstance(updates, types.UpdateShortMessage):
|
||||||
|
return self._has(updates.user_id)
|
||||||
|
elif isinstance(updates, types.UpdateShortChatMessage):
|
||||||
|
return self._has(updates.from_id)
|
||||||
|
elif isinstance(updates, types.UpdateShort):
|
||||||
|
success = True
|
||||||
|
update = updates.update
|
||||||
|
|
||||||
|
# In Python, we get to cheat rather than having hundreds of `if isinstance`
|
||||||
|
for field in ("message",):
|
||||||
|
message = getattr(update, field, None)
|
||||||
|
if isinstance(message, abcs.Message):
|
||||||
|
success &= self.extend_from_message(message)
|
||||||
|
|
||||||
|
for field in ("user_id", "inviter_id", "channel_id", "bot_id", "actor_id"):
|
||||||
|
int_id = getattr(update, field, None)
|
||||||
|
if isinstance(int_id, int):
|
||||||
|
success &= self._has(int_id)
|
||||||
|
|
||||||
|
for field in ("from_id", "peer"):
|
||||||
|
peer = getattr(update, field, None)
|
||||||
|
if isinstance(peer, abcs.Peer):
|
||||||
|
success &= self._has_peer(peer)
|
||||||
|
elif isinstance(peer, abcs.DialogPeer):
|
||||||
|
success &= self._has_dialog_peer(peer)
|
||||||
|
elif isinstance(peer, abcs.NotifyPeer):
|
||||||
|
success &= self._has_notify_peer(peer)
|
||||||
|
|
||||||
|
# TODO cover?:
|
||||||
|
# ChatParticipants.participants
|
||||||
|
# PinnedDialogs.order
|
||||||
|
# FolderPeers.folder_peers
|
||||||
|
# PeerLocated.peers
|
||||||
|
# GroupCallParticipants.participants
|
||||||
|
# ChatParticipant and ChannelParticipant .prev_participant, new_participant, invite
|
||||||
|
# BotChatInviteRequester.invite
|
||||||
|
|
||||||
|
return success
|
||||||
|
elif isinstance(updates, types.UpdatesCombined):
|
||||||
|
return self.extend(updates.users, updates.chats)
|
||||||
|
elif isinstance(updates, types.Updates):
|
||||||
|
return self.extend(updates.users, updates.chats)
|
||||||
|
elif isinstance(updates, types.UpdateShortSentMessage):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def extend_from_message(self, message: abcs.Message) -> bool:
|
||||||
|
if isinstance(message, types.MessageEmpty):
|
||||||
|
return message.peer_id is None or self._has_peer(message.peer_id)
|
||||||
|
elif isinstance(message, types.Message):
|
||||||
|
success = True
|
||||||
|
|
||||||
|
if message.from_id is not None:
|
||||||
|
success &= self._has_peer(message.from_id)
|
||||||
|
|
||||||
|
success &= self._has_peer(message.peer_id)
|
||||||
|
|
||||||
|
if isinstance(message.fwd_from, types.MessageFwdHeader):
|
||||||
|
if message.fwd_from.from_id:
|
||||||
|
success &= self._has_peer(message.fwd_from.from_id)
|
||||||
|
if message.fwd_from.saved_from_peer:
|
||||||
|
success &= self._has_peer(message.fwd_from.saved_from_peer)
|
||||||
|
elif message.fwd_from is not None:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
if isinstance(message.reply_to, types.MessageReplyHeader):
|
||||||
|
if message.reply_to.reply_to_peer_id:
|
||||||
|
success &= self._has_peer(message.reply_to.reply_to_peer_id)
|
||||||
|
elif message.reply_to is not None:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
if message.reply_markup is not None:
|
||||||
|
if isinstance(message.reply_markup, types.ReplyKeyboardMarkup):
|
||||||
|
for row in message.reply_markup.rows:
|
||||||
|
if isinstance(row, types.KeyboardButtonRow):
|
||||||
|
for button in row.buttons:
|
||||||
|
success &= self._has_button(button)
|
||||||
|
elif isinstance(message.reply_markup, types.ReplyInlineMarkup):
|
||||||
|
for row in message.reply_markup.rows:
|
||||||
|
if isinstance(row, types.KeyboardButtonRow):
|
||||||
|
for button in row.buttons:
|
||||||
|
success &= self._has_button(button)
|
||||||
|
|
||||||
|
if message.entities:
|
||||||
|
for entity in message.entities:
|
||||||
|
success &= self._has_entity(entity)
|
||||||
|
|
||||||
|
if isinstance(message.replies, types.MessageReplies):
|
||||||
|
if message.replies.recent_repliers:
|
||||||
|
for p in message.replies.recent_repliers:
|
||||||
|
success &= self._has_peer(p)
|
||||||
|
elif message.replies is not None:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
if isinstance(message.reactions, types.MessageReactions):
|
||||||
|
if message.reactions.recent_reactions:
|
||||||
|
for r in message.reactions.recent_reactions:
|
||||||
|
if isinstance(r, types.MessagePeerReaction):
|
||||||
|
success &= self._has_peer(r.peer_id)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
elif message.reactions is not None:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
return success
|
||||||
|
elif isinstance(message, types.MessageService):
|
||||||
|
success = True
|
||||||
|
|
||||||
|
if message.from_id:
|
||||||
|
success &= self._has_peer(message.from_id)
|
||||||
|
|
||||||
|
if message.peer_id:
|
||||||
|
success &= self._has_peer(message.peer_id)
|
||||||
|
|
||||||
|
if isinstance(message.reply_to, types.MessageReplyHeader):
|
||||||
|
if message.reply_to.reply_to_peer_id:
|
||||||
|
success &= self._has_peer(message.reply_to.reply_to_peer_id)
|
||||||
|
elif message.reply_to is not None:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
for field in ("user_id", "inviter_id", "channel_id"):
|
||||||
|
int_id = getattr(message.action, field, None)
|
||||||
|
if isinstance(int_id, int):
|
||||||
|
success &= self._has(int_id)
|
||||||
|
|
||||||
|
for field in ("from_id", "to_id", "peer"):
|
||||||
|
peer = getattr(message.action, field, None)
|
||||||
|
if isinstance(peer, abcs.Peer):
|
||||||
|
success &= self._has_peer(peer)
|
||||||
|
elif isinstance(peer, abcs.DialogPeer):
|
||||||
|
success &= self._has_dialog_peer(peer)
|
||||||
|
elif isinstance(peer, abcs.NotifyPeer):
|
||||||
|
success &= self._has_notify_peer(peer)
|
||||||
|
|
||||||
|
for field in ("users",):
|
||||||
|
users = getattr(message.action, field, None)
|
||||||
|
if isinstance(users, list):
|
||||||
|
for user in users:
|
||||||
|
if isinstance(user, int):
|
||||||
|
success &= self._has(user)
|
||||||
|
|
||||||
|
return success
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
107
client/src/telethon/_impl/session/chat/packed.py
Normal file
107
client/src/telethon/_impl/session/chat/packed.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import struct
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional, Self
|
||||||
|
|
||||||
|
from telethon._impl.tl import abcs, types
|
||||||
|
|
||||||
|
|
||||||
|
class PackedType(Enum):
|
||||||
|
# bits: zero, has-access-hash, channel, broadcast, group, chat, user, bot
|
||||||
|
USER = 0b0000_0010
|
||||||
|
BOT = 0b0000_0011
|
||||||
|
CHAT = 0b0000_0100
|
||||||
|
MEGAGROUP = 0b0010_1000
|
||||||
|
BROADCAST = 0b0011_0000
|
||||||
|
GIGAGROUP = 0b0011_1000
|
||||||
|
|
||||||
|
|
||||||
|
class PackedChat:
|
||||||
|
__slots__ = ("ty", "id", "access_hash")
|
||||||
|
|
||||||
|
def __init__(self, ty: PackedType, id: int, access_hash: Optional[int]) -> None:
|
||||||
|
self.ty = ty
|
||||||
|
self.id = id
|
||||||
|
self.access_hash = access_hash
|
||||||
|
|
||||||
|
def __bytes__(self) -> bytes:
|
||||||
|
return struct.pack(
|
||||||
|
"<Bqq",
|
||||||
|
self.ty.value | (0 if self.access_hash is None else 0b0100_0000),
|
||||||
|
self.id,
|
||||||
|
self.access_hash or 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, data: bytes) -> Self:
|
||||||
|
ty_byte, id, access_hash = struct.unpack("<Bqq", data)
|
||||||
|
has_hash = (ty_byte & 0b0100_0000) != 0
|
||||||
|
ty = PackedType(ty_byte & 0b0011_1111)
|
||||||
|
return cls(ty, id, access_hash if has_hash else None)
|
||||||
|
|
||||||
|
def is_user(self) -> bool:
|
||||||
|
return self.ty in (PackedType.USER, PackedType.BOT)
|
||||||
|
|
||||||
|
def is_chat(self) -> bool:
|
||||||
|
return self.ty in (PackedType.CHAT,)
|
||||||
|
|
||||||
|
def is_channel(self) -> bool:
|
||||||
|
return self.ty in (
|
||||||
|
PackedType.MEGAGROUP,
|
||||||
|
PackedType.BROADCAST,
|
||||||
|
PackedType.GIGAGROUP,
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_peer(self) -> abcs.Peer:
|
||||||
|
if self.is_user():
|
||||||
|
return types.PeerUser(user_id=self.id)
|
||||||
|
elif self.is_chat():
|
||||||
|
return types.PeerChat(chat_id=self.id)
|
||||||
|
elif self.is_channel():
|
||||||
|
return types.PeerChannel(channel_id=self.id)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def to_input_peer(self) -> abcs.InputPeer:
|
||||||
|
if self.is_user():
|
||||||
|
return types.InputPeerUser(
|
||||||
|
user_id=self.id, access_hash=self.access_hash or 0
|
||||||
|
)
|
||||||
|
elif self.is_chat():
|
||||||
|
return types.InputPeerChat(chat_id=self.id)
|
||||||
|
elif self.is_channel():
|
||||||
|
return types.InputPeerChannel(
|
||||||
|
channel_id=self.id, access_hash=self.access_hash or 0
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
def try_to_input_user(self) -> Optional[abcs.InputUser]:
|
||||||
|
if self.is_user():
|
||||||
|
return types.InputUser(user_id=self.id, access_hash=self.access_hash or 0)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def to_input_user_lossy(self) -> abcs.InputUser:
|
||||||
|
return self.try_to_input_user() or types.InputUser(user_id=0, access_hash=0)
|
||||||
|
|
||||||
|
def try_to_chat_id(self) -> Optional[int]:
|
||||||
|
return self.id if self.is_chat() else None
|
||||||
|
|
||||||
|
def try_to_input_channel(self) -> Optional[abcs.InputChannel]:
|
||||||
|
return (
|
||||||
|
types.InputChannel(channel_id=self.id, access_hash=self.access_hash or 0)
|
||||||
|
if self.is_channel()
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
return (
|
||||||
|
self.ty == other.ty
|
||||||
|
and self.id == other.id
|
||||||
|
and self.access_hash == other.access_hash
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"PackedChat.{self.ty.name}({self.id})"
|
243
client/src/telethon/_impl/session/message_box/adaptor.py
Normal file
243
client/src/telethon/_impl/session/message_box/adaptor.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
from ...tl import abcs, types
|
||||||
|
from ..chat.hash_cache import ChatHashCache
|
||||||
|
from .defs import ACCOUNT_WIDE, NO_SEQ, SECRET_CHATS, Gap
|
||||||
|
|
||||||
|
|
||||||
|
def updates_(updates: types.Updates) -> types.UpdatesCombined:
|
||||||
|
return types.UpdatesCombined(
|
||||||
|
updates=updates.updates,
|
||||||
|
users=updates.users,
|
||||||
|
chats=updates.chats,
|
||||||
|
date=updates.date,
|
||||||
|
seq_start=updates.seq,
|
||||||
|
seq=updates.seq,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_short(short: types.UpdateShort) -> types.UpdatesCombined:
|
||||||
|
return types.UpdatesCombined(
|
||||||
|
updates=[short.update],
|
||||||
|
users=[],
|
||||||
|
chats=[],
|
||||||
|
date=short.date,
|
||||||
|
seq_start=NO_SEQ,
|
||||||
|
seq=NO_SEQ,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_short_message(
|
||||||
|
short: types.UpdateShortMessage, self_id: int
|
||||||
|
) -> types.UpdatesCombined:
|
||||||
|
return update_short(
|
||||||
|
types.UpdateShort(
|
||||||
|
update=types.UpdateNewMessage(
|
||||||
|
message=types.Message(
|
||||||
|
out=short.out,
|
||||||
|
mentioned=short.mentioned,
|
||||||
|
media_unread=short.media_unread,
|
||||||
|
silent=short.silent,
|
||||||
|
post=False,
|
||||||
|
from_scheduled=False,
|
||||||
|
legacy=False,
|
||||||
|
edit_hide=False,
|
||||||
|
pinned=False,
|
||||||
|
noforwards=False,
|
||||||
|
reactions=None,
|
||||||
|
id=short.id,
|
||||||
|
from_id=types.PeerUser(
|
||||||
|
user_id=self_id if short.out else short.user_id
|
||||||
|
),
|
||||||
|
peer_id=types.PeerChat(
|
||||||
|
chat_id=short.user_id,
|
||||||
|
),
|
||||||
|
fwd_from=short.fwd_from,
|
||||||
|
via_bot_id=short.via_bot_id,
|
||||||
|
reply_to=short.reply_to,
|
||||||
|
date=short.date,
|
||||||
|
message=short.message,
|
||||||
|
media=None,
|
||||||
|
reply_markup=None,
|
||||||
|
entities=short.entities,
|
||||||
|
views=None,
|
||||||
|
forwards=None,
|
||||||
|
replies=None,
|
||||||
|
edit_date=None,
|
||||||
|
post_author=None,
|
||||||
|
grouped_id=None,
|
||||||
|
restriction_reason=None,
|
||||||
|
ttl_period=short.ttl_period,
|
||||||
|
),
|
||||||
|
pts=short.pts,
|
||||||
|
pts_count=short.pts_count,
|
||||||
|
),
|
||||||
|
date=short.date,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_short_chat_message(
|
||||||
|
short: types.UpdateShortChatMessage,
|
||||||
|
) -> types.UpdatesCombined:
|
||||||
|
return update_short(
|
||||||
|
types.UpdateShort(
|
||||||
|
update=types.UpdateNewMessage(
|
||||||
|
message=types.Message(
|
||||||
|
out=short.out,
|
||||||
|
mentioned=short.mentioned,
|
||||||
|
media_unread=short.media_unread,
|
||||||
|
silent=short.silent,
|
||||||
|
post=False,
|
||||||
|
from_scheduled=False,
|
||||||
|
legacy=False,
|
||||||
|
edit_hide=False,
|
||||||
|
pinned=False,
|
||||||
|
noforwards=False,
|
||||||
|
reactions=None,
|
||||||
|
id=short.id,
|
||||||
|
from_id=types.PeerUser(
|
||||||
|
user_id=short.from_id,
|
||||||
|
),
|
||||||
|
peer_id=types.PeerChat(
|
||||||
|
chat_id=short.chat_id,
|
||||||
|
),
|
||||||
|
fwd_from=short.fwd_from,
|
||||||
|
via_bot_id=short.via_bot_id,
|
||||||
|
reply_to=short.reply_to,
|
||||||
|
date=short.date,
|
||||||
|
message=short.message,
|
||||||
|
media=None,
|
||||||
|
reply_markup=None,
|
||||||
|
entities=short.entities,
|
||||||
|
views=None,
|
||||||
|
forwards=None,
|
||||||
|
replies=None,
|
||||||
|
edit_date=None,
|
||||||
|
post_author=None,
|
||||||
|
grouped_id=None,
|
||||||
|
restriction_reason=None,
|
||||||
|
ttl_period=short.ttl_period,
|
||||||
|
),
|
||||||
|
pts=short.pts,
|
||||||
|
pts_count=short.pts_count,
|
||||||
|
),
|
||||||
|
date=short.date,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_short_sent_message(
|
||||||
|
short: types.UpdateShortSentMessage,
|
||||||
|
) -> types.UpdatesCombined:
|
||||||
|
return update_short(
|
||||||
|
types.UpdateShort(
|
||||||
|
update=types.UpdateNewMessage(
|
||||||
|
message=types.MessageEmpty(
|
||||||
|
id=short.id,
|
||||||
|
peer_id=None,
|
||||||
|
),
|
||||||
|
pts=short.pts,
|
||||||
|
pts_count=short.pts_count,
|
||||||
|
),
|
||||||
|
date=short.date,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def adapt(updates: abcs.Updates, chat_hashes: ChatHashCache) -> types.UpdatesCombined:
|
||||||
|
if isinstance(updates, types.UpdatesTooLong):
|
||||||
|
raise Gap
|
||||||
|
elif isinstance(updates, types.UpdateShortMessage):
|
||||||
|
return update_short_message(updates, chat_hashes.self_id)
|
||||||
|
elif isinstance(updates, types.UpdateShortChatMessage):
|
||||||
|
return update_short_chat_message(updates)
|
||||||
|
elif isinstance(updates, types.UpdateShort):
|
||||||
|
return update_short(updates)
|
||||||
|
elif isinstance(updates, types.UpdatesCombined):
|
||||||
|
return updates
|
||||||
|
elif isinstance(updates, types.Updates):
|
||||||
|
return updates_(updates)
|
||||||
|
elif isinstance(updates, types.UpdateShortSentMessage):
|
||||||
|
return update_short_sent_message(updates)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
|
||||||
|
def message_peer(message: abcs.Message) -> Optional[abcs.Peer]:
|
||||||
|
if isinstance(message, types.MessageEmpty):
|
||||||
|
return None
|
||||||
|
elif isinstance(message, types.Message):
|
||||||
|
return message.peer_id
|
||||||
|
elif isinstance(message, types.MessageService):
|
||||||
|
return message.peer_id
|
||||||
|
else:
|
||||||
|
raise RuntimeError("unexpected case")
|
||||||
|
|
||||||
|
|
||||||
|
def message_channel_id(message: abcs.Message) -> Optional[int]:
|
||||||
|
peer = message_peer(message)
|
||||||
|
return peer.channel_id if isinstance(peer, types.PeerChannel) else None
|
||||||
|
|
||||||
|
|
||||||
|
def pts_info_from_update(update: abcs.Update) -> Optional[Tuple[int | str, int, int]]:
|
||||||
|
if isinstance(update, types.UpdateNewMessage):
|
||||||
|
assert not isinstance(message_peer(update.message), types.PeerChannel)
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateDeleteMessages):
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateNewEncryptedMessage):
|
||||||
|
return SECRET_CHATS, update.qts, 1
|
||||||
|
elif isinstance(update, types.UpdateReadHistoryInbox):
|
||||||
|
assert not isinstance(update.peer, types.PeerChannel)
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateReadHistoryOutbox):
|
||||||
|
assert not isinstance(update.peer, types.PeerChannel)
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateWebPage):
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateReadMessagesContents):
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateChannelTooLong):
|
||||||
|
if update.pts is not None:
|
||||||
|
return update.channel_id, update.pts, 0
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
elif isinstance(update, types.UpdateNewChannelMessage):
|
||||||
|
channel_id = message_channel_id(update.message)
|
||||||
|
if channel_id is not None:
|
||||||
|
return channel_id, update.pts, update.pts_count
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
elif isinstance(update, types.UpdateReadChannelInbox):
|
||||||
|
return update.channel_id, update.pts, 0
|
||||||
|
elif isinstance(update, types.UpdateDeleteChannelMessages):
|
||||||
|
return update.channel_id, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateEditChannelMessage):
|
||||||
|
channel_id = message_channel_id(update.message)
|
||||||
|
if channel_id is not None:
|
||||||
|
return channel_id, update.pts, update.pts_count
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
elif isinstance(update, types.UpdateEditMessage):
|
||||||
|
assert not isinstance(message_peer(update.message), types.PeerChannel)
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateChannelWebPage):
|
||||||
|
return update.channel_id, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateFolderPeers):
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdatePinnedMessages):
|
||||||
|
assert not isinstance(update.peer, types.PeerChannel)
|
||||||
|
return ACCOUNT_WIDE, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdatePinnedChannelMessages):
|
||||||
|
return update.channel_id, update.pts, update.pts_count
|
||||||
|
elif isinstance(update, types.UpdateChatParticipant):
|
||||||
|
return SECRET_CHATS, update.qts, 0
|
||||||
|
elif isinstance(update, types.UpdateChannelParticipant):
|
||||||
|
return SECRET_CHATS, update.qts, 0
|
||||||
|
elif isinstance(update, types.UpdateBotStopped):
|
||||||
|
return SECRET_CHATS, update.qts, 0
|
||||||
|
elif isinstance(update, types.UpdateBotChatInviteRequester):
|
||||||
|
return SECRET_CHATS, update.qts, 0
|
||||||
|
else:
|
||||||
|
return None
|
19
client/src/telethon/_impl/session/message_box/defs.py
Normal file
19
client/src/telethon/_impl/session/message_box/defs.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
NO_SEQ = 0
|
||||||
|
|
||||||
|
NO_PTS = 0
|
||||||
|
|
||||||
|
# https://core.telegram.org/method/updates.getChannelDifference
|
||||||
|
BOT_CHANNEL_DIFF_LIMIT = 100000
|
||||||
|
USER_CHANNEL_DIFF_LIMIT = 100
|
||||||
|
|
||||||
|
POSSIBLE_GAP_TIMEOUT = 0.5
|
||||||
|
|
||||||
|
# https://core.telegram.org/api/updates
|
||||||
|
NO_UPDATES_TIMEOUT = 15 * 60
|
||||||
|
|
||||||
|
ACCOUNT_WIDE = "ACCOUNT"
|
||||||
|
SECRET_CHATS = "SECRET"
|
||||||
|
|
||||||
|
|
||||||
|
class Gap(ValueError):
|
||||||
|
pass
|
10
client/tests/packed_chat_test.py
Normal file
10
client/tests/packed_chat_test.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from telethon._impl.session.chat.packed import PackedChat, PackedType
|
||||||
|
|
||||||
|
|
||||||
|
def test_hash_optional() -> None:
|
||||||
|
for ty in PackedType:
|
||||||
|
pc = PackedChat(ty, 123, 456789)
|
||||||
|
assert PackedChat.from_bytes(bytes(pc)) == pc
|
||||||
|
|
||||||
|
pc = PackedChat(ty, 987, None)
|
||||||
|
assert PackedChat.from_bytes(bytes(pc)) == pc
|
Loading…
Reference in New Issue
Block a user