Improve profile info syncing

This commit is contained in:
Tulir Asokan
2022-02-01 20:51:54 +02:00
parent ecf3a12bd4
commit a55d9ae36a
9 changed files with 234 additions and 105 deletions
+2
View File
@@ -7,6 +7,8 @@
a new user. a new user.
* Added support for adding an optional random prefix to relayed user displaynames * Added support for adding an optional random prefix to relayed user displaynames
to help distinguish them on the Telegram side. to help distinguish them on the Telegram side.
* Improved syncing profile info to room info when using encryption and/or the
`private_chat_profile_meta` config option.
* Fixed bug in v0.11.0 that broke `!tg create`. * Fixed bug in v0.11.0 that broke `!tg create`.
# v0.11.1 (2021-01-10) # v0.11.1 (2021-01-10)
+2
View File
@@ -469,9 +469,11 @@ class AbstractUser(ABC):
puppet.username = update.username puppet.username = update.username
if await puppet.update_displayname(self, update): if await puppet.update_displayname(self, update):
await puppet.save() await puppet.save()
await puppet.update_portals_meta()
elif isinstance(update, UpdateUserPhoto): elif isinstance(update, UpdateUserPhoto):
if await puppet.update_avatar(self, update.photo): if await puppet.update_avatar(self, update.photo):
await puppet.save() await puppet.save()
await puppet.update_portals_meta()
else: else:
self.log.warning(f"Unexpected other user info update: {type(update)}") self.log.warning(f"Unexpected other user info update: {type(update)}")
+26 -14
View File
@@ -54,6 +54,8 @@ class Portal:
title: str | None title: str | None
about: str | None about: str | None
photo_id: str | None photo_id: str | None
name_set: bool
avatar_set: bool
local_config: dict[str, Any] = attr.ib(factory=lambda: {}) local_config: dict[str, Any] = attr.ib(factory=lambda: {})
@@ -67,7 +69,8 @@ class Portal:
columns: ClassVar[str] = ( columns: ClassVar[str] = (
"tgid, tg_receiver, peer_type, megagroup, mxid, avatar_url, encrypted, sponsored_event_id," "tgid, tg_receiver, peer_type, megagroup, mxid, avatar_url, encrypted, sponsored_event_id,"
"sponsored_event_ts, sponsored_msg_random_id, username, title, about, photo_id, config" "sponsored_event_ts, sponsored_msg_random_id, username, title, about, photo_id, "
"name_set, avatar_set, config"
) )
@classmethod @classmethod
@@ -86,10 +89,15 @@ class Portal:
return cls._from_row(await cls.db.fetchrow(q, username.lower())) return cls._from_row(await cls.db.fetchrow(q, username.lower()))
@classmethod @classmethod
async def find_private_chats(cls, tg_receiver: TelegramID) -> list[Portal]: async def find_private_chats_of(cls, tg_receiver: TelegramID) -> list[Portal]:
q = f"SELECT {cls.columns} FROM portal WHERE tg_receiver=$1 AND peer_type='user'" q = f"SELECT {cls.columns} FROM portal WHERE tg_receiver=$1 AND peer_type='user'"
return [cls._from_row(row) for row in await cls.db.fetch(q, tg_receiver)] return [cls._from_row(row) for row in await cls.db.fetch(q, tg_receiver)]
@classmethod
async def find_private_chats_with(cls, tgid: TelegramID) -> list[Portal]:
q = f"SELECT {cls.columns} FROM portal WHERE tgid=$1 AND peer_type='user'"
return [cls._from_row(row) for row in await cls.db.fetch(q, tgid)]
@classmethod @classmethod
async def all(cls) -> list[Portal]: async def all(cls) -> list[Portal]:
rows = await cls.db.fetch(f"SELECT {cls.columns} FROM portal") rows = await cls.db.fetch(f"SELECT {cls.columns} FROM portal")
@@ -111,17 +119,20 @@ class Portal:
self.title, self.title,
self.about, self.about,
self.photo_id, self.photo_id,
self.name_set,
self.avatar_set,
self.megagroup, self.megagroup,
json.dumps(self.local_config) if self.local_config else None, json.dumps(self.local_config) if self.local_config else None,
) )
async def save(self) -> None: async def save(self) -> None:
q = ( q = """
"UPDATE portal SET mxid=$4, avatar_url=$5, encrypted=$6, sponsored_event_id=$7," UPDATE portal
" sponsored_event_ts=$8, sponsored_msg_random_id=$9, username=$10," SET mxid=$4, avatar_url=$5, encrypted=$6, sponsored_event_id=$7, sponsored_event_ts=$8,
" title=$11, about=$12, photo_id=$13, megagroup=$14, config=$15 " sponsored_msg_random_id=$9, username=$10, title=$11, about=$12, photo_id=$13,
"WHERE tgid=$1 AND tg_receiver=$2 AND (peer_type=$3 OR true)" name_set=$14, avatar_set=$15, megagroup=$16, config=$17
) WHERE tgid=$1 AND tg_receiver=$2 AND (peer_type=$3 OR true)
"""
await self.db.execute(q, *self._values) await self.db.execute(q, *self._values)
async def update_id(self, id: TelegramID, peer_type: str) -> None: async def update_id(self, id: TelegramID, peer_type: str) -> None:
@@ -135,12 +146,13 @@ class Portal:
self.peer_type = peer_type self.peer_type = peer_type
async def insert(self) -> None: async def insert(self) -> None:
q = ( q = """
"INSERT INTO portal (tgid, tg_receiver, peer_type, mxid, avatar_url, encrypted," INSERT INTO portal (
" sponsored_event_id, sponsored_event_ts, sponsored_msg_random_id," tgid, tg_receiver, peer_type, mxid, avatar_url, encrypted,
" username, title, about, photo_id, megagroup, config) " sponsored_event_id, sponsored_event_ts, sponsored_msg_random_id,
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)" username, title, about, photo_id, name_set, avatar_set, megagroup, config
) ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
"""
await self.db.execute(q, *self._values) await self.db.execute(q, *self._values)
async def delete(self) -> None: async def delete(self) -> None:
+24 -17
View File
@@ -21,7 +21,7 @@ from asyncpg import Record
from attr import dataclass from attr import dataclass
from yarl import URL from yarl import URL
from mautrix.types import SyncToken, UserID from mautrix.types import ContentURI, SyncToken, UserID
from mautrix.util.async_db import Database from mautrix.util.async_db import Database
from ..types import TelegramID from ..types import TelegramID
@@ -44,6 +44,9 @@ class Puppet:
disable_updates: bool disable_updates: bool
username: str | None username: str | None
photo_id: str | None photo_id: str | None
avatar_url: ContentURI | None
name_set: bool
avatar_set: bool
is_bot: bool | None is_bot: bool | None
is_channel: bool is_channel: bool
@@ -62,8 +65,8 @@ class Puppet:
columns: ClassVar[str] = ( columns: ClassVar[str] = (
"id, is_registered, displayname, displayname_source, displayname_contact, " "id, is_registered, displayname, displayname_source, displayname_contact, "
"displayname_quality, disable_updates, username, photo_id, is_bot, is_channel, " "displayname_quality, disable_updates, username, photo_id, avatar_url, "
"custom_mxid, access_token, next_batch, base_url" "name_set, avatar_set, is_bot, is_channel, custom_mxid, access_token, next_batch, base_url"
) )
@classmethod @classmethod
@@ -103,6 +106,9 @@ class Puppet:
self.disable_updates, self.disable_updates,
self.username, self.username,
self.photo_id, self.photo_id,
self.avatar_url,
self.name_set,
self.avatar_set,
self.is_bot, self.is_bot,
self.is_channel, self.is_channel,
self.custom_mxid, self.custom_mxid,
@@ -112,21 +118,22 @@ class Puppet:
) )
async def save(self) -> None: async def save(self) -> None:
q = ( q = """
"UPDATE puppet " UPDATE puppet
"SET is_registered=$2, displayname=$3, displayname_source=$4, displayname_contact=$5," SET is_registered=$2, displayname=$3, displayname_source=$4, displayname_contact=$5,
" displayname_quality=$6, disable_updates=$7, username=$8, photo_id=$9, is_bot=$10," displayname_quality=$6, disable_updates=$7, username=$8, photo_id=$9,
" is_channel=$11, custom_mxid=$12, access_token=$13, next_batch=$14, base_url=$15 " avatar_url=$10, name_set=$11, avatar_set=$12, is_bot=$13, is_channel=$14,
"WHERE id=$1" custom_mxid=$15, access_token=$16, next_batch=$17, base_url=$18
) WHERE id=$1
"""
await self.db.execute(q, *self._values) await self.db.execute(q, *self._values)
async def insert(self) -> None: async def insert(self) -> None:
q = ( q = """
"INSERT INTO puppet (" INSERT INTO puppet (
" id, is_registered, displayname, displayname_source, displayname_contact," id, is_registered, displayname, displayname_source, displayname_contact,
" displayname_quality, disable_updates, username, photo_id, is_bot, is_channel," displayname_quality, disable_updates, username, photo_id, avatar_url, name_set,
" custom_mxid, access_token, next_batch, base_url" avatar_set, is_bot, is_channel, custom_mxid, access_token, next_batch, base_url
") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)" ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
) """
await self.db.execute(q, *self._values) await self.db.execute(q, *self._values)
+1
View File
@@ -8,4 +8,5 @@ from . import (
v03_reactions, v03_reactions,
v04_disappearing_messages, v04_disappearing_messages,
v05_channel_ghosts, v05_channel_ghosts,
v06_puppet_avatar_url,
) )
@@ -0,0 +1,31 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from asyncpg import Connection
from . import upgrade_table
@upgrade_table.register(description="Store avatar mxc URI in puppet table")
async def upgrade_v6(conn: Connection) -> None:
await conn.execute("ALTER TABLE puppet ADD COLUMN avatar_url TEXT")
await conn.execute("ALTER TABLE puppet ADD COLUMN name_set BOOLEAN NOT NULL DEFAULT false")
await conn.execute("ALTER TABLE puppet ADD COLUMN avatar_set BOOLEAN NOT NULL DEFAULT false")
await conn.execute("UPDATE puppet SET name_set=true WHERE displayname<>''")
await conn.execute("UPDATE puppet SET avatar_set=true WHERE photo_id<>''")
await conn.execute("ALTER TABLE portal ADD COLUMN name_set BOOLEAN NOT NULL DEFAULT false")
await conn.execute("ALTER TABLE portal ADD COLUMN avatar_set BOOLEAN NOT NULL DEFAULT false")
await conn.execute("UPDATE portal SET name_set=true WHERE title<>'' AND mxid<>''")
await conn.execute("UPDATE portal SET avatar_set=true WHERE photo_id<>'' AND mxid<>''")
+99 -46
View File
@@ -147,6 +147,7 @@ from telethon.tl.types import (
TypePhotoSize, TypePhotoSize,
TypeUser, TypeUser,
TypeUserFull, TypeUserFull,
TypeUserProfilePhoto,
UpdateChannelUserTyping, UpdateChannelUserTyping,
UpdateChatUserTyping, UpdateChatUserTyping,
UpdateNewMessage, UpdateNewMessage,
@@ -305,6 +306,8 @@ class Portal(DBPortal, BasePortal):
title: str | None = None, title: str | None = None,
about: str | None = None, about: str | None = None,
photo_id: str | None = None, photo_id: str | None = None,
name_set: bool = False,
avatar_set: bool = False,
local_config: dict[str, Any] | None = None, local_config: dict[str, Any] | None = None,
) -> None: ) -> None:
super().__init__( super().__init__(
@@ -322,6 +325,8 @@ class Portal(DBPortal, BasePortal):
title=title, title=title,
about=about, about=about,
photo_id=photo_id, photo_id=photo_id,
name_set=name_set,
avatar_set=avatar_set,
local_config=local_config or {}, local_config=local_config or {},
) )
BasePortal.__init__(self) BasePortal.__init__(self)
@@ -636,14 +641,7 @@ class Portal(DBPortal, BasePortal):
puppet = await p.Puppet.get_by_tgid(self.tgid) puppet = await p.Puppet.get_by_tgid(self.tgid)
await puppet.update_info(user, entity) await puppet.update_info(user, entity)
await puppet.intent_for(self).join_room(self.mxid) await puppet.intent_for(self).join_room(self.mxid)
if self.encrypted or self.private_chat_portal_meta: await self.update_info_from_puppet(puppet, user, entity.photo)
# The bridge bot needs to join for e2ee, but that messes up the default name
# generation. If/when canonical DMs happen, this might not be necessary anymore.
changed = await self._update_title(puppet.displayname)
changed = await self._update_avatar(user, entity.photo) or changed
if changed:
await self.save()
await self.update_bridge_info()
puppet = await p.Puppet.get_by_custom_mxid(user.mxid) puppet = await p.Puppet.get_by_custom_mxid(user.mxid)
if puppet: if puppet:
@@ -657,6 +655,22 @@ class Portal(DBPortal, BasePortal):
if self.sync_matrix_state: if self.sync_matrix_state:
await self.main_intent.get_joined_members(self.mxid) await self.main_intent.get_joined_members(self.mxid)
async def update_info_from_puppet(
self,
puppet: p.Puppet,
source: au.AbstractUser | None = None,
photo: UserProfilePhoto | None = None,
) -> None:
if not self.encrypted and not self.private_chat_portal_meta:
return
# The bridge bot needs to join for e2ee, but that messes up the default name
# generation. If/when canonical DMs happen, this might not be necessary anymore.
changed = await self._update_avatar_from_puppet(puppet, source, photo)
changed = await self._update_title(puppet.displayname) or changed
if changed:
await self.save()
await self.update_bridge_info()
async def create_matrix_room( async def create_matrix_room(
self, self,
user: au.AbstractUser, user: au.AbstractUser,
@@ -802,8 +816,8 @@ class Portal(DBPortal, BasePortal):
"state_key": self.bridge_info_state_key, "state_key": self.bridge_info_state_key,
"content": self.bridge_info, "content": self.bridge_info,
}, },
# TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec
{ {
# TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec
"type": str(StateHalfShotBridge), "type": str(StateHalfShotBridge),
"state_key": self.bridge_info_state_key, "state_key": self.bridge_info_state_key,
"content": self.bridge_info, "content": self.bridge_info,
@@ -814,7 +828,7 @@ class Portal(DBPortal, BasePortal):
self.encrypted = True self.encrypted = True
initial_state.append( initial_state.append(
{ {
"type": "m.room.encryption", "type": str(EventType.ROOM_ENCRYPTION),
"content": {"algorithm": "m.megolm.v1.aes-sha2"}, "content": {"algorithm": "m.megolm.v1.aes-sha2"},
} }
) )
@@ -822,6 +836,8 @@ class Portal(DBPortal, BasePortal):
create_invites.append(self.az.bot_mxid) create_invites.append(self.az.bot_mxid)
if direct and (self.encrypted or self.private_chat_portal_meta): if direct and (self.encrypted or self.private_chat_portal_meta):
self.title = puppet.displayname self.title = puppet.displayname
self.avatar_url = puppet.avatar_url
self.photo_id = puppet.photo_id
if self.config["appservice.community_id"]: if self.config["appservice.community_id"]:
initial_state.append( initial_state.append(
{ {
@@ -832,6 +848,13 @@ class Portal(DBPortal, BasePortal):
creation_content = {} creation_content = {}
if not self.config["bridge.federate_rooms"]: if not self.config["bridge.federate_rooms"]:
creation_content["m.federate"] = False creation_content["m.federate"] = False
if self.avatar_url:
initial_state.append(
{
"type": str(EventType.ROOM_AVATAR),
"content": {"url": self.avatar_url},
}
)
with self.backfill_lock: with self.backfill_lock:
room_id = await self.main_intent.create_room( room_id = await self.main_intent.create_room(
@@ -846,6 +869,8 @@ class Portal(DBPortal, BasePortal):
) )
if not room_id: if not room_id:
raise Exception(f"Failed to create room") raise Exception(f"Failed to create room")
self.name_set = bool(self.title)
self.avatar_set = bool(self.avatar_url)
if self.encrypted and self.matrix.e2ee and direct: if self.encrypted and self.matrix.e2ee and direct:
try: try:
@@ -1106,21 +1131,48 @@ class Portal(DBPortal, BasePortal):
async def _update_title( async def _update_title(
self, title: str, sender: p.Puppet | None = None, save: bool = False self, title: str, sender: p.Puppet | None = None, save: bool = False
) -> bool: ) -> bool:
if self.title == title: if self.title == title and self.name_set:
return False return False
self.title = title self.title = title
await self._try_set_state( try:
sender, EventType.ROOM_NAME, RoomNameStateEventContent(name=self.title) await self._try_set_state(
) sender, EventType.ROOM_NAME, RoomNameStateEventContent(name=self.title)
)
self.name_set = True
except Exception as e:
self.log.warning(f"Failed to set room name: {e}")
self.name_set = False
if save: if save:
await self.save() await self.save()
return True return True
async def _update_avatar_from_puppet(
self, puppet: p.Puppet, user: au.AbstractUser | None, photo: UserProfilePhoto | None
) -> bool:
if self.photo_id == puppet.photo_id and self.avatar_set:
return False
if puppet.avatar_url:
self.photo_id = puppet.photo_id
self.avatar_url = puppet.avatar_url
try:
await self._try_set_state(
None, EventType.ROOM_AVATAR, RoomAvatarStateEventContent(url=self.avatar_url)
)
self.avatar_set = True
except Exception as e:
self.log.warning(f"Failed to set room avatar: {e}")
self.avatar_set = False
return True
elif photo is not None and user is not None:
return await self._update_avatar(user, photo=photo)
else:
return False
async def _update_avatar( async def _update_avatar(
self, self,
user: au.AbstractUser, user: au.AbstractUser,
photo: TypeChatPhoto, photo: TypeChatPhoto | TypeUserProfilePhoto,
sender: p.Puppet | None = None, sender: p.Puppet | None = None,
save: bool = False, save: bool = False,
) -> bool: ) -> bool:
@@ -1143,26 +1195,27 @@ class Portal(DBPortal, BasePortal):
and not self.config["bridge.allow_avatar_remove"] and not self.config["bridge.allow_avatar_remove"]
): ):
return False return False
if self.photo_id != photo_id: if self.photo_id != photo_id or not self.avatar_set:
if not photo_id: if not photo_id:
await self._try_set_state(
sender, EventType.ROOM_AVATAR, RoomAvatarStateEventContent(url=None)
)
self.photo_id = "" self.photo_id = ""
self.avatar_url = None self.avatar_url = None
if save: elif self.photo_id != photo_id or not self.avatar_url:
await self.save() file = await util.transfer_file_to_matrix(user.client, self.main_intent, loc)
return True if not file:
file = await util.transfer_file_to_matrix(user.client, self.main_intent, loc) return False
if file:
await self._try_set_state(
sender, EventType.ROOM_AVATAR, RoomAvatarStateEventContent(url=file.mxc)
)
self.photo_id = photo_id self.photo_id = photo_id
self.avatar_url = file.mxc self.avatar_url = file.mxc
if save: try:
await self.save() await self._try_set_state(
return True sender, EventType.ROOM_AVATAR, RoomAvatarStateEventContent(url=self.avatar_url)
)
self.avatar_set = True
except Exception as e:
self.log.warning(f"Failed to set room avatar: {e}")
self.avatar_set = False
if save:
await self.save()
return True
return False return False
# endregion # endregion
@@ -2120,11 +2173,8 @@ class Portal(DBPortal, BasePortal):
async def enable_dm_encryption(self) -> bool: async def enable_dm_encryption(self) -> bool:
ok = await super().enable_dm_encryption() ok = await super().enable_dm_encryption()
if ok: if ok:
try: puppet = await p.Puppet.get_by_tgid(self.tgid)
puppet = await p.Puppet.get_by_tgid(self.tgid) await self.update_info_from_puppet(puppet)
await self.main_intent.set_room_name(self.mxid, puppet.displayname)
except Exception:
self.log.warning(f"Failed to set room name", exc_info=True)
return ok return ok
# endregion # endregion
@@ -3373,8 +3423,10 @@ class Portal(DBPortal, BasePortal):
self.by_mxid[self.mxid] = self self.by_mxid[self.mxid] = self
@classmethod @classmethod
async def all(cls) -> AsyncGenerator[Portal, None]: async def _yield_portals(
portals = await super().all() cls, query: Awaitable[list[DBPortal]]
) -> AsyncGenerator[Portal, None]:
portals = await query
portal: cls portal: cls
for portal in portals: for portal in portals:
try: try:
@@ -3384,15 +3436,16 @@ class Portal(DBPortal, BasePortal):
yield portal yield portal
@classmethod @classmethod
async def find_private_chats(cls, tg_receiver: TelegramID) -> AsyncGenerator[Portal, None]: def all(cls) -> AsyncGenerator[Portal, None]:
portals = await super().find_private_chats(tg_receiver) return cls._yield_portals(super().all())
portal: cls
for portal in portals: @classmethod
try: def find_private_chats_of(cls, tg_receiver: TelegramID) -> AsyncGenerator[Portal, None]:
yield cls.by_tgid[portal.tgid_full] return cls._yield_portals(super().find_private_chats_of(tg_receiver))
except KeyError:
await portal.postinit() @classmethod
yield portal def find_private_chats_with(cls, tgid: TelegramID) -> AsyncGenerator[Portal, None]:
return cls._yield_portals(super().find_private_chats_with(tgid))
@classmethod @classmethod
@async_getter_lock @async_getter_lock
+48 -27
View File
@@ -41,7 +41,6 @@ from yarl import URL
from mautrix.appservice import IntentAPI from mautrix.appservice import IntentAPI
from mautrix.bridge import BasePuppet, async_getter_lock from mautrix.bridge import BasePuppet, async_getter_lock
from mautrix.errors import MatrixError
from mautrix.types import ContentURI, RoomID, SyncToken, UserID from mautrix.types import ContentURI, RoomID, SyncToken, UserID
from mautrix.util.simple_template import SimpleTemplate from mautrix.util.simple_template import SimpleTemplate
@@ -74,6 +73,9 @@ class Puppet(DBPuppet, BasePuppet):
disable_updates: bool = False, disable_updates: bool = False,
username: str | None = None, username: str | None = None,
photo_id: str | None = None, photo_id: str | None = None,
avatar_url: ContentURI | None = None,
name_set: bool = False,
avatar_set: bool = False,
is_bot: bool = False, is_bot: bool = False,
is_channel: bool = False, is_channel: bool = False,
custom_mxid: UserID | None = None, custom_mxid: UserID | None = None,
@@ -91,6 +93,9 @@ class Puppet(DBPuppet, BasePuppet):
disable_updates=disable_updates, disable_updates=disable_updates,
username=username, username=username,
photo_id=photo_id, photo_id=photo_id,
avatar_url=avatar_url,
name_set=name_set,
avatar_set=avatar_set,
is_bot=is_bot, is_bot=is_bot,
is_channel=is_channel, is_channel=is_channel,
custom_mxid=custom_mxid, custom_mxid=custom_mxid,
@@ -255,14 +260,28 @@ class Puppet(DBPuppet, BasePuppet):
self.log.exception(f"Failed to update info from source {source.tgid}") self.log.exception(f"Failed to update info from source {source.tgid}")
if changed: if changed:
await self.update_portals_meta()
await self.save() await self.save()
async def update_portals_meta(self) -> None:
if not p.Portal.private_chat_portal_meta and not self.mx.e2ee:
return
async for portal in p.Portal.find_private_chats_with(self.tgid):
await portal.update_info_from_puppet(self)
async def update_displayname( async def update_displayname(
self, source: au.AbstractUser, info: User | Channel | UpdateUserName self, source: au.AbstractUser, info: User | Channel | UpdateUserName
) -> bool: ) -> bool:
if self.disable_updates: if self.disable_updates:
return False return False
if source.is_relaybot or source.is_bot: if (
self.displayname
and self.displayname.startswith("Deleted user ")
and not getattr(info, "deleted", False)
):
allow_because = "target user was previously deleted"
self.displayname_quality = 0
elif source.is_relaybot or source.is_bot:
allow_because = "source user is a bot" allow_because = "source user is a bot"
elif self.displayname_source == source.tgid: elif self.displayname_source == source.tgid:
allow_because = "source user is the primary source" allow_because = "source user is the primary source"
@@ -288,7 +307,9 @@ class Puppet(DBPuppet, BasePuppet):
return False return False
displayname, quality = self.get_displayname(info) displayname, quality = self.get_displayname(info)
if displayname != self.displayname and quality >= self.displayname_quality: needs_reset = displayname != self.displayname or not self.name_set
is_high_quality = quality >= self.displayname_quality
if needs_reset and is_high_quality:
allow_because = f"{allow_because} and quality {quality} >= {self.displayname_quality}" allow_because = f"{allow_because} and quality {quality} >= {self.displayname_quality}"
self.log.debug( self.log.debug(
f"Updating displayname of {self.id} (src: {source.tgid}, allowed " f"Updating displayname of {self.id} (src: {source.tgid}, allowed "
@@ -302,11 +323,10 @@ class Puppet(DBPuppet, BasePuppet):
await self.default_mxid_intent.set_displayname( await self.default_mxid_intent.set_displayname(
displayname[: self.config["bridge.displayname_max_length"]] displayname[: self.config["bridge.displayname_max_length"]]
) )
except MatrixError: self.name_set = True
self.log.exception("Failed to set displayname") except Exception as e:
self.displayname = "" self.log.warning(f"Failed to set displayname: {e}")
self.displayname_source = None self.name_set = False
self.displayname_quality = 0
return True return True
elif source.is_relaybot or self.displayname_source is None: elif source.is_relaybot or self.displayname_source is None:
self.displayname_source = source.tgid self.displayname_source = source.tgid
@@ -328,28 +348,29 @@ class Puppet(DBPuppet, BasePuppet):
return False return False
if not photo_id and not self.config["bridge.allow_avatar_remove"]: if not photo_id and not self.config["bridge.allow_avatar_remove"]:
return False return False
if self.photo_id != photo_id: if self.photo_id != photo_id or not self.avatar_set:
if not photo_id: if not photo_id:
self.photo_id = "" self.photo_id = ""
try: self.avatar_url = None
await self.default_mxid_intent.set_avatar_url(ContentURI("")) elif self.photo_id != photo_id or not self.avatar_url:
except MatrixError: file = await util.transfer_file_to_matrix(
self.log.exception("Failed to set avatar") client=source.client,
self.photo_id = "" intent=self.default_mxid_intent,
return True location=InputPeerPhotoFileLocation(
peer=await self.get_input_entity(source), photo_id=photo.photo_id, big=True
loc = InputPeerPhotoFileLocation( ),
peer=await self.get_input_entity(source), photo_id=photo.photo_id, big=True )
) if not file:
file = await util.transfer_file_to_matrix(source.client, self.default_mxid_intent, loc) return False
if file:
self.photo_id = photo_id self.photo_id = photo_id
try: self.avatar_url = file.mxc
await self.default_mxid_intent.set_avatar_url(file.mxc) try:
except MatrixError: await self.default_mxid_intent.set_avatar_url(self.avatar_url or "")
self.log.exception("Failed to set avatar") self.avatar_set = True
self.photo_id = "" except Exception as e:
return True self.log.warning(f"Failed to set avatar: {e}")
self.avatar_set = False
return True
return False return False
async def default_puppet_should_leave_room(self, room_id: RoomID) -> bool: async def default_puppet_should_leave_room(self, room_id: RoomID) -> bool:
+1 -1
View File
@@ -448,7 +448,7 @@ class User(DBUser, AbstractUser, BaseUser):
async def get_direct_chats(self) -> dict[UserID, list[RoomID]]: async def get_direct_chats(self) -> dict[UserID, list[RoomID]]:
return { return {
pu.Puppet.get_mxid_from_id(portal.tgid): [portal.mxid] pu.Puppet.get_mxid_from_id(portal.tgid): [portal.mxid]
async for portal in po.Portal.find_private_chats(self.tgid) async for portal in po.Portal.find_private_chats_of(self.tgid)
if portal.mxid if portal.mxid
} }