mirror of
https://github.com/mautrix/telegram.git
synced 2026-05-17 07:25:46 +03:00
Improve profile info syncing
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
a new user.
|
||||
* Added support for adding an optional random prefix to relayed user displaynames
|
||||
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`.
|
||||
|
||||
# v0.11.1 (2021-01-10)
|
||||
|
||||
@@ -469,9 +469,11 @@ class AbstractUser(ABC):
|
||||
puppet.username = update.username
|
||||
if await puppet.update_displayname(self, update):
|
||||
await puppet.save()
|
||||
await puppet.update_portals_meta()
|
||||
elif isinstance(update, UpdateUserPhoto):
|
||||
if await puppet.update_avatar(self, update.photo):
|
||||
await puppet.save()
|
||||
await puppet.update_portals_meta()
|
||||
else:
|
||||
self.log.warning(f"Unexpected other user info update: {type(update)}")
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ class Portal:
|
||||
title: str | None
|
||||
about: str | None
|
||||
photo_id: str | None
|
||||
name_set: bool
|
||||
avatar_set: bool
|
||||
|
||||
local_config: dict[str, Any] = attr.ib(factory=lambda: {})
|
||||
|
||||
@@ -67,7 +69,8 @@ class Portal:
|
||||
|
||||
columns: ClassVar[str] = (
|
||||
"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
|
||||
@@ -86,10 +89,15 @@ class Portal:
|
||||
return cls._from_row(await cls.db.fetchrow(q, username.lower()))
|
||||
|
||||
@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'"
|
||||
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
|
||||
async def all(cls) -> list[Portal]:
|
||||
rows = await cls.db.fetch(f"SELECT {cls.columns} FROM portal")
|
||||
@@ -111,17 +119,20 @@ class Portal:
|
||||
self.title,
|
||||
self.about,
|
||||
self.photo_id,
|
||||
self.name_set,
|
||||
self.avatar_set,
|
||||
self.megagroup,
|
||||
json.dumps(self.local_config) if self.local_config else None,
|
||||
)
|
||||
|
||||
async def save(self) -> None:
|
||||
q = (
|
||||
"UPDATE portal SET mxid=$4, avatar_url=$5, encrypted=$6, sponsored_event_id=$7,"
|
||||
" sponsored_event_ts=$8, sponsored_msg_random_id=$9, username=$10,"
|
||||
" title=$11, about=$12, photo_id=$13, megagroup=$14, config=$15 "
|
||||
"WHERE tgid=$1 AND tg_receiver=$2 AND (peer_type=$3 OR true)"
|
||||
)
|
||||
q = """
|
||||
UPDATE portal
|
||||
SET mxid=$4, avatar_url=$5, encrypted=$6, sponsored_event_id=$7, sponsored_event_ts=$8,
|
||||
sponsored_msg_random_id=$9, username=$10, title=$11, about=$12, photo_id=$13,
|
||||
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)
|
||||
|
||||
async def update_id(self, id: TelegramID, peer_type: str) -> None:
|
||||
@@ -135,12 +146,13 @@ class Portal:
|
||||
self.peer_type = peer_type
|
||||
|
||||
async def insert(self) -> None:
|
||||
q = (
|
||||
"INSERT INTO portal (tgid, tg_receiver, peer_type, mxid, avatar_url, encrypted,"
|
||||
" sponsored_event_id, sponsored_event_ts, sponsored_msg_random_id,"
|
||||
" username, title, about, photo_id, megagroup, config) "
|
||||
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)"
|
||||
)
|
||||
q = """
|
||||
INSERT INTO portal (
|
||||
tgid, tg_receiver, peer_type, mxid, avatar_url, encrypted,
|
||||
sponsored_event_id, sponsored_event_ts, sponsored_msg_random_id,
|
||||
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)
|
||||
|
||||
async def delete(self) -> None:
|
||||
|
||||
@@ -21,7 +21,7 @@ from asyncpg import Record
|
||||
from attr import dataclass
|
||||
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 ..types import TelegramID
|
||||
@@ -44,6 +44,9 @@ class Puppet:
|
||||
disable_updates: bool
|
||||
username: str | None
|
||||
photo_id: str | None
|
||||
avatar_url: ContentURI | None
|
||||
name_set: bool
|
||||
avatar_set: bool
|
||||
is_bot: bool | None
|
||||
is_channel: bool
|
||||
|
||||
@@ -62,8 +65,8 @@ class Puppet:
|
||||
|
||||
columns: ClassVar[str] = (
|
||||
"id, is_registered, displayname, displayname_source, displayname_contact, "
|
||||
"displayname_quality, disable_updates, username, photo_id, is_bot, is_channel, "
|
||||
"custom_mxid, access_token, next_batch, base_url"
|
||||
"displayname_quality, disable_updates, username, photo_id, avatar_url, "
|
||||
"name_set, avatar_set, is_bot, is_channel, custom_mxid, access_token, next_batch, base_url"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -103,6 +106,9 @@ class Puppet:
|
||||
self.disable_updates,
|
||||
self.username,
|
||||
self.photo_id,
|
||||
self.avatar_url,
|
||||
self.name_set,
|
||||
self.avatar_set,
|
||||
self.is_bot,
|
||||
self.is_channel,
|
||||
self.custom_mxid,
|
||||
@@ -112,21 +118,22 @@ class Puppet:
|
||||
)
|
||||
|
||||
async def save(self) -> None:
|
||||
q = (
|
||||
"UPDATE puppet "
|
||||
"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,"
|
||||
" is_channel=$11, custom_mxid=$12, access_token=$13, next_batch=$14, base_url=$15 "
|
||||
"WHERE id=$1"
|
||||
)
|
||||
q = """
|
||||
UPDATE puppet
|
||||
SET is_registered=$2, displayname=$3, displayname_source=$4, displayname_contact=$5,
|
||||
displayname_quality=$6, disable_updates=$7, username=$8, photo_id=$9,
|
||||
avatar_url=$10, name_set=$11, avatar_set=$12, is_bot=$13, is_channel=$14,
|
||||
custom_mxid=$15, access_token=$16, next_batch=$17, base_url=$18
|
||||
WHERE id=$1
|
||||
"""
|
||||
await self.db.execute(q, *self._values)
|
||||
|
||||
async def insert(self) -> None:
|
||||
q = (
|
||||
"INSERT INTO puppet ("
|
||||
" id, is_registered, displayname, displayname_source, displayname_contact,"
|
||||
" displayname_quality, disable_updates, username, photo_id, 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)"
|
||||
)
|
||||
q = """
|
||||
INSERT INTO puppet (
|
||||
id, is_registered, displayname, displayname_source, displayname_contact,
|
||||
displayname_quality, disable_updates, username, photo_id, avatar_url, name_set,
|
||||
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, $16, $17, $18)
|
||||
"""
|
||||
await self.db.execute(q, *self._values)
|
||||
|
||||
@@ -8,4 +8,5 @@ from . import (
|
||||
v03_reactions,
|
||||
v04_disappearing_messages,
|
||||
v05_channel_ghosts,
|
||||
v06_puppet_avatar_url,
|
||||
)
|
||||
|
||||
31
mautrix_telegram/db/upgrade/v06_puppet_avatar_url.py
Normal file
31
mautrix_telegram/db/upgrade/v06_puppet_avatar_url.py
Normal file
@@ -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<>''")
|
||||
@@ -147,6 +147,7 @@ from telethon.tl.types import (
|
||||
TypePhotoSize,
|
||||
TypeUser,
|
||||
TypeUserFull,
|
||||
TypeUserProfilePhoto,
|
||||
UpdateChannelUserTyping,
|
||||
UpdateChatUserTyping,
|
||||
UpdateNewMessage,
|
||||
@@ -305,6 +306,8 @@ class Portal(DBPortal, BasePortal):
|
||||
title: str | None = None,
|
||||
about: str | None = None,
|
||||
photo_id: str | None = None,
|
||||
name_set: bool = False,
|
||||
avatar_set: bool = False,
|
||||
local_config: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
@@ -322,6 +325,8 @@ class Portal(DBPortal, BasePortal):
|
||||
title=title,
|
||||
about=about,
|
||||
photo_id=photo_id,
|
||||
name_set=name_set,
|
||||
avatar_set=avatar_set,
|
||||
local_config=local_config or {},
|
||||
)
|
||||
BasePortal.__init__(self)
|
||||
@@ -636,14 +641,7 @@ class Portal(DBPortal, BasePortal):
|
||||
puppet = await p.Puppet.get_by_tgid(self.tgid)
|
||||
await puppet.update_info(user, entity)
|
||||
await puppet.intent_for(self).join_room(self.mxid)
|
||||
if self.encrypted or self.private_chat_portal_meta:
|
||||
# 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()
|
||||
await self.update_info_from_puppet(puppet, user, entity.photo)
|
||||
|
||||
puppet = await p.Puppet.get_by_custom_mxid(user.mxid)
|
||||
if puppet:
|
||||
@@ -657,6 +655,22 @@ class Portal(DBPortal, BasePortal):
|
||||
if self.sync_matrix_state:
|
||||
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(
|
||||
self,
|
||||
user: au.AbstractUser,
|
||||
@@ -802,8 +816,8 @@ class Portal(DBPortal, BasePortal):
|
||||
"state_key": self.bridge_info_state_key,
|
||||
"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),
|
||||
"state_key": self.bridge_info_state_key,
|
||||
"content": self.bridge_info,
|
||||
@@ -814,7 +828,7 @@ class Portal(DBPortal, BasePortal):
|
||||
self.encrypted = True
|
||||
initial_state.append(
|
||||
{
|
||||
"type": "m.room.encryption",
|
||||
"type": str(EventType.ROOM_ENCRYPTION),
|
||||
"content": {"algorithm": "m.megolm.v1.aes-sha2"},
|
||||
}
|
||||
)
|
||||
@@ -822,6 +836,8 @@ class Portal(DBPortal, BasePortal):
|
||||
create_invites.append(self.az.bot_mxid)
|
||||
if direct and (self.encrypted or self.private_chat_portal_meta):
|
||||
self.title = puppet.displayname
|
||||
self.avatar_url = puppet.avatar_url
|
||||
self.photo_id = puppet.photo_id
|
||||
if self.config["appservice.community_id"]:
|
||||
initial_state.append(
|
||||
{
|
||||
@@ -832,6 +848,13 @@ class Portal(DBPortal, BasePortal):
|
||||
creation_content = {}
|
||||
if not self.config["bridge.federate_rooms"]:
|
||||
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:
|
||||
room_id = await self.main_intent.create_room(
|
||||
@@ -846,6 +869,8 @@ class Portal(DBPortal, BasePortal):
|
||||
)
|
||||
if not room_id:
|
||||
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:
|
||||
try:
|
||||
@@ -1106,21 +1131,48 @@ class Portal(DBPortal, BasePortal):
|
||||
async def _update_title(
|
||||
self, title: str, sender: p.Puppet | None = None, save: bool = False
|
||||
) -> bool:
|
||||
if self.title == title:
|
||||
if self.title == title and self.name_set:
|
||||
return False
|
||||
|
||||
self.title = title
|
||||
await self._try_set_state(
|
||||
sender, EventType.ROOM_NAME, RoomNameStateEventContent(name=self.title)
|
||||
)
|
||||
try:
|
||||
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:
|
||||
await self.save()
|
||||
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(
|
||||
self,
|
||||
user: au.AbstractUser,
|
||||
photo: TypeChatPhoto,
|
||||
photo: TypeChatPhoto | TypeUserProfilePhoto,
|
||||
sender: p.Puppet | None = None,
|
||||
save: bool = False,
|
||||
) -> bool:
|
||||
@@ -1143,26 +1195,27 @@ class Portal(DBPortal, BasePortal):
|
||||
and not self.config["bridge.allow_avatar_remove"]
|
||||
):
|
||||
return False
|
||||
if self.photo_id != photo_id:
|
||||
if self.photo_id != photo_id or not self.avatar_set:
|
||||
if not photo_id:
|
||||
await self._try_set_state(
|
||||
sender, EventType.ROOM_AVATAR, RoomAvatarStateEventContent(url=None)
|
||||
)
|
||||
self.photo_id = ""
|
||||
self.avatar_url = None
|
||||
if save:
|
||||
await self.save()
|
||||
return True
|
||||
file = await util.transfer_file_to_matrix(user.client, self.main_intent, loc)
|
||||
if file:
|
||||
await self._try_set_state(
|
||||
sender, EventType.ROOM_AVATAR, RoomAvatarStateEventContent(url=file.mxc)
|
||||
)
|
||||
elif self.photo_id != photo_id or not self.avatar_url:
|
||||
file = await util.transfer_file_to_matrix(user.client, self.main_intent, loc)
|
||||
if not file:
|
||||
return False
|
||||
self.photo_id = photo_id
|
||||
self.avatar_url = file.mxc
|
||||
if save:
|
||||
await self.save()
|
||||
return True
|
||||
try:
|
||||
await self._try_set_state(
|
||||
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
|
||||
|
||||
# endregion
|
||||
@@ -2120,11 +2173,8 @@ class Portal(DBPortal, BasePortal):
|
||||
async def enable_dm_encryption(self) -> bool:
|
||||
ok = await super().enable_dm_encryption()
|
||||
if ok:
|
||||
try:
|
||||
puppet = await p.Puppet.get_by_tgid(self.tgid)
|
||||
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)
|
||||
puppet = await p.Puppet.get_by_tgid(self.tgid)
|
||||
await self.update_info_from_puppet(puppet)
|
||||
return ok
|
||||
|
||||
# endregion
|
||||
@@ -3373,8 +3423,10 @@ class Portal(DBPortal, BasePortal):
|
||||
self.by_mxid[self.mxid] = self
|
||||
|
||||
@classmethod
|
||||
async def all(cls) -> AsyncGenerator[Portal, None]:
|
||||
portals = await super().all()
|
||||
async def _yield_portals(
|
||||
cls, query: Awaitable[list[DBPortal]]
|
||||
) -> AsyncGenerator[Portal, None]:
|
||||
portals = await query
|
||||
portal: cls
|
||||
for portal in portals:
|
||||
try:
|
||||
@@ -3384,15 +3436,16 @@ class Portal(DBPortal, BasePortal):
|
||||
yield portal
|
||||
|
||||
@classmethod
|
||||
async def find_private_chats(cls, tg_receiver: TelegramID) -> AsyncGenerator[Portal, None]:
|
||||
portals = await super().find_private_chats(tg_receiver)
|
||||
portal: cls
|
||||
for portal in portals:
|
||||
try:
|
||||
yield cls.by_tgid[portal.tgid_full]
|
||||
except KeyError:
|
||||
await portal.postinit()
|
||||
yield portal
|
||||
def all(cls) -> AsyncGenerator[Portal, None]:
|
||||
return cls._yield_portals(super().all())
|
||||
|
||||
@classmethod
|
||||
def find_private_chats_of(cls, tg_receiver: TelegramID) -> AsyncGenerator[Portal, None]:
|
||||
return cls._yield_portals(super().find_private_chats_of(tg_receiver))
|
||||
|
||||
@classmethod
|
||||
def find_private_chats_with(cls, tgid: TelegramID) -> AsyncGenerator[Portal, None]:
|
||||
return cls._yield_portals(super().find_private_chats_with(tgid))
|
||||
|
||||
@classmethod
|
||||
@async_getter_lock
|
||||
|
||||
@@ -41,7 +41,6 @@ from yarl import URL
|
||||
|
||||
from mautrix.appservice import IntentAPI
|
||||
from mautrix.bridge import BasePuppet, async_getter_lock
|
||||
from mautrix.errors import MatrixError
|
||||
from mautrix.types import ContentURI, RoomID, SyncToken, UserID
|
||||
from mautrix.util.simple_template import SimpleTemplate
|
||||
|
||||
@@ -74,6 +73,9 @@ class Puppet(DBPuppet, BasePuppet):
|
||||
disable_updates: bool = False,
|
||||
username: 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_channel: bool = False,
|
||||
custom_mxid: UserID | None = None,
|
||||
@@ -91,6 +93,9 @@ class Puppet(DBPuppet, BasePuppet):
|
||||
disable_updates=disable_updates,
|
||||
username=username,
|
||||
photo_id=photo_id,
|
||||
avatar_url=avatar_url,
|
||||
name_set=name_set,
|
||||
avatar_set=avatar_set,
|
||||
is_bot=is_bot,
|
||||
is_channel=is_channel,
|
||||
custom_mxid=custom_mxid,
|
||||
@@ -255,14 +260,28 @@ class Puppet(DBPuppet, BasePuppet):
|
||||
self.log.exception(f"Failed to update info from source {source.tgid}")
|
||||
|
||||
if changed:
|
||||
await self.update_portals_meta()
|
||||
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(
|
||||
self, source: au.AbstractUser, info: User | Channel | UpdateUserName
|
||||
) -> bool:
|
||||
if self.disable_updates:
|
||||
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"
|
||||
elif self.displayname_source == source.tgid:
|
||||
allow_because = "source user is the primary source"
|
||||
@@ -288,7 +307,9 @@ class Puppet(DBPuppet, BasePuppet):
|
||||
return False
|
||||
|
||||
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}"
|
||||
self.log.debug(
|
||||
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(
|
||||
displayname[: self.config["bridge.displayname_max_length"]]
|
||||
)
|
||||
except MatrixError:
|
||||
self.log.exception("Failed to set displayname")
|
||||
self.displayname = ""
|
||||
self.displayname_source = None
|
||||
self.displayname_quality = 0
|
||||
self.name_set = True
|
||||
except Exception as e:
|
||||
self.log.warning(f"Failed to set displayname: {e}")
|
||||
self.name_set = False
|
||||
return True
|
||||
elif source.is_relaybot or self.displayname_source is None:
|
||||
self.displayname_source = source.tgid
|
||||
@@ -328,28 +348,29 @@ class Puppet(DBPuppet, BasePuppet):
|
||||
return False
|
||||
if not photo_id and not self.config["bridge.allow_avatar_remove"]:
|
||||
return False
|
||||
if self.photo_id != photo_id:
|
||||
if self.photo_id != photo_id or not self.avatar_set:
|
||||
if not photo_id:
|
||||
self.photo_id = ""
|
||||
try:
|
||||
await self.default_mxid_intent.set_avatar_url(ContentURI(""))
|
||||
except MatrixError:
|
||||
self.log.exception("Failed to set avatar")
|
||||
self.photo_id = ""
|
||||
return True
|
||||
|
||||
loc = InputPeerPhotoFileLocation(
|
||||
peer=await self.get_input_entity(source), photo_id=photo.photo_id, big=True
|
||||
)
|
||||
file = await util.transfer_file_to_matrix(source.client, self.default_mxid_intent, loc)
|
||||
if file:
|
||||
self.avatar_url = None
|
||||
elif self.photo_id != photo_id or not self.avatar_url:
|
||||
file = await util.transfer_file_to_matrix(
|
||||
client=source.client,
|
||||
intent=self.default_mxid_intent,
|
||||
location=InputPeerPhotoFileLocation(
|
||||
peer=await self.get_input_entity(source), photo_id=photo.photo_id, big=True
|
||||
),
|
||||
)
|
||||
if not file:
|
||||
return False
|
||||
self.photo_id = photo_id
|
||||
try:
|
||||
await self.default_mxid_intent.set_avatar_url(file.mxc)
|
||||
except MatrixError:
|
||||
self.log.exception("Failed to set avatar")
|
||||
self.photo_id = ""
|
||||
return True
|
||||
self.avatar_url = file.mxc
|
||||
try:
|
||||
await self.default_mxid_intent.set_avatar_url(self.avatar_url or "")
|
||||
self.avatar_set = True
|
||||
except Exception as e:
|
||||
self.log.warning(f"Failed to set avatar: {e}")
|
||||
self.avatar_set = False
|
||||
return True
|
||||
return False
|
||||
|
||||
async def default_puppet_should_leave_room(self, room_id: RoomID) -> bool:
|
||||
|
||||
@@ -448,7 +448,7 @@ class User(DBUser, AbstractUser, BaseUser):
|
||||
async def get_direct_chats(self) -> dict[UserID, list[RoomID]]:
|
||||
return {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user