Add proper support for receiving messages sent as a channel. Fixes #740

This commit is contained in:
Tulir Asokan
2022-02-01 15:20:05 +02:00
parent 2182dfc86b
commit 0f050edcd9
10 changed files with 149 additions and 62 deletions
+69 -22
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2021 Tulir Asokan
# Copyright (C) 2022 Tulir Asokan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
@@ -20,10 +20,18 @@ from difflib import SequenceMatcher
import unicodedata
from telethon.tl.types import (
Channel,
ChatPhoto,
ChatPhotoEmpty,
InputPeerPhotoFileLocation,
PeerChannel,
PeerChat,
PeerUser,
TypeChatPhoto,
TypeInputPeer,
TypeInputUser,
TypePeer,
TypeUserProfilePhoto,
UpdateUserName,
User,
UserProfilePhoto,
@@ -67,6 +75,7 @@ class Puppet(DBPuppet, BasePuppet):
username: str | None = None,
photo_id: str | None = None,
is_bot: bool = False,
is_channel: bool = False,
custom_mxid: UserID | None = None,
access_token: str | None = None,
next_batch: SyncToken | None = None,
@@ -83,6 +92,7 @@ class Puppet(DBPuppet, BasePuppet):
username=username,
photo_id=photo_id,
is_bot=is_bot,
is_channel=is_channel,
custom_mxid=custom_mxid,
access_token=access_token,
next_batch=next_batch,
@@ -109,7 +119,9 @@ class Puppet(DBPuppet, BasePuppet):
@property
def peer(self) -> PeerUser:
return PeerUser(user_id=self.tgid)
return (
PeerChannel(channel_id=self.tgid) if self.is_channel else PeerUser(user_id=self.tgid)
)
@property
def plain_displayname(self) -> str:
@@ -185,9 +197,12 @@ class Puppet(DBPuppet, BasePuppet):
return name
@classmethod
def get_displayname(cls, info: User, enable_format: bool = True) -> tuple[str, int]:
fn = cls._filter_name(info.first_name)
ln = cls._filter_name(info.last_name)
def get_displayname(cls, info: User | Channel, enable_format: bool = True) -> tuple[str, int]:
if isinstance(info, Channel):
fn, ln = cls._filter_name(info.title), ""
else:
fn = cls._filter_name(info.first_name)
ln = cls._filter_name(info.last_name)
data = {
"phone number": info.phone if hasattr(info, "phone") else None,
"username": info.username,
@@ -214,14 +229,20 @@ class Puppet(DBPuppet, BasePuppet):
return (cls.displayname_template.format_full(name) if enable_format else name), quality
async def try_update_info(self, source: au.AbstractUser, info: User) -> None:
async def try_update_info(self, source: au.AbstractUser, info: User | Channel) -> None:
try:
await self.update_info(source, info)
except Exception:
source.log.exception(f"Failed to update info of {self.tgid}")
async def update_info(self, source: au.AbstractUser, info: User) -> None:
changed = False
async def update_info(self, source: au.AbstractUser, info: User | Channel) -> None:
is_bot = False if isinstance(info, Channel) else info.bot
is_channel = isinstance(info, Channel)
changed = is_bot != self.is_bot or is_channel != self.is_channel
self.is_bot = is_bot
self.is_channel = is_channel
if self.username != info.username:
self.username = info.username
changed = True
@@ -233,32 +254,32 @@ class Puppet(DBPuppet, BasePuppet):
except Exception:
self.log.exception(f"Failed to update info from source {source.tgid}")
self.is_bot = info.bot
if changed:
await self.save()
async def update_displayname(
self, source: au.AbstractUser, info: User | UpdateUserName
self, source: au.AbstractUser, info: User | Channel | UpdateUserName
) -> bool:
if self.disable_updates:
return False
if source.is_relaybot or source.is_bot:
allow_because = "user is bot"
allow_because = "source user is a bot"
elif self.displayname_source == source.tgid:
allow_because = "user is the primary source"
allow_because = "source user is the primary source"
elif isinstance(info, Channel):
allow_because = "target user is a channel"
elif not isinstance(info, UpdateUserName) and not info.contact:
allow_because = "user is not a contact"
allow_because = "target user is not a contact"
elif not self.displayname_source:
allow_because = "no primary source set"
elif not self.displayname:
allow_because = "user has no name"
allow_because = "target user has no name"
else:
return False
if isinstance(info, UpdateUserName):
info = await source.client.get_entity(PeerUser(self.tgid))
if not info.contact:
info = await source.client.get_entity(self.peer)
if isinstance(info, Channel) or not info.contact:
self.displayname_contact = False
elif not self.displayname_contact:
if not self.displayname:
@@ -293,14 +314,14 @@ class Puppet(DBPuppet, BasePuppet):
return False
async def update_avatar(
self, source: au.AbstractUser, photo: UserProfilePhoto | UserProfilePhotoEmpty
self, source: au.AbstractUser, photo: TypeUserProfilePhoto | TypeChatPhoto
) -> bool:
if self.disable_updates:
return False
if photo is None or isinstance(photo, UserProfilePhotoEmpty):
if photo is None or isinstance(photo, (UserProfilePhotoEmpty, ChatPhotoEmpty)):
photo_id = ""
elif isinstance(photo, UserProfilePhoto):
elif isinstance(photo, (UserProfilePhoto, ChatPhoto)):
photo_id = str(photo.photo_id)
else:
self.log.warning(f"Unknown user profile photo type: {type(photo)}")
@@ -345,7 +366,9 @@ class Puppet(DBPuppet, BasePuppet):
@classmethod
@async_getter_lock
async def get_by_tgid(cls, tgid: TelegramID, *, create: bool = True) -> Puppet | None:
async def get_by_tgid(
cls, tgid: TelegramID, *, create: bool = True, is_channel: bool = False
) -> Puppet | None:
if tgid is None:
return None
@@ -360,13 +383,37 @@ class Puppet(DBPuppet, BasePuppet):
return puppet
if create:
puppet = cls(tgid)
puppet = cls(tgid, is_channel=is_channel)
await puppet.insert()
puppet._add_to_cache()
return puppet
return None
@staticmethod
def get_id_from_peer(peer: TypePeer | User | Channel) -> TelegramID:
if isinstance(peer, PeerUser):
return TelegramID(peer.user_id)
elif isinstance(peer, PeerChannel):
return TelegramID(peer.channel_id)
elif isinstance(peer, PeerChat):
return TelegramID(peer.chat_id)
elif isinstance(peer, (User, Channel)):
return TelegramID(peer.id)
raise TypeError(f"invalid type {type(peer).__name__!r} in _id_from_peer()")
@classmethod
async def get_by_peer(
cls, peer: TypePeer | User | Channel, *, create: bool = True
) -> Puppet | None:
if isinstance(peer, PeerChat):
return None
return await cls.get_by_tgid(
cls.get_id_from_peer(peer),
create=create,
is_channel=isinstance(peer, (PeerChannel, Channel)),
)
@classmethod
def get_by_mxid(cls, mxid: UserID, create: bool = True) -> Awaitable[Puppet | None]:
return cls.get_by_tgid(cls.get_id_from_mxid(mxid), create=create)