mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-06-29 09:53:07 +03:00
Begin updating the way updates are built
This commit is contained in:
parent
c914a92dcf
commit
483e2aadf1
|
@ -170,7 +170,7 @@ async def _update_loop(self: 'TelegramClient'):
|
||||||
updates_to_dispatch = deque()
|
updates_to_dispatch = deque()
|
||||||
while self.is_connected():
|
while self.is_connected():
|
||||||
if updates_to_dispatch:
|
if updates_to_dispatch:
|
||||||
await _dispatch(self, updates_to_dispatch.popleft())
|
await _dispatch(self, *updates_to_dispatch.popleft())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
get_diff = self._message_box.get_difference()
|
get_diff = self._message_box.get_difference()
|
||||||
|
@ -178,8 +178,7 @@ async def _update_loop(self: 'TelegramClient'):
|
||||||
self._log[__name__].info('Getting difference for account updates')
|
self._log[__name__].info('Getting difference for account updates')
|
||||||
diff = await self(get_diff)
|
diff = await self(get_diff)
|
||||||
updates, users, chats = self._message_box.apply_difference(diff, self._entity_cache)
|
updates, users, chats = self._message_box.apply_difference(diff, self._entity_cache)
|
||||||
self._entity_cache.extend(users, chats)
|
updates_to_dispatch.extend(_preprocess_updates(self, updates, users, chats))
|
||||||
updates_to_dispatch.extend(updates)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
get_diff = self._message_box.get_channel_difference(self._entity_cache)
|
get_diff = self._message_box.get_channel_difference(self._entity_cache)
|
||||||
|
@ -187,8 +186,7 @@ async def _update_loop(self: 'TelegramClient'):
|
||||||
self._log[__name__].info('Getting difference for channel updates')
|
self._log[__name__].info('Getting difference for channel updates')
|
||||||
diff = await self(get_diff)
|
diff = await self(get_diff)
|
||||||
updates, users, chats = self._message_box.apply_channel_difference(get_diff, diff, self._entity_cache)
|
updates, users, chats = self._message_box.apply_channel_difference(get_diff, diff, self._entity_cache)
|
||||||
self._entity_cache.extend(users, chats)
|
updates_to_dispatch.extend(_preprocess_updates(self, updates, users, chats))
|
||||||
updates_to_dispatch.extend(updates)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
deadline = self._message_box.check_deadlines()
|
deadline = self._message_box.check_deadlines()
|
||||||
|
@ -203,21 +201,50 @@ async def _update_loop(self: 'TelegramClient'):
|
||||||
|
|
||||||
processed = []
|
processed = []
|
||||||
users, chats = self._message_box.process_updates(updates, self._entity_cache, processed)
|
users, chats = self._message_box.process_updates(updates, self._entity_cache, processed)
|
||||||
self._entity_cache.extend(users, chats)
|
updates_to_dispatch.extend(_preprocess_updates(self, processed, users, chats))
|
||||||
updates_to_dispatch.extend(processed)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self._log[__name__].exception('Fatal error handling updates (this is a bug in Telethon, please report it)')
|
self._log[__name__].exception('Fatal error handling updates (this is a bug in Telethon, please report it)')
|
||||||
|
|
||||||
|
|
||||||
async def _dispatch(self, update):
|
def _preprocess_updates(self, updates, users, chats):
|
||||||
self._dispatching_update_handlers = True
|
self._entity_cache.extend(users, chats)
|
||||||
|
entities = Entities(self, users, chats)
|
||||||
|
return ((u, entities) for u in updates)
|
||||||
|
|
||||||
|
|
||||||
|
class Entities:
|
||||||
|
def __init__(self, client, users, chats):
|
||||||
|
self.self_id = client._session_state.user_id
|
||||||
|
self._entities = {e.id: e for e in itertools.chain(
|
||||||
|
(User(client, u) for u in users),
|
||||||
|
(Chat(client, c) for u in chats),
|
||||||
|
)}
|
||||||
|
|
||||||
|
def get(self, client, peer):
|
||||||
|
if not peer:
|
||||||
|
return None
|
||||||
|
|
||||||
|
id = utils.get_peer_id(peer)
|
||||||
|
try:
|
||||||
|
return self._entities[id]
|
||||||
|
except KeyError:
|
||||||
|
entity = client._entity_cache.get(query.user_id)
|
||||||
|
if not entity:
|
||||||
|
raise RuntimeError('Update is missing a hash but did not trigger a gap')
|
||||||
|
|
||||||
|
self._entities[entity.id] = User(client, entity) if entity.is_user else Chat(client, entity)
|
||||||
|
return self._entities[entity.id]
|
||||||
|
|
||||||
|
|
||||||
|
async def _dispatch(self, update, entities):
|
||||||
|
self._dispatching_update_handlers = True
|
||||||
|
try:
|
||||||
event_cache = {}
|
event_cache = {}
|
||||||
for handler in self._update_handlers:
|
for handler in self._update_handlers:
|
||||||
event = event_cache.get(handler._event)
|
event, entities = event_cache.get(handler._event)
|
||||||
if not event:
|
if not event:
|
||||||
event_cache[handler._event] = event = handler._event._build(
|
# build can fail if we're missing an access hash; we want this to crash
|
||||||
update, [], self._session_state.user_id, {}, self)
|
event_cache[handler._event] = event = handler._event._build(self, update, entities)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# filters can be modified at any time, and there can be any amount of them which are not yet resolved
|
# filters can be modified at any time, and there can be any amount of them which are not yet resolved
|
||||||
|
@ -226,7 +253,6 @@ async def _dispatch(self, update):
|
||||||
try:
|
try:
|
||||||
await handler._callback(event)
|
await handler._callback(event)
|
||||||
except StopPropagation:
|
except StopPropagation:
|
||||||
self._dispatching_update_handlers = False
|
|
||||||
return
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
name = getattr(handler._callback, '__name__', repr(handler._callback))
|
name = getattr(handler._callback, '__name__', repr(handler._callback))
|
||||||
|
@ -244,5 +270,5 @@ async def _dispatch(self, update):
|
||||||
|
|
||||||
# we only want to continue on unresolved filter (to check if there are more unresolved)
|
# we only want to continue on unresolved filter (to check if there are more unresolved)
|
||||||
break
|
break
|
||||||
|
finally:
|
||||||
self._dispatching_update_handlers = False
|
self._dispatching_update_handlers = False
|
||||||
|
|
|
@ -107,7 +107,7 @@ class Album(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.Se
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, message.sender_id)
|
_custom.sendergetter.SenderGetter.__init__(self, message.sender_id)
|
||||||
self.messages = messages
|
self.messages = messages
|
||||||
|
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, client, update, entities):
|
||||||
if not others:
|
if not others:
|
||||||
return # We only care about albums which come inside the same Updates
|
return # We only care about albums which come inside the same Updates
|
||||||
|
|
||||||
|
@ -146,6 +146,12 @@ class Album(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergetter.Se
|
||||||
and u.message.grouped_id == group)
|
and u.message.grouped_id == group)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._client = client
|
||||||
|
self._sender = entities.get(_tl.PeerUser(update.user_id))
|
||||||
|
self._chat = entities.get(_tl.PeerUser(update.user_id))
|
||||||
|
return self
|
||||||
|
|
||||||
def _set_client(self, client):
|
def _set_client(self, client):
|
||||||
super()._set_client(client)
|
super()._set_client(client)
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
||||||
|
|
|
@ -34,15 +34,12 @@ class StopPropagation(Exception):
|
||||||
class EventBuilder(abc.ABC):
|
class EventBuilder(abc.ABC):
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _build(cls, update, others, self_id, entities, client):
|
def _build(cls, client, update, entities):
|
||||||
"""
|
"""
|
||||||
Builds an event for the given update if possible, or returns None.
|
Builds an event for the given update if possible, or returns None.
|
||||||
|
|
||||||
`others` are the rest of updates that came in the same container
|
`entities` must have `get(Peer) -> User|Chat` and `self_id`,
|
||||||
as the current `update`.
|
which must be the current user's ID.
|
||||||
|
|
||||||
`self_id` should be the current user's ID, since it is required
|
|
||||||
for some events which lack this information but still need it.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -69,29 +69,32 @@ class CallbackQuery(EventBuilder, _custom.chatgetter.ChatGetter, _custom.senderg
|
||||||
Button.inline('Nope', b'no')
|
Button.inline('Nope', b'no')
|
||||||
])
|
])
|
||||||
"""
|
"""
|
||||||
def __init__(self, query, peer, msg_id):
|
@classmethod
|
||||||
_custom.chatgetter.ChatGetter.__init__(self, peer)
|
def _build(cls, client, update, entities):
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, query.user_id)
|
query = update
|
||||||
|
if isinstance(update, _tl.UpdateBotCallbackQuery):
|
||||||
|
peer = update.peer
|
||||||
|
msg_id = update.msg_id
|
||||||
|
elif isinstance(update, _tl.UpdateInlineBotCallbackQuery):
|
||||||
|
# See https://github.com/LonamiWebs/Telethon/pull/1005
|
||||||
|
# The long message ID is actually just msg_id + peer_id
|
||||||
|
msg_id, pid = struct.unpack('<ii', struct.pack('<q', update.msg_id.id))
|
||||||
|
peer = _tl.PeerChannel(-pid) if pid < 0 else _tl.PeerUser(pid)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._client = client
|
||||||
|
self._sender = entities.get(_tl.PeerUser(query.user_id))
|
||||||
|
self._chat = entities.get(peer)
|
||||||
|
|
||||||
self.query = query
|
self.query = query
|
||||||
self.data_match = None
|
self.data_match = None
|
||||||
self.pattern_match = None
|
self.pattern_match = None
|
||||||
self._message = None
|
self._message = None
|
||||||
self._answered = False
|
self._answered = False
|
||||||
|
|
||||||
@classmethod
|
return self
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
|
||||||
if isinstance(update, _tl.UpdateBotCallbackQuery):
|
|
||||||
return cls.Event(update, update.peer, update.msg_id)
|
|
||||||
elif isinstance(update, _tl.UpdateInlineBotCallbackQuery):
|
|
||||||
# See https://github.com/LonamiWebs/Telethon/pull/1005
|
|
||||||
# The long message ID is actually just msg_id + peer_id
|
|
||||||
mid, pid = struct.unpack('<ii', struct.pack('<q', update.msg_id.id))
|
|
||||||
peer = _tl.PeerChannel(-pid) if pid < 0 else _tl.PeerUser(pid)
|
|
||||||
return cls.Event(update, peer, mid)
|
|
||||||
|
|
||||||
def _set_client(self, client):
|
|
||||||
super()._set_client(client)
|
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
|
|
|
@ -72,18 +72,110 @@ class ChatAction(EventBuilder):
|
||||||
await event.reply('Welcome to the group!')
|
await event.reply('Welcome to the group!')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, where, new_photo=None,
|
@classmethod
|
||||||
added_by=None, kicked_by=None, created=None, from_approval=None,
|
def _build(cls, client, update, entities):
|
||||||
users=None, new_title=None, pin_ids=None, pin=None, new_score=None):
|
where = None
|
||||||
|
new_photo = None
|
||||||
|
added_by = None
|
||||||
|
kicked_by = None
|
||||||
|
created = None
|
||||||
|
from_approval = None
|
||||||
|
users = None
|
||||||
|
new_title = None
|
||||||
|
pin_ids = None
|
||||||
|
pin = None
|
||||||
|
new_score = None
|
||||||
|
|
||||||
|
# Rely on specific pin updates for unpins, but otherwise ignore them
|
||||||
|
# for new pins (we'd rather handle the new service message with pin,
|
||||||
|
# so that we can act on that message').
|
||||||
|
if isinstance(update, _tl.UpdatePinnedChannelMessages) and not update.pinned:
|
||||||
|
where = _tl.PeerChannel(update.channel_id)
|
||||||
|
pin_ids = update.messages
|
||||||
|
pin = update.pinned
|
||||||
|
|
||||||
|
elif isinstance(update, _tl.UpdatePinnedMessages) and not update.pinned:
|
||||||
|
where = update.peer
|
||||||
|
pin_ids = update.messages
|
||||||
|
pin = update.pinned
|
||||||
|
|
||||||
|
elif isinstance(update, _tl.UpdateChatParticipantAdd):
|
||||||
|
where = _tl.PeerChat(update.chat_id)
|
||||||
|
added_by = update.inviter_id or True
|
||||||
|
users = update.user_id
|
||||||
|
|
||||||
|
elif isinstance(update, _tl.UpdateChatParticipantDelete):
|
||||||
|
where = _tl.PeerChat(update.chat_id)
|
||||||
|
kicked_by = True
|
||||||
|
users = update.user_id
|
||||||
|
|
||||||
|
# UpdateChannel is sent if we leave a channel, and the update._entities
|
||||||
|
# set by _process_update would let us make some guesses. However it's
|
||||||
|
# better not to rely on this. Rely only in MessageActionChatDeleteUser.
|
||||||
|
|
||||||
|
elif (isinstance(update, (
|
||||||
|
_tl.UpdateNewMessage, _tl.UpdateNewChannelMessage))
|
||||||
|
and isinstance(update.message, _tl.MessageService)):
|
||||||
|
msg = update.message
|
||||||
|
action = update.message.action
|
||||||
|
if isinstance(action, _tl.MessageActionChatJoinedByLink):
|
||||||
|
where = msg
|
||||||
|
added_by = True
|
||||||
|
users = msg.from_id
|
||||||
|
elif isinstance(action, _tl.MessageActionChatAddUser):
|
||||||
|
# If a user adds itself, it means they joined via the public chat username
|
||||||
|
added_by = ([msg.sender_id] == action.users) or msg.from_id
|
||||||
|
where = msg
|
||||||
|
added_by = added_by
|
||||||
|
users = action.users
|
||||||
|
elif isinstance(action, _tl.MessageActionChatJoinedByRequest):
|
||||||
|
# user joined from join request (after getting admin approval)
|
||||||
|
where = msg
|
||||||
|
from_approval = True
|
||||||
|
users = msg.from_id
|
||||||
|
elif isinstance(action, _tl.MessageActionChatDeleteUser):
|
||||||
|
where = msg
|
||||||
|
kicked_by = utils.get_peer_id(msg.from_id) if msg.from_id else True
|
||||||
|
users = action.user_id
|
||||||
|
elif isinstance(action, _tl.MessageActionChatCreate):
|
||||||
|
where = msg
|
||||||
|
users = action.users
|
||||||
|
created = True
|
||||||
|
new_title = action.title
|
||||||
|
elif isinstance(action, _tl.MessageActionChannelCreate):
|
||||||
|
where = msg
|
||||||
|
created = True
|
||||||
|
users = msg.from_id
|
||||||
|
new_title = action.title
|
||||||
|
elif isinstance(action, _tl.MessageActionChatEditTitle):
|
||||||
|
where = msg
|
||||||
|
users = msg.from_id
|
||||||
|
new_title = action.title
|
||||||
|
elif isinstance(action, _tl.MessageActionChatEditPhoto):
|
||||||
|
where = msg
|
||||||
|
users = msg.from_id
|
||||||
|
new_photo = action.photo
|
||||||
|
elif isinstance(action, _tl.MessageActionChatDeletePhoto):
|
||||||
|
where = msg
|
||||||
|
users = msg.from_id
|
||||||
|
new_photo = True
|
||||||
|
elif isinstance(action, _tl.MessageActionPinMessage) and msg.reply_to:
|
||||||
|
where = msg
|
||||||
|
pin_ids=[msg.reply_to_msg_id]
|
||||||
|
elif isinstance(action, _tl.MessageActionGameScore):
|
||||||
|
where = msg
|
||||||
|
new_score = action.score
|
||||||
|
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._client = client
|
||||||
|
|
||||||
if isinstance(where, _tl.MessageService):
|
if isinstance(where, _tl.MessageService):
|
||||||
self.action_message = where
|
self.action_message = where
|
||||||
where = where.peer_id
|
where = where.peer_id
|
||||||
else:
|
else:
|
||||||
self.action_message = None
|
self.action_message = None
|
||||||
|
|
||||||
# TODO needs some testing (can there be more than one id, and do they follow pin order?)
|
self._chat = entities.get(where)
|
||||||
# same in get_pinned_message
|
|
||||||
super().__init__(chat_peer=where, msg_id=pin_ids[0] if pin_ids else None)
|
|
||||||
|
|
||||||
self.new_pin = pin_ids is not None
|
self.new_pin = pin_ids is not None
|
||||||
self._pin_ids = pin_ids
|
self._pin_ids = pin_ids
|
||||||
|
@ -128,87 +220,7 @@ class ChatAction(EventBuilder):
|
||||||
self.new_score = new_score
|
self.new_score = new_score
|
||||||
self.unpin = not pin
|
self.unpin = not pin
|
||||||
|
|
||||||
@classmethod
|
return self
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
|
||||||
# Rely on specific pin updates for unpins, but otherwise ignore them
|
|
||||||
# for new pins (we'd rather handle the new service message with pin,
|
|
||||||
# so that we can act on that message').
|
|
||||||
if isinstance(update, _tl.UpdatePinnedChannelMessages) and not update.pinned:
|
|
||||||
return cls.Event(_tl.PeerChannel(update.channel_id),
|
|
||||||
pin_ids=update.messages,
|
|
||||||
pin=update.pinned)
|
|
||||||
|
|
||||||
elif isinstance(update, _tl.UpdatePinnedMessages) and not update.pinned:
|
|
||||||
return cls.Event(update.peer,
|
|
||||||
pin_ids=update.messages,
|
|
||||||
pin=update.pinned)
|
|
||||||
|
|
||||||
elif isinstance(update, _tl.UpdateChatParticipantAdd):
|
|
||||||
return cls.Event(_tl.PeerChat(update.chat_id),
|
|
||||||
added_by=update.inviter_id or True,
|
|
||||||
users=update.user_id)
|
|
||||||
|
|
||||||
elif isinstance(update, _tl.UpdateChatParticipantDelete):
|
|
||||||
return cls.Event(_tl.PeerChat(update.chat_id),
|
|
||||||
kicked_by=True,
|
|
||||||
users=update.user_id)
|
|
||||||
|
|
||||||
# UpdateChannel is sent if we leave a channel, and the update._entities
|
|
||||||
# set by _process_update would let us make some guesses. However it's
|
|
||||||
# better not to rely on this. Rely only in MessageActionChatDeleteUser.
|
|
||||||
|
|
||||||
elif (isinstance(update, (
|
|
||||||
_tl.UpdateNewMessage, _tl.UpdateNewChannelMessage))
|
|
||||||
and isinstance(update.message, _tl.MessageService)):
|
|
||||||
msg = update.message
|
|
||||||
action = update.message.action
|
|
||||||
if isinstance(action, _tl.MessageActionChatJoinedByLink):
|
|
||||||
return cls.Event(msg,
|
|
||||||
added_by=True,
|
|
||||||
users=msg.from_id)
|
|
||||||
elif isinstance(action, _tl.MessageActionChatAddUser):
|
|
||||||
# If a user adds itself, it means they joined via the public chat username
|
|
||||||
added_by = ([msg.sender_id] == action.users) or msg.from_id
|
|
||||||
return cls.Event(msg,
|
|
||||||
added_by=added_by,
|
|
||||||
users=action.users)
|
|
||||||
elif isinstance(action, _tl.MessageActionChatJoinedByRequest):
|
|
||||||
# user joined from join request (after getting admin approval)
|
|
||||||
return cls.Event(msg,
|
|
||||||
from_approval=True,
|
|
||||||
users=msg.from_id)
|
|
||||||
elif isinstance(action, _tl.MessageActionChatDeleteUser):
|
|
||||||
return cls.Event(msg,
|
|
||||||
kicked_by=utils.get_peer_id(msg.from_id) if msg.from_id else True,
|
|
||||||
users=action.user_id)
|
|
||||||
elif isinstance(action, _tl.MessageActionChatCreate):
|
|
||||||
return cls.Event(msg,
|
|
||||||
users=action.users,
|
|
||||||
created=True,
|
|
||||||
new_title=action.title)
|
|
||||||
elif isinstance(action, _tl.MessageActionChannelCreate):
|
|
||||||
return cls.Event(msg,
|
|
||||||
created=True,
|
|
||||||
users=msg.from_id,
|
|
||||||
new_title=action.title)
|
|
||||||
elif isinstance(action, _tl.MessageActionChatEditTitle):
|
|
||||||
return cls.Event(msg,
|
|
||||||
users=msg.from_id,
|
|
||||||
new_title=action.title)
|
|
||||||
elif isinstance(action, _tl.MessageActionChatEditPhoto):
|
|
||||||
return cls.Event(msg,
|
|
||||||
users=msg.from_id,
|
|
||||||
new_photo=action.photo)
|
|
||||||
elif isinstance(action, _tl.MessageActionChatDeletePhoto):
|
|
||||||
return cls.Event(msg,
|
|
||||||
users=msg.from_id,
|
|
||||||
new_photo=True)
|
|
||||||
elif isinstance(action, _tl.MessageActionPinMessage) and msg.reply_to:
|
|
||||||
return cls.Event(msg,
|
|
||||||
pin_ids=[msg.reply_to_msg_id])
|
|
||||||
elif isinstance(action, _tl.MessageActionGameScore):
|
|
||||||
return cls.Event(msg,
|
|
||||||
new_score=action.score)
|
|
||||||
|
|
||||||
async def respond(self, *args, **kwargs):
|
async def respond(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -40,21 +40,19 @@ class InlineQuery(EventBuilder, _custom.chatgetter.ChatGetter, _custom.senderget
|
||||||
builder.article('lowercase', text=event.text.lower()),
|
builder.article('lowercase', text=event.text.lower()),
|
||||||
])
|
])
|
||||||
"""
|
"""
|
||||||
def __init__(self, query):
|
@classmethod
|
||||||
_custom.chatgetter.ChatGetter.__init__(self, _tl.PeerUser(query.user_id))
|
def _build(cls, client, update, entities):
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, query.user_id)
|
if not isinstance(update, _tl.UpdateBotInlineQuery):
|
||||||
self.query = query
|
return None
|
||||||
|
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._client = client
|
||||||
|
self._sender = entities.get(_tl.PeerUser(update.user_id))
|
||||||
|
self._chat = entities.get(_tl.PeerUser(update.user_id))
|
||||||
|
self.query = update
|
||||||
self.pattern_match = None
|
self.pattern_match = None
|
||||||
self._answered = False
|
self._answered = False
|
||||||
|
return self
|
||||||
@classmethod
|
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
|
||||||
if isinstance(update, _tl.UpdateBotInlineQuery):
|
|
||||||
return cls.Event(update)
|
|
||||||
|
|
||||||
def _set_client(self, client):
|
|
||||||
super()._set_client(client)
|
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
|
|
|
@ -35,20 +35,18 @@ class MessageDeleted(EventBuilder, _custom.chatgetter.ChatGetter):
|
||||||
for msg_id in event.deleted_ids:
|
for msg_id in event.deleted_ids:
|
||||||
print('Message', msg_id, 'was deleted in', event.chat_id)
|
print('Message', msg_id, 'was deleted in', event.chat_id)
|
||||||
"""
|
"""
|
||||||
def __init__(self, deleted_ids, peer):
|
|
||||||
_custom.chatgetter.ChatGetter.__init__(self, chat_peer=peer)
|
|
||||||
self.deleted_id = None if not deleted_ids else deleted_ids[0]
|
|
||||||
self.deleted_ids = deleted_ids
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, client, update, entities):
|
||||||
if isinstance(update, _tl.UpdateDeleteMessages):
|
if isinstance(update, _tl.UpdateDeleteMessages):
|
||||||
return cls.Event(
|
peer = None
|
||||||
deleted_ids=update.messages,
|
|
||||||
peer=None
|
|
||||||
)
|
|
||||||
elif isinstance(update, _tl.UpdateDeleteChannelMessages):
|
elif isinstance(update, _tl.UpdateDeleteChannelMessages):
|
||||||
return cls.Event(
|
peer = _tl.PeerChannel(update.channel_id)
|
||||||
deleted_ids=update.messages,
|
else:
|
||||||
peer=_tl.PeerChannel(update.channel_id)
|
return None
|
||||||
)
|
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._client = client
|
||||||
|
self._chat = entities.get(peer)
|
||||||
|
self.deleted_id = None if not update.messages else update.messages[0]
|
||||||
|
self.deleted_ids = update.messages
|
||||||
|
return self
|
||||||
|
|
|
@ -41,7 +41,7 @@ class MessageEdited(EventBuilder):
|
||||||
print('Message', event.id, 'changed at', event.date)
|
print('Message', event.id, 'changed at', event.date)
|
||||||
"""
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build(cls, update, others, self_id, entities, client):
|
def _build(cls, client, update, entities):
|
||||||
if isinstance(update, (_tl.UpdateEditMessage,
|
if isinstance(update, (_tl.UpdateEditMessage,
|
||||||
_tl.UpdateEditChannelMessage)):
|
_tl.UpdateEditChannelMessage)):
|
||||||
return cls._new(client, update.message, entities, None)
|
return cls._new(client, update.message, entities, None)
|
||||||
|
|
|
@ -45,24 +45,39 @@ class MessageRead(EventBuilder):
|
||||||
super().__init__(peer, self.max_id)
|
super().__init__(peer, self.max_id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, client, update, entities):
|
||||||
|
out = False
|
||||||
|
contents = False
|
||||||
|
message_ids = None
|
||||||
if isinstance(update, _tl.UpdateReadHistoryInbox):
|
if isinstance(update, _tl.UpdateReadHistoryInbox):
|
||||||
return cls.Event(update.peer, update.max_id, False)
|
peer = update.peer
|
||||||
|
max_id = update.max_id
|
||||||
|
out = False
|
||||||
elif isinstance(update, _tl.UpdateReadHistoryOutbox):
|
elif isinstance(update, _tl.UpdateReadHistoryOutbox):
|
||||||
return cls.Event(update.peer, update.max_id, True)
|
peer = update.peer
|
||||||
|
max_id = update.max_id
|
||||||
|
out = True
|
||||||
elif isinstance(update, _tl.UpdateReadChannelInbox):
|
elif isinstance(update, _tl.UpdateReadChannelInbox):
|
||||||
return cls.Event(_tl.PeerChannel(update.channel_id),
|
peer = _tl.PeerChannel(update.channel_id)
|
||||||
update.max_id, False)
|
max_id = update.max_id
|
||||||
|
out = False
|
||||||
elif isinstance(update, _tl.UpdateReadChannelOutbox):
|
elif isinstance(update, _tl.UpdateReadChannelOutbox):
|
||||||
return cls.Event(_tl.PeerChannel(update.channel_id),
|
peer = _tl.PeerChannel(update.channel_id)
|
||||||
update.max_id, True)
|
max_id = update.max_id
|
||||||
|
out = True
|
||||||
elif isinstance(update, _tl.UpdateReadMessagesContents):
|
elif isinstance(update, _tl.UpdateReadMessagesContents):
|
||||||
return cls.Event(message_ids=update.messages,
|
peer = None
|
||||||
contents=True)
|
message_ids = update.messages
|
||||||
|
contents = True
|
||||||
elif isinstance(update, _tl.UpdateChannelReadMessagesContents):
|
elif isinstance(update, _tl.UpdateChannelReadMessagesContents):
|
||||||
return cls.Event(_tl.PeerChannel(update.channel_id),
|
peer = _tl.PeerChannel(update.channel_id)
|
||||||
message_ids=update.messages,
|
message_ids = update.messages
|
||||||
contents=True)
|
contents = True
|
||||||
|
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._client = client
|
||||||
|
self._chat = entities.get(peer)
|
||||||
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inbox(self):
|
def inbox(self):
|
||||||
|
|
|
@ -57,16 +57,8 @@ class NewMessage(EventBuilder, _custom.Message):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
await client.delete_messages(event.chat_id, [event.id, m.id])
|
await client.delete_messages(event.chat_id, [event.id, m.id])
|
||||||
"""
|
"""
|
||||||
def __init__(self, message):
|
|
||||||
self.__dict__['_init'] = False
|
|
||||||
super().__init__(chat_peer=message.peer_id,
|
|
||||||
msg_id=message.id, broadcast=bool(message.post))
|
|
||||||
|
|
||||||
self.pattern_match = None
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build(cls, update, others, self_id, entities, client):
|
def _build(cls, client, update, entities):
|
||||||
if isinstance(update,
|
if isinstance(update,
|
||||||
(_tl.UpdateNewMessage, _tl.UpdateNewChannelMessage)):
|
(_tl.UpdateNewMessage, _tl.UpdateNewChannelMessage)):
|
||||||
if not isinstance(update.message, _tl.Message):
|
if not isinstance(update.message, _tl.Message):
|
||||||
|
|
|
@ -19,5 +19,5 @@ class Raw(EventBuilder):
|
||||||
print(update.stringify())
|
print(update.stringify())
|
||||||
"""
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
def _build(cls, client, update, entities):
|
||||||
return update
|
return update
|
||||||
|
|
|
@ -62,33 +62,35 @@ class UserUpdate(EventBuilder, _custom.chatgetter.ChatGetter, _custom.sendergett
|
||||||
if event.uploading:
|
if event.uploading:
|
||||||
await client.send_message(event.user_id, 'What are you sending?')
|
await client.send_message(event.user_id, 'What are you sending?')
|
||||||
"""
|
"""
|
||||||
def __init__(self, peer, *, status=None, chat_peer=None, typing=None):
|
@classmethod
|
||||||
_custom.chatgetter.ChatGetter.__init__(self, chat_peer or peer)
|
def _build(cls, client, update, entities):
|
||||||
_custom.sendergetter.SenderGetter.__init__(self, utils.get_peer_id(peer))
|
chat_peer = None
|
||||||
|
status = None
|
||||||
|
if isinstance(update, _tl.UpdateUserStatus):
|
||||||
|
peer = _tl.PeerUser(update.user_id)
|
||||||
|
status = update.status
|
||||||
|
typing = None
|
||||||
|
elif isinstance(update, _tl.UpdateChannelUserTyping):
|
||||||
|
peer = update.from_id
|
||||||
|
chat_peer = _tl.PeerChannel(update.channel_id)
|
||||||
|
typing = update.action
|
||||||
|
elif isinstance(update, _tl.UpdateChatUserTyping):
|
||||||
|
peer = update.from_id
|
||||||
|
chat_peer = _tl.PeerChat(update.chat_id)
|
||||||
|
typing = update.action
|
||||||
|
elif isinstance(update, _tl.UpdateUserTyping):
|
||||||
|
peer = update.user_id
|
||||||
|
typing = update.action
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self._client = client
|
||||||
|
self._sender = entities.get(peer)
|
||||||
|
self._chat = entities.get(chat_peer or peer)
|
||||||
self.status = status
|
self.status = status
|
||||||
self.action = typing
|
self.action = typing
|
||||||
|
return self
|
||||||
@classmethod
|
|
||||||
def _build(cls, update, others=None, self_id=None, *todo, **todo2):
|
|
||||||
if isinstance(update, _tl.UpdateUserStatus):
|
|
||||||
return UserUpdateEvent(_tl.PeerUser(update.user_id),
|
|
||||||
status=update.status)
|
|
||||||
elif isinstance(update, _tl.UpdateChannelUserTyping):
|
|
||||||
return UserUpdateEvent(update.from_id,
|
|
||||||
chat_peer=_tl.PeerChannel(update.channel_id),
|
|
||||||
typing=update.action)
|
|
||||||
elif isinstance(update, _tl.UpdateChatUserTyping):
|
|
||||||
return UserUpdateEvent(update.from_id,
|
|
||||||
chat_peer=_tl.PeerChat(update.chat_id),
|
|
||||||
typing=update.action)
|
|
||||||
elif isinstance(update, _tl.UpdateUserTyping):
|
|
||||||
return UserUpdateEvent(update.user_id,
|
|
||||||
typing=update.action)
|
|
||||||
|
|
||||||
def _set_client(self, client):
|
|
||||||
super()._set_client(client)
|
|
||||||
self._sender, self._input_sender = utils._get_entity_pair(self.sender_id, self._entities)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user(self):
|
def user(self):
|
||||||
|
|
|
@ -578,20 +578,6 @@ def get_input_group_call(call):
|
||||||
_raise_cast_fail(call, 'InputGroupCall')
|
_raise_cast_fail(call, 'InputGroupCall')
|
||||||
|
|
||||||
|
|
||||||
def _get_entity_pair(entity_id, entities,
|
|
||||||
get_input_peer=get_input_peer):
|
|
||||||
"""
|
|
||||||
Returns ``(entity, input_entity)`` for the given entity ID.
|
|
||||||
"""
|
|
||||||
entity = entities.get(entity_id)
|
|
||||||
try:
|
|
||||||
input_entity = get_input_peer(entity)
|
|
||||||
except TypeError:
|
|
||||||
input_entity = None
|
|
||||||
|
|
||||||
return entity, input_entity
|
|
||||||
|
|
||||||
|
|
||||||
def get_message_id(message):
|
def get_message_id(message):
|
||||||
"""Similar to :meth:`get_input_peer`, but for message IDs."""
|
"""Similar to :meth:`get_input_peer`, but for message IDs."""
|
||||||
if message is None:
|
if message is None:
|
||||||
|
@ -977,27 +963,13 @@ def get_peer(peer):
|
||||||
|
|
||||||
def get_peer_id(peer):
|
def get_peer_id(peer):
|
||||||
"""
|
"""
|
||||||
Extract the integer ID from the given peer.
|
Extract the integer ID from the given :tl:`Peer`.
|
||||||
"""
|
"""
|
||||||
# First we assert it's a Peer TLObject, or early return for integers
|
pid = getattr(peer, 'user_id', None) or getattr(peer, 'channel_id', None) or getattr(peer, 'chat_id', None)
|
||||||
if isinstance(peer, int):
|
if not isisintance(pid, int):
|
||||||
return peer
|
|
||||||
|
|
||||||
# Tell the user to use their client to resolve InputPeerSelf if we got one
|
|
||||||
if isinstance(peer, _tl.InputPeerSelf):
|
|
||||||
_raise_cast_fail(peer, 'int (you might want to use client.get_peer_id)')
|
|
||||||
|
|
||||||
try:
|
|
||||||
peer = get_peer(peer)
|
|
||||||
except TypeError:
|
|
||||||
_raise_cast_fail(peer, 'int')
|
_raise_cast_fail(peer, 'int')
|
||||||
|
|
||||||
if isinstance(peer, _tl.PeerUser):
|
return pid
|
||||||
return peer.user_id
|
|
||||||
elif isinstance(peer, _tl.PeerChat):
|
|
||||||
return peer.chat_id
|
|
||||||
else: # if isinstance(peer, _tl.PeerChannel):
|
|
||||||
return peer.channel_id
|
|
||||||
|
|
||||||
|
|
||||||
def _rle_decode(data):
|
def _rle_decode(data):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Optional, List, TYPE_CHECKING
|
from typing import Optional, List, TYPE_CHECKING
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from dataclasses import dataclass
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from .chatgetter import ChatGetter
|
from .chatgetter import ChatGetter
|
||||||
from .sendergetter import SenderGetter
|
from .sendergetter import SenderGetter
|
||||||
|
@ -27,17 +28,21 @@ def _fwd(field, doc):
|
||||||
return property(fget, fset, None, doc)
|
return property(fget, fset, None, doc)
|
||||||
|
|
||||||
|
|
||||||
class _InputChat:
|
@dataclass(frozen=True)
|
||||||
"""
|
class _TinyChat:
|
||||||
Input channels and peer chats use a different name for "id" which breaks the property forwarding.
|
|
||||||
|
|
||||||
This class simply holds the two fields with proper names.
|
|
||||||
"""
|
|
||||||
__slots__ = ('id', 'access_hash')
|
__slots__ = ('id', 'access_hash')
|
||||||
|
|
||||||
def __init__(self, input):
|
id: int
|
||||||
self.id = getattr(input, 'channel_id', None) or input.chat_id
|
access_hash: int
|
||||||
self.access_hash = getattr(input, 'access_hash', None)
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _TinyChannel:
|
||||||
|
__slots__ = ('id', 'access_hash', 'megagroup')
|
||||||
|
|
||||||
|
id: int
|
||||||
|
access_hash: int
|
||||||
|
megagroup: bool # gigagroup is not present in channelForbidden but megagroup is
|
||||||
|
|
||||||
|
|
||||||
class Chat:
|
class Chat:
|
||||||
|
@ -159,7 +164,11 @@ class Chat:
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
migrated = None
|
migrated = None
|
||||||
|
|
||||||
return Chat(_InputChat(migrated), self._client) if migrated else None
|
if migrated is None:
|
||||||
|
return migrated
|
||||||
|
|
||||||
|
# Small chats don't migrate to other small chats, nor do they migrate to broadcast channels
|
||||||
|
return type(self)._new(self._client, _TinyChannel(migrated.channel_id, migrated.access_hash, True))
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
raise TypeError('You cannot create Chat instances by hand!')
|
raise TypeError('You cannot create Chat instances by hand!')
|
||||||
|
@ -168,7 +177,18 @@ class Chat:
|
||||||
def _new(cls, client, chat):
|
def _new(cls, client, chat):
|
||||||
self = cls.__new__(cls)
|
self = cls.__new__(cls)
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
self._chat = chat
|
self._chat = chat
|
||||||
|
if isinstance(cls, Entity):
|
||||||
|
if chat.is_user:
|
||||||
|
raise TypeError('Tried to construct Chat with non-chat Entity')
|
||||||
|
elif chat.ty == EntityType.GROUP:
|
||||||
|
self._chat = _TinyChat(chat.id)
|
||||||
|
else:
|
||||||
|
self._chat = _TinyChannel(chat.id, chat.hash, chat.is_group)
|
||||||
|
else:
|
||||||
|
self._chat = chat
|
||||||
|
|
||||||
self._full = None
|
self._full = None
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -237,7 +257,7 @@ class Chat:
|
||||||
|
|
||||||
.. _megagroup: https://telegram.org/blog/supergroups5k
|
.. _megagroup: https://telegram.org/blog/supergroups5k
|
||||||
"""
|
"""
|
||||||
return True
|
return isinstance(self._chat, (_tl.Chat, _TinyChat, _tl.ChatForbidden, _tl.ChatEmpty)) or self._chat.megagroup
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_broadcast(self):
|
def is_broadcast(self):
|
||||||
|
@ -253,7 +273,7 @@ class Chat:
|
||||||
|
|
||||||
.. _broadcast group: https://telegram.org/blog/autodelete-inv2#groups-with-unlimited-members
|
.. _broadcast group: https://telegram.org/blog/autodelete-inv2#groups-with-unlimited-members
|
||||||
"""
|
"""
|
||||||
return True
|
return not self.is_group
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
|
|
|
@ -7,11 +7,9 @@ from ... import errors, _tl
|
||||||
class ChatGetter(abc.ABC):
|
class ChatGetter(abc.ABC):
|
||||||
"""
|
"""
|
||||||
Helper base class that introduces the chat-related properties and methods.
|
Helper base class that introduces the chat-related properties and methods.
|
||||||
"""
|
|
||||||
def __init__(self, chat, client):
|
|
||||||
self._chat = chat
|
|
||||||
self._client = client
|
|
||||||
|
|
||||||
|
The parent class must set both ``_chat`` and ``_client``.
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def chat(self):
|
def chat(self):
|
||||||
"""
|
"""
|
||||||
|
@ -60,7 +58,7 @@ class ChatGetter(abc.ABC):
|
||||||
# Here there's no need to fetch the chat - get_messages already did
|
# Here there's no need to fetch the chat - get_messages already did
|
||||||
print(message.chat.stringify())
|
print(message.chat.stringify())
|
||||||
"""
|
"""
|
||||||
raise RuntimeError('TODO')
|
raise RuntimeError('TODO fetch if it is tiny')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def chat_id(self):
|
def chat_id(self):
|
||||||
|
|
|
@ -428,10 +428,10 @@ class Message(ChatGetter, SenderGetter):
|
||||||
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 = utils.get_peer_id(message.from_id)
|
||||||
|
|
||||||
# Note that these calls would reset the client
|
self = cls.__new__(cls)
|
||||||
ChatGetter.__init__(self, message.peer_id, broadcast=message.post)
|
|
||||||
SenderGetter.__init__(self, sender_id)
|
|
||||||
self._client = client
|
self._client = client
|
||||||
|
self._sender = entities.get(_tl.PeerUser(update.user_id))
|
||||||
|
self._chat = entities.get(_tl.PeerUser(update.user_id))
|
||||||
self._message = message
|
self._message = message
|
||||||
|
|
||||||
# Convenient storage for custom functions
|
# Convenient storage for custom functions
|
||||||
|
|
|
@ -4,11 +4,9 @@ import abc
|
||||||
class SenderGetter(abc.ABC):
|
class SenderGetter(abc.ABC):
|
||||||
"""
|
"""
|
||||||
Helper base class that introduces the sender-related properties and methods.
|
Helper base class that introduces the sender-related properties and methods.
|
||||||
"""
|
|
||||||
def __init__(self, sender, client):
|
|
||||||
self._sender = sender
|
|
||||||
self._client = client
|
|
||||||
|
|
||||||
|
The parent class must set both ``_sender`` and ``_client``.
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def sender(self):
|
def sender(self):
|
||||||
"""
|
"""
|
||||||
|
@ -57,7 +55,7 @@ class SenderGetter(abc.ABC):
|
||||||
# Here there's no need to fetch the sender - get_messages already did
|
# Here there's no need to fetch the sender - get_messages already did
|
||||||
print(message.sender.stringify())
|
print(message.sender.stringify())
|
||||||
"""
|
"""
|
||||||
raise RuntimeError('TODO')
|
raise RuntimeError('TODO fetch if it is tiny')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sender_id(self):
|
def sender_id(self):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Optional, List, TYPE_CHECKING
|
from typing import Optional, List, TYPE_CHECKING
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from dataclasses import dataclass
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from .chatgetter import ChatGetter
|
from .chatgetter import ChatGetter
|
||||||
from .sendergetter import SenderGetter
|
from .sendergetter import SenderGetter
|
||||||
|
@ -67,6 +68,14 @@ class BotInfo:
|
||||||
self._user = user
|
self._user = user
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class _TinyUser:
|
||||||
|
__slots__ = ('id', 'access_hash')
|
||||||
|
|
||||||
|
id: int
|
||||||
|
access_hash: int
|
||||||
|
|
||||||
|
|
||||||
class User:
|
class User:
|
||||||
"""
|
"""
|
||||||
Represents a :tl:`User` (or :tl:`UserEmpty`, or :tl:`UserFull`) from the API.
|
Represents a :tl:`User` (or :tl:`UserEmpty`, or :tl:`UserFull`) from the API.
|
||||||
|
@ -161,9 +170,16 @@ class User:
|
||||||
@classmethod
|
@classmethod
|
||||||
def _new(cls, client, user):
|
def _new(cls, client, user):
|
||||||
self = cls.__new__(cls)
|
self = cls.__new__(cls)
|
||||||
|
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
|
if isinstance(user, Entity):
|
||||||
|
if not user.is_user:
|
||||||
|
raise TypeError('Tried to construct User with non-user Entity')
|
||||||
|
|
||||||
|
self._user = _TinyUser(user.id, user.hash)
|
||||||
|
else:
|
||||||
self._user = user
|
self._user = user
|
||||||
|
|
||||||
self._full = None
|
self._full = None
|
||||||
raise RuntimeError('self._i_need_to_include_participant_info')
|
raise RuntimeError('self._i_need_to_include_participant_info')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user