__main__.py 5.4 KB

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