__main__.py 5.9 KB

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