errors.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # Copyright (c) 2022 Tulir Asokan
  2. #
  3. # This Source Code Form is subject to the terms of the Mozilla Public
  4. # License, v. 2.0. If a copy of the MPL was not distributed with this
  5. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. from __future__ import annotations
  7. from typing import Any
  8. from mautrix.util.format_duration import format_duration
  9. class RPCError(Exception):
  10. pass
  11. class UnexpectedError(RPCError):
  12. pass
  13. class UnexpectedResponse(RPCError):
  14. def __init__(self, resp_type: str, data: Any) -> None:
  15. super().__init__(f"Got unexpected response type {resp_type}")
  16. self.resp_type = resp_type
  17. self.data = data
  18. class NotConnected(RPCError):
  19. pass
  20. class ResponseError(RPCError):
  21. def __init__(
  22. self,
  23. data: dict[str, Any],
  24. error_type: str | None = None,
  25. message_override: str | None = None,
  26. ) -> None:
  27. self.data = data
  28. msg = message_override or data["message"]
  29. if error_type:
  30. msg = f"{error_type}: {msg}"
  31. super().__init__(msg)
  32. class TimeoutException(ResponseError):
  33. pass
  34. class UnknownIdentityKey(ResponseError):
  35. pass
  36. class CaptchaRequiredError(ResponseError):
  37. pass
  38. class AuthorizationFailedError(ResponseError):
  39. pass
  40. class ScanTimeoutError(ResponseError):
  41. pass
  42. class UserAlreadyExistsError(ResponseError):
  43. def __init__(self, data: dict[str, Any]) -> None:
  44. super().__init__(data, message_override="You're already logged in")
  45. class OwnProfileKeyDoesNotExistError(ResponseError):
  46. def __init__(self, data: dict[str, Any]) -> None:
  47. super().__init__(
  48. data,
  49. message_override=(
  50. "Cannot find own profile key. Please make sure you have a Signal profile name set."
  51. ),
  52. )
  53. class RequestValidationFailure(ResponseError):
  54. def __init__(self, data: dict[str, Any]) -> None:
  55. results = data["validationResults"]
  56. result_str = ", ".join(results) if isinstance(results, list) else str(results)
  57. super().__init__(data, message_override=result_str)
  58. class InternalError(ResponseError):
  59. """
  60. If you find yourself using this, please file an issue against signald. We want to make
  61. explicit error types at the protocol for anything a client might normally expect.
  62. """
  63. def __init__(self, data: dict[str, Any]) -> None:
  64. exceptions = data.get("exceptions", [])
  65. self.exceptions = exceptions
  66. message = data.get("message")
  67. super().__init__(data, error_type=", ".join(exceptions), message_override=message)
  68. class AttachmentTooLargeError(ResponseError):
  69. def __init__(self, data: dict[str, Any]) -> None:
  70. self.filename = data.get("filename", "")
  71. super().__init__(data, message_override="File is over the 100MB limit.")
  72. class UnregisteredUserError(ResponseError):
  73. pass
  74. class ProfileUnavailableError(ResponseError):
  75. pass
  76. class NoSuchAccountError(ResponseError):
  77. pass
  78. class GroupPatchNotAcceptedError(ResponseError):
  79. pass
  80. class ProofRequiredError(ResponseError):
  81. def __init__(self, data: dict[str, Any]) -> None:
  82. self.options = data.get("options")
  83. self.retry_after = data.get("retry_after")
  84. self.token = data.get("token")
  85. message = "You have been rate limited by Signal."
  86. if isinstance(self.retry_after, (int, float)):
  87. message += (
  88. f" Try again in {format_duration(int(self.retry_after))} "
  89. "or complete a captcha challenge using the `submit-challenge` command."
  90. )
  91. super().__init__(data, message_override=message)
  92. response_error_types = {
  93. "invalid_request": RequestValidationFailure,
  94. "TimeoutException": TimeoutException,
  95. "UserAlreadyExists": UserAlreadyExistsError,
  96. "RequestValidationFailure": RequestValidationFailure,
  97. "UnknownIdentityKey": UnknownIdentityKey,
  98. "CaptchaRequiredError": CaptchaRequiredError,
  99. "InternalError": InternalError,
  100. "AttachmentTooLargeError": AttachmentTooLargeError,
  101. "AuthorizationFailedError": AuthorizationFailedError,
  102. "ScanTimeoutError": ScanTimeoutError,
  103. "OwnProfileKeyDoesNotExistError": OwnProfileKeyDoesNotExistError,
  104. "UnregisteredUserError": UnregisteredUserError,
  105. "ProfileUnavailableError": ProfileUnavailableError,
  106. "NoSuchAccountError": NoSuchAccountError,
  107. "GroupPatchNotAcceptedError": GroupPatchNotAcceptedError,
  108. "ProofRequiredError": ProofRequiredError,
  109. # TODO add rest from https://gitlab.com/signald/signald/-/tree/main/src/main/java/io/finn/signald/clientprotocol/v1/exceptions
  110. }
  111. def make_response_error(data: dict[str, Any]) -> ResponseError:
  112. error_data = data["error"]
  113. if isinstance(error_data, str):
  114. error_data = {"message": error_data}
  115. elif not isinstance(error_data, dict):
  116. error_data = {"message": str(error_data)}
  117. if "message" not in error_data:
  118. error_data["message"] = "no message, see signald logs"
  119. error_type = data.get("error_type")
  120. try:
  121. error_class = response_error_types[error_type]
  122. except KeyError:
  123. return ResponseError(error_data, error_type=error_type)
  124. else:
  125. return error_class(error_data)