matrix.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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 List, Union, TYPE_CHECKING
  17. from mausignald.types import Address
  18. from mautrix.bridge import BaseMatrixHandler
  19. from mautrix.types import (Event, ReactionEvent, MessageEvent, StateEvent, EncryptedEvent, RoomID,
  20. EventID, UserID, ReactionEventContent, RelationType, EventType,
  21. ReceiptEvent, TypingEvent, PresenceEvent, RedactionEvent)
  22. from .db import Message as DBMessage
  23. from . import commands as com, puppet as pu, portal as po, user as u, signal as s
  24. if TYPE_CHECKING:
  25. from .__main__ import SignalBridge
  26. class MatrixHandler(BaseMatrixHandler):
  27. commands: 'com.CommandProcessor'
  28. signal: 's.SignalHandler'
  29. def __init__(self, bridge: 'SignalBridge') -> None:
  30. prefix, suffix = bridge.config["bridge.username_template"].format(userid=":").split(":")
  31. homeserver = bridge.config["homeserver.domain"]
  32. self.user_id_prefix = f"@{prefix}"
  33. self.user_id_suffix = f"{suffix}:{homeserver}"
  34. self.signal = bridge.signal
  35. super().__init__(command_processor=com.CommandProcessor(bridge), bridge=bridge)
  36. def filter_matrix_event(self, evt: Event) -> bool:
  37. if not isinstance(evt, (ReactionEvent, MessageEvent, StateEvent, EncryptedEvent,
  38. RedactionEvent)):
  39. return True
  40. return (evt.sender == self.az.bot_mxid
  41. or pu.Puppet.get_id_from_mxid(evt.sender) is not None)
  42. async def send_welcome_message(self, room_id: RoomID, inviter: 'u.User') -> None:
  43. await super().send_welcome_message(room_id, inviter)
  44. if not inviter.notice_room:
  45. inviter.notice_room = room_id
  46. await inviter.update()
  47. await self.az.intent.send_notice(room_id, "This room has been marked as your "
  48. "Signal bridge notice room.")
  49. async def handle_leave(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None:
  50. portal = await po.Portal.get_by_mxid(room_id)
  51. if not portal:
  52. return
  53. user = await u.User.get_by_mxid(user_id, create=False)
  54. if not user:
  55. return
  56. await portal.handle_matrix_leave(user)
  57. @staticmethod
  58. async def allow_bridging_message(user: 'u.User', portal: 'po.Portal') -> bool:
  59. return user.is_whitelisted and bool(user.username)
  60. # @staticmethod
  61. # async def handle_redaction(room_id: RoomID, user_id: UserID, event_id: EventID,
  62. # redaction_event_id: EventID) -> None:
  63. # user = await u.User.get_by_mxid(user_id)
  64. # if not user:
  65. # return
  66. #
  67. # portal = await po.Portal.get_by_mxid(room_id)
  68. # if not portal:
  69. # return
  70. #
  71. # await portal.handle_matrix_redaction(user, event_id, redaction_event_id)
  72. @classmethod
  73. async def handle_reaction(cls, room_id: RoomID, user_id: UserID, event_id: EventID,
  74. content: ReactionEventContent) -> None:
  75. if content.relates_to.rel_type != RelationType.ANNOTATION:
  76. cls.log.debug(f"Ignoring m.reaction event in {room_id} from {user_id} with unexpected "
  77. f"relation type {content.relates_to.rel_type}")
  78. return
  79. user = await u.User.get_by_mxid(user_id)
  80. if not user:
  81. return
  82. portal = await po.Portal.get_by_mxid(room_id)
  83. if not portal:
  84. return
  85. await portal.handle_matrix_reaction(user, event_id, content.relates_to.event_id,
  86. content.relates_to.key)
  87. async def handle_receipt(self, evt: ReceiptEvent) -> None:
  88. # These events come from custom puppet syncing, so there's always only one user.
  89. event_id, receipts = evt.content.popitem()
  90. receipt_type, users = receipts.popitem()
  91. user_id, data = users.popitem()
  92. user = await u.User.get_by_mxid(user_id, create=False)
  93. if not user or not user.username:
  94. return
  95. portal = await po.Portal.get_by_mxid(evt.room_id)
  96. if not portal:
  97. return
  98. message = await DBMessage.get_by_mxid(event_id, portal.mxid)
  99. if not message:
  100. return
  101. user.log.trace(f"Sending read receipt for {message.timestamp} to {message.sender}")
  102. await self.signal.mark_read(user.username, Address(uuid=message.sender),
  103. timestamps=[message.timestamp], when=data.ts)
  104. async def handle_typing(self, room_id: RoomID, typing: List[UserID]) -> None:
  105. pass
  106. # portal = await po.Portal.get_by_mxid(room_id)
  107. # if not portal:
  108. # return
  109. #
  110. # for user_id in typing:
  111. # user = await u.User.get_by_mxid(user_id, create=False)
  112. # if not user or not user.username:
  113. # continue
  114. # # TODO
  115. async def handle_event(self, evt: Event) -> None:
  116. if evt.type == EventType.ROOM_REDACTION:
  117. evt: RedactionEvent
  118. # await self.handle_redaction(evt.room_id, evt.sender, evt.redacts, evt.event_id)
  119. elif evt.type == EventType.REACTION:
  120. evt: ReactionEvent
  121. await self.handle_reaction(evt.room_id, evt.sender, evt.event_id, evt.content)
  122. async def handle_ephemeral_event(self, evt: Union[ReceiptEvent, PresenceEvent, TypingEvent]
  123. ) -> None:
  124. if evt.type == EventType.TYPING:
  125. await self.handle_typing(evt.room_id, evt.content.user_ids)
  126. elif evt.type == EventType.RECEIPT:
  127. await self.handle_receipt(evt)