__main__.py 5.4 KB

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