types.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  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 typing import Dict, List, NewType, Optional
  7. from datetime import datetime, timedelta
  8. from uuid import UUID
  9. from attr import dataclass
  10. from mautrix.types import ExtensibleEnum, SerializableAttrs, SerializableEnum, field
  11. GroupID = NewType("GroupID", str)
  12. @dataclass(frozen=True, eq=False)
  13. class Address(SerializableAttrs):
  14. number: Optional[str] = None
  15. uuid: Optional[UUID] = None
  16. @property
  17. def is_valid(self) -> bool:
  18. return bool(self.number) or bool(self.uuid)
  19. @property
  20. def best_identifier(self) -> str:
  21. return str(self.uuid) if self.uuid else self.number
  22. @property
  23. def number_or_uuid(self) -> str:
  24. return self.number or str(self.uuid)
  25. def __eq__(self, other: "Address") -> bool:
  26. if not isinstance(other, Address):
  27. return False
  28. if self.uuid and other.uuid:
  29. return self.uuid == other.uuid
  30. elif self.number and other.number:
  31. return self.number == other.number
  32. return False
  33. def __hash__(self) -> int:
  34. if self.uuid:
  35. return hash(self.uuid)
  36. return hash(self.number)
  37. @classmethod
  38. def parse(cls, value: str) -> "Address":
  39. return Address(number=value) if value.startswith("+") else Address(uuid=UUID(value))
  40. @dataclass
  41. class Account(SerializableAttrs):
  42. account_id: str
  43. device_id: int
  44. address: Address
  45. pending: bool = False
  46. pni: Optional[str] = None
  47. def pluralizer(val: int) -> str:
  48. if val == 1:
  49. return ""
  50. return "s"
  51. @dataclass
  52. class DeviceInfo(SerializableAttrs):
  53. id: int
  54. created: int
  55. last_seen: int = field(json="lastSeen")
  56. name: Optional[str] = None
  57. @property
  58. def name_with_default(self) -> str:
  59. if self.name:
  60. return self.name
  61. return "primary device" if self.id == 1 else "unnamed device"
  62. @property
  63. def created_fmt(self) -> str:
  64. return datetime.utcfromtimestamp(self.created / 1000).strftime("%Y-%m-%d %H:%M:%S UTC")
  65. @property
  66. def last_seen_fmt(self) -> str:
  67. dt = datetime.utcfromtimestamp(self.last_seen / 1000)
  68. now = datetime.utcnow()
  69. if dt.date() == now.date():
  70. return "today"
  71. elif (dt + timedelta(days=1)).date() == now.date():
  72. return "yesterday"
  73. day_diff = (now - dt).days
  74. if day_diff < 30:
  75. return f"{day_diff} day{pluralizer(day_diff)} ago"
  76. return dt.strftime("%Y-%m-%d")
  77. @dataclass
  78. class LinkSession(SerializableAttrs):
  79. uri: str
  80. session_id: str
  81. class TrustLevel(SerializableEnum):
  82. TRUSTED_UNVERIFIED = "TRUSTED_UNVERIFIED"
  83. TRUSTED_VERIFIED = "TRUSTED_VERIFIED"
  84. UNTRUSTED = "UNTRUSTED"
  85. @property
  86. def human_str(self) -> str:
  87. if self == TrustLevel.TRUSTED_VERIFIED:
  88. return "trusted"
  89. elif self == TrustLevel.TRUSTED_UNVERIFIED:
  90. return "trusted (unverified)"
  91. elif self == TrustLevel.UNTRUSTED:
  92. return "untrusted"
  93. return "unknown"
  94. @dataclass
  95. class Identity(SerializableAttrs):
  96. trust_level: TrustLevel
  97. added: int
  98. safety_number: str
  99. qr_code_data: str
  100. @dataclass
  101. class GetIdentitiesResponse(SerializableAttrs):
  102. address: Address
  103. identities: List[Identity]
  104. @dataclass
  105. class Capabilities(SerializableAttrs):
  106. gv2: bool = False
  107. storage: bool = False
  108. gv1_migration: bool = field(default=False, json="gv1-migration")
  109. announcement_group: bool = False
  110. change_number: bool = False
  111. sender_key: bool = False
  112. stories: bool = False
  113. @dataclass
  114. class Profile(SerializableAttrs):
  115. address: Optional[Address] = None
  116. name: str = ""
  117. contact_name: str = ""
  118. profile_name: str = ""
  119. about: str = ""
  120. avatar: str = ""
  121. color: str = ""
  122. emoji: str = ""
  123. inbox_position: Optional[int] = None
  124. mobilecoin_address: Optional[str] = None
  125. expiration_time: Optional[int] = None
  126. capabilities: Optional[Capabilities] = None
  127. # visible_badge_ids: List[str]
  128. @dataclass
  129. class Group(SerializableAttrs):
  130. group_id: GroupID = field(json="groupId")
  131. name: str = "Unknown group"
  132. # Sometimes "UPDATE"
  133. type: Optional[str] = None
  134. # Not always present
  135. members: List[Address] = field(factory=lambda: [])
  136. avatar_id: int = field(default=0, json="avatarId")
  137. class AccessControlMode(SerializableEnum):
  138. UNKNOWN = "UNKNOWN"
  139. ANY = "ANY"
  140. MEMBER = "MEMBER"
  141. ADMINISTRATOR = "ADMINISTRATOR"
  142. UNSATISFIABLE = "UNSATISFIABLE"
  143. UNRECOGNIZED = "UNRECOGNIZED"
  144. class AnnouncementsMode(SerializableEnum):
  145. UNKNOWN = "UNKNOWN"
  146. ENABLED = "ENABLED"
  147. DISABLED = "DISABLED"
  148. @dataclass
  149. class GroupAccessControl(SerializableAttrs):
  150. attributes: Optional[AccessControlMode] = None
  151. link: Optional[AccessControlMode] = None
  152. members: Optional[AccessControlMode] = None
  153. class GroupMemberRole(SerializableEnum):
  154. UNKNOWN = "UNKNOWN"
  155. DEFAULT = "DEFAULT"
  156. ADMINISTRATOR = "ADMINISTRATOR"
  157. UNRECOGNIZED = "UNRECOGNIZED"
  158. @dataclass
  159. class GroupMember(SerializableAttrs):
  160. uuid: UUID
  161. joined_revision: int = 0
  162. role: GroupMemberRole = GroupMemberRole.UNKNOWN
  163. @dataclass
  164. class BannedGroupMember(SerializableAttrs):
  165. uuid: UUID
  166. timestamp: int
  167. @dataclass
  168. class GroupChange(SerializableAttrs):
  169. revision: int
  170. editor: Address
  171. delete_members: Optional[List[Address]] = None
  172. delete_pending_members: Optional[List[Address]] = None
  173. delete_requesting_members: Optional[List[Address]] = None
  174. modified_profile_keys: Optional[List[GroupMember]] = None
  175. modify_member_roles: Optional[List[GroupMember]] = None
  176. new_access_control: Optional[GroupAccessControl] = None
  177. new_avatar: bool = False
  178. new_banned_members: Optional[List[GroupMember]] = None
  179. new_description: Optional[str] = None
  180. new_invite_link_password: bool = False
  181. new_is_announcement_group: Optional[AnnouncementsMode] = None
  182. new_members: Optional[List[GroupMember]] = None
  183. new_pending_members: Optional[List[GroupMember]] = None
  184. new_requesting_members: Optional[List[GroupMember]] = None
  185. new_timer: Optional[int] = None
  186. new_title: Optional[str] = None
  187. new_unbanned_members: Optional[List[GroupMember]] = None
  188. promote_pending_members: Optional[List[GroupMember]] = None
  189. promote_requesting_members: Optional[List[GroupMember]] = None
  190. @dataclass(kw_only=True)
  191. class GroupV2ID(SerializableAttrs):
  192. id: GroupID
  193. revision: Optional[int] = None
  194. removed: Optional[bool] = False
  195. group_change: Optional[GroupChange] = None
  196. @dataclass(kw_only=True)
  197. class GroupV2(GroupV2ID, SerializableAttrs):
  198. title: str = None
  199. description: Optional[str] = None
  200. avatar: Optional[str] = None
  201. timer: Optional[int] = None
  202. master_key: Optional[str] = field(default=None, json="masterKey")
  203. invite_link: Optional[str] = field(default=None, json="inviteLink")
  204. access_control: GroupAccessControl = field(
  205. factory=lambda: GroupAccessControl(), json="accessControl"
  206. )
  207. members: List[Address] = None
  208. member_detail: List[GroupMember] = field(factory=lambda: [], json="memberDetail")
  209. pending_members: List[Address] = field(factory=lambda: [], json="pendingMembers")
  210. pending_member_detail: List[GroupMember] = field(
  211. factory=lambda: [], json="pendingMemberDetail"
  212. )
  213. requesting_members: List[Address] = field(factory=lambda: [], json="requestingMembers")
  214. announcements: AnnouncementsMode = field(default=AnnouncementsMode.UNKNOWN)
  215. banned_members: Optional[List[BannedGroupMember]] = None
  216. @dataclass
  217. class Attachment(SerializableAttrs):
  218. width: int = 0
  219. height: int = 0
  220. caption: Optional[str] = None
  221. preview: Optional[str] = None
  222. blurhash: Optional[str] = None
  223. voice_note: bool = field(default=False, json="voiceNote")
  224. content_type: Optional[str] = field(default=None, json="contentType")
  225. custom_filename: Optional[str] = field(default=None, json="customFilename")
  226. # Only for incoming
  227. id: Optional[str] = None
  228. incoming_filename: Optional[str] = field(default=None, json="storedFilename")
  229. digest: Optional[str] = None
  230. size: Optional[int] = None
  231. # Only for outgoing
  232. outgoing_filename: Optional[str] = field(default=None, json="filename")
  233. @dataclass
  234. class Mention(SerializableAttrs):
  235. uuid: UUID
  236. length: int
  237. start: int = 0
  238. @dataclass
  239. class QuotedAttachment(SerializableAttrs):
  240. content_type: Optional[str] = field(default=None, json="contentType")
  241. filename: Optional[str] = field(default=None, json="fileName")
  242. @dataclass
  243. class Quote(SerializableAttrs):
  244. id: int
  245. author: Address
  246. text: Optional[str] = None
  247. attachments: Optional[List[QuotedAttachment]] = None
  248. mentions: Optional[List[Mention]] = None
  249. @dataclass(kw_only=True)
  250. class Reaction(SerializableAttrs):
  251. emoji: str
  252. remove: bool = False
  253. target_author: Address = field(json="targetAuthor")
  254. target_sent_timestamp: int = field(json="targetSentTimestamp")
  255. @dataclass
  256. class Sticker(SerializableAttrs):
  257. attachment: Attachment
  258. pack_id: str = field(json="packID")
  259. pack_key: str = field(json="packKey")
  260. sticker_id: int = field(json="stickerID")
  261. @dataclass
  262. class RemoteDelete(SerializableAttrs):
  263. target_sent_timestamp: int
  264. class SharedContactDetailType(SerializableEnum):
  265. HOME = "HOME"
  266. WORK = "WORK"
  267. MOBILE = "MOBILE"
  268. CUSTOM = "CUSTOM"
  269. @dataclass
  270. class SharedContactDetail(SerializableAttrs):
  271. type: SharedContactDetailType
  272. value: str
  273. label: Optional[str] = None
  274. @property
  275. def type_or_label(self) -> str:
  276. if self.type != SharedContactDetailType.CUSTOM:
  277. return self.type.value.title()
  278. return self.label
  279. @dataclass
  280. class SharedContactAvatar(SerializableAttrs):
  281. attachment: Attachment
  282. is_profile: bool
  283. @dataclass
  284. class SharedContactName(SerializableAttrs):
  285. display: Optional[str] = None
  286. given: Optional[str] = None
  287. middle: Optional[str] = None
  288. family: Optional[str] = None
  289. prefix: Optional[str] = None
  290. suffix: Optional[str] = None
  291. @property
  292. def parts(self) -> List[str]:
  293. return [self.prefix, self.given, self.middle, self.family, self.suffix]
  294. def __str__(self) -> str:
  295. if self.display:
  296. return self.display
  297. return " ".join(part for part in self.parts if part)
  298. @dataclass
  299. class SharedContactAddress(SerializableAttrs):
  300. type: SharedContactDetailType
  301. label: Optional[str] = None
  302. street: Optional[str] = None
  303. pobox: Optional[str] = None
  304. neighborhood: Optional[str] = None
  305. city: Optional[str] = None
  306. region: Optional[str] = None
  307. postcode: Optional[str] = None
  308. country: Optional[str] = None
  309. @dataclass
  310. class SharedContact(SerializableAttrs):
  311. name: SharedContactName
  312. organization: Optional[str] = None
  313. avatar: Optional[SharedContactAvatar] = None
  314. email: List[SharedContactDetail] = field(factory=lambda: [])
  315. phone: List[SharedContactDetail] = field(factory=lambda: [])
  316. address: Optional[SharedContactAddress] = None
  317. @dataclass
  318. class LinkPreview(SerializableAttrs):
  319. url: str
  320. title: str
  321. description: str
  322. attachment: Optional[Attachment] = None
  323. @dataclass
  324. class MessageData(SerializableAttrs):
  325. timestamp: int
  326. body: Optional[str] = None
  327. quote: Optional[Quote] = None
  328. reaction: Optional[Reaction] = None
  329. attachments: List[Attachment] = field(factory=lambda: [])
  330. sticker: Optional[Sticker] = None
  331. mentions: List[Mention] = field(factory=lambda: [])
  332. contacts: List[SharedContact] = field(factory=lambda: [])
  333. group: Optional[Group] = None
  334. group_v2: Optional[GroupV2ID] = field(default=None, json="groupV2")
  335. end_session: bool = field(default=False, json="endSession")
  336. expires_in_seconds: int = field(default=0, json="expiresInSeconds")
  337. is_expiration_update: bool = field(default=False)
  338. profile_key_update: bool = field(default=False, json="profileKeyUpdate")
  339. view_once: bool = field(default=False, json="viewOnce")
  340. remote_delete: Optional[RemoteDelete] = field(default=None, json="remoteDelete")
  341. previews: List[LinkPreview] = field(factory=lambda: [])
  342. @property
  343. def is_message(self) -> bool:
  344. return bool(self.body or self.attachments or self.sticker or self.contacts)
  345. @dataclass
  346. class SentSyncMessage(SerializableAttrs):
  347. message: MessageData
  348. timestamp: int
  349. expiration_start_timestamp: Optional[int] = field(
  350. default=None, json="expirationStartTimestamp"
  351. )
  352. is_recipient_update: bool = field(default=False, json="isRecipientUpdate")
  353. unidentified_status: Dict[str, bool] = field(factory=lambda: {})
  354. destination: Optional[Address] = None
  355. class TypingAction(SerializableEnum):
  356. UNKNOWN = "UNKNOWN"
  357. STARTED = "STARTED"
  358. STOPPED = "STOPPED"
  359. @dataclass
  360. class TypingMessage(SerializableAttrs):
  361. action: TypingAction
  362. timestamp: int
  363. group_id: Optional[GroupID] = None
  364. @dataclass
  365. class OwnReadReceipt(SerializableAttrs):
  366. sender: Address
  367. timestamp: int
  368. class ReceiptType(SerializableEnum):
  369. UNKNOWN = "UNKNOWN"
  370. DELIVERY = "DELIVERY"
  371. READ = "READ"
  372. VIEWED = "VIEWED"
  373. @dataclass
  374. class ReceiptMessage(SerializableAttrs):
  375. type: ReceiptType
  376. timestamps: List[int]
  377. when: int
  378. @dataclass
  379. class ContactSyncMeta(SerializableAttrs):
  380. id: Optional[str] = None
  381. @dataclass
  382. class ConfigItem(SerializableAttrs):
  383. present: bool = False
  384. @dataclass
  385. class ClientConfiguration(SerializableAttrs):
  386. read_receipts: Optional[ConfigItem] = field(factory=lambda: ConfigItem(), json="readReceipts")
  387. typing_indicators: Optional[ConfigItem] = field(
  388. factory=lambda: ConfigItem(), json="typingIndicators"
  389. )
  390. link_previews: Optional[ConfigItem] = field(factory=lambda: ConfigItem(), json="linkPreviews")
  391. unidentified_delivery_indicators: Optional[ConfigItem] = field(
  392. factory=lambda: ConfigItem(), json="unidentifiedDeliveryIndicators"
  393. )
  394. class StickerPackOperation(ExtensibleEnum):
  395. INSTALL = "INSTALL"
  396. # there are very likely others
  397. @dataclass
  398. class StickerPackOperations(SerializableAttrs):
  399. type: StickerPackOperation
  400. pack_id: str = field(json="packID")
  401. pack_key: str = field(json="packKey")
  402. @dataclass
  403. class SyncMessage(SerializableAttrs):
  404. sent: Optional[SentSyncMessage] = None
  405. read_messages: Optional[List[OwnReadReceipt]] = field(default=None, json="readMessages")
  406. contacts: Optional[ContactSyncMeta] = None
  407. groups: Optional[ContactSyncMeta] = None
  408. configuration: Optional[ClientConfiguration] = None
  409. # blocked_list: Optional[???] = field(default=None, json="blockedList")
  410. sticker_pack_operations: Optional[List[StickerPackOperations]] = field(
  411. default=None, json="stickerPackOperations"
  412. )
  413. contacts_complete: bool = field(default=False, json="contactsComplete")
  414. class OfferMessageType(SerializableEnum):
  415. AUDIO_CALL = "audio_call"
  416. VIDEO_CALL = "video_call"
  417. @dataclass
  418. class OfferMessage(SerializableAttrs):
  419. id: int
  420. type: OfferMessageType
  421. opaque: Optional[str] = None
  422. sdp: Optional[str] = None
  423. @dataclass
  424. class AnswerMessage(SerializableAttrs):
  425. id: int
  426. opaque: Optional[str] = None
  427. sdp: Optional[str] = None
  428. @dataclass
  429. class ICEUpdateMessage(SerializableAttrs):
  430. id: int
  431. opaque: Optional[str] = None
  432. sdp: Optional[str] = None
  433. @dataclass
  434. class BusyMessage(SerializableAttrs):
  435. id: int
  436. class HangupMessageType(SerializableEnum):
  437. NORMAL = "normal"
  438. ACCEPTED = "accepted"
  439. DECLINED = "declined"
  440. BUSY = "busy"
  441. NEED_PERMISSION = "need_permission"
  442. @dataclass
  443. class HangupMessage(SerializableAttrs):
  444. id: int
  445. type: HangupMessageType
  446. device_id: int
  447. legacy: bool = False
  448. @dataclass
  449. class CallMessage(SerializableAttrs):
  450. offer_message: Optional[OfferMessage] = None
  451. hangup_message: Optional[HangupMessage] = None
  452. answer_message: Optional[AnswerMessage] = None
  453. busy_message: Optional[BusyMessage] = None
  454. ice_update_message: Optional[List[ICEUpdateMessage]] = None
  455. multi_ring: bool = False
  456. destination_device_id: Optional[int] = None
  457. class MessageType(SerializableEnum):
  458. CIPHERTEXT = "CIPHERTEXT"
  459. UNIDENTIFIED_SENDER = "UNIDENTIFIED_SENDER"
  460. RECEIPT = "RECEIPT"
  461. PREKEY_BUNDLE = "PREKEY_BUNDLE"
  462. KEY_EXCHANGE = "KEY_EXCHANGE"
  463. UNKNOWN = "UNKNOWN"
  464. @dataclass(kw_only=True)
  465. class IncomingMessage(SerializableAttrs):
  466. account: str
  467. source: Address
  468. timestamp: int
  469. type: MessageType
  470. source_device: Optional[int] = None
  471. server_guid: str
  472. server_receiver_timestamp: int
  473. server_deliver_timestamp: int
  474. has_content: bool
  475. unidentified_sender: bool
  476. has_legacy_message: bool
  477. call_message: Optional[CallMessage] = field(default=None)
  478. data_message: Optional[MessageData] = field(default=None)
  479. sync_message: Optional[SyncMessage] = field(default=None)
  480. typing_message: Optional[TypingMessage] = None
  481. receipt_message: Optional[ReceiptMessage] = None
  482. @dataclass(kw_only=True)
  483. class ErrorMessageData(SerializableAttrs):
  484. sender: str
  485. timestamp: int
  486. message: str
  487. sender_device: int
  488. content_hint: int
  489. @dataclass(kw_only=True)
  490. class ErrorMessage(SerializableAttrs):
  491. type: str
  492. version: str
  493. data: ErrorMessageData
  494. error: bool
  495. account: str
  496. @dataclass(kw_only=True)
  497. class StorageChangeData(SerializableAttrs):
  498. version: int
  499. @dataclass(kw_only=True)
  500. class StorageChange(SerializableAttrs):
  501. type: str
  502. version: str
  503. data: StorageChangeData
  504. account: str
  505. class WebsocketConnectionState(SerializableEnum):
  506. # States from signald itself
  507. DISCONNECTED = "DISCONNECTED"
  508. CONNECTING = "CONNECTING"
  509. CONNECTED = "CONNECTED"
  510. RECONNECTING = "RECONNECTING"
  511. DISCONNECTING = "DISCONNECTING"
  512. AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED"
  513. FAILED = "FAILED"
  514. # Socket disconnect state
  515. SOCKET_DISCONNECTED = "SOCKET_DISCONNECTED"
  516. class WebsocketType(SerializableEnum):
  517. IDENTIFIED = "IDENTIFIED"
  518. UNIDENTIFIED = "UNIDENTIFIED"
  519. @dataclass
  520. class WebsocketConnectionStateChangeEvent(SerializableAttrs):
  521. state: WebsocketConnectionState
  522. account: str
  523. socket: Optional[WebsocketType] = None
  524. exception: Optional[str] = None
  525. @dataclass
  526. class JoinGroupResponse(SerializableAttrs):
  527. group_id: str = field(json="groupID")
  528. pending_admin_approval: bool = field(json="pendingAdminApproval")
  529. member_count: Optional[int] = field(json="memberCount", default=None)
  530. revision: Optional[int] = None
  531. title: Optional[str] = None
  532. description: Optional[str] = None
  533. class ProofRequiredType(SerializableEnum):
  534. RECAPTCHA = "RECAPTCHA"
  535. PUSH_CHALLENGE = "PUSH_CHALLENGE"
  536. @dataclass
  537. class ProofRequiredError(SerializableAttrs):
  538. options: List[ProofRequiredType] = field(factory=lambda: [])
  539. message: Optional[str] = None
  540. retry_after: Optional[int] = None
  541. token: Optional[str] = None
  542. @dataclass
  543. class SendSuccessData(SerializableAttrs):
  544. devices: List[int] = field(factory=lambda: [])
  545. duration: Optional[int] = None
  546. needs_sync: bool = field(json="needsSync", default=False)
  547. unidentified: bool = field(json="unidentified", default=False)
  548. @dataclass
  549. class SendMessageResult(SerializableAttrs):
  550. address: Address
  551. success: Optional[SendSuccessData] = None
  552. proof_required_failure: Optional[ProofRequiredError] = None
  553. identity_failure: Optional[str] = field(json="identityFailure", default=None)
  554. network_failure: bool = field(json="networkFailure", default=False)
  555. unregistered_failure: bool = field(json="unregisteredFailure", default=False)
  556. @dataclass
  557. class SendMessageResponse(SerializableAttrs):
  558. results: List[SendMessageResult]
  559. timestamp: int