Răsfoiți Sursa

Add real-time bridge status push option

Tulir Asokan 4 ani în urmă
părinte
comite
bfcd7bb43d

+ 4 - 0
mautrix_signal/example-config.yaml

@@ -10,6 +10,10 @@ homeserver:
     asmux: false
     # Number of retries for all HTTP requests if the homeserver isn't reachable.
     http_retry_count: 4
+    # The URL to push real-time bridge status to.
+    # If set, the bridge will make POST requests to this URL whenever a user's Signal connection state changes.
+    # The bridge will use the appservice as_token to authorize requests.
+    status_endpoint: null
 
 # Application service host/registration related details
 # Changing these values requires regeneration of the registration.

+ 27 - 6
mautrix_signal/user.py

@@ -14,12 +14,11 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 from typing import Union, Dict, Optional, AsyncGenerator, TYPE_CHECKING, cast
-from collections import defaultdict
 from uuid import UUID
 import asyncio
 
 from mausignald.types import Account, Address, Profile, Group, GroupV2, ListenEvent, ListenAction
-from mautrix.bridge import BaseUser, async_getter_lock
+from mautrix.bridge import BaseUser, BridgeState, async_getter_lock
 from mautrix.types import UserID, RoomID
 from mautrix.appservice import AppService
 from mautrix.util.opt_prometheus import Gauge
@@ -34,6 +33,11 @@ if TYPE_CHECKING:
 METRIC_CONNECTED = Gauge('bridge_connected', 'Bridge users connected to Signal')
 METRIC_LOGGED_IN = Gauge('bridge_logged_in', 'Bridge users logged into Signal')
 
+BridgeState.human_readable_errors.update({
+    "logged-out": "You're not logged into Signal",
+    "signal-not-connected": None,
+})
+
 
 class User(DBUser, BaseUser):
     by_mxid: Dict[UserID, 'User'] = {}
@@ -49,18 +53,17 @@ class User(DBUser, BaseUser):
 
     _sync_lock: asyncio.Lock
     _notice_room_lock: asyncio.Lock
+    _connected: bool
 
     def __init__(self, mxid: UserID, username: Optional[str] = None, uuid: Optional[UUID] = None,
                  notice_room: Optional[RoomID] = None) -> None:
         super().__init__(mxid=mxid, username=username, uuid=uuid, notice_room=notice_room)
+        BaseUser.__init__(self)
         self._notice_room_lock = asyncio.Lock()
         self._sync_lock = asyncio.Lock()
+        self._connected = False
         perms = self.config.get_permissions(mxid)
         self.is_whitelisted, self.is_admin, self.permission_level = perms
-        self.log = self.log.getChild(self.mxid)
-        self.dm_update_lock = asyncio.Lock()
-        self.command_status = None
-        self._metric_value = defaultdict(lambda: False)
 
     @classmethod
     def init_cls(cls, bridge: 'SignalBridge') -> None:
@@ -94,6 +97,20 @@ class User(DBUser, BaseUser):
         await asyncio.sleep(1)
         await self.bridge.signal.delete_account(username)
         self._track_metric(METRIC_LOGGED_IN, False)
+        await self.push_bridge_state(ok=False, error="logged-out")
+
+    async def fill_bridge_state(self, state: BridgeState) -> None:
+        await super().fill_bridge_state(state)
+        state.remote_id = self.username
+        puppet = await pu.Puppet.get_by_address(self.address)
+        state.remote_name = puppet.name or self.username
+
+    async def get_bridge_state(self) -> BridgeState:
+        if not self.username:
+            return BridgeState(ok=False, error="logged-out")
+        elif not self._connected:
+            return BridgeState(ok=False, error="signal-not-connected")
+        return BridgeState(ok=True)
 
     async def on_signin(self, account: Account) -> None:
         self.username = account.account_id
@@ -109,12 +126,16 @@ class User(DBUser, BaseUser):
             self.log.info("Connected to Signal")
             self._track_metric(METRIC_CONNECTED, True)
             self._track_metric(METRIC_LOGGED_IN, True)
+            self._connected = True
+            asyncio.create_task(self.push_bridge_state(ok=True))
         elif evt.action == ListenAction.STOPPED:
             if evt.exception:
                 self.log.warning(f"Disconnected from Signal: {evt.exception}")
             else:
                 self.log.info("Disconnected from Signal")
             self._track_metric(METRIC_CONNECTED, False)
+            asyncio.create_task(self.push_bridge_state(ok=False, error="signal-not-connected"))
+            self._connected = False
         else:
             self.log.warning(f"Unrecognized listen action {evt.action}")
 

+ 1 - 1
optional-requirements.txt

@@ -7,7 +7,7 @@ pycryptodome>=3,<4
 unpaddedbase64>=1,<2
 
 #/metrics
-prometheus_client>=0.6,<0.11
+prometheus_client>=0.6,<0.12
 
 #/formattednumbers
 phonenumbers>=8,<9

+ 2 - 2
requirements.txt

@@ -4,5 +4,5 @@ commonmark>=0.8,<0.10
 aiohttp>=3,<4
 yarl>=1,<2
 attrs>=19.1
-mautrix>=0.9,<0.10
-asyncpg>=0.20,<0.23
+mautrix>=0.9.5,<0.10
+asyncpg>=0.20,<0.24