Ver código fonte

Store name quality in database to prevent updating to a lower quality name

Tulir Asokan 3 anos atrás
pai
commit
b793f24de2

+ 12 - 14
mausignald/types.py

@@ -132,33 +132,31 @@ class GetIdentitiesResponse(SerializableAttrs):
     identities: List[Identity]
 
 
-@dataclass
-class Contact(SerializableAttrs):
-    address: Address
-    name: Optional[str] = None
-    color: Optional[str] = None
-    profile_key: Optional[str] = field(default=None, json="profileKey")
-    message_expiration_time: int = field(default=0, json="messageExpirationTime")
-
-
 @dataclass
 class Capabilities(SerializableAttrs):
     gv2: bool = False
     storage: bool = False
     gv1_migration: bool = field(default=False, json="gv1-migration")
+    announcement_group: bool = False
+    change_number: bool = False
+    sender_key: bool = False
+    stories: bool = False
 
 
 @dataclass
 class Profile(SerializableAttrs):
+    address: Optional[Address] = None
     name: str = ""
     profile_name: str = ""
+    about: str = ""
     avatar: str = ""
-    identity_key: str = ""
-    unidentified_access: str = ""
-    unrestricted_unidentified_access: bool = False
-    address: Optional[Address] = None
-    expiration_time: int = 0
+    color: str = ""
+    emoji: str = ""
+    inbox_position: Optional[int] = None
+    mobilecoin_address: Optional[str] = None
+    expiration_time: Optional[int] = None
     capabilities: Optional[Capabilities] = None
+    # visible_badge_ids: List[str]
 
 
 @dataclass

+ 13 - 10
mautrix_signal/db/puppet.py

@@ -36,6 +36,7 @@ class Puppet:
     uuid: UUID | None
     number: str | None
     name: str | None
+    name_quality: int
     avatar_hash: str | None
     avatar_url: ContentURI | None
     name_set: bool
@@ -54,17 +55,18 @@ class Puppet:
         return str(self.base_url) if self.base_url else None
 
     async def insert(self) -> None:
-        q = (
-            "INSERT INTO puppet (uuid, number, name, avatar_hash, avatar_url, name_set, "
-            "                    avatar_set, uuid_registered, number_registered, "
-            "                    custom_mxid, access_token, next_batch, base_url) "
-            "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)"
-        )
+        q = """
+        INSERT INTO puppet (uuid, number, name, name_quality, avatar_hash, avatar_url, name_set,
+                            avatar_set, uuid_registered, number_registered,
+                            custom_mxid, access_token, next_batch, base_url)
+        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
+        """
         await self.db.execute(
             q,
             self.uuid,
             self.number,
             self.name,
+            self.name_quality,
             self.avatar_hash,
             self.avatar_url,
             self.name_set,
@@ -109,9 +111,9 @@ class Puppet:
 
     async def update(self) -> None:
         set_columns = (
-            "name=$3, avatar_hash=$4, avatar_url=$5, name_set=$6, avatar_set=$7, "
-            "uuid_registered=$8, number_registered=$9, "
-            "custom_mxid=$10, access_token=$11, next_batch=$12, base_url=$13"
+            "name=$3, name_quality=$4, avatar_hash=$5, avatar_url=$6, name_set=$7, avatar_set=$8, "
+            "uuid_registered=$9, number_registered=$10, "
+            "custom_mxid=$11, access_token=$12, next_batch=$13, base_url=$14"
         )
         q = (
             f"UPDATE puppet SET uuid=$1, {set_columns} WHERE number=$2"
@@ -123,6 +125,7 @@ class Puppet:
             self.uuid,
             self.number,
             self.name,
+            self.name_quality,
             self.avatar_hash,
             self.avatar_url,
             self.name_set,
@@ -143,7 +146,7 @@ class Puppet:
         return cls(base_url=base_url, **data)
 
     _select_base = (
-        "SELECT uuid, number, name, avatar_hash, avatar_url, name_set, avatar_set, "
+        "SELECT uuid, number, name, name_quality, avatar_hash, avatar_url, name_set, avatar_set, "
         "       uuid_registered, number_registered, custom_mxid, access_token, "
         "       next_batch, base_url "
         "FROM puppet"

+ 1 - 0
mautrix_signal/db/upgrade/__init__.py

@@ -12,4 +12,5 @@ from . import (
     v07_portal_relay_user,
     v08_disappearing_messages,
     v09_group_topic,
+    v10_puppet_name_quality,
 )

+ 9 - 8
mautrix_signal/db/upgrade/v00_latest_revision.py

@@ -18,7 +18,7 @@ from mautrix.util.async_db import Connection
 from . import upgrade_table
 
 
-@upgrade_table.register(description="Initial revision", upgrades_to=9)
+@upgrade_table.register(description="Initial revision", upgrades_to=10)
 async def upgrade_latest(conn: Connection) -> None:
     await conn.execute(
         """CREATE TABLE portal (
@@ -49,13 +49,14 @@ async def upgrade_latest(conn: Connection) -> None:
     )
     await conn.execute(
         """CREATE TABLE puppet (
-            uuid        UUID UNIQUE,
-            number      TEXT UNIQUE,
-            name        TEXT,
-            avatar_hash TEXT,
-            avatar_url  TEXT,
-            name_set    BOOLEAN NOT NULL DEFAULT false,
-            avatar_set  BOOLEAN NOT NULL DEFAULT false,
+            uuid         UUID UNIQUE,
+            number       TEXT UNIQUE,
+            name         TEXT,
+            name_quality INTEGER NOT NULL DEFAULT 0,
+            avatar_hash  TEXT,
+            avatar_url   TEXT,
+            name_set     BOOLEAN NOT NULL DEFAULT false,
+            avatar_set   BOOLEAN NOT NULL DEFAULT false,
 
             uuid_registered   BOOLEAN NOT NULL DEFAULT false,
             number_registered BOOLEAN NOT NULL DEFAULT false,

+ 23 - 0
mautrix_signal/db/upgrade/v10_puppet_name_quality.py

@@ -0,0 +1,23 @@
+# mautrix-signal - A Matrix-Signal 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 mautrix.util.async_db import Connection
+
+from . import upgrade_table
+
+
+@upgrade_table.register(description="Store puppet name quality in database")
+async def upgrade_v10(conn: Connection) -> None:
+    await conn.execute("ALTER TABLE puppet ADD COLUMN name_quality INTEGER NOT NULL DEFAULT 0")

+ 5 - 6
mautrix_signal/portal.py

@@ -37,7 +37,6 @@ from mausignald.types import (
     Address,
     AnnouncementsMode,
     Attachment,
-    Contact,
     Group,
     GroupAccessControl,
     GroupID,
@@ -118,7 +117,7 @@ except ImportError:
 
 StateBridge = EventType.find("m.bridge", EventType.Class.STATE)
 StateHalfShotBridge = EventType.find("uk.half-shot.bridge", EventType.Class.STATE)
-ChatInfo = Union[Group, GroupV2, GroupV2ID, Contact, Profile, Address]
+ChatInfo = Union[Group, GroupV2, GroupV2ID, Profile, Address]
 MAX_MATRIX_MESSAGE_SIZE = 60000
 BEEPER_LINK_PREVIEWS_KEY = "com.beeper.linkpreviews"
 BEEPER_IMAGE_ENCRYPTION_KEY = "beeper:image:encryption"
@@ -1514,12 +1513,12 @@ class Portal(DBPortal, BasePortal):
         self, source: u.User, info: ChatInfo, sender: p.Puppet | None = None
     ) -> None:
         if self.is_direct:
-            if not isinstance(info, (Contact, Profile, Address)):
+            if not isinstance(info, (Profile, Address)):
                 raise ValueError(f"Unexpected type for direct chat update_info: {type(info)}")
             if not self.name:
                 puppet = await self.get_dm_puppet()
                 if not puppet.name:
-                    await puppet.update_info(info)
+                    await puppet.update_info(info, source)
                 self.name = puppet.name
             return
 
@@ -1819,7 +1818,7 @@ class Portal(DBPortal, BasePortal):
     async def update_matrix_room(self, source: u.User, info: ChatInfo) -> None:
         if not self.is_direct and not isinstance(info, (Group, GroupV2, GroupV2ID)):
             raise ValueError(f"Unexpected type for updating group portal: {type(info)}")
-        elif self.is_direct and not isinstance(info, (Contact, Profile, Address)):
+        elif self.is_direct and not isinstance(info, (Profile, Address)):
             raise ValueError(f"Unexpected type for updating direct chat portal: {type(info)}")
         try:
             await self._update_matrix_room(source, info)
@@ -1829,7 +1828,7 @@ class Portal(DBPortal, BasePortal):
     async def create_matrix_room(self, source: u.User, info: ChatInfo) -> RoomID | None:
         if not self.is_direct and not isinstance(info, (Group, GroupV2, GroupV2ID)):
             raise ValueError(f"Unexpected type for creating group portal: {type(info)}")
-        elif self.is_direct and not isinstance(info, (Contact, Profile, Address)):
+        elif self.is_direct and not isinstance(info, (Profile, Address)):
             raise ValueError(f"Unexpected type for creating direct chat portal: {type(info)}")
         if isinstance(info, Group) and not info.members:
             try:

+ 50 - 31
mautrix_signal/puppet.py

@@ -23,7 +23,7 @@ import os.path
 
 from yarl import URL
 
-from mausignald.types import Address, Contact, Profile
+from mausignald.types import Address, Profile
 from mautrix.appservice import IntentAPI
 from mautrix.bridge import BasePuppet, async_getter_lock
 from mautrix.errors import MForbidden
@@ -70,6 +70,7 @@ class Puppet(DBPuppet, BasePuppet):
         uuid: UUID | None,
         number: str | None,
         name: str | None = None,
+        name_quality: int = 0,
         avatar_url: ContentURI | None = None,
         avatar_hash: str | None = None,
         name_set: bool = False,
@@ -85,6 +86,7 @@ class Puppet(DBPuppet, BasePuppet):
             uuid=uuid,
             number=number,
             name=name,
+            name_quality=name_quality,
             avatar_url=avatar_url,
             avatar_hash=avatar_hash,
             name_set=name_set,
@@ -228,39 +230,22 @@ class Puppet(DBPuppet, BasePuppet):
         except Exception:
             self.log.warning("Failed to migrate power levels", exc_info=True)
 
-    async def update_info(self, info: Profile | Contact | Address) -> None:
-        address = info.address if isinstance(info, (Contact, Profile)) else info
+    async def update_info(self, info: Profile | Address, source: u.User) -> None:
+        address = info.address if isinstance(info, Profile) else info
         if address.uuid and not self.uuid:
             await self.handle_uuid_receive(address.uuid)
         if address.number and not self.number:
             await self.handle_number_receive(address.number)
-        self.log.debug("Updating info for %s", address)
-
-        contact_names = self.config["bridge.contact_list_names"]
-        name = None
-        if isinstance(info, Profile):
-            if info.profile_name:
-                self.log.debug(
-                    "Found profile name on profile for %s: '%s'", address, info.profile_name
-                )
-                name = info.profile_name
-            if contact_names == "prefer" or (contact_names == "allow" and not name):
-                # Try and overwrite the name with the contact name if that's the preference, or we
-                # didn't get a profile name.
-                self.log.debug("Found contact name on profile for %s: '%s'", address, info.name)
-                name = info.name or name
-        elif isinstance(info, Contact) and contact_names != "disallow":
-            self.log.debug("Found contact name on contact for %s: '%s'", address, info.name)
-            name = info.name
-        self.log.debug("Using name '%s' for %s", name, address)
+        self.log.debug("Updating info with %s (source: %s)", info, source.mxid)
 
         async with self._update_info_lock:
             update = False
-            if name is not None or self.name is None:
-                update = await self._update_name(name) or update
+            if isinstance(info, Profile) or self.name is None:
+                update = await self._update_name(info) or update
             if isinstance(info, Profile):
                 update = await self._update_avatar(info.avatar) or update
-            elif contact_names != "disallow" and self.number:
+            elif self.config["bridge.contact_list_names"] != "disallow" and self.number:
+                # Try to use a contact list avatar
                 update = await self._update_avatar(f"contact-{self.number}") or update
             if update:
                 await self.update()
@@ -275,8 +260,26 @@ class Puppet(DBPuppet, BasePuppet):
         return phonenumbers.format_number(parsed, fmt)
 
     @classmethod
-    def _get_displayname(cls, address: Address, name: str | None) -> str:
-        names = name.split("\x00") if name else []
+    def _get_displayname(cls, info: Profile | Address) -> tuple[str, int]:
+        quality = 10
+        if isinstance(info, Profile):
+            address = info.address
+            name = None
+            contact_names = cls.config["bridge.contact_list_names"]
+            if info.profile_name:
+                name = info.profile_name
+                quality = 90 if contact_names == "prefer" else 100
+            if info.name:
+                if contact_names == "prefer":
+                    quality = 100
+                    name = info.name
+                elif contact_names == "allow" and not name:
+                    quality = 50
+                    name = info.name
+            names = name.split("\x00") if name else []
+        else:
+            address = info
+            names = []
         data = {
             "first_name": names[0] if len(names) > 0 else "",
             "last_name": names[-1] if len(names) > 1 else "",
@@ -291,12 +294,16 @@ class Puppet(DBPuppet, BasePuppet):
                 data["displayname"] = value
                 break
 
-        return cls.config["bridge.displayname_template"].format(**data)
+        return cls.config["bridge.displayname_template"].format(**data), quality
 
-    async def _update_name(self, name: str | None) -> bool:
-        name = self._get_displayname(self.address, name)
-        if name != self.name or not self.name_set:
+    async def _update_name(self, info: Profile | Address) -> bool:
+        name, quality = self._get_displayname(info)
+        if quality >= self.name_quality and (name != self.name or not self.name_set):
+            self.log.debug(
+                "Updating name from '%s' to '%s' (quality: %d)", self.name, name, quality
+            )
             self.name = name
+            self.name_quality = quality
             try:
                 await self.default_mxid_intent.set_displayname(self.name)
                 self.name_set = True
@@ -304,6 +311,18 @@ class Puppet(DBPuppet, BasePuppet):
                 self.log.exception("Error setting displayname")
                 self.name_set = False
             return True
+        elif name != self.name or not self.name_set:
+            self.log.debug(
+                "Not updating name from '%s' to '%s', new quality (%d) is lower than old (%d)",
+                self.name,
+                name,
+                quality,
+                self.name_quality,
+            )
+        elif self.name_quality == 0:
+            # Name matches, but quality is not stored in database - store it now
+            self.name_quality = quality
+            return True
         return False
 
     @staticmethod

+ 2 - 2
mautrix_signal/user.py

@@ -21,7 +21,7 @@ from datetime import datetime
 from uuid import UUID
 import asyncio
 
-from mausignald.errors import AuthorizationFailedError, ResponseError
+from mausignald.errors import AuthorizationFailedError
 from mausignald.types import (
     Account,
     Address,
@@ -301,7 +301,7 @@ class User(DBUser, BaseUser):
                 address = contact.address
                 profile = contact
             puppet = await pu.Puppet.get_by_address(address)
-            await puppet.update_info(profile)
+            await puppet.update_info(profile, self)
             if create_portals:
                 portal = await po.Portal.get_by_chat_id(
                     puppet.address, receiver=self.username, create=True