Ver Fonte

Update to subscribe v1 and fix some bugs

Tulir Asokan há 3 anos atrás
pai
commit
13e8057ca2
5 ficheiros alterados com 74 adições e 77 exclusões
  1. 3 3
      mausignald/rpc.py
  2. 6 6
      mausignald/signald.py
  3. 25 20
      mausignald/types.py
  4. 12 16
      mautrix_signal/portal.py
  5. 28 32
      mautrix_signal/signal.py

+ 3 - 3
mausignald/rpc.py

@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Tulir Asokan
+# Copyright (c) 2022 Tulir Asokan
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -221,11 +221,11 @@ class SignaldRPCClient:
         await self._send_request(data)
         return await asyncio.shield(future)
 
-    async def request(self, command: str, expected_response: str, **data: Any) -> Any:
+    async def _request(self, command: str, expected_response: str, **data: Any) -> Any:
         resp_type, resp_data = await self._raw_request(command, **data)
         if resp_type != expected_response:
             raise UnexpectedResponse(resp_type, resp_data)
         return resp_data
 
     async def request_v1(self, command: str, **data: Any) -> Any:
-        return await self.request(command, expected_response=command, version="v1", **data)
+        return await self._request(command, expected_response=command, version="v1", **data)

+ 6 - 6
mausignald/signald.py

@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Tulir Asokan
+# Copyright (c) 2022 Tulir Asokan
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -19,9 +19,9 @@ from .types import (
     Group,
     GroupID,
     GroupV2,
+    IncomingMessage,
     LinkSession,
     Mention,
-    Message,
     Profile,
     Quote,
     Reaction,
@@ -46,7 +46,7 @@ class SignaldClient(SignaldRPCClient):
         super().__init__(socket_path, log, loop)
         self._event_handlers = {}
         self._subscriptions = set()
-        self.add_rpc_handler("message", self._parse_message)
+        self.add_rpc_handler("IncomingMessage", self._parse_message)
         self.add_rpc_handler(
             "websocket_connection_state_change", self._websocket_connection_state_change
         )
@@ -76,7 +76,7 @@ class SignaldClient(SignaldRPCClient):
         event_type = data["type"]
         event_data = data["data"]
         event_class = {
-            "message": Message,
+            "IncomingMessage": IncomingMessage,
         }[event_type]
         event = event_class.deserialize(event_data)
         await self._run_event_handler(event)
@@ -92,7 +92,7 @@ class SignaldClient(SignaldRPCClient):
 
     async def subscribe(self, username: str) -> bool:
         try:
-            await self.request("subscribe", "subscribed", username=username)
+            await self.request_v1("subscribe", account=username)
             self._subscriptions.add(username)
             return True
         except UnexpectedError as e:
@@ -110,7 +110,7 @@ class SignaldClient(SignaldRPCClient):
 
     async def unsubscribe(self, username: str) -> bool:
         try:
-            await self.request("unsubscribe", "unsubscribed", username=username)
+            await self.request_v1("unsubscribe", account=username)
             self._subscriptions.remove(username)
             return True
         except UnexpectedError as e:

+ 25 - 20
mausignald/types.py

@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Tulir Asokan
+# Copyright (c) 2022 Tulir Asokan
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -277,6 +277,11 @@ class RemoteDelete(SerializableAttrs):
     target_sent_timestamp: int = field(json="targetSentTimestamp")
 
 
+@dataclass
+class SharedContact(SerializableAttrs):
+    pass
+
+
 @dataclass
 class MessageData(SerializableAttrs):
     timestamp: int
@@ -287,6 +292,7 @@ class MessageData(SerializableAttrs):
     attachments: List[Attachment] = field(factory=lambda: [])
     sticker: Optional[Sticker] = None
     mentions: List[Mention] = field(factory=lambda: [])
+    contacts: List[SharedContact] = field(factory=lambda: [])
 
     group: Optional[Group] = None
     group_v2: Optional[GroupV2ID] = field(default=None, json="groupV2")
@@ -318,10 +324,10 @@ class TypingAction(SerializableEnum):
 
 
 @dataclass
-class TypingNotification(SerializableAttrs):
+class TypingMessage(SerializableAttrs):
     action: TypingAction
     timestamp: int
-    group_id: Optional[GroupID] = field(default=None, json="groupId")
+    group_id: Optional[GroupID]
 
 
 @dataclass
@@ -338,7 +344,7 @@ class ReceiptType(SerializableEnum):
 
 
 @dataclass
-class Receipt(SerializableAttrs):
+class ReceiptMessage(SerializableAttrs):
     type: ReceiptType
     timestamps: List[int]
     when: int
@@ -381,7 +387,6 @@ class StickerPackOperations(SerializableAttrs):
 @dataclass
 class SyncMessage(SerializableAttrs):
     sent: Optional[SentSyncMessage] = None
-    typing: Optional[TypingNotification] = None
     read_messages: Optional[List[OwnReadReceipt]] = field(default=None, json="readMessages")
     contacts: Optional[ContactSyncMeta] = None
     groups: Optional[ContactSyncMeta] = None
@@ -435,25 +440,25 @@ class MessageType(SerializableEnum):
 
 
 @dataclass(kw_only=True)
-class Message(SerializableAttrs):
-    username: str
+class IncomingMessage(SerializableAttrs):
+    account: str
     source: Address
     timestamp: int
-    timestamp_iso: str = field(json="timestampISO")
 
     type: MessageType
-    source_device: Optional[int] = field(json="sourceDevice", default=None)
-    server_timestamp: Optional[int] = field(json="serverTimestamp", default=None)
-    server_delivered_timestamp: int = field(json="serverDeliveredTimestamp")
-    has_content: bool = field(json="hasContent", default=False)
-    is_unidentified_sender: Optional[bool] = field(json="isUnidentifiedSender", default=None)
-    has_legacy_message: bool = field(default=False, json="hasLegacyMessage")
-
-    call_message: Optional[CallMessage] = field(default=None, json="callMessage")
-    data_message: Optional[MessageData] = field(default=None, json="dataMessage")
-    sync_message: Optional[SyncMessage] = field(default=None, json="syncMessage")
-    typing: Optional[TypingNotification] = None
-    receipt: Optional[Receipt] = None
+    source_device: Optional[int] = None
+    server_guid: str
+    server_receiver_timestamp: int
+    server_deliver_timestamp: int
+    has_content: bool
+    unidentified_sender: bool
+    has_legacy_message: bool
+
+    call_message: Optional[CallMessage] = field(default=None)
+    data_message: Optional[MessageData] = field(default=None)
+    sync_message: Optional[SyncMessage] = field(default=None)
+    typing_message: Optional[TypingMessage] = None
+    receipt_message: Optional[ReceiptMessage] = None
 
 
 class WebsocketConnectionState(SerializableEnum):

+ 12 - 16
mautrix_signal/portal.py

@@ -1,5 +1,5 @@
 # mautrix-signal - A Matrix-Signal puppeting bridge
-# Copyright (C) 2021 Tulir Asokan
+# 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
@@ -68,8 +68,8 @@ from mautrix.types import (
     UserID,
     VideoInfo,
 )
+from mautrix.util import ffmpeg, variation_selector
 from mautrix.util.bridge_state import BridgeStateEvent
-from mautrix.util.ffmpeg import convert_bytes, convert_path
 from mautrix.util.format_duration import format_duration
 from mautrix.util.message_send_checkpoint import MessageSendCheckpointStatus
 
@@ -122,7 +122,7 @@ class Portal(DBPortal, BasePortal):
     _main_intent: IntentAPI | None
     _create_room_lock: asyncio.Lock
     _msgts_dedup: deque[tuple[Address, int]]
-    _reaction_dedup: deque[tuple[Address, int, str]]
+    _reaction_dedup: deque[tuple[Address, int, str, Address]]
     _reaction_lock: asyncio.Lock
     _pending_members: set[UUID] | None
     _expiration_lock: asyncio.Lock
@@ -255,16 +255,14 @@ class Portal(DBPortal, BasePortal):
     async def _make_attachment(message: MediaMessageEventContent, path: str) -> Attachment:
         outgoing_filename = path
         if message.msgtype == MessageType.AUDIO:
-            outgoing_filename = (
-                (await convert_path(path, ".m4a", output_args=("-c:a", "aac"), remove_input=True))
-                .absolute()
-                .as_posix()
+            outgoing_filename = await ffmpeg.convert_path(
+                path, ".m4a", output_args=("-c:a", "aac"), remove_input=True
             )
             message.info.mimetype = "audio/mp4"
         attachment = Attachment(
             custom_filename=message.body,
             content_type=message.info.mimetype,
-            outgoing_filename=outgoing_filename,
+            outgoing_filename=str(outgoing_filename),
         )
         info = message.info
         attachment.width = info.get("w", info.get("width", 0))
@@ -421,7 +419,7 @@ class Portal(DBPortal, BasePortal):
             return
 
         # Signal doesn't seem to use variation selectors at all
-        emoji = emoji.rstrip("\ufe0f")
+        emoji = variation_selector.remove(emoji)
 
         message = await DBMessage.get_by_mxid(reacting_to, self.mxid)
         if not message:
@@ -434,7 +432,7 @@ class Portal(DBPortal, BasePortal):
         if existing and existing.emoji == emoji:
             return
 
-        dedup_id = (message.sender, message.timestamp, emoji)
+        dedup_id = (message.sender, message.timestamp, emoji, sender.address)
         self._reaction_dedup.appendleft(dedup_id)
         async with self._reaction_lock:
             reaction = Reaction(
@@ -892,7 +890,7 @@ class Portal(DBPortal, BasePortal):
                 **(content.info.serialize() if content.info else {}),
             }
             content["org.matrix.msc3245.voice"] = {}
-            data = await convert_bytes(
+            data = await ffmpeg.convert_bytes(
                 data, ".ogg", output_args=("-c:a", "libopus"), input_mime=attachment.content_type
             )
 
@@ -1024,7 +1022,7 @@ class Portal(DBPortal, BasePortal):
         author_address = await self._resolve_address(reaction.target_author)
         target_id = reaction.target_sent_timestamp
         async with self._reaction_lock:
-            dedup_id = (author_address, target_id, reaction.emoji)
+            dedup_id = (author_address, target_id, reaction.emoji, sender.address)
             if dedup_id in self._reaction_dedup:
                 return
             self._reaction_dedup.appendleft(dedup_id)
@@ -1053,10 +1051,8 @@ class Portal(DBPortal, BasePortal):
             return
 
         intent = sender.intent_for(self)
-        # TODO add variation selectors to emoji before sending to Matrix
-        mxid = await intent.react(
-            message.mx_room, message.mxid, reaction.emoji, timestamp=timestamp
-        )
+        matrix_emoji = variation_selector.add(reaction.emoji)
+        mxid = await intent.react(message.mx_room, message.mxid, matrix_emoji, timestamp=timestamp)
         self.log.debug(f"{sender.address} reacted to {message.mxid} -> {mxid}")
         await self._upsert_reaction(existing, intent, mxid, sender, message, reaction.emoji)
 

+ 28 - 32
mautrix_signal/signal.py

@@ -1,5 +1,5 @@
 # mautrix-signal - A Matrix-Signal puppeting bridge
-# Copyright (C) 2021 Tulir Asokan
+# 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
@@ -22,14 +22,14 @@ import logging
 from mausignald import SignaldClient
 from mausignald.types import (
     Address,
-    Message,
+    IncomingMessage,
     MessageData,
     OfferMessageType,
     OwnReadReceipt,
-    Receipt,
+    ReceiptMessage,
     ReceiptType,
     TypingAction,
-    TypingNotification,
+    TypingMessage,
     WebsocketConnectionStateChangeEvent,
 )
 from mautrix.types import MessageType
@@ -55,22 +55,22 @@ class SignalHandler(SignaldClient):
         super().__init__(bridge.config["signal.socket_path"], loop=bridge.loop)
         self.data_dir = bridge.config["signal.data_dir"]
         self.delete_unknown_accounts = bridge.config["signal.delete_unknown_accounts_on_start"]
-        self.add_event_handler(Message, self.on_message)
+        self.add_event_handler(IncomingMessage, self.on_message)
         self.add_event_handler(
             WebsocketConnectionStateChangeEvent, self.on_websocket_connection_state_change
         )
 
-    async def on_message(self, evt: Message) -> None:
+    async def on_message(self, evt: IncomingMessage) -> None:
         sender = await pu.Puppet.get_by_address(evt.source)
-        user = await u.User.get_by_username(evt.username)
+        user = await u.User.get_by_username(evt.account)
         # TODO add lots of logging
 
         if evt.data_message:
             await self.handle_message(user, sender, evt.data_message)
-        if evt.typing:
-            await self.handle_typing(user, sender, evt.typing)
-        if evt.receipt:
-            await self.handle_receipt(sender, evt.receipt)
+        if evt.typing_message:
+            await self.handle_typing(user, sender, evt.typing_message)
+        if evt.receipt_message:
+            await self.handle_receipt(sender, evt.receipt_message)
         if evt.call_message:
             await self.handle_call_message(user, sender, evt)
         if evt.sync_message:
@@ -83,9 +83,6 @@ class SignalHandler(SignaldClient):
                     evt.sync_message.sent.message,
                     addr_override=evt.sync_message.sent.destination,
                 )
-            if evt.sync_message.typing:
-                # Typing notification from own device
-                pass
             if evt.sync_message.contacts or evt.sync_message.contacts_complete:
                 self.log.debug("Sync message includes contacts meta, syncing contacts...")
                 await user.sync_contacts()
@@ -155,31 +152,30 @@ class SignalHandler(SignaldClient):
             await portal.handle_signal_delete(sender, msg.remote_delete.target_sent_timestamp)
 
     @staticmethod
-    async def handle_call_message(user: "u.User", sender: "pu.Puppet", msg: Message) -> None:
+    async def handle_call_message(user: u.User, sender: pu.Puppet, msg: IncomingMessage) -> None:
         assert msg.call_message
         portal = await po.Portal.get_by_chat_id(
             sender.address, receiver=user.username, create=True
         )
         if not portal.mxid:
-            await portal.create_matrix_room(
-                user, (msg.group_v2 or msg.group or addr_override or sender.address)
-            )
-            if not portal.mxid:
-                user.log.debug(
-                    f"Failed to create room for incoming message {msg.timestamp},"
-                    " dropping message"
-                )
-                return
+            # FIXME
+            # await portal.create_matrix_room(
+            #     user, (msg.group_v2 or msg.group or addr_override or sender.address)
+            # )
+            # if not portal.mxid:
+            #     user.log.debug(
+            #         f"Failed to create room for incoming message {msg.timestamp},"
+            #         " dropping message"
+            #     )
+            return
 
         msg_html = f'<a href="https://matrix.to/#/{sender.mxid}">{sender.name}</a>'
         if msg.call_message.offer_message:
             call_type = {
-                OfferMessageType.AUDIO_CALL: " voice ",
-                OfferMessageType.VIDEO_CALL: " video ",
-            }.get(msg.call_message.offer_message.type, " ")
-            msg_html += (
-                f" started a{call_type}call on Signal. Use the native app to answer the call."
-            )
+                OfferMessageType.AUDIO_CALL: "voice call",
+                OfferMessageType.VIDEO_CALL: "video call",
+            }.get(msg.call_message.offer_message.type, "call")
+            msg_html += f" started a {call_type} on Signal. Use the native app to answer the call."
             msg_type = MessageType.TEXT
         elif msg.call_message.hangup_message:
             msg_html += " ended a call on Signal."
@@ -205,7 +201,7 @@ class SignalHandler(SignaldClient):
             await sender.intent_for(portal).mark_read(portal.mxid, message.mxid)
 
     @staticmethod
-    async def handle_typing(user: u.User, sender: pu.Puppet, typing: TypingNotification) -> None:
+    async def handle_typing(user: u.User, sender: pu.Puppet, typing: TypingMessage) -> None:
         if typing.group_id:
             portal = await po.Portal.get_by_chat_id(typing.group_id)
         else:
@@ -218,7 +214,7 @@ class SignalHandler(SignaldClient):
         )
 
     @staticmethod
-    async def handle_receipt(sender: pu.Puppet, receipt: Receipt) -> None:
+    async def handle_receipt(sender: pu.Puppet, receipt: ReceiptMessage) -> None:
         if receipt.type != ReceiptType.READ:
             return
         messages = await DBMessage.find_by_timestamps(receipt.timestamps)