import Api from '../api' ; import { AnyIncomingMessage , DeliveryReceipt , IncomingEvent , IncomingEventData , IncomingEventType , IncomingMessage , IncomingMessageReaction , IncomingMessageReply , IncomingMessageType , IncomingMessageUnsend , ReadReceipt } from '../types/incomingMessages' ; import { UserID } from '../types/users' ; import { AnyAttachment , AttachmentAnimatedImage , AttachmentAudio , AttachmentFile , AttachmentImage , AttachmentSticker , AttachmentType , AttachmentVideo } from '../types/attachments' ; export function parseDelta ( delta , api ) { if ( delta . class === 'NewMessage' ) { let formattedMessage ; try { formattedMessage = formatDeltaMessage ( delta ) ; } catch ( error ) { throw new Error ( `There was an unknown WS error. Contact the dev team about this (error code 935468). Original error: ${ error }. Delta: ${ JSON . stringify ( delta ) }` ) ; } if ( ! formattedMessage ) throw new Error ( 'Error code 935468-b' ) ; if ( api . ctx . globalOptions . autoMarkDelivery ) { } if ( ! api . ctx . globalOptions . selfListen && formattedMessage . senderId == api . ctx . userID ) return [ ] ; return [ formattedMessage ] ; } if ( delta . class == 'ClientPayload' ) { let clientPayload ; try { clientPayload = JSON . parse ( Buffer . from ( delta . payload ) . toString ( ) ) ; } catch ( error ) { throw new Error ( `There was an error parsing WS. Contact the dev team about this (error code 935469). Original error: ${ error }. Delta: ${ JSON . stringify ( delta ) }` ) ; } if ( ! ( clientPayload && clientPayload . deltas ) ) throw new Error ( 'Error code 935469-b' ) ; const toBeReturned = [ ] ; for ( const payloadDelta of clientPayload . deltas ) { if ( payloadDelta . deltaMessageReaction && api . ctx . globalOptions . listenEvents ) { const messageReaction = { type : IncomingMessageType . MessageReaction , threadId : parseInt ( payloadDelta . deltaMessageReaction . threadKey . threadFbId || payloadDelta . deltaMessageReaction . threadKey . otherUserFbId ) , messageId : payloadDelta . deltaMessageReaction . messageId , reaction : payloadDelta . deltaMessageReaction . reaction , messageSenderId : parseInt ( payloadDelta . deltaMessageReaction . senderId ) , reactionSenderId : parseInt ( payloadDelta . deltaMessageReaction . userId ) } ; toBeReturned . push ( messageReaction ) ; } else if ( payloadDelta . deltaRecallMessageData && api . ctx . globalOptions . listenEvents ) { const messageUnsend = { type : IncomingMessageType . MessageUnsend , threadId : parseInt ( payloadDelta . deltaRecallMessageData . threadKey . threadFbId || payloadDelta . deltaRecallMessageData . threadKey . otherUserFbId ) , messageId : payloadDelta . deltaRecallMessageData . messageID , messageSenderId : parseInt ( payloadDelta . deltaRecallMessageData . senderID ) , deletionTimestamp : parseInt ( payloadDelta . deltaRecallMessageData . deletionTimestamp ) } ; toBeReturned . push ( messageUnsend ) ; } else if ( payloadDelta . deltaMessageReply ) { let replyMessage ; try { replyMessage = formatDeltaReplyMessage ( payloadDelta . deltaMessageReply . repliedToMessage , payloadDelta . deltaMessageReply . message ) ; } catch ( error ) { throw new Error ( `There was an unknown WS error. Contact the dev team about this (error code 935470). Original error: ${ error }. Delta: ${ JSON . stringify ( delta ) }` ) ; } if ( ! replyMessage ) throw new Error ( 'Error code 935470-b' ) ; if ( api . ctx . globalOptions . autoMarkDelivery ) { } toBeReturned . push ( replyMessage ) ; } } return toBeReturned ; } if ( delta . class !== 'NewMessage' && ! api . ctx . globalOptions . listenEvents ) return [ ] ; switch ( delta . class ) { case 'DeliveryReceipt' : { let formattedDelivery ; try { formattedDelivery = formatDeltaDeliveryReceipt ( delta ) ; } catch ( error ) { throw new Error ( `There was an unknown WS error. Contact the dev team about this (error code 935471). Original error: ${ error }. Delta: ${ JSON . stringify ( delta ) }` ) ; } if ( ! formattedDelivery ) throw new Error ( 'Error code 935471-b' ) ; return [ formattedDelivery ] ; } case 'ReadReceipt' : { let formattedMessage ; try { formattedMessage = formatDeltaReadReceipt ( delta ) ; } catch ( error ) { throw new Error ( `There was an unknown WS error. Contact the dev team about this (error code 935472). Original error: ${ error }. Delta: ${ JSON . stringify ( delta ) }` ) ; } if ( ! formattedMessage ) throw new Error ( 'Error code 935472-b' ) ; return [ formattedMessage ] ; } case 'AdminTextMessage' : case 'ThreadName' : case 'ParticipantsAddedToGroupThread' : case 'ParticipantLeftGroupThread' : { let formattedAdminText ; try { formattedAdminText = formatDeltaEvent ( delta ) ; } catch ( error ) { throw new Error ( `There was an unknown WS error. Contact the dev team about this (error code 935473). Original error: ${ error }. Delta: ${ JSON . stringify ( delta ) }` ) ; } if ( ! formattedAdminText ) throw new Error ( 'Error code 935473-b' ) ; return [ formattedAdminText ] ; } default : break ; } return [ ] ; } function formatDeltaMessage ( delta ) { const messageMetadata = delta . messageMetadata ; const mentions = [ ] ; if ( delta . data ?. prng ) { let mentionRawData ; try { mentionRawData = JSON . parse ( delta . data . prng ) ; } finally { if ( mentionRawData instanceof Array ) for ( const onePerson of mentionRawData ) mentions . push ( { name : delta . body ?. substring ( onePerson . o , onePerson . o + onePerson . l ) , id : parseInt ( onePerson . i ) } ) ; } } const formatted = { type : IncomingMessageType . MessageRegular , senderId : parseInt ( messageMetadata . actorFbId ) , body : delta . body || '' , threadId : parseInt ( messageMetadata . threadKey . threadFbId || messageMetadata . threadKey . otherUserFbId ) , messageId : messageMetadata . messageId , attachments : formatAttachments ( delta . attachments ) , mentions , timestamp : parseInt ( messageMetadata . timestamp ) , isGroup : ! ! messageMetadata . threadKey . threadFbId } ; return formatted ; } function formatDeltaReplyMessage ( deltaSourceMessage , deltaReplyMessage ) { const formattedReplyMessage = { ... formatDeltaMessage ( deltaReplyMessage ) , sourceMessage : formatDeltaMessage ( deltaSourceMessage ) } ; formattedReplyMessage . type = IncomingMessageType . MessageReply ; return formattedReplyMessage as IncomingMessageReply ; } export function getAdminTextMessageType ( type ) { switch ( type ) { case 'change_thread_theme' : return IncomingEventType . ChangeThreadColorTheme ; case 'change_thread_nickname' : return IncomingEventType . ChangeNickname ; case 'change_thread_icon' : return IncomingEventType . ChangeThreadImage ; default : return undefined ; } } function formatDeltaEvent ( delta ) { let eventType ; let additionalData = { } ; switch ( delta . class ) { case 'AdminTextMessage' : if ( ! ( delta . type && delta . untypedData ) ) break ; switch ( delta . type ) { case 'change_thread_theme' : eventType = IncomingEventType . ChangeThreadColorTheme ; additionalData = { newThreadColorTheme : { should_show_icon : delta . untypedData . should_show_icon == '1' , theme_color : delta . untypedData . theme_color , accessibility_label : delta . untypedData . accessibility_label , theme_name_with_subtitle : delta . untypedData . theme_name_with_subtitle , gradient : delta . untypedData . gradient ? JSON . parse ( delta . untypedData . gradient ) : undefined } } ; break ; case 'change_thread_nickname' : eventType = IncomingEventType . ChangeNickname ; break ; case 'change_thread_icon' : eventType = IncomingEventType . ChangeThreadEmoji ; additionalData = { newThreadEmoji : delta . untypedData } ; break ; case 'change_thread_admins' : eventType = IncomingEventType . ChangeAdminStatus ; additionalData = { changeAdminInfo : { targetId : parseInt ( delta . untypedData . TARGET_ID ) , isAdminFromNow : delta . untypedData . ADMIN_EVENT === 'add_admin' } } ; break ; default : additionalData = delta . untypedData ; eventType = getAdminTextMessageType ( delta . type ) ; break ; } break ; case 'ThreadName' : eventType = IncomingEventType . ChangeThreadName ; additionalData = { newThreadName : delta . name } ; break ; case 'ParticipantsAddedToGroupThread' : eventType = IncomingEventType . AddedParticipants ; additionalData = { addedParticipants : delta . addedParticipants . map ( ( p ) => ( { firstName : p . firstName , fullName : p . fullName , userId : parseInt ( p . userFbId ) } ) ) } ; break ; case 'ParticipantLeftGroupThread' : eventType = IncomingEventType . RemovedParticipant ; additionalData = { leftParticipantFbId : parseInt ( delta . leftParticipantFbId ) } ; break ; default : break ; } return { type : IncomingMessageType . ThreadEvent , threadId : parseInt ( delta . messageMetadata . threadKey . threadFbId || delta . messageMetadata . threadKey . otherUserFbId ) , senderId : parseInt ( delta . messageMetadata . actorFbId ) , body : delta . messageMetadata . adminText , timestamp : parseInt ( delta . messageMetadata . timestamp ) , eventType , data : additionalData } ; } function formatDeltaDeliveryReceipt ( delta ) { return { type : IncomingMessageType . DeliveryReceipt , timestamp : parseInt ( delta . deliveredWatermarkTimestampMs ) , threadId : parseInt ( delta . threadKey . otherUserFbId || delta . threadKey . threadFbId ) , recipient : parseInt ( delta . actorFbId || delta . threadKey . otherUserFbId ) , deliveredMessageIds : delta . messageIds } ; } function formatDeltaReadReceipt ( delta ) { return { type : IncomingMessageType . ReadReceipt , reader : parseInt ( delta . actorFbId || delta . threadKey . otherUserFbId ) , timestamp : parseInt ( delta . actionTimestampMs ) , threadId : parseInt ( delta . threadKey . otherUserFbId || delta . threadKey . threadFbId ) } ; } function formatAttachments ( rawData ) { if ( ! rawData || ! rawData . length || rawData . length === 0 ) return [ ] ; return ( rawData as any [ ] ) . map ( att => { if ( att . mercuryJSON && ! att . mercury ) att . mercury = JSON . parse ( att . mercuryJSON ) ; let attDetails = att . mercury ?. blob_attachment ; if ( attDetails ) { const semiformattedAttachment = { attachmentId : parseInt ( att . fbid ?? att . id ?? att . hash ) , fileSize : parseInt ( att . fileSize ?? att . filesize ) , fileName : att . filename || att . fileName || '' , mimeType : att . mimeType || '' } ; switch ( attDetails . __typename ) { case 'MessageImage' : return { type : AttachmentType . Image , ... semiformattedAttachment , previewSmall : attDetails . preview , previewLarge : attDetails . large_preview , thumbnailUrl : attDetails . thumbnail ?. uri || '' , originalDimensions : attDetails . original_dimensions , originalExtension : attDetails . original_extension } as AttachmentImage ; case 'MessageVideo' : return { type : AttachmentType . Video , ... semiformattedAttachment , chatImage : attDetails . chat_image , largeImage : attDetails . large_image , inboxImage : attDetails . inbox_image , videoType : attDetails . video_type , playableUrl : attDetails . playable_url , originalDimensions : attDetails . original_dimensions , duration : parseInt ( attDetails . playable_duration_in_ms ) } as AttachmentVideo ; case 'MessageAudio' : return { type : AttachmentType . Audio , ... semiformattedAttachment , playableUrl : attDetails . playable_url , duration : parseInt ( attDetails . playable_duration_in_ms ) , isVoiceMail : attDetails . is_voicemail , audioType : attDetails . audio_type } as AttachmentAudio ; case 'MessageFile' : return { type : AttachmentType . File , ... semiformattedAttachment , url : attDetails . url , isMalicious : attDetails . is_malicious } as AttachmentFile ; case 'MessageAnimatedImage' : return { type : AttachmentType . Gif , ... semiformattedAttachment , attributionApp : attDetails . attribution_app , animatedImage : attDetails . animated_image , previewImage : attDetails . preview_image , originalDimensions : attDetails . original_dimensions } as AttachmentAnimatedImage ; default : break ; } } attDetails = att . mercury ?. sticker_attachment ; if ( attDetails ) { return { type : AttachmentType . Sticker , info : attDetails } as AttachmentSticker ; } } ) . filter ( parsed => Boolean ( parsed ) ) as AnyAttachment [ ] ; }