types.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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
  7. from uuid import UUID
  8. from attr import dataclass
  9. import attr
  10. from mautrix.types import SerializableAttrs, SerializableEnum
  11. @dataclass
  12. class Account(SerializableAttrs['Account']):
  13. device_id: int = attr.ib(metadata={"json": "deviceId"})
  14. username: str
  15. filename: str
  16. registered: bool
  17. has_keys: bool
  18. subscribed: bool
  19. uuid: Optional[UUID] = None
  20. @dataclass
  21. class Address(SerializableAttrs['Address']):
  22. number: Optional[str] = None
  23. uuid: Optional[UUID] = None
  24. @property
  25. def is_valid(self) -> bool:
  26. return bool(self.number) or bool(self.uuid)
  27. @dataclass
  28. class Contact(SerializableAttrs['Contact']):
  29. address: Address
  30. name: Optional[str] = None
  31. color: Optional[str] = None
  32. profile_key: Optional[str] = attr.ib(default=None, metadata={"json": "profileKey"})
  33. message_expiration_time: int = attr.ib(default=0, metadata={"json": "messageExpirationTime"})
  34. @dataclass
  35. class Profile(SerializableAttrs['Profile']):
  36. name: str
  37. avatar: str = ""
  38. identity_key: str = ""
  39. unidentified_access: str = ""
  40. unrestricted_unidentified_access: bool = False
  41. @dataclass
  42. class Group(SerializableAttrs['Group']):
  43. group_id: str = attr.ib(metadata={"json": "groupId"})
  44. name: str
  45. # Sometimes "UPDATE"
  46. type: Optional[str] = None
  47. # Not always present
  48. members: List[Address] = attr.ib(factory=lambda: [])
  49. avatar_id: int = attr.ib(default=0, metadata={"json": "avatarId"})
  50. @dataclass
  51. class Attachment(SerializableAttrs['Attachment']):
  52. width: int = 0
  53. height: int = 0
  54. caption: Optional[str] = None
  55. preview: Optional[str] = None
  56. blurhash: Optional[str] = None
  57. voice_note: bool = attr.ib(default=False, metadata={"json": "voiceNote"})
  58. content_type: Optional[str] = attr.ib(default=None, metadata={"json": "contentType"})
  59. custom_filename: Optional[str] = attr.ib(default=None, metadata={"json": "customFilename"})
  60. # Only for incoming
  61. id: Optional[str] = None
  62. incoming_filename: Optional[str] = attr.ib(default=None, metadata={"json": "storedFilename"})
  63. digest: Optional[str] = None
  64. # Only for outgoing
  65. outgoing_filename: Optional[str] = attr.ib(default=None, metadata={"json": "filename"})
  66. @dataclass
  67. class Quote(SerializableAttrs['Quote']):
  68. id: int
  69. author: Address
  70. text: str
  71. # TODO: attachments, mentions
  72. @dataclass
  73. class Reaction(SerializableAttrs['Reaction']):
  74. emoji: str
  75. remove: bool
  76. target_author: Address = attr.ib(metadata={"json": "targetAuthor"})
  77. target_sent_timestamp: int = attr.ib(metadata={"json": "targetSentTimestamp"})
  78. @dataclass
  79. class Sticker(SerializableAttrs['Sticker']):
  80. attachment: Attachment
  81. pack_id: str = attr.ib(metadata={"json": "packID"})
  82. pack_key: str = attr.ib(metadata={"json": "packKey"})
  83. sticker_id: int = attr.ib(metadata={"json": "stickerID"})
  84. @dataclass
  85. class MessageData(SerializableAttrs['MessageData']):
  86. timestamp: int
  87. body: Optional[str] = None
  88. quote: Optional[Quote] = None
  89. reaction: Optional[Reaction] = None
  90. attachments: List[Attachment] = attr.ib(factory=lambda: [])
  91. sticker: Optional[Sticker] = None
  92. # TODO mentions (although signald doesn't support group v2 yet)
  93. group: Optional[Group] = None
  94. end_session: bool = attr.ib(default=False, metadata={"json": "endSession"})
  95. expires_in_seconds: int = attr.ib(default=0, metadata={"json": "expiresInSeconds"})
  96. profile_key_update: bool = attr.ib(default=False, metadata={"json": "profileKeyUpdate"})
  97. view_once: bool = attr.ib(default=False, metadata={"json": "viewOnce"})
  98. @property
  99. def all_attachments(self) -> List[Attachment]:
  100. return self.attachments + ([self.sticker] if self.sticker else [])
  101. @dataclass
  102. class SentSyncMessage(SerializableAttrs['SentSyncMessage']):
  103. message: MessageData
  104. timestamp: int
  105. expiration_start_timestamp: int = attr.ib(metadata={"json": "expirationStartTimestamp"})
  106. is_recipient_update: bool = attr.ib(default=False, metadata={"json": "isRecipientUpdate"})
  107. unidentified_status: Dict[str, bool] = attr.ib(factory=lambda: {})
  108. destination: Optional[Address] = None
  109. class TypingAction(SerializableEnum):
  110. UNKNOWN = "UNKNOWN"
  111. STARTED = "STARTED"
  112. STOPPED = "STOPPED"
  113. @dataclass
  114. class TypingNotification(SerializableAttrs['TypingNotification']):
  115. action: TypingAction
  116. timestamp: int
  117. group_id: Optional[str] = attr.ib(default=None, metadata={"json": "groupId"})
  118. @dataclass
  119. class OwnReadReceipt(SerializableAttrs['OwnReadReceipt']):
  120. sender: Address
  121. timestamp: int
  122. class ReceiptType(SerializableEnum):
  123. UNKNOWN = "UNKNOWN"
  124. DELIVERY = "DELIVERY"
  125. READ = "READ"
  126. @dataclass
  127. class Receipt(SerializableAttrs['Receipt']):
  128. type: ReceiptType
  129. timestamps: List[int]
  130. when: int
  131. @dataclass
  132. class SyncMessage(SerializableAttrs['SyncMessage']):
  133. sent: Optional[SentSyncMessage] = None
  134. typing: Optional[TypingNotification] = None
  135. read_messages: Optional[List[OwnReadReceipt]] = attr.ib(default=None,
  136. metadata={"json": "readMessages"})
  137. contacts: Optional[Dict[str, Any]] = None
  138. contacts_complete: bool = attr.ib(default=False, metadata={"json": "contactsComplete"})
  139. class MessageType(SerializableEnum):
  140. CIPHERTEXT = "CIPHERTEXT"
  141. UNIDENTIFIED_SENDER = "UNIDENTIFIED_SENDER"
  142. RECEIPT = "RECEIPT"
  143. PREKEY_BUNDLE = "PREKEY_BUNDLE"
  144. KEY_EXCHANGE = "KEY_EXCHANGE"
  145. UNKNOWN = "UNKNOWN"
  146. @dataclass
  147. class Message(SerializableAttrs['Message']):
  148. username: str
  149. source: Address
  150. timestamp: int
  151. timestamp_iso: str = attr.ib(metadata={"json": "timestampISO"})
  152. type: MessageType
  153. source_device: int = attr.ib(metadata={"json": "sourceDevice"})
  154. server_timestamp: int = attr.ib(metadata={"json": "serverTimestamp"})
  155. server_delivered_timestamp: int = attr.ib(metadata={"json": "serverDeliveredTimestamp"})
  156. has_content: bool = attr.ib(metadata={"json": "hasContent"})
  157. is_unidentified_sender: bool = attr.ib(metadata={"json": "isUnidentifiedSender"})
  158. has_legacy_message: bool = attr.ib(default=False, metadata={"json": "hasLegacyMessage"})
  159. data_message: Optional[MessageData] = attr.ib(default=None, metadata={"json": "dataMessage"})
  160. sync_message: Optional[SyncMessage] = attr.ib(default=None, metadata={"json": "syncMessage"})
  161. typing: Optional[TypingNotification] = None
  162. receipt: Optional[Receipt] = None