From c385aa0b8d80a7d82db1ad0088eadc890e0d9278 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Jun 2021 20:04:17 +0300 Subject: [PATCH] Add real-time bridge status push option --- mautrix_telegram/example-config.yaml | 4 ++++ mautrix_telegram/user.py | 34 ++++++++++++++++++++-------- optional-requirements.txt | 4 ++-- requirements.txt | 2 +- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/mautrix_telegram/example-config.yaml b/mautrix_telegram/example-config.yaml index 6e2a3fcf..7b429210 100644 --- a/mautrix_telegram/example-config.yaml +++ b/mautrix_telegram/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 Telegram 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. diff --git a/mautrix_telegram/user.py b/mautrix_telegram/user.py index 89bd9837..272089ec 100644 --- a/mautrix_telegram/user.py +++ b/mautrix_telegram/user.py @@ -15,7 +15,6 @@ # along with this program. If not, see . from typing import (Awaitable, Dict, List, Iterable, NamedTuple, Optional, Tuple, Any, cast, TYPE_CHECKING) -from collections import defaultdict from datetime import datetime, timezone import logging import asyncio @@ -32,7 +31,7 @@ from telethon.tl.functions.account import UpdateStatusRequest from mautrix.client import Client from mautrix.errors import MatrixRequestError, MNotFound from mautrix.types import UserID, RoomID, PushRuleScope, PushRuleKind, PushActionType, RoomTagInfo -from mautrix.bridge import BaseUser +from mautrix.bridge import BaseUser, BridgeState from mautrix.util.logging import TraceLogger from mautrix.util.opt_prometheus import Gauge @@ -52,6 +51,11 @@ SearchResult = NamedTuple('SearchResult', puppet='pu.Puppet', similarity=int) METRIC_LOGGED_IN = Gauge('bridge_logged_in', 'Users logged into bridge') METRIC_CONNECTED = Gauge('bridge_connected', 'Users connected to Telegram') +BridgeState.human_readable_errors.update({ + "tg-not-connected": "Your Telegram connection failed", + "logged-out": "You're not logged into Telegram", +}) + class User(AbstractUser, BaseUser): log: TraceLogger = logging.getLogger("mau.user") @@ -74,8 +78,9 @@ class User(AbstractUser, BaseUser): saved_contacts: int = 0, is_bot: bool = False, db_portals: Optional[Iterable[Tuple[TelegramID, TelegramID]]] = None, db_instance: Optional[DBUser] = None) -> None: - super().__init__() + AbstractUser.__init__(self) self.mxid = mxid + BaseUser.__init__(self) self.tgid = tgid self.is_bot = is_bot self.username = username @@ -87,12 +92,8 @@ class User(AbstractUser, BaseUser): self.db_portals = db_portals or [] self._db_instance = db_instance self._ensure_started_lock = asyncio.Lock() - self.dm_update_lock = asyncio.Lock() - self._metric_value = defaultdict(lambda: False) self._track_connection_task = None - self.command_status = None - (self.relaybot_whitelisted, self.whitelisted, self.puppet_whitelisted, @@ -104,8 +105,6 @@ class User(AbstractUser, BaseUser): if tgid: self.by_tgid[tgid] = self - self.log = self.log.getChild(self.mxid) - @property def name(self) -> str: return self.mxid @@ -219,6 +218,21 @@ class User(AbstractUser, BaseUser): connected = bool(self.client._sender._transport_connected if self.client and self.client._sender else False) self._track_metric(METRIC_CONNECTED, connected) + await self.push_bridge_state(ok=connected, ttl=3600 if connected else 240, + error="tg-not-connected" if not connected else None) + + async def fill_bridge_state(self, state: BridgeState) -> None: + await super().fill_bridge_state(state) + state.remote_id = str(self.tgid) + state.remote_name = self.human_tg_id + + async def get_bridge_state(self) -> BridgeState: + if not self.client: + return BridgeState(ok=False, error="logged-out") + elif not self.client._sender or not self.client._sender._transport_connected: + return BridgeState(ok=False, error="tg-not-connected") + else: + return BridgeState(ok=True) async def stop(self) -> None: await super().stop() @@ -226,6 +240,7 @@ class User(AbstractUser, BaseUser): self._track_connection_task.cancel() self._track_connection_task = None self._track_metric(METRIC_CONNECTED, False) + await self.push_bridge_state(ok=False, error="tg-not-connected") async def post_login(self, info: TLUser = None, first_login: bool = False) -> None: if config["metrics.enabled"] and not self._track_connection_task: @@ -330,6 +345,7 @@ class User(AbstractUser, BaseUser): self.delete() await self.stop() self._track_metric(METRIC_LOGGED_IN, False) + await self.push_bridge_state(ok=False, error="logged-out") return True def _search_local(self, query: str, max_results: int = 5, min_similarity: int = 45 diff --git a/optional-requirements.txt b/optional-requirements.txt index b0df0532..6f1c9531 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -15,13 +15,13 @@ qrcode>=6,<7 moviepy>=1,<2 #/metrics -prometheus_client>=0.6,<0.11 +prometheus_client>=0.6,<0.12 #/postgres psycopg2-binary>=2,<3 #/e2be -asyncpg>=0.20,<0.23 +asyncpg>=0.20,<0.24 python-olm>=3,<4 pycryptodome>=3,<4 unpaddedbase64>=1,<2 diff --git a/requirements.txt b/requirements.txt index 7b3ba0eb..0b49879a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,6 @@ python-magic>=0.4,<0.5 commonmark>=0.8,<0.10 aiohttp>=3,<4 yarl>=1,<2 -mautrix>=0.9.1,<0.10 +mautrix>=0.9.3,<0.10 telethon>=1.20,<1.22 telethon-session-sqlalchemy>=0.2.14,<0.3