types.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. # Copyright (c) 2020 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 Optional, Dict, Any, List, NewType
  7. from uuid import UUID
  8. from attr import dataclass
  9. import attr
  10. from mautrix.types import SerializableAttrs, SerializableEnum, ExtensibleEnum
  11. GroupID = NewType('GroupID', str)
  12. @dataclass(frozen=True, eq=False)
  13. class Address(SerializableAttrs['Address']):
  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. def __eq__(self, other: 'Address') -> bool:
  23. if not isinstance(other, Address):
  24. return False
  25. if self.uuid and other.uuid:
  26. return self.uuid == other.uuid
  27. elif self.number and other.number:
  28. return self.number == other.number
  29. return False
  30. def __hash__(self) -> int:
  31. if self.uuid:
  32. return hash(self.uuid)
  33. return hash(self.number)
  34. @classmethod
  35. def parse(cls, value: str) -> 'Address':
  36. return Address(number=value) if value.startswith("+") else Address(uuid=UUID(value))
  37. @dataclass
  38. class Account(SerializableAttrs['Account']):
  39. account_id: str
  40. device_id: int
  41. address: Address
  42. @dataclass
  43. class LinkSession(SerializableAttrs['LinkSession']):
  44. uri: str
  45. session_id: str
  46. @dataclass
  47. class TrustLevel(SerializableEnum):
  48. TRUSTED_UNVERIFIED = "TRUSTED_UNVERIFIED"
  49. TRUSTED_VERIFIED = "TRUSTED_VERIFIED"
  50. UNTRUSTED = "UNTRUSTED"
  51. @dataclass
  52. class Identity(SerializableAttrs['Identity']):
  53. trust_level: TrustLevel
  54. added: int
  55. safety_number: str
  56. qr_code_data: str
  57. @dataclass
  58. class GetIdentitiesResponse(SerializableAttrs['GetIdentitiesResponse']):
  59. address: Address
  60. identities: List[Identity]
  61. @dataclass
  62. class Contact(SerializableAttrs['Contact']):
  63. address: Address
  64. name: Optional[str] = None
  65. color: Optional[str] = None
  66. profile_key: Optional[str] = attr.ib(default=None, metadata={"json": "profileKey"})
  67. message_expiration_time: int = attr.ib(default=0, metadata={"json": "messageExpirationTime"})
  68. @dataclass
  69. class Capabilities(SerializableAttrs['Capabilities']):
  70. gv2: bool = False
  71. storage: bool = False
  72. gv1_migration: bool = attr.ib(default=False, metadata={"json": "gv1-migration"})
  73. @dataclass
  74. class Profile(SerializableAttrs['Profile']):
  75. name: str = ""
  76. profile_name: str = ""
  77. avatar: str = ""
  78. identity_key: str = ""
  79. unidentified_access: str = ""
  80. unrestricted_unidentified_access: bool = False
  81. address: Optional[Address] = None
  82. expiration_time: int = 0
  83. capabilities: Optional[Capabilities] = None
  84. @dataclass
  85. class Group(SerializableAttrs['Group']):
  86. group_id: GroupID = attr.ib(metadata={"json": "groupId"})
  87. name: str = "Unknown group"
  88. # Sometimes "UPDATE"
  89. type: Optional[str] = None
  90. # Not always present
  91. members: List[Address] = attr.ib(factory=lambda: [])
  92. avatar_id: int = attr.ib(default=0, metadata={"json": "avatarId"})
  93. @dataclass(kw_only=True)
  94. class GroupV2ID(SerializableAttrs['GroupV2ID']):
  95. id: GroupID
  96. revision: Optional[int] = None
  97. class AccessControlMode(SerializableEnum):
  98. UNKNOWN = "UNKNOWN"
  99. ANY = "ANY"
  100. MEMBER = "MEMBER"
  101. ADMINISTRATOR = "ADMINISTRATOR"
  102. UNSATISFIABLE = "UNSATISFIABLE"
  103. UNRECOGNIZED = "UNRECOGNIZED"
  104. @dataclass
  105. class GroupAccessControl(SerializableAttrs['GroupAccessControl']):
  106. attributes: AccessControlMode = AccessControlMode.UNKNOWN
  107. link: AccessControlMode = AccessControlMode.UNKNOWN
  108. members: AccessControlMode = AccessControlMode.UNKNOWN
  109. class GroupMemberRole(SerializableEnum):
  110. UNKNOWN = "UNKNOWN"
  111. DEFAULT = "DEFAULT"
  112. ADMINISTRATOR = "ADMINISTRATOR"
  113. UNRECOGNIZED = "UNRECOGNIZED"
  114. @dataclass
  115. class GroupMember(SerializableAttrs['GroupMember']):
  116. uuid: UUID
  117. joined_revision: int = 0
  118. role: GroupMemberRole = GroupMemberRole.UNKNOWN
  119. @dataclass(kw_only=True)
  120. class GroupV2(GroupV2ID, SerializableAttrs['GroupV2']):
  121. title: str
  122. avatar: Optional[str] = None
  123. timer: Optional[int] = None
  124. master_key: Optional[str] = attr.ib(default=None, metadata={"json": "masterKey"})
  125. invite_link: Optional[str] = attr.ib(default=None, metadata={"json": "inviteLink"})
  126. access_control: GroupAccessControl = attr.ib(factory=lambda: GroupAccessControl(),
  127. metadata={"json": "accessControl"})
  128. members: List[Address]
  129. member_detail: List[GroupMember] = attr.ib(factory=lambda: [],
  130. metadata={"json": "memberDetail"})
  131. pending_members: List[Address] = attr.ib(factory=lambda: [],
  132. metadata={"json": "pendingMembers"})
  133. pending_member_detail: List[GroupMember] = attr.ib(factory=lambda: [],
  134. metadata={"json": "pendingMemberDetail"})
  135. requesting_members: List[Address] = attr.ib(factory=lambda: [],
  136. metadata={"json": "requestingMembers"})
  137. @dataclass
  138. class Attachment(SerializableAttrs['Attachment']):
  139. width: int = 0
  140. height: int = 0
  141. caption: Optional[str] = None
  142. preview: Optional[str] = None
  143. blurhash: Optional[str] = None
  144. voice_note: bool = attr.ib(default=False, metadata={"json": "voiceNote"})
  145. content_type: Optional[str] = attr.ib(default=None, metadata={"json": "contentType"})
  146. custom_filename: Optional[str] = attr.ib(default=None, metadata={"json": "customFilename"})
  147. # Only for incoming
  148. id: Optional[str] = None
  149. incoming_filename: Optional[str] = attr.ib(default=None, metadata={"json": "storedFilename"})
  150. digest: Optional[str] = None
  151. # Only for outgoing
  152. outgoing_filename: Optional[str] = attr.ib(default=None, metadata={"json": "filename"})
  153. @dataclass
  154. class Quote(SerializableAttrs['Quote']):
  155. id: int
  156. author: Address
  157. text: Optional[str] = None
  158. # TODO: attachments, mentions
  159. @dataclass(kw_only=True)
  160. class Reaction(SerializableAttrs['Reaction']):
  161. emoji: str
  162. remove: bool = False
  163. target_author: Address = attr.ib(metadata={"json": "targetAuthor"})
  164. target_sent_timestamp: int = attr.ib(metadata={"json": "targetSentTimestamp"})
  165. @dataclass
  166. class Sticker(SerializableAttrs['Sticker']):
  167. attachment: Attachment
  168. pack_id: str = attr.ib(metadata={"json": "packID"})
  169. pack_key: str = attr.ib(metadata={"json": "packKey"})
  170. sticker_id: int = attr.ib(metadata={"json": "stickerID"})
  171. @dataclass
  172. class RemoteDelete(SerializableAttrs['RemoteDelete']):
  173. target_sent_timestamp: int = attr.ib(metadata={"json": "targetSentTimestamp"})
  174. @dataclass
  175. class Mention(SerializableAttrs['Mention']):
  176. uuid: UUID
  177. length: int
  178. start: int = 0
  179. @dataclass
  180. class MessageData(SerializableAttrs['MessageData']):
  181. timestamp: int
  182. body: Optional[str] = None
  183. quote: Optional[Quote] = None
  184. reaction: Optional[Reaction] = None
  185. attachments: List[Attachment] = attr.ib(factory=lambda: [])
  186. sticker: Optional[Sticker] = None
  187. mentions: List[Mention] = attr.ib(factory=lambda: [])
  188. group: Optional[Group] = None
  189. group_v2: Optional[GroupV2ID] = attr.ib(default=None, metadata={"json": "groupV2"})
  190. end_session: bool = attr.ib(default=False, metadata={"json": "endSession"})
  191. expires_in_seconds: int = attr.ib(default=0, metadata={"json": "expiresInSeconds"})
  192. profile_key_update: bool = attr.ib(default=False, metadata={"json": "profileKeyUpdate"})
  193. view_once: bool = attr.ib(default=False, metadata={"json": "viewOnce"})
  194. remote_delete: Optional[RemoteDelete] = attr.ib(default=None,
  195. metadata={"json": "remoteDelete"})
  196. @dataclass
  197. class SentSyncMessage(SerializableAttrs['SentSyncMessage']):
  198. message: MessageData
  199. timestamp: int
  200. expiration_start_timestamp: Optional[int] = attr.ib(default=None, metadata={
  201. "json": "expirationStartTimestamp"})
  202. is_recipient_update: bool = attr.ib(default=False, metadata={"json": "isRecipientUpdate"})
  203. unidentified_status: Dict[str, bool] = attr.ib(factory=lambda: {})
  204. destination: Optional[Address] = None
  205. class TypingAction(SerializableEnum):
  206. UNKNOWN = "UNKNOWN"
  207. STARTED = "STARTED"
  208. STOPPED = "STOPPED"
  209. @dataclass
  210. class TypingNotification(SerializableAttrs['TypingNotification']):
  211. action: TypingAction
  212. timestamp: int
  213. group_id: Optional[GroupID] = attr.ib(default=None, metadata={"json": "groupId"})
  214. @dataclass
  215. class OwnReadReceipt(SerializableAttrs['OwnReadReceipt']):
  216. sender: Address
  217. timestamp: int
  218. class ReceiptType(SerializableEnum):
  219. UNKNOWN = "UNKNOWN"
  220. DELIVERY = "DELIVERY"
  221. READ = "READ"
  222. VIEWED = "VIEWED"
  223. @dataclass
  224. class Receipt(SerializableAttrs['Receipt']):
  225. type: ReceiptType
  226. timestamps: List[int]
  227. when: int
  228. @dataclass
  229. class ContactSyncMeta(SerializableAttrs['ContactSyncMeta']):
  230. id: Optional[str] = None
  231. @dataclass
  232. class ConfigItem(SerializableAttrs['ConfigItem']):
  233. present: bool = False
  234. @dataclass
  235. class ClientConfiguration(SerializableAttrs['ClientConfiguration']):
  236. read_receipts: Optional[ConfigItem] = attr.ib(factory=lambda: ConfigItem(),
  237. metadata={"json": "readReceipts"})
  238. typing_indicators: Optional[ConfigItem] = attr.ib(factory=lambda: ConfigItem(),
  239. metadata={"json": "typingIndicators"})
  240. link_previews: Optional[ConfigItem] = attr.ib(factory=lambda: ConfigItem(),
  241. metadata={"json": "linkPreviews"})
  242. unidentified_delivery_indicators: Optional[ConfigItem] = attr.ib(
  243. factory=lambda: ConfigItem(), metadata={"json": "unidentifiedDeliveryIndicators"})
  244. class StickerPackOperation(ExtensibleEnum):
  245. INSTALL = "INSTALL"
  246. # there are very likely others
  247. @dataclass
  248. class StickerPackOperations(SerializableAttrs['StickerPackOperations']):
  249. type: StickerPackOperation
  250. pack_id: str = attr.ib(metadata={"json": "packID"})
  251. pack_key: str = attr.ib(metadata={"json": "packKey"})
  252. @dataclass
  253. class SyncMessage(SerializableAttrs['SyncMessage']):
  254. sent: Optional[SentSyncMessage] = None
  255. typing: Optional[TypingNotification] = None
  256. read_messages: Optional[List[OwnReadReceipt]] = attr.ib(default=None,
  257. metadata={"json": "readMessages"})
  258. contacts: Optional[ContactSyncMeta] = None
  259. groups: Optional[ContactSyncMeta] = None
  260. configuration: Optional[ClientConfiguration] = None
  261. # blocked_list: Optional[???] = attr.ib(default=None, metadata={"json": "blockedList"})
  262. sticker_pack_operations: Optional[List[StickerPackOperations]] = attr.ib(
  263. default=None, metadata={"json": "stickerPackOperations"})
  264. contacts_complete: bool = attr.ib(default=False, metadata={"json": "contactsComplete"})
  265. class MessageType(SerializableEnum):
  266. CIPHERTEXT = "CIPHERTEXT"
  267. UNIDENTIFIED_SENDER = "UNIDENTIFIED_SENDER"
  268. RECEIPT = "RECEIPT"
  269. PREKEY_BUNDLE = "PREKEY_BUNDLE"
  270. KEY_EXCHANGE = "KEY_EXCHANGE"
  271. UNKNOWN = "UNKNOWN"
  272. @dataclass(kw_only=True)
  273. class Message(SerializableAttrs['Message']):
  274. username: str
  275. source: Address
  276. timestamp: int
  277. timestamp_iso: str = attr.ib(metadata={"json": "timestampISO"})
  278. type: MessageType
  279. source_device: Optional[int] = attr.ib(metadata={"json": "sourceDevice"}, default=None)
  280. server_timestamp: Optional[int] = attr.ib(metadata={"json": "serverTimestamp"}, default=None)
  281. server_delivered_timestamp: int = attr.ib(metadata={"json": "serverDeliveredTimestamp"})
  282. has_content: bool = attr.ib(metadata={"json": "hasContent"}, default=False)
  283. is_unidentified_sender: Optional[bool] = attr.ib(metadata={"json": "isUnidentifiedSender"},
  284. default=None)
  285. has_legacy_message: bool = attr.ib(default=False, metadata={"json": "hasLegacyMessage"})
  286. data_message: Optional[MessageData] = attr.ib(default=None, metadata={"json": "dataMessage"})
  287. sync_message: Optional[SyncMessage] = attr.ib(default=None, metadata={"json": "syncMessage"})
  288. typing: Optional[TypingNotification] = None
  289. receipt: Optional[Receipt] = None
  290. class ListenAction(SerializableEnum):
  291. STARTED = "started"
  292. STOPPED = "stopped"
  293. @dataclass
  294. class ListenEvent(SerializableAttrs['ListenEvent']):
  295. action: ListenAction
  296. username: str
  297. exception: Optional[str] = None