Improve profile info syncing
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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)}")
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user