matrix.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # mautrix-signal - A Matrix-Signal puppeting bridge
  2. # Copyright (C) 2021 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 datetime import datetime
  18. from mautrix.bridge import BaseMatrixHandler
  19. from mautrix.types import (Event, ReactionEvent, StateEvent, RoomID, EventID, UserID, TypingEvent,
  20. ReactionEventContent, RelationType, EventType, ReceiptEvent,
  21. PresenceEvent, RedactionEvent, SingleReceiptEventContent)
  22. from mautrix_signal.db.disappearing_message import DisappearingMessage
  23. from .db import Message as DBMessage
  24. from . import portal as po, user as u, signal as s
  25. if TYPE_CHECKING:
  26. from .__main__ import SignalBridge
  27. class MatrixHandler(BaseMatrixHandler):
  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__(bridge=bridge)
  36. async def send_welcome_message(self, room_id: RoomID, inviter: 'u.User') -> None:
  37. await super().send_welcome_message(room_id, inviter)
  38. if not inviter.notice_room:
  39. inviter.notice_room = room_id
  40. await inviter.update()
  41. await self.az.intent.send_notice(room_id, "This room has been marked as your "
  42. "Signal bridge notice room.")
  43. async def handle_leave(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None:
  44. portal = await po.Portal.get_by_mxid(room_id)
  45. if not portal:
  46. return
  47. user = await u.User.get_by_mxid(user_id, create=False)
  48. if not user:
  49. return
  50. await portal.handle_matrix_leave(user)
  51. async def handle_join(self, room_id: RoomID, user_id: UserID, event_id: EventID) -> None:
  52. portal = await po.Portal.get_by_mxid(room_id)
  53. if not portal:
  54. return
  55. user = await u.User.get_by_mxid(user_id, create=False)
  56. if not user:
  57. return
  58. await portal.handle_matrix_join(user)
  59. @classmethod
  60. async def handle_reaction(cls, room_id: RoomID, user_id: UserID, event_id: EventID,
  61. content: ReactionEventContent) -> None:
  62. if content.relates_to.rel_type != RelationType.ANNOTATION:
  63. cls.log.debug(f"Ignoring m.reaction event in {room_id} from {user_id} with unexpected "
  64. f"relation type {content.relates_to.rel_type}")
  65. return
  66. user = await u.User.get_by_mxid(user_id)
  67. if not user:
  68. return
  69. portal = await po.Portal.get_by_mxid(room_id)
  70. if not portal:
  71. return
  72. await portal.handle_matrix_reaction(user, event_id, content.relates_to.event_id,
  73. content.relates_to.key)
  74. @staticmethod
  75. async def handle_redaction(room_id: RoomID, user_id: UserID, event_id: EventID,
  76. redaction_event_id: EventID) -> None:
  77. user = await u.User.get_by_mxid(user_id)
  78. if not user:
  79. return
  80. portal = await po.Portal.get_by_mxid(room_id)
  81. if not portal:
  82. return
  83. await portal.handle_matrix_redaction(user, event_id, redaction_event_id)
  84. async def handle_read_receipt(self, user: 'u.User', portal: 'po.Portal', event_id: EventID,
  85. data: SingleReceiptEventContent) -> None:
  86. await portal.handle_read_receipt(event_id, data)
  87. message = await DBMessage.get_by_mxid(event_id, portal.mxid)
  88. if not message:
  89. return
  90. user.log.trace(f"Sending read receipt for {message.timestamp} to {message.sender}")
  91. await self.signal.send_receipt(user.username, message.sender,
  92. timestamps=[message.timestamp], when=data.ts, read=True)
  93. async def handle_typing(self, room_id: RoomID, typing: List[UserID]) -> None:
  94. pass
  95. # portal = await po.Portal.get_by_mxid(room_id)
  96. # if not portal:
  97. # return
  98. #
  99. # for user_id in typing:
  100. # user = await u.User.get_by_mxid(user_id, create=False)
  101. # if not user or not user.username:
  102. # continue
  103. # # TODO
  104. async def handle_event(self, evt: Event) -> None:
  105. if evt.type == EventType.REACTION:
  106. evt: ReactionEvent
  107. await self.handle_reaction(evt.room_id, evt.sender, evt.event_id, evt.content)
  108. elif evt.type == EventType.ROOM_REDACTION:
  109. evt: RedactionEvent
  110. await self.handle_redaction(evt.room_id, evt.sender, evt.redacts, evt.event_id)
  111. async def handle_ephemeral_event(self, evt: Union[ReceiptEvent, PresenceEvent, TypingEvent]
  112. ) -> None:
  113. if evt.type == EventType.TYPING:
  114. await self.handle_typing(evt.room_id, evt.content.user_ids)
  115. else:
  116. await super().handle_ephemeral_event(evt)
  117. async def handle_state_event(self, evt: StateEvent) -> None:
  118. if evt.type not in (EventType.ROOM_NAME, EventType.ROOM_AVATAR):
  119. return
  120. user = await u.User.get_by_mxid(evt.sender)
  121. if not user:
  122. return
  123. portal = await po.Portal.get_by_mxid(evt.room_id)
  124. if not portal:
  125. return
  126. if evt.type == EventType.ROOM_NAME:
  127. await portal.handle_matrix_name(user, evt.content.name)
  128. elif evt.type == EventType.ROOM_AVATAR:
  129. await portal.handle_matrix_avatar(user, evt.content.url)
  130. async def allow_message(self, user: 'u.User') -> bool:
  131. return user.relay_whitelisted
  132. async def allow_bridging_message(self, user: 'u.User', portal: 'po.Portal') -> bool:
  133. return portal.has_relay or await user.is_logged_in()