class MessageSyncEvent(ThriftObject): # index 1: unknown struct (no fields known) message: Message = field(index=2, default=None) own_read_receipt: OwnReadReceipt = field(index=4, default=None) add_member: AddMember = field(index=8, default=None) remove_member: RemoveMember = field(index=9, default=None) name_change: NameChange = field(index=10, default=None) avatar_change: AvatarChange = field(index=11, default=None) thread_change: ThreadChange = field(index=17, default=None) read_receipt: ReadReceipt = field(index=19, default=None) unknown_receipt_1: UnknownReceipt1 = field(index=25, default=None) binary: BinaryData = field(index=42, default=None) def get_parts(self) -> List[Any]: parts = [ self.message, self.own_read_receipt, self.add_member, self.remove_member, self.name_change, self.avatar_change, self.thread_change, self.read_receipt, self.unknown_receipt_1 ] if self.binary: for inner_item in self.binary.parse().items: parts += [ inner_item.reaction, inner_item.extended_message, inner_item.unsend_message, inner_item.extended_add_member ] return [part for part in parts if part is not None]
class Reaction(ThriftObject): thread: ThreadKey message_id: str # index 3: unknown int32 (zero) reaction_sender_id: int = field(TType.I64, index=4) reaction: str = field(default=None) message_sender_id: int = field(TType.I64)
class ThreadChange(ThriftObject): metadata: MessageMetadata action: ThreadChangeAction = field(TType.BINARY) action_data: Dict[str, str] = field(TType.MAP, key_type=RecursiveType(TType.BINARY, python_type=str), value_type=RecursiveType(TType.BINARY, python_type=str))
class ForegroundStateConfig(ThriftObject): in_foreground_app: bool in_foreground_device: bool keep_alive_timeout: int = field(TType.I32) subscribe_topics: List[str] subscribe_generic_topics: List[str] unsubscribe_topics: List[str] unsubscribe_generic_topics: List[str] request_id: int = field(TType.I64)
class MarkReadRequest(ThriftObject): receipt_type: str = "read" unknown_boolean: bool = True # indices 3-5: ??? group_id: int = field(TType.I64, index=6, default=None) user_id: int = field(TType.I64, default=None) # index 8: ??? read_to: int = field(TType.I64, index=9) offline_threading_id: int = field(TType.I64, index=13)
class Attachment(ThriftObject): media_id_str: str mime_type: str = field(default=None) file_name: str = field(default=None) media_id: int = field(TType.I64, default=None) file_size: int = field(TType.I64, default=None) # index 6: ??? extensible_media: str = field(default=None, index=7) # indices 8 and 9: ??? image_info: ImageInfo = field(default=None, index=10) video_info: VideoInfo = field(default=None) audio_info: AudioInfo = field(default=None) # can contain a dash_manifest key with some XML as the value # or fbtype key with a number as value extra_metadata: Dict[str, str] = field(factory=lambda: {}) # index 1007?!: unknown bool def parse_extensible(self) -> ExtensibleAttachment: if not self.extensible_media: raise ValueError( "This attachment does not contain an extensible attachment") data = json.loads(self.extensible_media) raw_media_key = f"extensible_message_attachment:{self.media_id_str}" expected_key = base64.b64encode( raw_media_key.encode("utf-8")).decode("utf-8").rstrip("=") try: media_data = data[expected_key] except KeyError: media_data = list(data.values())[0] return ExtensibleAttachment.deserialize(media_data)
class OpenedThreadRequest(ThriftObject): unknown_i64: int = field(TType.I64, default=0) _chat_id: bytes = field(default=None) @property def chat_id(self) -> int: return int(ChatIDWrapper.from_thrift(self._chat_id).chat_id) @chat_id.setter def chat_id(self, value: int) -> None: self._chat_id = ChatIDWrapper(str(value)).to_thrift()
class ThreadKey(ThriftObject): other_user_id: int = field(TType.I64, default=None) thread_fbid: int = field(TType.I64, default=None) @property def id(self) -> Optional[int]: if self.other_user_id: return self.other_user_id elif self.thread_fbid: return self.thread_fbid else: return None
class RegionHintPayload(ThriftObject): unknown_int64: int = field(TType.I64) region_hint_data: bytes @property def region_hint(self) -> RegionHint: return RegionHint.from_thrift(self.region_hint_data)
class RealtimeConfig(ThriftObject): client_identifier: str will_topic: str = None will_message: str = None client_info: RealtimeClientInfo password: str get_diffs_request: List[str] = None zero_rating_token_hash: str = None # mysterious_struct_list: List[Any] = field(TType.LIST, TType.STRUCT, factory=lambda: []) app_specific_info: Dict[str, str] = field(index=9)
class MessageSyncPayload(ThriftObject): items: List[MessageSyncEvent] = field(factory=lambda: []) first_seq_id: int = field(TType.I64, default=None) last_seq_id: int = field(TType.I64, default=None) viewer: int = field(TType.I64, default=None) # indices 5-10: ??? subscribe_ok: str = field(index=11, default=None) error: MessageSyncError = field(TType.BINARY, default=None)
class Message(ThriftObject): metadata: MessageMetadata text: str = field(default=None) # index 3: ??? sticker: int = field(TType.I64, index=4, default=None) attachments: List[Attachment] = field(factory=lambda: []) # index 6: some sort of struct: # 1: List[BinaryThreadKey]? # 2: ??? # 3: timestamp? # 4: timestamp? extra_metadata: Dict[str, bytes] = field(index=7, factory=lambda: {}) # index 1000?!: int64 (ex: 81) # index 1017: int64 (ex: 924) # index 1003: struct # index 1: struct # index 1: binary, replying to message id # index 1012: map<binary, binary> # key apiArgs: binary containing thrift # index 2: binary url, https://www.facebook.com/intern/agent/realtime_delivery/ # index 4: int64 (ex: 0) # index 7: binary, empty? # index 5: binary, some sort of uuid # index 8: list<map> # item 1: map<binary, binary> # {"layer": "www", "push_phase": "C3", "www_rev": "1003179603", # "buenopath": "XRealtimeDeliveryThriftServerController:sendRealtimeDeliveryRequest:/ls_req:TASK_LABEL=SEND_MESSAGE_V{N}"} # index 9: binary (ex: www) # index 10: boolean (ex: false) # index 1015: list<binary>, some sort of tags @property def mentions(self) -> List[Mention]: return [ Mention.deserialize(item) for item in json.loads(self.extra_metadata.get("prng", "[]")) ]
class Attachment(ThriftObject): media_id_str: str mime_type: str = field(default=None) file_name: str = field(default=None) media_id: int = field(TType.I64, default=None) file_size: int = field(TType.I64, default=None) # index 6: ??? extensible_media: str = field(default=None, index=7) # indices 8 and 9: ??? image_info: ImageInfo = field(default=None, index=10) video_info: VideoInfo = field(default=None) audio_info: AudioInfo = field(default=None) # can contain a dash_manifest key with some XML as the value # or fbtype key with a number as value extra_metadata: Dict[str, str] = field(factory=lambda: {}) # index 1007?!: unknown bool def parse_extensible(self) -> ExtensibleAttachment: if not self.extensible_media: raise ValueError("This attachment does not contain an extensible attachment") return ExtensibleAttachment.parse_json(self.extensible_media)
class ImageInfo(ThriftObject): original_width: int = field(TType.I32) original_height: int = field(TType.I32) previews: Dict[int, str] = field(TType.MAP, key_type=TType.I32, default=None, value_type=RecursiveType(TType.BINARY, python_type=str)) # index 4: unknown int32 # indices 5 and 6: ??? alt_previews: Dict[int, str] = field(TType.MAP, key_type=TType.I32, default=None, index=7, value_type=RecursiveType(TType.BINARY, python_type=str)) image_type: str = field(default=None) alt_preview_type: str = field(default=None)
class MessageMetadata(ThriftObject): thread: ThreadKey id: str offline_threading_id: int = field(TType.I64, default=None) sender: int = field(TType.I64) timestamp: int = field(TType.I64) # index 6: unknown bool (ex: true) action_summary: str = field(index=7, default=None) tags: List[str] = field(factory=lambda: []) # index 9: unknown int32 (ex: 3) # index 10: unknown bool (ex: false) # index 11: ??? message_unsendability: Unsendability = field( TType.BINARY, index=12, default=Unsendability.DENY_FOR_NON_SENDER)
class UnknownReceipt1(ThriftObject): thread: ThreadKey user_id: int = field(TType.I64) # indices 3-5: ??? message_id_list: List[str] = field(index=6) timestamp: int = field(TType.I64)
class RealtimeClientInfo(ThriftObject): user_id: int = field(TType.I64) user_agent: str client_capabilities: int = field(TType.I64) endpoint_capabilities: int = field(TType.I64) # 0 = no zlib?, 1 = always zlib, 2 = optional zlib publish_format: int = field(TType.I32) no_automatic_foreground: bool make_user_available_in_foreground: bool device_id: str is_initially_foreground: bool network_type: int = field(TType.I32) network_subtype: int = field(TType.I32) client_mqtt_session_id: int = field(TType.I64) client_ip_address: str = None subscribe_topics: List[int] = field(TType.LIST, item_type=TType.I32) client_type: str app_id: int = field(TType.I64) override_nectar_logging: bool = None connect_token_hash: str = None region_preference: str device_secret: str client_stack: int = field(TType.BYTE) fbns_connection_key: int = field(TType.I64, default=None) fbns_connection_secret: str = None fbns_device_id: str = None fbns_device_secret: str = None another_unknown: int = field(TType.I64, default=None) yet_another_unknown: int = field(TType.I32, default=None)
class SendMessageRequest(ThriftObject): # tfbid_<groupid> for groups, plain user id for users chat_id: str message: str offline_threading_id: int = field(TType.I64) # index 4: ??? # Example values: # 'is_in_chatheads': 'false' # 'ld': '{"u":1674434.........}' # 'entrypoint': 'messenger_inbox:in_thread' # 'trigger': '2:thread_list:thread' or 'thread_view_messages_fragment_unknown' # 'active_now': '{"is_online":"false","last_active_seconds":"1431"}' # # 'media_camera_mode': 'VIDEO' # 'trigger': 'thread_view_messages_fragment_unknown' # 'entry_point': 'THREAD_CAMERA_COMPOSER_BUTTON' flags: Dict[str, str] = field(index=5, factory=lambda: {}) # 369239263222822 = "like" sticker: str = field(default=None) # indices 7 and 8: ??? media_ids: List[str] = field(default=None) # indices 10 and 11: ??? sender_id: int = field(TType.I64, index=12) # indices 13-17: ??? unknown_int32: int = field(TType.I32, index=18, default=0) # index 19: ??? extra_metadata: Dict[str, str] = field(index=20, default=None) unknown_int64: int = field(TType.I64, index=21, default=0) # index 22: ??? unknown_bool: bool = field(TType.BOOL, index=23, default=False) # this is weird int64 that looks like offline_threading_id, but isn't quite the same tid2: int = field(TType.I64, index=24) # indices 25-27: ??? reply_to: str = field(index=28)
class VideoInfo(ThriftObject): original_width: int = field(TType.I32) original_height: int = field(TType.I32) duration_ms: int = field(TType.I32) thumbnail_url: str download_url: str
class ExtendedAddMemberParticipant(ThriftObject): addee_user_id: int = field(TType.I64) adder_user_id: int = field(TType.I64) # index 3: unknown int32 (ex: 0) timestamp: int = field(TType.I64, index=4)
class SendMessageResponse(ThriftObject): offline_threading_id: int = field(TType.I64) success: bool # index 3: unknown i32 present for errors error_message: str = field(default=None, index=4)
class MessageSyncInnerEvent(ThriftObject): reaction: Reaction = field(index=10, default=None) extended_add_member: ExtendedAddMember = field(index=42, default=None) extended_message: ExtendedMessage = field(index=55, default=None) unsend_message: UnsendMessage = field(index=67, default=None)
class ReadReceipt(ThriftObject): thread: ThreadKey user_id: int = field(TType.I64) read_at: int = field(TType.I64) read_to: int = field(TType.I64)
class UnsendMessage(ThriftObject): thread: ThreadKey message_id: str timestamp: int = field(TType.I64) user_id: int = field(TType.I64)
class RemoveMember(ThriftObject): metadata: MessageMetadata user_id: int = field(TType.I64)
class AddMemberParticipant(ThriftObject): id: int = field(TType.I64) first_name: str name: str
class AudioInfo(ThriftObject): # index 1: mysterious boolean (true) # index 2: mysterious binary (empty) url: str = field(index=3) duration_ms: int = field(TType.I32)
class OwnReadReceipt(ThriftObject): threads: List[ThreadKey] # index 2: ??? read_to: int = field(TType.I64, index=3) read_at: int = field(TType.I64)