Răsfoiți Sursa

Merge remote-tracking branch 'maltee1/use_groupchange'

Tulir Asokan 2 ani în urmă
părinte
comite
7840b162f4

+ 38 - 12
mausignald/types.py

@@ -173,13 +173,6 @@ class Group(SerializableAttrs):
     avatar_id: int = field(default=0, json="avatarId")
     avatar_id: int = field(default=0, json="avatarId")
 
 
 
 
-@dataclass(kw_only=True)
-class GroupV2ID(SerializableAttrs):
-    id: GroupID
-    revision: Optional[int] = None
-    removed: Optional[bool] = False
-
-
 class AccessControlMode(SerializableEnum):
 class AccessControlMode(SerializableEnum):
     UNKNOWN = "UNKNOWN"
     UNKNOWN = "UNKNOWN"
     ANY = "ANY"
     ANY = "ANY"
@@ -197,9 +190,9 @@ class AnnouncementsMode(SerializableEnum):
 
 
 @dataclass
 @dataclass
 class GroupAccessControl(SerializableAttrs):
 class GroupAccessControl(SerializableAttrs):
-    attributes: Optional[AccessControlMode] = AccessControlMode.UNKNOWN
-    link: Optional[AccessControlMode] = AccessControlMode.UNKNOWN
-    members: Optional[AccessControlMode] = AccessControlMode.UNKNOWN
+    attributes: Optional[AccessControlMode] = None
+    link: Optional[AccessControlMode] = None
+    members: Optional[AccessControlMode] = None
 
 
 
 
 class GroupMemberRole(SerializableEnum):
 class GroupMemberRole(SerializableEnum):
@@ -222,9 +215,42 @@ class BannedGroupMember(SerializableAttrs):
     timestamp: int
     timestamp: int
 
 
 
 
+@dataclass
+class GroupChange(SerializableAttrs):
+    revision: int
+    editor: Address
+    delete_members: Optional[List[Address]] = None
+    delete_pending_members: Optional[List[Address]] = None
+    delete_requesting_members: Optional[List[Address]] = None
+    modified_profile_keys: Optional[List[GroupMember]] = None
+    modify_member_roles: Optional[List[GroupMember]] = None
+    new_access_control: Optional[GroupAccessControl] = None
+    new_avatar: bool = False
+    new_banned_members: Optional[List[GroupMember]] = None
+    new_description: Optional[str] = None
+    new_invite_link_password: bool = False
+    new_is_announcement_group: Optional[AnnouncementsMode] = None
+    new_members: Optional[List[GroupMember]] = None
+    new_pending_members: Optional[List[GroupMember]] = None
+    new_requesting_members: Optional[List[GroupMember]] = None
+    new_timer: Optional[int] = None
+    new_title: Optional[str] = None
+    new_unbanned_members: Optional[List[GroupMember]] = None
+    promote_pending_members: Optional[List[GroupMember]] = None
+    promote_requesting_members: Optional[List[GroupMember]] = None
+
+
+@dataclass(kw_only=True)
+class GroupV2ID(SerializableAttrs):
+    id: GroupID
+    revision: Optional[int] = None
+    removed: Optional[bool] = False
+    group_change: Optional[GroupChange] = None
+
+
 @dataclass(kw_only=True)
 @dataclass(kw_only=True)
 class GroupV2(GroupV2ID, SerializableAttrs):
 class GroupV2(GroupV2ID, SerializableAttrs):
-    title: str
+    title: str = None
     description: Optional[str] = None
     description: Optional[str] = None
     avatar: Optional[str] = None
     avatar: Optional[str] = None
     timer: Optional[int] = None
     timer: Optional[int] = None
@@ -233,7 +259,7 @@ class GroupV2(GroupV2ID, SerializableAttrs):
     access_control: GroupAccessControl = field(
     access_control: GroupAccessControl = field(
         factory=lambda: GroupAccessControl(), json="accessControl"
         factory=lambda: GroupAccessControl(), json="accessControl"
     )
     )
-    members: List[Address]
+    members: List[Address] = None
     member_detail: List[GroupMember] = field(factory=lambda: [], json="memberDetail")
     member_detail: List[GroupMember] = field(factory=lambda: [], json="memberDetail")
     pending_members: List[Address] = field(factory=lambda: [], json="pendingMembers")
     pending_members: List[Address] = field(factory=lambda: [], json="pendingMembers")
     pending_member_detail: List[GroupMember] = field(
     pending_member_detail: List[GroupMember] = field(

+ 1 - 0
mautrix_signal/config.py

@@ -68,6 +68,7 @@ class Config(BaseBridgeConfig):
         copy("bridge.autocreate_group_portal")
         copy("bridge.autocreate_group_portal")
         copy("bridge.autocreate_contact_portal")
         copy("bridge.autocreate_contact_portal")
         copy("bridge.sync_with_custom_puppets")
         copy("bridge.sync_with_custom_puppets")
+        copy("bridge.public_portals")
         copy("bridge.sync_direct_chat_list")
         copy("bridge.sync_direct_chat_list")
         copy("bridge.double_puppet_server_map")
         copy("bridge.double_puppet_server_map")
         copy("bridge.double_puppet_allow_discovery")
         copy("bridge.double_puppet_allow_discovery")

+ 3 - 0
mautrix_signal/example-config.yaml

@@ -134,6 +134,9 @@ bridge:
     autocreate_group_portal: true
     autocreate_group_portal: true
     # Whether or not to create portals for all contacts on login/connect.
     # Whether or not to create portals for all contacts on login/connect.
     autocreate_contact_portal: false
     autocreate_contact_portal: false
+    # Whether or not to make portals of Signal groups in which joining via invite link does
+    # not need to be approved by an administrator publicly joinable on Matrix.
+    public_portals: false
     # Whether or not to use /sync to get read receipts and typing notifications
     # Whether or not to use /sync to get read receipts and typing notifications
     # when double puppeting is enabled
     # when double puppeting is enabled
     sync_with_custom_puppets: true
     sync_with_custom_puppets: true

+ 260 - 55
mautrix_signal/portal.py

@@ -39,6 +39,7 @@ from mausignald.types import (
     Attachment,
     Attachment,
     Group,
     Group,
     GroupAccessControl,
     GroupAccessControl,
+    GroupChange,
     GroupID,
     GroupID,
     GroupMember,
     GroupMember,
     GroupMemberRole,
     GroupMemberRole,
@@ -56,7 +57,7 @@ from mausignald.types import (
 )
 )
 from mautrix.appservice import AppService, IntentAPI
 from mautrix.appservice import AppService, IntentAPI
 from mautrix.bridge import BasePortal, RejectMatrixInvite, async_getter_lock
 from mautrix.bridge import BasePortal, RejectMatrixInvite, async_getter_lock
-from mautrix.errors import IntentError, MatrixError, MForbidden
+from mautrix.errors import IntentError, MatrixError, MBadState, MForbidden
 from mautrix.types import (
 from mautrix.types import (
     AudioInfo,
     AudioInfo,
     BeeperMessageStatusEventContent,
     BeeperMessageStatusEventContent,
@@ -67,6 +68,7 @@ from mautrix.types import (
     EventType,
     EventType,
     FileInfo,
     FileInfo,
     ImageInfo,
     ImageInfo,
+    JoinRule,
     MediaMessageEventContent,
     MediaMessageEventContent,
     Membership,
     Membership,
     MessageEvent,
     MessageEvent,
@@ -1215,6 +1217,219 @@ class Portal(DBPortal, BasePortal):
         self.log.debug(f"{user.mxid} was kicked by {sender.number} from {self.mxid}")
         self.log.debug(f"{user.mxid} was kicked by {sender.number} from {self.mxid}")
         await self._kick_with_puppet(user, sender)
         await self._kick_with_puppet(user, sender)
 
 
+    async def handle_signal_group_change(self, group_change: GroupChange, source: u.User) -> None:
+        if self.revision < group_change.revision:
+            self.revision = group_change.revision
+        else:
+            return
+        editor = await p.Puppet.get_by_address(group_change.editor)
+        editor_intent = editor.intent_for(self)
+        if (
+            group_change.delete_members
+            or group_change.delete_pending_members
+            or group_change.delete_requesting_members
+        ):
+            for address in (
+                (group_change.delete_members or [])
+                + (group_change.delete_pending_members or [])
+                + (group_change.delete_requesting_members or [])
+            ):
+                users = [
+                    await p.Puppet.get_by_address(address),
+                    await u.User.get_by_address(address),
+                ]
+                for user in users:
+                    if not user:
+                        continue
+                    if user == editor:
+                        await editor_intent.leave_room(self.mxid)
+                    else:
+                        await self._kick_with_puppet(user, editor)
+        if group_change.modify_member_roles:
+            levels = await editor.intent_for(self).get_power_levels(self.mxid)
+            for group_member in group_change.modify_member_roles:
+                users = [
+                    await p.Puppet.get_by_address(Address(uuid=group_member.uuid)),
+                    await u.User.get_by_uuid(group_member.uuid),
+                ]
+                for user in users:
+                    if not user:
+                        continue
+                    if group_member.role == GroupMemberRole.ADMINISTRATOR:
+                        new_pl = 50
+                    else:
+                        new_pl = 0
+                    levels.users[user.mxid] = new_pl
+            await self._try_with_puppet(
+                lambda i: i.set_power_levels(self.mxid, levels), puppet=editor
+            )
+
+        if group_change.new_banned_members:
+            for banned_member in group_change.new_banned_members:
+                users = [
+                    await p.Puppet.get_by_address(Address(uuid=banned_member.uuid)),
+                    await u.User.get_by_uuid(banned_member.uuid),
+                ]
+                for user in users:
+                    if not user:
+                        continue
+                    try:
+                        await editor_intent.ban_user(self.mxid, user.mxid)
+                    except MForbidden:
+                        try:
+                            await self.main_intent.ban_user(
+                                self.mxid, user.mxid, reason=f"banned by {editor.name}"
+                            )
+                        except MForbidden as e:
+                            self.log.debug(f"Could not ban {user.mxid}: {e}")
+                    except MBadState as e:
+                        self.log.debug(f"Could not ban {user.mxid}: {e}")
+        if group_change.new_unbanned_members:
+            for banned_member in group_change.new_unbanned_members:
+                users = [
+                    await p.Puppet.get_by_address(Address(uuid=banned_member.uuid)),
+                    await u.User.get_by_uuid(banned_member.uuid),
+                ]
+                for user in users:
+                    if not user:
+                        continue
+                    try:
+                        await editor_intent.unban_user(self.mxid, user.mxid)
+                    except MForbidden:
+                        try:
+                            await self.main_intent.unban_user(
+                                self.mxid, user.mxid, reason=f"unbanned by {editor.name}"
+                            )
+                        except MForbidden as e:
+                            self.log.debug(f"Could not unban {user.mxid}: {e}")
+                    except MBadState as e:
+                        self.log.debug(f"Could not unban {user.mxid}: {e}")
+        if (
+            group_change.new_members
+            or group_change.new_pending_members
+            or group_change.promote_requesting_members
+        ):
+            banned_users = await self.az.intent.get_room_members(self.mxid, (Membership.BAN,))
+            for group_member in (
+                (group_change.new_members or [])
+                + (group_change.new_pending_members or [])
+                + (group_change.promote_requesting_members or [])
+            ):
+                puppet = await p.Puppet.get_by_address(Address(uuid=group_member.uuid))
+                try:
+                    await source.sync_contact(Address(uuid=group_member.uuid))
+                except ProfileUnavailableError:
+                    self.log.debug(
+                        f"Profile of puppet with uuid {group_member.uuid} is unavailable"
+                    )
+                users = [puppet, await u.User.get_by_uuid(group_member.uuid)]
+                for user in users:
+                    if not user:
+                        continue
+                    if user.mxid in banned_users:
+                        await self._try_with_puppet(
+                            lambda i: i.unban_user(self.mxid, user.mxid), puppet=editor
+                        )
+                    try:
+                        await editor_intent.invite_user(self.mxid, user.mxid, check_cache=True)
+                    except (MForbidden, IntentError):
+                        try:
+                            await self.main_intent.invite_user(
+                                self.mxid,
+                                user.mxid,
+                                reason=f"invited by {editor.name}",
+                                check_cache=True,
+                            )
+                        except (MForbidden, IntentError) as e:
+                            self.log.debug(f"{editor.name} could not invite {user.mxid}: {e}")
+                    except MBadState as e:
+                        self.log.debug(f"{editor.name} could not invite {user.mxid}: {e}")
+                    if group_member in (group_change.new_members or []) + (
+                        group_change.promote_requesting_members or []
+                    ) and isinstance(user, p.Puppet):
+                        try:
+                            await user.intent_for(self).ensure_joined(self.mxid)
+                        except IntentError as e:
+                            self.log.debug(f"{user.name} could not join group: {e}")
+        if group_change.promote_pending_members:
+            for member in group_change.promote_pending_members:
+                try:
+                    await source.sync_contact(Address(uuid=group_member.uuid))
+                except ProfileUnavailableError:
+                    self.log.debug(
+                        f"Profile of puppet with uuid {group_member.uuid} is unavailable"
+                    )
+                user = await p.Puppet.get_by_address(address)
+                if not user:
+                    continue
+                try:
+                    await user.intent_for(self).ensure_joined(self.mxid)
+                except IntentError as e:
+                    self.log.debug(f"{user.name} could not join group: {e}")
+        if group_change.new_requesting_members:
+            for member in group_change.new_requesting_members:
+                try:
+                    await source.sync_contact(Address(uuid=group_member.uuid))
+                except ProfileUnavailableError:
+                    self.log.debug(
+                        f"Profile of puppet with uuid {group_member.uuid} is unavailable"
+                    )
+                user = await p.Puppet.get_by_address(Address(uuid=member.uuid))
+                try:
+                    await user.intent_for(self).knock(self.mxid, reason="via invite link")
+                except (MForbidden, MBadState) as e:
+                    self.log.debug(f"{user.name} failed knock: {e}")
+        if group_change.new_access_control:
+            ac = group_change.new_access_control
+            if ac.attributes or ac.members:
+                levels = await editor.intent_for(self).get_power_levels(self.mxid)
+                if ac.attributes:
+                    meta_edit_level = 50 if ac.attributes == AccessControlMode.ADMINISTRATOR else 0
+                    levels.events[EventType.ROOM_NAME] = meta_edit_level
+                    levels.events[EventType.ROOM_AVATAR] = meta_edit_level
+                    levels.events[EventType.ROOM_TOPIC] = meta_edit_level
+                if ac.members:
+                    levels.invite = 50 if ac.members == AccessControlMode.ADMINISTRATOR else 0
+                await self._try_with_puppet(
+                    lambda i: i.set_power_levels(self.mxid, levels), puppet=editor
+                )
+            if ac.link:
+                join_rule = JoinRule.INVITE
+                if ac.link == AccessControlMode.ANY and self.config["bridge.public_portals"]:
+                    join_rule = JoinRule.PUBLIC
+                elif ac.link == AccessControlMode.ADMINISTRATOR:
+                    join_rule = JoinRule.KNOCK
+                await self._try_with_puppet(
+                    lambda i: i.set_join_rule(self.mxid, join_rule), puppet=editor
+                )
+        if group_change.new_is_announcement_group:
+            levels = await editor.intent_for(self).get_power_levels(self.mxid)
+            if group_change.new_is_announcement_group == AnnouncementsMode.ENABLED:
+                levels.events_default = 50
+            elif group_change.new_is_announcement_group == AnnouncementsMode.DISABLED:
+                levels.events_default = 0
+            await self._try_with_puppet(
+                lambda i: i.set_power_levels(self.mxid, levels), puppet=editor
+            )
+        changed = False
+        if group_change.new_description:
+            changed = await self._update_topic(group_change.new_description, editor)
+        if group_change.new_title:
+            changed = await self._update_name(group_change.new_title, editor) or changed
+        if group_change.new_avatar:
+            changed = (
+                await self._update_avatar(
+                    await self.signal.get_group(
+                        source.username, self.chat_id, group_change.revision
+                    ),
+                    editor,
+                )
+                or changed
+            )
+        if changed:
+            await self.update_bridge_info()
+            await self.update()
+
     @staticmethod
     @staticmethod
     async def _make_media_content(
     async def _make_media_content(
         attachment: Attachment, data: bytes
         attachment: Attachment, data: bytes
@@ -1512,9 +1727,7 @@ class Portal(DBPortal, BasePortal):
     # endregion
     # endregion
     # region Updating portal info
     # region Updating portal info
 
 
-    async def update_info(
-        self, source: u.User, info: ChatInfo, sender: p.Puppet | None = None
-    ) -> None:
+    async def update_info(self, source: u.User, info: ChatInfo) -> None:
         if self.is_direct:
         if self.is_direct:
             if not isinstance(info, (Profile, Address)):
             if not isinstance(info, (Profile, Address)):
                 raise ValueError(f"Unexpected type for direct chat update_info: {type(info)}")
                 raise ValueError(f"Unexpected type for direct chat update_info: {type(info)}")
@@ -1540,7 +1753,7 @@ class Portal(DBPortal, BasePortal):
 
 
         changed = False
         changed = False
         if isinstance(info, Group):
         if isinstance(info, Group):
-            changed = await self._update_name(info.name, sender) or changed
+            changed = await self._update_name(info.name) or changed
         elif isinstance(info, GroupV2):
         elif isinstance(info, GroupV2):
             if self.revision < info.revision:
             if self.revision < info.revision:
                 self.revision = info.revision
                 self.revision = info.revision
@@ -1551,14 +1764,14 @@ class Portal(DBPortal, BasePortal):
                     f"({info.revision} < {self.revision}), ignoring..."
                     f"({info.revision} < {self.revision}), ignoring..."
                 )
                 )
                 return
                 return
-            changed = await self._update_name(info.title, sender) or changed
-            changed = await self._update_topic(info.description, sender) or changed
+            changed = await self._update_name(info.title) or changed
+            changed = await self._update_topic(info.description) or changed
         elif isinstance(info, GroupV2ID):
         elif isinstance(info, GroupV2ID):
             return
             return
         else:
         else:
             raise ValueError(f"Unexpected type for group update_info: {type(info)}")
             raise ValueError(f"Unexpected type for group update_info: {type(info)}")
-        changed = await self._update_avatar(info, sender) or changed
-        await self._update_participants(source, info, sender)
+        changed = await self._update_avatar(info) or changed
+        await self._update_participants(source, info)
         try:
         try:
             await self._update_power_levels(info)
             await self._update_power_levels(info)
         except Exception:
         except Exception:
@@ -1688,9 +1901,7 @@ class Portal(DBPortal, BasePortal):
             self.avatar_set = False
             self.avatar_set = False
         return True
         return True
 
 
-    async def _update_participants(
-        self, source: u.User, info: ChatInfo, sender: p.Puppet | None = None
-    ) -> None:
+    async def _update_participants(self, source: u.User, info: ChatInfo) -> None:
         if not self.mxid or not isinstance(info, (Group, GroupV2)):
         if not self.mxid or not isinstance(info, (Group, GroupV2)):
             return
             return
 
 
@@ -1704,68 +1915,62 @@ class Portal(DBPortal, BasePortal):
         pending_members = info.pending_members if isinstance(info, GroupV2) else []
         pending_members = info.pending_members if isinstance(info, GroupV2) else []
         self._pending_members = {addr.uuid for addr in pending_members}
         self._pending_members = {addr.uuid for addr in pending_members}
 
 
-        for address in info.members:
+        for address in info.members + pending_members:
             user = await u.User.get_by_address(address)
             user = await u.User.get_by_address(address)
             if user:
             if user:
                 remove_users.discard(user.mxid)
                 remove_users.discard(user.mxid)
-                await self._try_with_puppet(
-                    lambda i: i.invite_user(self.mxid, user.mxid, check_cache=True), puppet=sender
-                )
+                try:
+                    await self.main_intent.invite_user(self.mxid, user.mxid, check_cache=True)
+                except (MForbidden, IntentError, MBadState) as e:
+                    self.log.debug(f"could not invite {user.mxid}: {e}")
 
 
             puppet = await p.Puppet.get_by_address(address)
             puppet = await p.Puppet.get_by_address(address)
             try:
             try:
                 await source.sync_contact(address)
                 await source.sync_contact(address)
             except ProfileUnavailableError:
             except ProfileUnavailableError:
                 self.log.debug(f"Profile of puppet with {address} is unavailable")
                 self.log.debug(f"Profile of puppet with {address} is unavailable")
-            await self._try_with_puppet(
-                lambda i: i.invite_user(self.mxid, puppet.intent_for(self).mxid, check_cache=True),
-                puppet=sender,
-            )
-            await puppet.intent_for(self).ensure_joined(self.mxid)
-            remove_users.discard(puppet.default_mxid)
-
-        for address in pending_members:
-            user = await u.User.get_by_address(address)
-            if user:
-                remove_users.discard(user.mxid)
-                await self._try_with_puppet(
-                    lambda i: i.invite_user(self.mxid, user.mxid, check_cache=True), puppet=sender
-                )
-
-            puppet = await p.Puppet.get_by_address(address)
             try:
             try:
-                await source.sync_contact(address)
-            except ProfileUnavailableError:
-                self.log.debug(f"Profile of puppet with {address} is unavailable")
-            await self._try_with_puppet(
-                lambda i: i.invite_user(self.mxid, puppet.intent_for(self).mxid, check_cache=True),
-                puppet=sender,
-            )
+                await self.main_intent.invite_user(
+                    self.mxid, puppet.intent_for(self).mxid, check_cache=True
+                )
+            except (MForbidden, IntentError, MBadState) as e:
+                self.log.debug(f"could not invite {user.mxid}: {e}")
+            if not address.uuid in self._pending_members:
+                await puppet.intent_for(self).ensure_joined(self.mxid)
             remove_users.discard(puppet.default_mxid)
             remove_users.discard(puppet.default_mxid)
 
 
         for mxid in remove_users:
         for mxid in remove_users:
             user = await u.User.get_by_mxid(mxid, create=False)
             user = await u.User.get_by_mxid(mxid, create=False)
             if user and await user.is_logged_in():
             if user and await user.is_logged_in():
-                await self._kick_with_puppet(user, sender)
+                try:
+                    await self.main_intent.kick_user(
+                        self.mxid, user.mxid, reason="not a member of this Signal group"
+                    )
+                except (MForbidden, MBadState) as e:
+                    self.log.debug(f"could not kick {user.mxid}: {e}")
             puppet = await p.Puppet.get_by_mxid(mxid, create=False)
             puppet = await p.Puppet.get_by_mxid(mxid, create=False)
             if puppet:
             if puppet:
-                if puppet == sender:
-                    await puppet.intent_for(self).leave_room(self.mxid)
-                else:
-                    await self._kick_with_puppet(puppet, sender)
+                try:
+                    await self.main_intent.kick_user(
+                        self.mxid,
+                        puppet.intent_for(self).mxid,
+                        reason="not a member of this Signal group",
+                    )
+                except (MForbidden, MBadState) as e:
+                    self.log.debug(f"could not kick {user.mxid}: {e}")
 
 
-    async def _kick_with_puppet(
-        self, user: p.Puppet | u.User, sender: p.Puppet | None = None
-    ) -> None:
-        if sender:
+    async def _kick_with_puppet(self, user: p.Puppet | u.User, sender: p.Puppet) -> None:
+        try:
+            await sender.intent_for(self).kick_user(self.mxid, user.mxid)
+        except MForbidden:
             try:
             try:
-                await sender.intent_for(self).kick_user(self.mxid, user.mxid)
-            except (MForbidden, IntentError):
-                await self.main_intent.kick_user(self.mxid, user.mxid, f"kicked by {sender.name}")
-        else:
-            await self.main_intent.kick_user(
-                self.mxid, user.mxid, "Not a member of this Signal group"
-            )
+                await self.main_intent.kick_user(
+                    self.mxid, user.mxid, reason=f"removed by {sender.name}"
+                )
+            except MForbidden as e:
+                self.log.debug(f"Could not remove {user.mxid}: {e}")
+        except MBadState as e:
+            self.log.debug(f"Could not remove {user.mxid}: {e}")
 
 
     async def _update_power_levels(self, info: ChatInfo) -> None:
     async def _update_power_levels(self, info: ChatInfo) -> None:
         if not self.mxid:
         if not self.mxid:

+ 8 - 1
mautrix_signal/signal.py

@@ -217,9 +217,16 @@ class SignalHandler(SignaldClient):
                     f"Failed to create room for incoming message {msg.timestamp}, dropping message"
                     f"Failed to create room for incoming message {msg.timestamp}, dropping message"
                 )
                 )
                 return
                 return
+        elif (
+            msg.group_v2
+            and msg.group_v2.group_change
+            and msg.group_v2.revision == portal.revision + 1
+        ):
+            self.log.debug(f"Got GroupChange for {msg.group_v2.id}, updating info")
+            await portal.handle_signal_group_change(msg.group_v2.group_change, user)
         elif msg.group_v2 and msg.group_v2.revision > portal.revision:
         elif msg.group_v2 and msg.group_v2.revision > portal.revision:
             self.log.debug(f"Got new revision of {msg.group_v2.id}, updating info")
             self.log.debug(f"Got new revision of {msg.group_v2.id}, updating info")
-            await portal.update_info(user, msg.group_v2, sender)
+            await portal.update_info(user, msg.group_v2)
         if msg.expires_in_seconds is not None and (msg.is_message or msg.is_expiration_update):
         if msg.expires_in_seconds is not None and (msg.is_message or msg.is_expiration_update):
             await portal.update_expires_in_seconds(sender, msg.expires_in_seconds)
             await portal.update_expires_in_seconds(sender, msg.expires_in_seconds)
         if msg.reaction:
         if msg.reaction: