Remove PackedChat

In favour of using the session entity type consistently.
This commit is contained in:
Lonami Exo 2022-02-08 11:31:24 +01:00
parent 07faa53c5a
commit 9b4808a558
8 changed files with 86 additions and 74 deletions

View File

@ -241,11 +241,11 @@ async def get_input_entity(
entity = await self._session.get_entity(None, peer_id) entity = await self._session.get_entity(None, peer_id)
if entity: if entity:
if entity.ty in (Entity.USER, Entity.BOT): if entity.ty in (Entity.USER, Entity.BOT):
return _tl.InputPeerUser(entity.id, entity.access_hash) return _tl.InputPeerUser(entity.id, entity.hash)
elif entity.ty in (Entity.GROUP): elif entity.ty in (Entity.GROUP):
return _tl.InputPeerChat(peer.chat_id) return _tl.InputPeerChat(peer.chat_id)
elif entity.ty in (Entity.CHANNEL, Entity.MEGAGROUP, Entity.GIGAGROUP): elif entity.ty in (Entity.CHANNEL, Entity.MEGAGROUP, Entity.GIGAGROUP):
return _tl.InputPeerChannel(entity.id, entity.access_hash) return _tl.InputPeerChannel(entity.id, entity.hash)
# Only network left to try # Only network left to try
if isinstance(peer, str): if isinstance(peer, str):

View File

@ -64,15 +64,15 @@ class Session(ABC):
Get the `Entity` with matching ``ty`` and ``id``. Get the `Entity` with matching ``ty`` and ``id``.
The following groups of ``ty`` should be treated to be equivalent, that is, for a given The following groups of ``ty`` should be treated to be equivalent, that is, for a given
``ty`` and ``id``, if the ``ty`` is in a given group, a matching ``access_hash`` with ``ty`` and ``id``, if the ``ty`` is in a given group, a matching ``hash`` with that ``id``
that ``id`` from within any ``ty`` in that group should be returned. from within any ``ty`` in that group should be returned.
* `EntityType.USER` and `EntityType.BOT`. * `EntityType.USER` and `EntityType.BOT`.
* `EntityType.GROUP`. * `EntityType.GROUP`.
* `EntityType.CHANNEL`, `EntityType.MEGAGROUP` and `EntityType.GIGAGROUP`. * `EntityType.CHANNEL`, `EntityType.MEGAGROUP` and `EntityType.GIGAGROUP`.
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 ``hash`` should still be returned.
You may use ``EntityType.canonical`` to find out the canonical type. You may use ``EntityType.canonical`` to find out the canonical type.

View File

@ -34,12 +34,12 @@ 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((e.id, (e.ty, e.access_hash)) for e in entities) self.entities.update((e.id, (e.ty, e.hash)) for e in entities)
async def get_entity(self, ty: Optional[int], id: int) -> Optional[Entity]: async def get_entity(self, ty: Optional[int], id: int) -> Optional[Entity]:
try: try:
ty, access_hash = self.entities[id] ty, hash = self.entities[id]
return Entity(ty, id, access_hash) return Entity(ty, id, hash)
except KeyError: except KeyError:
return None return None

View File

@ -127,7 +127,7 @@ class SQLiteSession(Session):
limit 1 limit 1
''') ''')
c.execute(''' c.execute('''
insert into entity (id, access_hash, ty) insert into entity (id, hash, ty)
select select
case case
when id < -1000000000000 then -(id + 1000000000000) when id < -1000000000000 then -(id + 1000000000000)
@ -173,7 +173,7 @@ class SQLiteSession(Session):
)''', )''',
'''entity ( '''entity (
id integer primary key, id integer primary key,
access_hash integer not null, hash integer not null,
ty integer not null ty integer not null
)''', )''',
) )
@ -245,13 +245,13 @@ class SQLiteSession(Session):
try: try:
c.executemany( c.executemany(
'insert or replace into entity values (?,?,?)', 'insert or replace into entity values (?,?,?)',
[(e.id, e.access_hash, e.ty) for e in entities] [(e.id, e.hash, e.ty) for e in entities]
) )
finally: finally:
c.close() c.close()
async def get_entity(self, ty: Optional[int], id: int) -> Optional[Entity]: async def get_entity(self, ty: Optional[int], id: int) -> Optional[Entity]:
row = self._execute('select ty, id, access_hash from entity where id = ?', id) row = self._execute('select ty, id, hash from entity where id = ?', id)
return Entity(*row) if row else None return Entity(*row) if row else None
async def save(self): async def save(self):

View File

@ -105,12 +105,74 @@ class Entity:
""" """
Stores the information needed to use a certain user, chat or channel with the API. Stores the information needed to use a certain user, chat or channel with the API.
* ty: 8-bit number indicating the type of the entity. * ty: 8-bit number indicating the type of the entity (of type `EntityType`).
* id: 64-bit number uniquely identifying the entity among those of the same type. * id: 64-bit number uniquely identifying the entity among those of the same type.
* access_hash: 64-bit number needed to use this entity with the API. * hash: 64-bit signed number needed to use this entity with the API.
The string representation of this class is considered to be stable, for as long as
Telegram doesn't need to add more fields to the entities. It can also be converted
to bytes with ``bytes(entity)``, for a more compact representation.
""" """
__slots__ = ('ty', 'id', 'access_hash') __slots__ = ('ty', 'id', 'hash')
ty: EntityType ty: EntityType
id: int id: int
access_hash: int hash: int
@property
def is_user(self):
"""
``True`` if the entity is either a user or a bot.
"""
return self.ty in (EntityType.USER, EntityType.BOT)
@property
def is_group(self):
"""
``True`` if the entity is a small group chat or `megagroup`_.
.. _megagroup: https://telegram.org/blog/supergroups5k
"""
return self.ty in (EntityType.GROUP, EntityType.MEGAGROUP)
@property
def is_channel(self):
"""
``True`` if the entity is a broadcast channel or `broadcast group`_.
.. _broadcast group: https://telegram.org/blog/autodelete-inv2#groups-with-unlimited-members
"""
return self.ty in (EntityType.CHANNEL, EntityType.GIGAGROUP)
@classmethod
def from_str(cls, string: str):
"""
Convert the string into an `Entity`.
"""
try:
ty, id, hash = string.split('.')
ty, id, hash = ord(ty), int(id), int(hash)
except AttributeError:
raise TypeError(f'expected str, got {string!r}') from None
except (TypeError, ValueError):
raise ValueError(f'malformed entity str (must be T.id.hash), got {string!r}') from None
return cls(EntityType(ty), id, hash)
@classmethod
def from_bytes(cls, blob):
"""
Convert the bytes into an `Entity`.
"""
try:
ty, id, hash = struct.unpack('<Bqq', blob)
except struct.error:
raise ValueError(f'malformed entity data, got {string!r}') from None
return cls(EntityType(ty), id, hash)
def __str__(self):
return f'{chr(self.ty)}.{self.id}.{self.hash}'
def __bytes__(self):
return struct.pack('<Bqq', self.ty, self.id, self.hash)

View File

@ -1,2 +1,2 @@
from .entitycache import EntityCache, PackedChat from .entitycache import EntityCache
from .messagebox import MessageBox from .messagebox import MessageBox

View File

@ -8,59 +8,6 @@ from .. import _tl
from .._sessions.types import EntityType, Entity from .._sessions.types import EntityType, Entity
class PackedChat(namedtuple('PackedChat', 'ty id hash')):
__slots__ = ()
@property
def is_user(self):
return self.ty in (EntityType.USER, EntityType.BOT)
@property
def is_chat(self):
return self.ty in (EntityType.GROUP,)
@property
def is_channel(self):
return self.ty in (EntityType.CHANNEL, EntityType.MEGAGROUP, EntityType.GIGAGROUP)
def to_peer(self):
if self.is_user:
return _tl.PeerUser(user_id=self.id)
elif self.is_chat:
return _tl.PeerChat(chat_id=self.id)
elif self.is_channel:
return _tl.PeerChannel(channel_id=self.id)
def to_input_peer(self):
if self.is_user:
return _tl.InputPeerUser(user_id=self.id, access_hash=self.hash)
elif self.is_chat:
return _tl.InputPeerChat(chat_id=self.id)
elif self.is_channel:
return _tl.InputPeerChannel(channel_id=self.id, access_hash=self.hash)
def try_to_input_user(self):
if self.is_user:
return _tl.InputUser(user_id=self.id, access_hash=self.hash)
else:
return None
def try_to_chat_id(self):
if self.is_chat:
return self.id
else:
return None
def try_to_input_channel(self):
if self.is_channel:
return _tl.InputChannel(channel_id=self.id, access_hash=self.hash)
else:
return None
def __str__(self):
return f'{chr(self.ty.value)}.{self.id}.{self.hash}'
@dataclass @dataclass
class EntityCache: class EntityCache:
hash_map: dict = field(default_factory=dict) # id -> (hash, ty) hash_map: dict = field(default_factory=dict) # id -> (hash, ty)
@ -72,8 +19,11 @@ class EntityCache:
self.self_bot = bot self.self_bot = bot
def get(self, id): def get(self, id):
value = self.hash_map.get(id) try:
return PackedChat(ty=value[1], id=id, hash=value[0]) if value else None hash, ty = self.hash_map[id]
return Entity(ty, id, hash)
except KeyError:
return None
def extend(self, users, chats): def extend(self, users, chats):
# See https://core.telegram.org/api/min for "issues" with "min constructors". # See https://core.telegram.org/api/min for "issues" with "min constructors".
@ -100,4 +50,4 @@ class EntityCache:
return [Entity(ty, id, hash) for id, (hash, ty) in self.hash_map.items()] return [Entity(ty, id, hash) for id, (hash, ty) in self.hash_map.items()]
def put(self, entity): def put(self, entity):
self.hash_map[entity.id] = (entity.access_hash, entity.ty) self.hash_map[entity.id] = (entity.hash, entity.ty)

View File

@ -530,7 +530,7 @@ class MessageBox:
return _tl.fn.updates.GetChannelDifference( return _tl.fn.updates.GetChannelDifference(
force=False, force=False,
channel=packed.try_to_input_channel(), channel=_tl.InputChannel(packed.id, packed.hash),
filter=_tl.ChannelMessagesFilterEmpty(), filter=_tl.ChannelMessagesFilterEmpty(),
pts=state.pts, pts=state.pts,
limit=BOT_CHANNEL_DIFF_LIMIT if chat_hashes.self_bot else USER_CHANNEL_DIFF_LIMIT limit=BOT_CHANNEL_DIFF_LIMIT if chat_hashes.self_bot else USER_CHANNEL_DIFF_LIMIT