__main__.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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 Any, Dict
  17. from random import uniform
  18. import asyncio
  19. import logging
  20. from mautrix.bridge import Bridge
  21. from mautrix.types import RoomID, UserID
  22. from . import commands
  23. from .config import Config
  24. from .db import init as init_db, upgrade_table
  25. from .matrix import MatrixHandler
  26. from .portal import Portal
  27. from .puppet import Puppet
  28. from .signal import SignalHandler
  29. from .user import User
  30. from .version import linkified_version, version
  31. from .web import ProvisioningAPI
  32. SYNC_JITTER = 10
  33. class SignalBridge(Bridge):
  34. module = "mautrix_signal"
  35. name = "mautrix-signal"
  36. beeper_service_name = "signal"
  37. beeper_network_name = "signal"
  38. command = "python -m mautrix-signal"
  39. description = "A Matrix-Signal puppeting bridge."
  40. repo_url = "https://github.com/mautrix/signal"
  41. version = version
  42. markdown_version = linkified_version
  43. config_class = Config
  44. matrix_class = MatrixHandler
  45. upgrade_table = upgrade_table
  46. matrix: MatrixHandler
  47. signal: SignalHandler
  48. config: Config
  49. provisioning_api: ProvisioningAPI
  50. periodic_sync_task: asyncio.Task
  51. def prepare_db(self) -> None:
  52. super().prepare_db()
  53. init_db(self.db)
  54. def prepare_bridge(self) -> None:
  55. self.signal = SignalHandler(self)
  56. super().prepare_bridge()
  57. cfg = self.config["bridge.provisioning"]
  58. self.provisioning_api = ProvisioningAPI(
  59. self, cfg["shared_secret"], cfg["segment_key"], cfg["segment_user_id"]
  60. )
  61. self.az.app.add_subapp(cfg["prefix"], self.provisioning_api.app)
  62. async def start(self) -> None:
  63. User.init_cls(self)
  64. self.add_startup_actions(Puppet.init_cls(self))
  65. Portal.init_cls(self)
  66. self.add_startup_actions(Portal.restart_scheduled_disappearing())
  67. if self.config["bridge.resend_bridge_info"]:
  68. self.add_startup_actions(self.resend_bridge_info())
  69. self.add_startup_actions(self.signal.start())
  70. await super().start()
  71. self.periodic_sync_task = asyncio.create_task(self._periodic_sync_loop())
  72. async def _actual_periodic_sync_loop(self, log: logging.Logger, interval: int) -> None:
  73. n = 0
  74. hacky_daily_resync = self.config["bridge.hacky_contact_name_mixup_detection"]
  75. while True:
  76. n += 1
  77. try:
  78. await asyncio.sleep(interval)
  79. except asyncio.CancelledError:
  80. return
  81. log.info("Executing periodic syncs")
  82. for user in User.by_username.values():
  83. # Add some randomness to the sync to avoid a thundering herd
  84. await asyncio.sleep(uniform(0, SYNC_JITTER))
  85. try:
  86. await user.sync(is_startup=hacky_daily_resync and n % 24 == 23)
  87. except asyncio.CancelledError:
  88. return
  89. except Exception:
  90. log.exception("Error while syncing %s", user.mxid)
  91. async def _periodic_sync_loop(self) -> None:
  92. log = logging.getLogger("mau.periodic_sync")
  93. interval = self.config["bridge.periodic_sync"]
  94. if interval <= 0:
  95. log.debug("Periodic sync is not enabled")
  96. return
  97. log.debug("Starting periodic sync loop")
  98. try:
  99. await self._actual_periodic_sync_loop(log, interval)
  100. except Exception:
  101. log.fatal("Error in periodic resync", exc_info=True)
  102. log.debug("Periodic sync stopped")
  103. def prepare_stop(self) -> None:
  104. self.add_shutdown_actions(self.signal.stop())
  105. for puppet in Puppet.by_custom_mxid.values():
  106. puppet.stop()
  107. async def resend_bridge_info(self) -> None:
  108. self.config["bridge.resend_bridge_info"] = False
  109. self.config.save()
  110. self.log.info("Re-sending bridge info state event to all portals")
  111. async for portal in Portal.all_with_room():
  112. await portal.update_bridge_info()
  113. self.log.info("Finished re-sending bridge info state events")
  114. async def get_user(self, user_id: UserID, create: bool = True) -> User:
  115. return await User.get_by_mxid(user_id, create=create)
  116. async def get_portal(self, room_id: RoomID) -> Portal:
  117. return await Portal.get_by_mxid(room_id)
  118. async def get_puppet(self, user_id: UserID, create: bool = False) -> Puppet:
  119. return await Puppet.get_by_mxid(user_id, create=create)
  120. async def get_double_puppet(self, user_id: UserID) -> Puppet:
  121. return await Puppet.get_by_custom_mxid(user_id)
  122. def is_bridge_ghost(self, user_id: UserID) -> bool:
  123. return bool(Puppet.get_id_from_mxid(user_id))
  124. async def count_logged_in_users(self) -> int:
  125. return len([user for user in User.by_username.values() if user.username])
  126. async def manhole_global_namespace(self, user_id: UserID) -> Dict[str, Any]:
  127. return {
  128. **await super().manhole_global_namespace(user_id),
  129. "User": User,
  130. "Portal": Portal,
  131. "Puppet": Puppet,
  132. }
  133. SignalBridge().run()