mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
implement encrypting app patches
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
import { decodeSyncdPatch, encodeSyncdPatch } from "../Utils/chat-utils";
|
||||
import { SocketConfig, WAPresence, PresenceData, Chat, ChatModification, WAMediaUpload, ChatMutation } from "../Types";
|
||||
import { encodeSyncdPatch, decodePatches, extractSyncdPatches } from "../Utils/chat-utils";
|
||||
import { SocketConfig, WAPresence, PresenceData, Chat, ChatModification, WAMediaUpload, ChatMutation, WAPatchName, LTHashState } from "../Types";
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, S_WHATSAPP_NET } from "../WABinary";
|
||||
import { makeSocket } from "./socket";
|
||||
import { proto } from '../../WAProto'
|
||||
import { toNumber } from "../Utils/generics";
|
||||
import { generateProfilePicture } from "../Utils";
|
||||
import { generateProfilePicture, toNumber } from "../Utils";
|
||||
import { randomBytes } from "crypto";
|
||||
import { makeMessagesRecvSocket } from "./messages-recv";
|
||||
|
||||
export const makeChatsSocket = (config: SocketConfig) => {
|
||||
const { logger } = config
|
||||
const sock = makeSocket(config)
|
||||
const sock = makeMessagesRecvSocket(config)
|
||||
const {
|
||||
ev,
|
||||
ws,
|
||||
authState,
|
||||
processMessage,
|
||||
relayMessage,
|
||||
generateMessageTag,
|
||||
sendNode,
|
||||
query
|
||||
@@ -25,7 +27,6 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'get',
|
||||
xmlns: 'usync',
|
||||
|
||||
},
|
||||
content: [
|
||||
{
|
||||
@@ -188,30 +189,38 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
})
|
||||
}
|
||||
|
||||
const collectionSync = async() => {
|
||||
const COLLECTIONS = ['critical_block', 'critical_unblock_low', 'regular_low', 'regular_high']
|
||||
await sendNode({
|
||||
const collectionSync = async(collections: { name: WAPatchName, version: number }[]) => {
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: S_WHATSAPP_NET,
|
||||
xmlns: 'w:sync:app:state',
|
||||
type: 'set',
|
||||
id: generateMessageTag(),
|
||||
type: 'set'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'sync',
|
||||
attrs: { },
|
||||
content: COLLECTIONS.map(
|
||||
name => ({
|
||||
content: collections.map(
|
||||
({ name, version }) => ({
|
||||
tag: 'collection',
|
||||
attrs: { name, version: '0', return_snapshot: 'true' }
|
||||
attrs: { name, version: version.toString(), return_snapshot: 'true' }
|
||||
})
|
||||
)
|
||||
}
|
||||
]
|
||||
})
|
||||
logger.info('synced collection')
|
||||
const syncNode = getBinaryNodeChild(result, 'sync')
|
||||
const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
|
||||
return collectionNodes.reduce(
|
||||
(dict, node) => {
|
||||
const snapshotNode = getBinaryNodeChild(node, 'snapshot')
|
||||
if(snapshotNode) {
|
||||
dict[node.attrs.name] = snapshotNode.content as Uint8Array
|
||||
}
|
||||
return dict
|
||||
}, { } as { [P in WAPatchName]: Uint8Array }
|
||||
)
|
||||
}
|
||||
|
||||
const profilePictureUrl = async(jid: string) => {
|
||||
@@ -311,18 +320,31 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
} else {
|
||||
logger.warn({ action, id }, 'unprocessable update')
|
||||
}
|
||||
updates.push(update)
|
||||
if(Object.keys(update).length > 1) {
|
||||
updates.push(update)
|
||||
}
|
||||
}
|
||||
ev.emit('chats.update', updates)
|
||||
}
|
||||
|
||||
const patchChat = async(
|
||||
jid: string,
|
||||
modification: ChatModification
|
||||
const appPatch = async(
|
||||
syncAction: proto.ISyncActionValue,
|
||||
index: [string, string],
|
||||
name: WAPatchName,
|
||||
operation: proto.SyncdMutation.SyncdMutationSyncdOperation.SET,
|
||||
) => {
|
||||
const patch = await encodeSyncdPatch(modification, { remoteJid: jid }, authState)
|
||||
const type = 'regular_high'
|
||||
const ver = authState.creds.appStateVersion![type] || 0
|
||||
await resyncState(name, false)
|
||||
const { patch, state } = await encodeSyncdPatch(
|
||||
syncAction,
|
||||
index,
|
||||
name,
|
||||
operation,
|
||||
authState,
|
||||
)
|
||||
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)
|
||||
|
||||
const node: BinaryNode = {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
@@ -332,29 +354,35 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'patch',
|
||||
attrs: {
|
||||
name: type,
|
||||
version: (ver+1).toString(),
|
||||
return_snapshot: 'false'
|
||||
},
|
||||
content: proto.SyncdPatch.encode(patch).finish()
|
||||
tag: 'sync',
|
||||
attrs: { },
|
||||
content: [
|
||||
{
|
||||
tag: 'collection',
|
||||
attrs: {
|
||||
name,
|
||||
version: (state.version-1).toString(),
|
||||
return_snapshot: 'false'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'patch',
|
||||
attrs: { },
|
||||
content: proto.SyncdPatch.encode(patch).finish()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
await query(node)
|
||||
|
||||
authState.creds.appStateVersion![type] += 1
|
||||
await authState.keys.setAppStateSyncVersion(name, state)
|
||||
ev.emit('auth-state.update', authState)
|
||||
}
|
||||
|
||||
const resyncState = async(name: 'regular_high' | 'regular_low' = 'regular_high') => {
|
||||
authState.creds.appStateVersion = authState.creds.appStateVersion || {
|
||||
regular_high: 0,
|
||||
regular_low: 0,
|
||||
critical_unblock_low: 0,
|
||||
critical_block: 0
|
||||
}
|
||||
const fetchAppState = async(name: WAPatchName, fromVersion: number) => {
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
@@ -371,7 +399,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
tag: 'collection',
|
||||
attrs: {
|
||||
name,
|
||||
version: authState.creds.appStateVersion[name].toString(),
|
||||
version: fromVersion.toString(),
|
||||
return_snapshot: 'false'
|
||||
}
|
||||
}
|
||||
@@ -379,30 +407,24 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
}
|
||||
]
|
||||
})
|
||||
const syncNode = getBinaryNodeChild(result, 'sync')
|
||||
const collectionNode = getBinaryNodeChild(syncNode, 'collection')
|
||||
const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
|
||||
return result
|
||||
}
|
||||
|
||||
const resyncState = async(name: WAPatchName, fromScratch: boolean) => {
|
||||
let state: LTHashState = fromScratch ? undefined : await authState.keys.getAppStateSyncVersion(name)
|
||||
if(!state) state = { version: 0, hash: Buffer.alloc(128), mutations: [] }
|
||||
|
||||
logger.info(`resyncing ${name} from v${state.version}`)
|
||||
|
||||
const result = await fetchAppState(name, state.version)
|
||||
const decoded = extractSyncdPatches(result) // extract from binary node
|
||||
const { newMutations, state: newState } = await decodePatches(decoded, state, authState, true)
|
||||
|
||||
await authState.keys.setAppStateSyncVersion(name, newState)
|
||||
|
||||
logger.info(`synced ${name} to v${newState.version}`)
|
||||
processSyncActions(newMutations)
|
||||
|
||||
const patches = getBinaryNodeChildren(patchesNode, 'patch')
|
||||
const successfulMutations: ChatMutation[] = []
|
||||
for(const { content } of patches) {
|
||||
if(content) {
|
||||
const syncd = proto.SyncdPatch.decode(content! as Uint8Array)
|
||||
const version = toNumber(syncd.version!.version!)
|
||||
if(version) {
|
||||
authState.creds.appStateVersion[name] = Math.max(version, authState.creds.appStateVersion[name])
|
||||
}
|
||||
const { mutations, failures } = await decodeSyncdPatch(syncd, authState)
|
||||
if(failures.length) {
|
||||
logger.info(
|
||||
{ failures: failures.map(f => ({ trace: f.stack, data: f.data })) },
|
||||
'failed to decode'
|
||||
)
|
||||
}
|
||||
successfulMutations.push(...mutations)
|
||||
}
|
||||
}
|
||||
processSyncActions(successfulMutations)
|
||||
ev.emit('auth-state.update', authState)
|
||||
}
|
||||
|
||||
@@ -412,7 +434,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
ws.on('CB:notification,type:server_sync', (node: BinaryNode) => {
|
||||
const update = getBinaryNodeChild(node, 'collection')
|
||||
if(update) {
|
||||
resyncState(update.attrs.name as any)
|
||||
resyncState(update.attrs.name as WAPatchName, false)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -421,13 +443,12 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
sendPresenceUpdate('available')
|
||||
fetchBlocklist()
|
||||
fetchPrivacySettings()
|
||||
//collectionSync()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
...sock,
|
||||
patchChat,
|
||||
appPatch,
|
||||
sendPresenceUpdate,
|
||||
presenceSubscribe,
|
||||
profilePictureUrl,
|
||||
@@ -436,6 +457,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
||||
fetchPrivacySettings,
|
||||
fetchStatus,
|
||||
updateProfilePicture,
|
||||
updateBlockStatus
|
||||
updateBlockStatus,
|
||||
resyncState,
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,10 @@
|
||||
import { generateMessageID } from "../Utils";
|
||||
import { SocketConfig, GroupMetadata, ParticipantAction } from "../Types";
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidDecode, jidEncode } from "../WABinary";
|
||||
import { makeChatsSocket } from "./chats";
|
||||
|
||||
const extractGroupMetadata = (result: BinaryNode) => {
|
||||
const group = getBinaryNodeChild(result, 'group')
|
||||
const descChild = getBinaryNodeChild(group, 'description')
|
||||
let desc: string | undefined
|
||||
let descId: string | undefined
|
||||
if(descChild) {
|
||||
desc = getBinaryNodeChild(descChild, 'body')?.content as string
|
||||
descId = descChild.attrs.id
|
||||
}
|
||||
const groupId = group.attrs.id.includes('@') ? group.attrs.id : jidEncode(group.attrs.id, 'g.us')
|
||||
const metadata: GroupMetadata = {
|
||||
id: groupId,
|
||||
subject: group.attrs.subject,
|
||||
creation: +group.attrs.creation,
|
||||
owner: group.attrs.creator,
|
||||
desc,
|
||||
descId,
|
||||
restrict: !!getBinaryNodeChild(result, 'locked'),
|
||||
announce: !!getBinaryNodeChild(result, 'announcement'),
|
||||
participants: getBinaryNodeChildren(group, 'participant').map(
|
||||
({ attrs }) => {
|
||||
return {
|
||||
id: attrs.jid,
|
||||
admin: attrs.type || null as any,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidEncode } from "../WABinary";
|
||||
import { makeSocket } from "./socket";
|
||||
|
||||
export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
const sock = makeChatsSocket(config)
|
||||
const sock = makeSocket(config)
|
||||
const { query } = sock
|
||||
|
||||
const groupQuery = async(jid: string, type: 'get' | 'set', content: BinaryNode[]) => (
|
||||
@@ -146,4 +115,36 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
await groupQuery(jid, 'set', [ { tag: setting, attrs: { } } ])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const extractGroupMetadata = (result: BinaryNode) => {
|
||||
const group = getBinaryNodeChild(result, 'group')
|
||||
const descChild = getBinaryNodeChild(group, 'description')
|
||||
let desc: string | undefined
|
||||
let descId: string | undefined
|
||||
if(descChild) {
|
||||
desc = getBinaryNodeChild(descChild, 'body')?.content as string
|
||||
descId = descChild.attrs.id
|
||||
}
|
||||
const groupId = group.attrs.id.includes('@') ? group.attrs.id : jidEncode(group.attrs.id, 'g.us')
|
||||
const metadata: GroupMetadata = {
|
||||
id: groupId,
|
||||
subject: group.attrs.subject,
|
||||
creation: +group.attrs.creation,
|
||||
owner: group.attrs.creator,
|
||||
desc,
|
||||
descId,
|
||||
restrict: !!getBinaryNodeChild(result, 'locked'),
|
||||
announce: !!getBinaryNodeChild(result, 'announcement'),
|
||||
participants: getBinaryNodeChildren(group, 'participant').map(
|
||||
({ attrs }) => {
|
||||
return {
|
||||
id: attrs.jid,
|
||||
admin: attrs.type || null as any,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SocketConfig } from '../Types'
|
||||
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults'
|
||||
import { makeMessagesSocket as _makeSocket } from './messages-send'
|
||||
import { makeChatsSocket as _makeSocket } from './chats'
|
||||
|
||||
// export the last socket layer
|
||||
const makeWASocket = (config: Partial<SocketConfig>) => (
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
|
||||
import { makeGroupsSocket } from "./groups"
|
||||
import { SocketConfig, WAMessageStubType, ParticipantAction, Chat, GroupMetadata } from "../Types"
|
||||
import { decodeMessageStanza, encodeBigEndian, toNumber } from "../Utils"
|
||||
import { BinaryNode, jidDecode, jidEncode, isJidStatusBroadcast, areJidsSameUser, getBinaryNodeChildren, jidNormalizedUser } from '../WABinary'
|
||||
import { downloadIfHistory } from '../Utils/history'
|
||||
import { downloadHistory } from '../Utils/history'
|
||||
import { proto } from "../../WAProto"
|
||||
import { generateSignalPubKey, xmppPreKey, xmppSignedPreKey } from "../Utils/signal"
|
||||
import { KEY_BUNDLE_TYPE } from "../Defaults"
|
||||
import { makeMessagesSocket } from "./messages-send"
|
||||
|
||||
export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const { logger } = config
|
||||
const sock = makeGroupsSocket(config)
|
||||
const sock = makeMessagesSocket(config)
|
||||
const {
|
||||
ev,
|
||||
authState,
|
||||
ws,
|
||||
assertingPreKeys,
|
||||
sendNode,
|
||||
relayMessage,
|
||||
} = sock
|
||||
|
||||
const sendMessageAck = async({ attrs }: BinaryNode) => {
|
||||
@@ -103,11 +104,45 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const protocolMsg = message.message?.protocolMessage
|
||||
if(protocolMsg) {
|
||||
switch(protocolMsg.type) {
|
||||
case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION:
|
||||
const history = await downloadHistory(protocolMsg!.historySyncNotification)
|
||||
processHistoryMessage(history)
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_REQUEST:
|
||||
const keys = await Promise.all(
|
||||
protocolMsg.appStateSyncKeyRequest!.keyIds!.map(
|
||||
async id => {
|
||||
const keyId = Buffer.from(id.keyId!).toString('base64')
|
||||
const keyData = await authState.keys.getAppStateSyncKey(keyId)
|
||||
logger.info({ keyId }, 'received key request')
|
||||
return {
|
||||
keyId: id,
|
||||
keyData
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const msg: proto.IMessage = {
|
||||
protocolMessage: {
|
||||
type: proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE,
|
||||
appStateSyncKeyShare: {
|
||||
keys
|
||||
}
|
||||
}
|
||||
}
|
||||
await relayMessage(message.key.remoteJid!, msg, { })
|
||||
logger.info({ with: message.key.remoteJid! }, 'shared key')
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE:
|
||||
for(const { keyData, keyId } of protocolMsg.appStateSyncKeyShare!.keys || []) {
|
||||
const str = Buffer.from(keyId.keyId!).toString('base64')
|
||||
logger.info({ str }, 'injecting new app state sync key')
|
||||
await authState.keys.setAppStateSyncKey(str, keyData)
|
||||
|
||||
authState.creds.myAppStateKeyId = str
|
||||
}
|
||||
|
||||
ev.emit('auth-state.update', authState)
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
|
||||
@@ -303,14 +338,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
|
||||
logger.debug({ msgId: dec.msgId }, 'send message receipt')
|
||||
|
||||
const possibleHistory = downloadIfHistory(msg)
|
||||
if(possibleHistory) {
|
||||
const history = await possibleHistory
|
||||
logger.info({ msgId: dec.msgId, type: history.syncType }, 'recv history')
|
||||
|
||||
processHistoryMessage(history)
|
||||
} else {
|
||||
const message = msg.deviceSentMessage?.message || msg
|
||||
const message = msg.deviceSentMessage?.message || msg
|
||||
fullMessages.push({
|
||||
key: {
|
||||
remoteJid: dec.chatId,
|
||||
@@ -323,7 +351,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
messageTimestamp: dec.timestamp,
|
||||
pushName: dec.pushname
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if(dec.successes.length) {
|
||||
@@ -434,7 +461,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
ev.emit('auth-state.update', authState)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await processMessage(msg, chat)
|
||||
if(!!msg.message && !msg.message!.protocolMessage) {
|
||||
chat.conversationTimestamp = toNumber(msg.messageTimestamp)
|
||||
@@ -453,5 +480,5 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
}
|
||||
})
|
||||
|
||||
return sock
|
||||
return { ...sock, processMessage }
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import { makeMessagesRecvSocket } from "./messages-recv"
|
||||
import { SocketConfig, MediaConnInfo, AnyMessageContent, MiscMessageGenerationOptions, WAMediaUploadFunction } from "../Types"
|
||||
import { SocketConfig, MediaConnInfo, AnyMessageContent, MiscMessageGenerationOptions, WAMediaUploadFunction, MessageRelayOptions } from "../Types"
|
||||
import { encodeWAMessage, generateMessageID, generateWAMessage } from "../Utils"
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { proto } from "../../WAProto"
|
||||
@@ -8,10 +7,11 @@ import { encryptSenderKeyMsgSignalProto, encryptSignalProto, extractDeviceJids,
|
||||
import { WA_DEFAULT_EPHEMERAL, DEFAULT_ORIGIN, MEDIA_PATH_MAP } from "../Defaults"
|
||||
import got from "got"
|
||||
import { Boom } from "@hapi/boom"
|
||||
import { makeGroupsSocket } from "./groups"
|
||||
|
||||
export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const { logger } = config
|
||||
const sock = makeMessagesRecvSocket(config)
|
||||
const sock = makeGroupsSocket(config)
|
||||
const {
|
||||
ev,
|
||||
authState,
|
||||
@@ -84,10 +84,12 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
}
|
||||
|
||||
const getUSyncDevices = async(jids: string[], ignoreZeroDevices: boolean) => {
|
||||
jids = Array.from(new Set(jids))
|
||||
const users = jids.map<BinaryNode>(jid => ({
|
||||
tag: 'user',
|
||||
attrs: { jid: jidNormalizedUser(jid) }
|
||||
}))
|
||||
|
||||
const iq: BinaryNode = {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
@@ -175,7 +177,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
return node
|
||||
}
|
||||
|
||||
const relayMessage = async(jid: string, message: proto.IMessage, msgId?: string) => {
|
||||
const relayMessage = async(
|
||||
jid: string,
|
||||
message: proto.IMessage,
|
||||
{ messageId: msgId, cachedGroupMetadata }: MessageRelayOptions
|
||||
) => {
|
||||
const { user, server } = jidDecode(jid)
|
||||
const isGroup = server === 'g.us'
|
||||
msgId = msgId || generateMessageID()
|
||||
@@ -188,7 +194,10 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
|
||||
if(isGroup) {
|
||||
const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, authState)
|
||||
const groupData = await groupMetadata(jid)
|
||||
|
||||
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
||||
if(!groupData) groupData = await groupMetadata(jid)
|
||||
|
||||
const participantsList = groupData.participants.map(p => p.id)
|
||||
const devices = await getUSyncDevices(participantsList, false)
|
||||
|
||||
@@ -384,7 +393,7 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
upload: waUploadToServer
|
||||
}
|
||||
)
|
||||
await relayMessage(jid, fullMsg.message, fullMsg.key.id!)
|
||||
await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id! })
|
||||
process.nextTick(() => {
|
||||
ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user