From 173d1ddf8bb8478bf78b832344933fc360614067 Mon Sep 17 00:00:00 2001 From: Adhiraj Singh Date: Wed, 29 Sep 2021 12:31:22 +0530 Subject: [PATCH] implement chat modifications --- src/Socket/chats.ts | 48 +++++++++++++++++++++----- src/Types/Chat.ts | 6 ++-- src/Types/index.ts | 4 +-- src/Utils/chat-utils.ts | 76 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 16 deletions(-) diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index fd10bfc..fa4a320 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -1,5 +1,5 @@ -import { encodeSyncdPatch, decodePatches, extractSyncdPatches } from "../Utils/chat-utils"; -import { SocketConfig, WAPresence, PresenceData, Chat, WAPatchCreate, WAMediaUpload, ChatMutation, WAPatchName, LTHashState } from "../Types"; +import { encodeSyncdPatch, decodePatches, extractSyncdPatches, chatModificationToAppPatch } from "../Utils/chat-utils"; +import { SocketConfig, WAPresence, PresenceData, Chat, WAPatchCreate, WAMediaUpload, ChatMutation, WAPatchName, LTHashState, ChatModification, Contact } from "../Types"; import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, S_WHATSAPP_NET } from "../WABinary"; import { proto } from '../../WAProto' import { generateProfilePicture, toNumber } from "../Utils"; @@ -298,8 +298,12 @@ export const makeChatsSocket = (config: SocketConfig) => { } const processSyncActions = (actions: ChatMutation[]) => { - const updates: Partial[] = [] - for(const { action, index: [_, id] } of actions) { + + const updates: { [jid: string]: Partial } = {} + const contactUpdates: { [jid: string]: Partial } = {} + const msgDeletes: proto.IMessageKey[] = [] + + for(const { action, index: [_, id, msgId, fromMe] } of actions) { const update: Partial = { id } if(action?.muteAction) { update.mute = action.muteAction?.muted ? @@ -310,9 +314,17 @@ export const makeChatsSocket = (config: SocketConfig) => { } else if(action?.markChatAsReadAction) { update.unreadCount = !!action.markChatAsReadAction?.read ? 0 : -1 } else if(action?.clearChatAction) { - console.log(action.clearChatAction) + msgDeletes.push({ + remoteJid: id, + id: msgId, + fromMe: fromMe === '1' + }) } else if(action?.contactAction) { - ev.emit('contacts.update', [{ id, name: action.contactAction!.fullName }]) + contactUpdates[id] = { + ...(contactUpdates[id] || {}), + id, + name: action.contactAction!.fullName + } } else if(action?.pushNameSetting) { authState.creds.me!.name = action?.pushNameSetting?.name! ev.emit('auth-state.update', authState) @@ -320,10 +332,22 @@ export const makeChatsSocket = (config: SocketConfig) => { logger.warn({ action, id }, 'unprocessable update') } if(Object.keys(update).length > 1) { - updates.push(update) + updates[update.id] = { + ...(updates[update.id] || {}), + ...update + } } } - ev.emit('chats.update', updates) + + if(Object.values(updates).length) { + ev.emit('chats.update', Object.values(updates)) + } + if(Object.values(contactUpdates).length) { + ev.emit('contacts.update', Object.values(contactUpdates)) + } + if(msgDeletes.length) { + ev.emit('messages.delete', { keys: msgDeletes }) + } } const appPatch = async(patchCreate: WAPatchCreate) => { @@ -336,7 +360,7 @@ export const makeChatsSocket = (config: SocketConfig) => { ) const initial = await authState.keys.getAppStateSyncVersion(name) // temp: verify it was encoded correctly - const result = await decodePatches({ syncds: [{ ...patch, version: { version: state.version }, }], name }, initial, authState) + await decodePatches({ syncds: [{ ...patch, version: { version: state.version }, }], name }, initial, authState) const node: BinaryNode = { tag: 'iq', @@ -375,6 +399,11 @@ export const makeChatsSocket = (config: SocketConfig) => { ev.emit('auth-state.update', authState) } + const chatModify = (mod: ChatModification, jid: string, lastMessages: Pick[]) => { + const patch = chatModificationToAppPatch(mod, jid, lastMessages) + return appPatch(patch) + } + const fetchAppState = async(name: WAPatchName, fromVersion: number) => { const result = await query({ tag: 'iq', @@ -452,5 +481,6 @@ export const makeChatsSocket = (config: SocketConfig) => { updateProfilePicture, updateBlockStatus, resyncState, + chatModify, } } \ No newline at end of file diff --git a/src/Types/Chat.ts b/src/Types/Chat.ts index 8d2c320..9fec2bb 100644 --- a/src/Types/Chat.ts +++ b/src/Types/Chat.ts @@ -10,11 +10,11 @@ export interface PresenceData { lastSeen?: number } -export type ChatMutation = { action: proto.ISyncActionValue, index: [string, string], indexMac: Uint8Array, valueMac: Uint8Array, operation: number } +export type ChatMutation = { action: proto.ISyncActionValue, index: string[], indexMac: Uint8Array, valueMac: Uint8Array, operation: number } export type WAPatchCreate = { syncAction: proto.ISyncActionValue - index: [string, string] + index: string[] type: WAPatchName apiVersion: number } @@ -38,7 +38,7 @@ export type ChatModification = mute: number | null } | { - clear: 'all' | { messages: { id: string, fromMe?: boolean }[] } + clear: 'all' | { message: {id: string, fromMe?: boolean} } } | { star: { diff --git a/src/Types/index.ts b/src/Types/index.ts index 3822933..351bb6a 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -16,7 +16,7 @@ import { Contact } from './Contact' import { ConnectionState } from './State' import { GroupMetadata, ParticipantAction } from './GroupMetadata' -import { MessageInfoUpdate, MessageUpdateType, WAMessage, WAMessageUpdate } from './Message' +import { MessageInfoUpdate, MessageUpdateType, WAMessage, WAMessageUpdate, WAMessageKey } from './Message' export type WAVersion = [number, number, number] export type WABrowserDescription = [string, string, string] @@ -103,7 +103,7 @@ export type BaileysEventMap = { 'contacts.upsert': Contact[] 'contacts.update': Partial[] - 'messages.delete': { jid: string, ids: string[] } | { jid: string, all: true } + 'messages.delete': { keys: WAMessageKey[] } | { jid: string, all: true } 'messages.update': WAMessageUpdate[] /** * add/update the given messages. If they were received while the connection was online, diff --git a/src/Utils/chat-utils.ts b/src/Utils/chat-utils.ts index e78dd1e..95ac3ca 100644 --- a/src/Utils/chat-utils.ts +++ b/src/Utils/chat-utils.ts @@ -1,6 +1,6 @@ import { Boom } from '@hapi/boom' import { aesDecrypt, hmacSign, aesEncrypt, hkdf } from "./crypto" -import { AuthenticationState, WAPatchCreate, ChatMutation, WAPatchName, LTHashState } from "../Types" +import { AuthenticationState, WAPatchCreate, ChatMutation, WAPatchName, LTHashState, ChatModification } from "../Types" import { proto } from '../../WAProto' import { LT_HASH_ANTI_TAMPERING } from '../WABinary/LTHash' import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren } from '../WABinary' @@ -313,7 +313,6 @@ export const decodePatches = async( } const decodeResult = await decodeSyncdPatch(syncd, name, auth!, validateMacs) - console.log(currentVersion, decodeResult.mutations) successfulMutations.push(...decodeResult.mutations) } return { @@ -324,4 +323,77 @@ export const decodePatches = async( mutations: [...initial.mutations, ...successfulMutations] } as LTHashState } +} + +export const chatModificationToAppPatch = ( + mod: ChatModification, + jid: string, + lastMessages: Pick[] +) => { + const messageRange: proto.ISyncActionMessageRange = { + lastMessageTimestamp: lastMessages[lastMessages.length-1].messageTimestamp, + messages: lastMessages + } + const timestamp = Date.now() + let patch: WAPatchCreate + if('mute' in mod) { + patch = { + syncAction: { + timestamp, + muteAction: { + muted: !!mod.mute, + muteEndTimestamp: mod.mute || undefined + } + }, + index: ['mute', jid], + type: 'regular_high', + apiVersion: 2 + } + } else if('archive' in mod) { + patch = { + syncAction: { + timestamp, + archiveChatAction: { + archived: !!mod.archive, + messageRange + } + }, + index: ['archive', jid], + type: 'regular_low', + apiVersion: 3 + } + } else if('markRead' in mod) { + patch = { + syncAction: { + timestamp, + markChatAsReadAction: { + read: mod.markRead, + messageRange + } + }, + index: ['markChatAsRead', jid], + type: 'regular_low', + apiVersion: 3 + } + } else if('clear' in mod) { + if(mod.clear === 'all') { + throw new Boom('not supported') + } else { + const key = mod.clear.message + patch = { + syncAction: { + timestamp, + deleteMessageForMeAction: { + deleteMedia: false + } + }, + index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'], + type: 'regular_high', + apiVersion: 3, + } + } + } else { + throw new Boom('not supported') + } + return patch } \ No newline at end of file