Просмотр исходного кода

Add ProofRequiredError and submit-challenge command (#320)

Malte E 2 лет назад
Родитель
Сommit
5822fb0b2d

+ 17 - 0
mausignald/errors.py

@@ -7,6 +7,8 @@ from __future__ import annotations
 
 from typing import Any
 
+from mautrix.util.format_duration import format_duration
+
 
 class RPCError(Exception):
     pass
@@ -118,6 +120,20 @@ class GroupPatchNotAcceptedError(ResponseError):
     pass
 
 
+class ProofRequiredError(ResponseError):
+    def __init__(self, data: dict[str, Any]) -> None:
+        self.options = data.get("options")
+        self.retry_after = data.get("retry_after")
+        self.token = data.get("token")
+        message = "You have been rate limited by Signal."
+        if isinstance(self.retry_after, (int, float)):
+            message += (
+                f" Try again in {format_duration(int(self.retry_after))} "
+                "or complete a captcha challenge using the `submit-challenge` command."
+            )
+        super().__init__(data, message_override=message)
+
+
 response_error_types = {
     "invalid_request": RequestValidationFailure,
     "TimeoutException": TimeoutException,
@@ -134,6 +150,7 @@ response_error_types = {
     "ProfileUnavailableError": ProfileUnavailableError,
     "NoSuchAccountError": NoSuchAccountError,
     "GroupPatchNotAcceptedError": GroupPatchNotAcceptedError,
+    "ProofRequiredError": ProofRequiredError,
     # TODO add rest from https://gitlab.com/signald/signald/-/tree/main/src/main/java/io/finn/signald/clientprotocol/v1/exceptions
 }
 

+ 7 - 0
mausignald/signald.py

@@ -548,3 +548,10 @@ class SignaldClient(SignaldRPCClient):
             "resolve_address", partial=Address(number=number).serialize(), account=username
         )
         return Address.deserialize(resp).uuid
+
+    async def submit_challenge(
+        self, username: str, captcha_token: str | None, challenge: str | None
+    ) -> None:
+        await self.request_v1(
+            "submit_challenge", account=username, captcha_token=captcha_token, challenge=challenge
+        )

+ 28 - 0
mautrix_signal/commands/signal.py

@@ -510,6 +510,34 @@ async def confirm_bridge(evt: CommandEvent) -> EventID | None:
         )
 
 
+@command_handler(
+    needs_auth=True,
+    management_only=False,
+    help_section=SECTION_SIGNAL,
+    help_text="Submit a captcha challenge for the last occuring token",
+    help_args="<captcha token>",
+)
+async def submit_challenge(evt: CommandEvent) -> None:
+    if len(evt.args == 0):
+        await evt.reply("**Usage:** `$cmdprefix+sp submit-challenge <captcha token>`")
+        return
+    challenge_token = evt.sender.challenge_token
+    captcha_token = evt.args[0]
+    if not challenge_token:
+        return await evt.reply(
+            "No open challenge found. If the bridge was restarted, try"
+            "triggering another ProofRequiredError"
+        )
+    evt.log.debug(f"Submitting challenge for token {challenge_token}")
+    try:
+        await evt.bridge.signal.submit_challenge(
+            evt.sender.username, captcha_token=evt.args[0], challenge=challenge_token
+        )
+    except Exception as e:
+        return await evt.reply(f"Failed to submit captcha challenge: {e}")
+    return await evt.reply("Captcha challenge submitted successfully")
+
+
 async def _locked_confirm_bridge(
     evt: CommandEvent, portal: po.Portal, room_id: RoomID, is_logged_in: bool
 ) -> EventID | None:

+ 3 - 0
mautrix_signal/portal.py

@@ -31,6 +31,7 @@ from mausignald.errors import (
     GroupPatchNotAcceptedError,
     NotConnected,
     ProfileUnavailableError,
+    ProofRequiredError,
     RPCError,
 )
 from mausignald.types import (
@@ -328,6 +329,8 @@ class Portal(DBPortal, BasePortal):
                 status, event_id, self.mxid, EventType.ROOM_MESSAGE, message.msgtype, error=e
             )
             await sender.handle_auth_failure(e)
+            if isinstance(e, ProofRequiredError):
+                sender.challenge_token = e.token
             await self._send_error_notice("message", e)
             background_task.create(self._send_message_status(event_id, e))
 

+ 3 - 0
mautrix_signal/user.py

@@ -77,6 +77,8 @@ class User(DBUser, BaseUser):
     _websocket_connection_state: BridgeStateEvent | None
     _latest_non_transient_bridge_state: datetime | None
 
+    challenge_token: str | None
+
     def __init__(
         self,
         mxid: UserID,
@@ -92,6 +94,7 @@ class User(DBUser, BaseUser):
         self._state_id = self.username
         self._websocket_connection_state = None
         self._latest_non_transient_bridge_state = None
+        self.challenge_token = None
         perms = self.config.get_permissions(mxid)
         self.relay_whitelisted, self.is_whitelisted, self.is_admin, self.permission_level = perms