signal.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. # mautrix-signal - A Matrix-Signal puppeting bridge
  2. # Copyright (C) 2020 Tulir Asokan
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. from typing import Optional, List, TYPE_CHECKING
  17. import asyncio
  18. import logging
  19. from mausignald import SignaldClient
  20. from mausignald.types import (Message, MessageData, Receipt, TypingNotification, OwnReadReceipt,
  21. Address, ReceiptType)
  22. from mautrix.util.logging import TraceLogger
  23. from .db import Message as DBMessage
  24. from . import user as u, portal as po, puppet as pu
  25. if TYPE_CHECKING:
  26. from .__main__ import SignalBridge
  27. class SignalHandler(SignaldClient):
  28. log: TraceLogger = logging.getLogger("mau.signal")
  29. loop: asyncio.AbstractEventLoop
  30. def __init__(self, bridge: 'SignalBridge') -> None:
  31. super().__init__(bridge.config["signal.socket_path"], loop=bridge.loop)
  32. self.add_event_handler(Message, self.on_message)
  33. async def on_message(self, evt: Message) -> None:
  34. sender = await pu.Puppet.get_by_address(evt.source)
  35. if not sender.uuid:
  36. self.log.debug("Got message sender puppet with no UUID, not handling message")
  37. self.log.trace("Message content: %s", evt)
  38. return
  39. user = await u.User.get_by_username(evt.username)
  40. # TODO add lots of logging
  41. if evt.data_message:
  42. await self.handle_message(user, sender, evt.data_message)
  43. if evt.typing:
  44. # Typing notification from someone else
  45. pass
  46. if evt.receipt:
  47. await self.handle_receipt(sender, evt.receipt)
  48. if evt.sync_message:
  49. if evt.sync_message.read_messages:
  50. await self.handle_own_receipts(sender, evt.sync_message.read_messages)
  51. if evt.sync_message.contacts:
  52. # Contact list update?
  53. pass
  54. if evt.sync_message.sent:
  55. await self.handle_message(user, sender, evt.sync_message.sent.message,
  56. recipient_override=evt.sync_message.sent.destination)
  57. if evt.sync_message.typing:
  58. # Typing notification from own device
  59. pass
  60. @staticmethod
  61. async def handle_message(user: 'u.User', sender: 'pu.Puppet', msg: MessageData,
  62. recipient_override: Optional[Address] = None) -> None:
  63. if msg.group:
  64. portal = await po.Portal.get_by_chat_id(msg.group.group_id, receiver=user.username)
  65. else:
  66. portal = await po.Portal.get_by_chat_id(recipient_override.uuid
  67. if recipient_override else sender.uuid,
  68. receiver=user.username)
  69. if not portal.mxid:
  70. # TODO create room?
  71. # TODO definitely at least log
  72. return
  73. if msg.reaction:
  74. await portal.handle_signal_reaction(sender, msg.reaction)
  75. if msg.body:
  76. await portal.handle_signal_message(sender, msg)
  77. @staticmethod
  78. async def handle_own_receipts(sender: 'pu.Puppet', receipts: List[OwnReadReceipt]) -> None:
  79. for receipt in receipts:
  80. puppet = await pu.Puppet.get_by_address(receipt.sender, create=False)
  81. if not puppet or not puppet.uuid:
  82. continue
  83. message = await DBMessage.find_by_sender_timestamp(puppet.uuid, receipt.timestamp)
  84. if not message:
  85. continue
  86. portal = await po.Portal.get_by_mxid(message.mx_room)
  87. if not portal:
  88. continue
  89. await sender.intent_for(portal).mark_read(portal.mxid, message.mxid)
  90. @staticmethod
  91. async def handle_receipt(sender: 'pu.Puppet', receipt: Receipt) -> None:
  92. if receipt.type != ReceiptType.READ:
  93. pass
  94. messages = await DBMessage.find_by_timestamps(receipt.timestamps)
  95. for message in messages:
  96. portal = await po.Portal.get_by_mxid(message.mx_room)
  97. await sender.intent_for(portal).mark_read(portal.mxid, message.mxid)
  98. async def start(self) -> None:
  99. await self.connect()
  100. async for user in u.User.all_logged_in():
  101. # TODO handle errors
  102. await self.subscribe(user.username)
  103. self.loop.create_task(user.sync())
  104. async def stop(self) -> None:
  105. await self.disconnect()