mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
chore: add linting
This commit is contained in:
1216
src/Socket/chats.ts
1216
src/Socket/chats.ts
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
import { generateMessageID } from "../Utils";
|
||||
import { SocketConfig, GroupMetadata, ParticipantAction } from "../Types";
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidEncode, jidNormalizedUser } from "../WABinary";
|
||||
import { makeSocket } from "./socket";
|
||||
import { GroupMetadata, ParticipantAction, SocketConfig } from '../Types'
|
||||
import { generateMessageID } from '../Utils'
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidEncode, jidNormalizedUser } from '../WABinary'
|
||||
import { makeSocket } from './socket'
|
||||
|
||||
export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
const sock = makeSocket(config)
|
||||
@@ -9,24 +9,24 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
|
||||
const groupQuery = async(jid: string, type: 'get' | 'set', content: BinaryNode[]) => (
|
||||
query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
type,
|
||||
xmlns: 'w:g2',
|
||||
to: jid,
|
||||
},
|
||||
content
|
||||
})
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
type,
|
||||
xmlns: 'w:g2',
|
||||
to: jid,
|
||||
},
|
||||
content
|
||||
})
|
||||
)
|
||||
|
||||
const groupMetadata = async(jid: string) => {
|
||||
const result = await groupQuery(
|
||||
const groupMetadata = async(jid: string) => {
|
||||
const result = await groupQuery(
|
||||
jid,
|
||||
'get',
|
||||
[ { tag: 'query', attrs: { request: 'interactive' } } ]
|
||||
)
|
||||
return extractGroupMetadata(result)
|
||||
}
|
||||
return extractGroupMetadata(result)
|
||||
}
|
||||
|
||||
return {
|
||||
...sock,
|
||||
@@ -101,8 +101,8 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
return participantsAffected.map(p => p.attrs.jid)
|
||||
},
|
||||
groupUpdateDescription: async(jid: string, description?: string) => {
|
||||
const metadata = await groupMetadata(jid);
|
||||
const prev = metadata.descId ?? null;
|
||||
const metadata = await groupMetadata(jid)
|
||||
const prev = metadata.descId ?? null
|
||||
|
||||
await groupQuery(
|
||||
jid,
|
||||
@@ -111,10 +111,10 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
{
|
||||
tag: 'description',
|
||||
attrs: {
|
||||
...( description ? { id: generateMessageID() } : { delete: 'true' } ),
|
||||
...(description ? { id: generateMessageID() } : { delete: 'true' }),
|
||||
...(prev ? { prev } : {})
|
||||
},
|
||||
content: description ? [{tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8')}] : null
|
||||
content: description ? [{ tag: 'body', attrs: {}, content: Buffer.from(description, 'utf-8') }] : null
|
||||
}
|
||||
]
|
||||
)
|
||||
@@ -124,12 +124,12 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
const inviteNode = getBinaryNodeChild(result, 'invite')
|
||||
return inviteNode.attrs.code
|
||||
},
|
||||
groupRevokeInvite: async (jid: string) => {
|
||||
groupRevokeInvite: async(jid: string) => {
|
||||
const result = await groupQuery(jid, 'set', [{ tag: 'invite', attrs: {} }])
|
||||
const inviteNode = getBinaryNodeChild(result, 'invite')
|
||||
return inviteNode.attrs.code
|
||||
},
|
||||
groupAcceptInvite: async (code: string) => {
|
||||
groupAcceptInvite: async(code: string) => {
|
||||
const results = await groupQuery('@g.us', 'set', [{ tag: 'invite', attrs: { code } }])
|
||||
const result = getBinaryNodeChild(results, 'group')
|
||||
return result.attrs.jid
|
||||
@@ -148,8 +148,8 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: '@g.us',
|
||||
xmlns: 'w:g2',
|
||||
type: 'get',
|
||||
xmlns: 'w:g2',
|
||||
type: 'get',
|
||||
},
|
||||
content: [
|
||||
{
|
||||
@@ -175,6 +175,7 @@ export const makeGroupsSocket = (config: SocketConfig) => {
|
||||
data[meta.id] = meta
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -190,6 +191,7 @@ export const extractGroupMetadata = (result: BinaryNode) => {
|
||||
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 eph = getBinaryNodeChild(group, 'ephemeral')?.attrs.expiration
|
||||
const metadata: GroupMetadata = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SocketConfig } from '../Types'
|
||||
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults'
|
||||
import { SocketConfig } from '../Types'
|
||||
import { makeMessagesRecvSocket as _makeSocket } from './messages-recv'
|
||||
|
||||
// export the last socket layer
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
|
||||
import { SocketConfig, WAMessageStubType, ParticipantAction, Chat, GroupMetadata } from "../Types"
|
||||
import { decodeMessageStanza, encodeBigEndian, toNumber, downloadAndProcessHistorySyncNotification, generateSignalPubKey, xmppPreKey, xmppSignedPreKey } from "../Utils"
|
||||
import { BinaryNode, jidDecode, jidEncode, areJidsSameUser, getBinaryNodeChildren, jidNormalizedUser, getAllBinaryNodeChildren, BinaryNodeAttributes, isJidGroup } from '../WABinary'
|
||||
import { proto } from "../../WAProto"
|
||||
import { KEY_BUNDLE_TYPE } from "../Defaults"
|
||||
import { makeChatsSocket } from "./chats"
|
||||
import { extractGroupMetadata } from "./groups"
|
||||
import { proto } from '../../WAProto'
|
||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import { Chat, GroupMetadata, ParticipantAction, SocketConfig, WAMessageStubType } from '../Types'
|
||||
import { decodeMessageStanza, downloadAndProcessHistorySyncNotification, encodeBigEndian, generateSignalPubKey, toNumber, xmppPreKey, xmppSignedPreKey } from '../Utils'
|
||||
import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getAllBinaryNodeChildren, getBinaryNodeChildren, isJidGroup, jidDecode, jidEncode, jidNormalizedUser } from '../WABinary'
|
||||
import { makeChatsSocket } from './chats'
|
||||
import { extractGroupMetadata } from './groups'
|
||||
|
||||
const STATUS_MAP: { [_: string]: proto.WebMessageInfo.WebMessageInfoStatus } = {
|
||||
'played': proto.WebMessageInfo.WebMessageInfoStatus.PLAYED,
|
||||
'read': proto.WebMessageInfo.WebMessageInfoStatus.READ,
|
||||
'read-self': proto.WebMessageInfo.WebMessageInfoStatus.READ
|
||||
'played': proto.WebMessageInfo.WebMessageInfoStatus.PLAYED,
|
||||
'read': proto.WebMessageInfo.WebMessageInfoStatus.READ,
|
||||
'read-self': proto.WebMessageInfo.WebMessageInfoStatus.READ
|
||||
}
|
||||
|
||||
const getStatusFromReceiptType = (type: string | undefined) => {
|
||||
const status = STATUS_MAP[type]
|
||||
if(typeof type === 'undefined') {
|
||||
return proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK
|
||||
}
|
||||
return status
|
||||
const status = STATUS_MAP[type]
|
||||
if(typeof type === 'undefined') {
|
||||
return proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
@@ -26,477 +27,501 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const sock = makeChatsSocket(config)
|
||||
const {
|
||||
ev,
|
||||
authState,
|
||||
authState,
|
||||
ws,
|
||||
assertSessions,
|
||||
assertingPreKeys,
|
||||
assertSessions,
|
||||
assertingPreKeys,
|
||||
sendNode,
|
||||
relayMessage,
|
||||
sendReceipt,
|
||||
resyncMainAppState,
|
||||
relayMessage,
|
||||
sendReceipt,
|
||||
resyncMainAppState,
|
||||
} = sock
|
||||
|
||||
const msgRetryMap = config.msgRetryCounterMap || { }
|
||||
const msgRetryMap = config.msgRetryCounterMap || { }
|
||||
|
||||
const historyCache = new Set<string>()
|
||||
const historyCache = new Set<string>()
|
||||
|
||||
const sendMessageAck = async({ tag, attrs }: BinaryNode, extraAttrs: BinaryNodeAttributes) => {
|
||||
const stanza: BinaryNode = {
|
||||
tag: 'ack',
|
||||
attrs: {
|
||||
id: attrs.id,
|
||||
to: attrs.from,
|
||||
...extraAttrs,
|
||||
}
|
||||
}
|
||||
if(!!attrs.participant) {
|
||||
stanza.attrs.participant = attrs.participant
|
||||
}
|
||||
logger.debug({ recv: attrs, sent: stanza.attrs }, `sent "${tag}" ack`)
|
||||
await sendNode(stanza)
|
||||
}
|
||||
const sendMessageAck = async({ tag, attrs }: BinaryNode, extraAttrs: BinaryNodeAttributes) => {
|
||||
const stanza: BinaryNode = {
|
||||
tag: 'ack',
|
||||
attrs: {
|
||||
id: attrs.id,
|
||||
to: attrs.from,
|
||||
...extraAttrs,
|
||||
}
|
||||
}
|
||||
if(!!attrs.participant) {
|
||||
stanza.attrs.participant = attrs.participant
|
||||
}
|
||||
|
||||
const sendRetryRequest = async(node: BinaryNode) => {
|
||||
const msgId = node.attrs.id
|
||||
const retryCount = msgRetryMap[msgId] || 1
|
||||
if(retryCount >= 5) {
|
||||
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing')
|
||||
delete msgRetryMap[msgId]
|
||||
return
|
||||
}
|
||||
msgRetryMap[msgId] = retryCount+1
|
||||
logger.debug({ recv: attrs, sent: stanza.attrs }, `sent "${tag}" ack`)
|
||||
await sendNode(stanza)
|
||||
}
|
||||
|
||||
const isGroup = !!node.attrs.participant
|
||||
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
|
||||
const sendRetryRequest = async(node: BinaryNode) => {
|
||||
const msgId = node.attrs.id
|
||||
const retryCount = msgRetryMap[msgId] || 1
|
||||
if(retryCount >= 5) {
|
||||
logger.debug({ retryCount, msgId }, 'reached retry limit, clearing')
|
||||
delete msgRetryMap[msgId]
|
||||
return
|
||||
}
|
||||
|
||||
msgRetryMap[msgId] = retryCount+1
|
||||
|
||||
const isGroup = !!node.attrs.participant
|
||||
const { account, signedPreKey, signedIdentityKey: identityKey } = authState.creds
|
||||
|
||||
const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account).finish()
|
||||
await assertingPreKeys(1, async preKeys => {
|
||||
const [keyId] = Object.keys(preKeys)
|
||||
const key = preKeys[+keyId]
|
||||
const deviceIdentity = proto.ADVSignedDeviceIdentity.encode(account).finish()
|
||||
await assertingPreKeys(1, async preKeys => {
|
||||
const [keyId] = Object.keys(preKeys)
|
||||
const key = preKeys[+keyId]
|
||||
|
||||
const decFrom = node.attrs.from ? jidDecode(node.attrs.from) : undefined
|
||||
const receipt: BinaryNode = {
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: msgId,
|
||||
type: 'retry',
|
||||
to: isGroup ? node.attrs.from : jidEncode(decFrom!.user, 's.whatsapp.net', decFrom!.device, 0)
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'retry',
|
||||
attrs: {
|
||||
count: retryCount.toString(),
|
||||
id: node.attrs.id,
|
||||
t: node.attrs.t,
|
||||
v: '1'
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'registration',
|
||||
attrs: { },
|
||||
content: encodeBigEndian(authState.creds.registrationId)
|
||||
}
|
||||
]
|
||||
}
|
||||
if(node.attrs.recipient) {
|
||||
receipt.attrs.recipient = node.attrs.recipient
|
||||
}
|
||||
if(node.attrs.participant) {
|
||||
receipt.attrs.participant = node.attrs.participant
|
||||
}
|
||||
if(retryCount > 1) {
|
||||
const exec = generateSignalPubKey(Buffer.from(KEY_BUNDLE_TYPE)).slice(0, 1);
|
||||
const decFrom = node.attrs.from ? jidDecode(node.attrs.from) : undefined
|
||||
const receipt: BinaryNode = {
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: msgId,
|
||||
type: 'retry',
|
||||
to: isGroup ? node.attrs.from : jidEncode(decFrom!.user, 's.whatsapp.net', decFrom!.device, 0)
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'retry',
|
||||
attrs: {
|
||||
count: retryCount.toString(),
|
||||
id: node.attrs.id,
|
||||
t: node.attrs.t,
|
||||
v: '1'
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'registration',
|
||||
attrs: { },
|
||||
content: encodeBigEndian(authState.creds.registrationId)
|
||||
}
|
||||
]
|
||||
}
|
||||
if(node.attrs.recipient) {
|
||||
receipt.attrs.recipient = node.attrs.recipient
|
||||
}
|
||||
|
||||
(receipt.content! as BinaryNode[]).push({
|
||||
tag: 'keys',
|
||||
attrs: { },
|
||||
content: [
|
||||
{ tag: 'type', attrs: { }, content: exec },
|
||||
{ tag: 'identity', attrs: { }, content: identityKey.public },
|
||||
xmppPreKey(key, +keyId),
|
||||
xmppSignedPreKey(signedPreKey),
|
||||
{ tag: 'device-identity', attrs: { }, content: deviceIdentity }
|
||||
]
|
||||
})
|
||||
}
|
||||
await sendNode(receipt)
|
||||
if(node.attrs.participant) {
|
||||
receipt.attrs.participant = node.attrs.participant
|
||||
}
|
||||
|
||||
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
|
||||
})
|
||||
}
|
||||
if(retryCount > 1) {
|
||||
const exec = generateSignalPubKey(Buffer.from(KEY_BUNDLE_TYPE)).slice(0, 1);
|
||||
|
||||
const processMessage = async(message: proto.IWebMessageInfo, chatUpdate: Partial<Chat>) => {
|
||||
const protocolMsg = message.message?.protocolMessage
|
||||
if(protocolMsg) {
|
||||
switch(protocolMsg.type) {
|
||||
case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION:
|
||||
const histNotification = protocolMsg!.historySyncNotification
|
||||
(receipt.content! as BinaryNode[]).push({
|
||||
tag: 'keys',
|
||||
attrs: { },
|
||||
content: [
|
||||
{ tag: 'type', attrs: { }, content: exec },
|
||||
{ tag: 'identity', attrs: { }, content: identityKey.public },
|
||||
xmppPreKey(key, +keyId),
|
||||
xmppSignedPreKey(signedPreKey),
|
||||
{ tag: 'device-identity', attrs: { }, content: deviceIdentity }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
await sendNode(receipt)
|
||||
|
||||
logger.info({ msgAttrs: node.attrs, retryCount }, 'sent retry receipt')
|
||||
})
|
||||
}
|
||||
|
||||
const processMessage = async(message: proto.IWebMessageInfo, chatUpdate: Partial<Chat>) => {
|
||||
const protocolMsg = message.message?.protocolMessage
|
||||
if(protocolMsg) {
|
||||
switch (protocolMsg.type) {
|
||||
case proto.ProtocolMessage.ProtocolMessageType.HISTORY_SYNC_NOTIFICATION:
|
||||
const histNotification = protocolMsg!.historySyncNotification
|
||||
|
||||
logger.info({ histNotification, id: message.key.id }, 'got history notification')
|
||||
const { chats, contacts, messages, isLatest } = await downloadAndProcessHistorySyncNotification(histNotification, historyCache)
|
||||
logger.info({ histNotification, id: message.key.id }, 'got history notification')
|
||||
const { chats, contacts, messages, isLatest } = await downloadAndProcessHistorySyncNotification(histNotification, historyCache)
|
||||
|
||||
const meJid = authState.creds.me!.id
|
||||
await sendNode({
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: message.key.id,
|
||||
type: 'hist_sync',
|
||||
to: jidEncode(jidDecode(meJid).user, 'c.us')
|
||||
}
|
||||
})
|
||||
const meJid = authState.creds.me!.id
|
||||
await sendNode({
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: message.key.id,
|
||||
type: 'hist_sync',
|
||||
to: jidEncode(jidDecode(meJid).user, 'c.us')
|
||||
}
|
||||
})
|
||||
|
||||
if(chats.length) ev.emit('chats.set', { chats, isLatest })
|
||||
if(messages.length) ev.emit('messages.set', { messages, isLatest })
|
||||
if(contacts.length) ev.emit('contacts.set', { contacts })
|
||||
if(chats.length) {
|
||||
ev.emit('chats.set', { chats, isLatest })
|
||||
}
|
||||
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE:
|
||||
const keys = protocolMsg.appStateSyncKeyShare!.keys
|
||||
if(keys?.length) {
|
||||
let newAppStateSyncKeyId = ''
|
||||
for(const { keyData, keyId } of keys) {
|
||||
const strKeyId = Buffer.from(keyId.keyId!).toString('base64')
|
||||
if(messages.length) {
|
||||
ev.emit('messages.set', { messages, isLatest })
|
||||
}
|
||||
|
||||
if(contacts.length) {
|
||||
ev.emit('contacts.set', { contacts })
|
||||
}
|
||||
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE:
|
||||
const keys = protocolMsg.appStateSyncKeyShare!.keys
|
||||
if(keys?.length) {
|
||||
let newAppStateSyncKeyId = ''
|
||||
for(const { keyData, keyId } of keys) {
|
||||
const strKeyId = Buffer.from(keyId.keyId!).toString('base64')
|
||||
|
||||
logger.info({ strKeyId }, 'injecting new app state sync key')
|
||||
await authState.keys.set({ 'app-state-sync-key': { [strKeyId]: keyData } })
|
||||
logger.info({ strKeyId }, 'injecting new app state sync key')
|
||||
await authState.keys.set({ 'app-state-sync-key': { [strKeyId]: keyData } })
|
||||
|
||||
newAppStateSyncKeyId = strKeyId
|
||||
}
|
||||
newAppStateSyncKeyId = strKeyId
|
||||
}
|
||||
|
||||
ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId })
|
||||
ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId })
|
||||
|
||||
resyncMainAppState()
|
||||
} else [
|
||||
logger.info({ protocolMsg }, 'recv app state sync with 0 keys')
|
||||
]
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
|
||||
ev.emit('messages.update', [
|
||||
{
|
||||
key: {
|
||||
...message.key,
|
||||
id: protocolMsg.key!.id
|
||||
},
|
||||
update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key }
|
||||
}
|
||||
])
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.EPHEMERAL_SETTING:
|
||||
chatUpdate.ephemeralSettingTimestamp = toNumber(message.messageTimestamp)
|
||||
chatUpdate.ephemeralExpiration = protocolMsg.ephemeralExpiration || null
|
||||
break
|
||||
}
|
||||
} else if(message.messageStubType) {
|
||||
const meJid = authState.creds.me!.id
|
||||
const jid = message.key!.remoteJid!
|
||||
//let actor = whatsappID (message.participant)
|
||||
let participants: string[]
|
||||
const emitParticipantsUpdate = (action: ParticipantAction) => (
|
||||
ev.emit('group-participants.update', { id: jid, participants, action })
|
||||
)
|
||||
const emitGroupUpdate = (update: Partial<GroupMetadata>) => {
|
||||
ev.emit('groups.update', [ { id: jid, ...update } ])
|
||||
}
|
||||
resyncMainAppState()
|
||||
} else {
|
||||
[
|
||||
logger.info({ protocolMsg }, 'recv app state sync with 0 keys')
|
||||
]
|
||||
}
|
||||
|
||||
switch (message.messageStubType) {
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
|
||||
participants = message.messageStubParameters
|
||||
emitParticipantsUpdate('remove')
|
||||
// mark the chat read only if you left the group
|
||||
if(participants.includes(meJid)) {
|
||||
chatUpdate.readOnly = true
|
||||
}
|
||||
break
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
|
||||
participants = message.messageStubParameters
|
||||
if (participants.includes(meJid)) {
|
||||
chatUpdate.readOnly = false
|
||||
}
|
||||
emitParticipantsUpdate('add')
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_ANNOUNCE:
|
||||
const announceValue = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ announce: announceValue === 'true' || announceValue === 'on' })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_RESTRICT:
|
||||
const restrictValue = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ restrict: restrictValue === 'true' || restrictValue === 'on' })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_SUBJECT:
|
||||
chatUpdate.name = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ subject: chatUpdate.name })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
|
||||
ev.emit('messages.update', [
|
||||
{
|
||||
key: {
|
||||
...message.key,
|
||||
id: protocolMsg.key!.id
|
||||
},
|
||||
update: { message: null, messageStubType: WAMessageStubType.REVOKE, key: message.key }
|
||||
}
|
||||
])
|
||||
break
|
||||
case proto.ProtocolMessage.ProtocolMessageType.EPHEMERAL_SETTING:
|
||||
chatUpdate.ephemeralSettingTimestamp = toNumber(message.messageTimestamp)
|
||||
chatUpdate.ephemeralExpiration = protocolMsg.ephemeralExpiration || null
|
||||
break
|
||||
}
|
||||
} else if(message.messageStubType) {
|
||||
const meJid = authState.creds.me!.id
|
||||
const jid = message.key!.remoteJid!
|
||||
//let actor = whatsappID (message.participant)
|
||||
let participants: string[]
|
||||
const emitParticipantsUpdate = (action: ParticipantAction) => (
|
||||
ev.emit('group-participants.update', { id: jid, participants, action })
|
||||
)
|
||||
const emitGroupUpdate = (update: Partial<GroupMetadata>) => {
|
||||
ev.emit('groups.update', [ { id: jid, ...update } ])
|
||||
}
|
||||
|
||||
const processNotification = (node: BinaryNode): Partial<proto.IWebMessageInfo> => {
|
||||
const result: Partial<proto.IWebMessageInfo> = { }
|
||||
const [child] = getAllBinaryNodeChildren(node)
|
||||
switch (message.messageStubType) {
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
|
||||
participants = message.messageStubParameters
|
||||
emitParticipantsUpdate('remove')
|
||||
// mark the chat read only if you left the group
|
||||
if(participants.includes(meJid)) {
|
||||
chatUpdate.readOnly = true
|
||||
}
|
||||
|
||||
if(node.attrs.type === 'w:gp2') {
|
||||
switch(child?.tag) {
|
||||
case 'create':
|
||||
const metadata = extractGroupMetadata(child)
|
||||
break
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
|
||||
participants = message.messageStubParameters
|
||||
if(participants.includes(meJid)) {
|
||||
chatUpdate.readOnly = false
|
||||
}
|
||||
|
||||
result.messageStubType = WAMessageStubType.GROUP_CREATE
|
||||
result.messageStubParameters = [metadata.subject]
|
||||
result.key = { participant: metadata.owner }
|
||||
emitParticipantsUpdate('add')
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_ANNOUNCE:
|
||||
const announceValue = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ announce: announceValue === 'true' || announceValue === 'on' })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_RESTRICT:
|
||||
const restrictValue = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ restrict: restrictValue === 'true' || restrictValue === 'on' })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_SUBJECT:
|
||||
chatUpdate.name = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ subject: chatUpdate.name })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ev.emit('chats.upsert', [{
|
||||
id: metadata.id,
|
||||
name: metadata.subject,
|
||||
conversationTimestamp: metadata.creation,
|
||||
}])
|
||||
ev.emit('groups.upsert', [metadata])
|
||||
break
|
||||
case 'ephemeral':
|
||||
case 'not_ephemeral':
|
||||
result.message = {
|
||||
protocolMessage: {
|
||||
type: proto.ProtocolMessage.ProtocolMessageType.EPHEMERAL_SETTING,
|
||||
ephemeralExpiration: +(child.attrs.expiration || 0)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'promote':
|
||||
case 'demote':
|
||||
case 'remove':
|
||||
case 'add':
|
||||
case 'leave':
|
||||
const stubType = `GROUP_PARTICIPANT_${child.tag!.toUpperCase()}`
|
||||
result.messageStubType = WAMessageStubType[stubType]
|
||||
const processNotification = (node: BinaryNode): Partial<proto.IWebMessageInfo> => {
|
||||
const result: Partial<proto.IWebMessageInfo> = { }
|
||||
const [child] = getAllBinaryNodeChildren(node)
|
||||
|
||||
const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
|
||||
if(
|
||||
participants.length === 1 &&
|
||||
if(node.attrs.type === 'w:gp2') {
|
||||
switch (child?.tag) {
|
||||
case 'create':
|
||||
const metadata = extractGroupMetadata(child)
|
||||
|
||||
result.messageStubType = WAMessageStubType.GROUP_CREATE
|
||||
result.messageStubParameters = [metadata.subject]
|
||||
result.key = { participant: metadata.owner }
|
||||
|
||||
ev.emit('chats.upsert', [{
|
||||
id: metadata.id,
|
||||
name: metadata.subject,
|
||||
conversationTimestamp: metadata.creation,
|
||||
}])
|
||||
ev.emit('groups.upsert', [metadata])
|
||||
break
|
||||
case 'ephemeral':
|
||||
case 'not_ephemeral':
|
||||
result.message = {
|
||||
protocolMessage: {
|
||||
type: proto.ProtocolMessage.ProtocolMessageType.EPHEMERAL_SETTING,
|
||||
ephemeralExpiration: +(child.attrs.expiration || 0)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'promote':
|
||||
case 'demote':
|
||||
case 'remove':
|
||||
case 'add':
|
||||
case 'leave':
|
||||
const stubType = `GROUP_PARTICIPANT_${child.tag!.toUpperCase()}`
|
||||
result.messageStubType = WAMessageStubType[stubType]
|
||||
|
||||
const participants = getBinaryNodeChildren(child, 'participant').map(p => p.attrs.jid)
|
||||
if(
|
||||
participants.length === 1 &&
|
||||
// if recv. "remove" message and sender removed themselves
|
||||
// mark as left
|
||||
areJidsSameUser(participants[0], node.attrs.participant) &&
|
||||
child.tag === 'remove'
|
||||
) {
|
||||
result.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
|
||||
}
|
||||
result.messageStubParameters = participants
|
||||
break
|
||||
case 'subject':
|
||||
result.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT
|
||||
result.messageStubParameters = [ child.attrs.subject ]
|
||||
break
|
||||
case 'announcement':
|
||||
case 'not_announcement':
|
||||
result.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE
|
||||
result.messageStubParameters = [ (child.tag === 'announcement') ? 'on' : 'off' ]
|
||||
break
|
||||
case 'locked':
|
||||
case 'unlocked':
|
||||
result.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
|
||||
result.messageStubParameters = [ (child.tag === 'locked') ? 'on' : 'off' ]
|
||||
break
|
||||
) {
|
||||
result.messageStubType = WAMessageStubType.GROUP_PARTICIPANT_LEAVE
|
||||
}
|
||||
|
||||
result.messageStubParameters = participants
|
||||
break
|
||||
case 'subject':
|
||||
result.messageStubType = WAMessageStubType.GROUP_CHANGE_SUBJECT
|
||||
result.messageStubParameters = [ child.attrs.subject ]
|
||||
break
|
||||
case 'announcement':
|
||||
case 'not_announcement':
|
||||
result.messageStubType = WAMessageStubType.GROUP_CHANGE_ANNOUNCE
|
||||
result.messageStubParameters = [ (child.tag === 'announcement') ? 'on' : 'off' ]
|
||||
break
|
||||
case 'locked':
|
||||
case 'unlocked':
|
||||
result.messageStubType = WAMessageStubType.GROUP_CHANGE_RESTRICT
|
||||
result.messageStubParameters = [ (child.tag === 'locked') ? 'on' : 'off' ]
|
||||
break
|
||||
|
||||
}
|
||||
} else {
|
||||
switch(child.tag) {
|
||||
case 'devices':
|
||||
const devices = getBinaryNodeChildren(child, 'device')
|
||||
if(areJidsSameUser(child.attrs.jid, authState.creds!.me!.id)) {
|
||||
const deviceJids = devices.map(d => d.attrs.jid)
|
||||
logger.info({ deviceJids }, 'got my own devices')
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if(Object.keys(result).length) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
// recv a message
|
||||
ws.on('CB:message', async(stanza: BinaryNode) => {
|
||||
const msg = await decodeMessageStanza(stanza, authState)
|
||||
// message failed to decrypt
|
||||
if(msg.messageStubType === proto.WebMessageInfo.WebMessageInfoStubType.CIPHERTEXT) {
|
||||
logger.error(
|
||||
{ msgId: msg.key.id, params: msg.messageStubParameters },
|
||||
'failure in decrypting message'
|
||||
)
|
||||
await sendRetryRequest(stanza)
|
||||
} else {
|
||||
await sendMessageAck(stanza, { class: 'receipt' })
|
||||
// no type in the receipt => message delivered
|
||||
await sendReceipt(msg.key.remoteJid!, msg.key.participant, [msg.key.id!], undefined)
|
||||
logger.debug({ msg: msg.key }, 'sent delivery receipt')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (child.tag) {
|
||||
case 'devices':
|
||||
const devices = getBinaryNodeChildren(child, 'device')
|
||||
if(areJidsSameUser(child.attrs.jid, authState.creds!.me!.id)) {
|
||||
const deviceJids = devices.map(d => d.attrs.jid)
|
||||
logger.info({ deviceJids }, 'got my own devices')
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if(Object.keys(result).length) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// recv a message
|
||||
ws.on('CB:message', async(stanza: BinaryNode) => {
|
||||
const msg = await decodeMessageStanza(stanza, authState)
|
||||
// message failed to decrypt
|
||||
if(msg.messageStubType === proto.WebMessageInfo.WebMessageInfoStubType.CIPHERTEXT) {
|
||||
logger.error(
|
||||
{ msgId: msg.key.id, params: msg.messageStubParameters },
|
||||
'failure in decrypting message'
|
||||
)
|
||||
await sendRetryRequest(stanza)
|
||||
} else {
|
||||
await sendMessageAck(stanza, { class: 'receipt' })
|
||||
// no type in the receipt => message delivered
|
||||
await sendReceipt(msg.key.remoteJid!, msg.key.participant, [msg.key.id!], undefined)
|
||||
logger.debug({ msg: msg.key }, 'sent delivery receipt')
|
||||
}
|
||||
|
||||
msg.key.remoteJid = jidNormalizedUser(msg.key.remoteJid!)
|
||||
ev.emit('messages.upsert', { messages: [msg], type: stanza.attrs.offline ? 'append' : 'notify' })
|
||||
})
|
||||
msg.key.remoteJid = jidNormalizedUser(msg.key.remoteJid!)
|
||||
ev.emit('messages.upsert', { messages: [msg], type: stanza.attrs.offline ? 'append' : 'notify' })
|
||||
})
|
||||
|
||||
ws.on('CB:ack,class:message', async(node: BinaryNode) => {
|
||||
await sendNode({
|
||||
tag: 'ack',
|
||||
attrs: {
|
||||
class: 'receipt',
|
||||
id: node.attrs.id,
|
||||
from: node.attrs.from
|
||||
}
|
||||
})
|
||||
logger.debug({ attrs: node.attrs }, 'sending receipt for ack')
|
||||
})
|
||||
ws.on('CB:ack,class:message', async(node: BinaryNode) => {
|
||||
await sendNode({
|
||||
tag: 'ack',
|
||||
attrs: {
|
||||
class: 'receipt',
|
||||
id: node.attrs.id,
|
||||
from: node.attrs.from
|
||||
}
|
||||
})
|
||||
logger.debug({ attrs: node.attrs }, 'sending receipt for ack')
|
||||
})
|
||||
|
||||
ws.on('CB:call', async(node: BinaryNode) => {
|
||||
logger.info({ node }, 'recv call')
|
||||
ws.on('CB:call', async(node: BinaryNode) => {
|
||||
logger.info({ node }, 'recv call')
|
||||
|
||||
const [child] = getAllBinaryNodeChildren(node)
|
||||
if(!!child?.tag) {
|
||||
await sendMessageAck(node, { class: 'call', type: child.tag })
|
||||
}
|
||||
})
|
||||
const [child] = getAllBinaryNodeChildren(node)
|
||||
if(!!child?.tag) {
|
||||
await sendMessageAck(node, { class: 'call', type: child.tag })
|
||||
}
|
||||
})
|
||||
|
||||
const sendMessagesAgain = async(key: proto.IMessageKey, ids: string[]) => {
|
||||
const msgs = await Promise.all(
|
||||
ids.map(id => (
|
||||
config.getMessage({ ...key, id })
|
||||
))
|
||||
)
|
||||
const sendMessagesAgain = async(key: proto.IMessageKey, ids: string[]) => {
|
||||
const msgs = await Promise.all(
|
||||
ids.map(id => (
|
||||
config.getMessage({ ...key, id })
|
||||
))
|
||||
)
|
||||
|
||||
const participant = key.participant || key.remoteJid
|
||||
await assertSessions([participant], true)
|
||||
const participant = key.participant || key.remoteJid
|
||||
await assertSessions([participant], true)
|
||||
|
||||
if(isJidGroup(key.remoteJid)) {
|
||||
await authState.keys.set({ 'sender-key-memory': { [key.remoteJid]: null } })
|
||||
}
|
||||
if(isJidGroup(key.remoteJid)) {
|
||||
await authState.keys.set({ 'sender-key-memory': { [key.remoteJid]: null } })
|
||||
}
|
||||
|
||||
logger.debug({ participant }, 'forced new session for retry recp')
|
||||
logger.debug({ participant }, 'forced new session for retry recp')
|
||||
|
||||
for(let i = 0; i < msgs.length;i++) {
|
||||
if(msgs[i]) {
|
||||
await relayMessage(key.remoteJid, msgs[i], {
|
||||
messageId: ids[i],
|
||||
participant
|
||||
})
|
||||
} else {
|
||||
logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available')
|
||||
}
|
||||
}
|
||||
}
|
||||
for(let i = 0; i < msgs.length;i++) {
|
||||
if(msgs[i]) {
|
||||
await relayMessage(key.remoteJid, msgs[i], {
|
||||
messageId: ids[i],
|
||||
participant
|
||||
})
|
||||
} else {
|
||||
logger.debug({ jid: key.remoteJid, id: ids[i] }, 'recv retry request, but message not available')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleReceipt = async(node: BinaryNode) => {
|
||||
let shouldAck = true
|
||||
const handleReceipt = async(node: BinaryNode) => {
|
||||
let shouldAck = true
|
||||
|
||||
const { attrs, content } = node
|
||||
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, authState.creds.me?.id)
|
||||
const remoteJid = !isNodeFromMe ? attrs.from : attrs.recipient
|
||||
const fromMe = !attrs.recipient
|
||||
const { attrs, content } = node
|
||||
const isNodeFromMe = areJidsSameUser(attrs.participant || attrs.from, authState.creds.me?.id)
|
||||
const remoteJid = !isNodeFromMe ? attrs.from : attrs.recipient
|
||||
const fromMe = !attrs.recipient
|
||||
|
||||
const ids = [attrs.id]
|
||||
if(Array.isArray(content)) {
|
||||
const items = getBinaryNodeChildren(content[0], 'item')
|
||||
ids.push(...items.map(i => i.attrs.id))
|
||||
}
|
||||
const ids = [attrs.id]
|
||||
if(Array.isArray(content)) {
|
||||
const items = getBinaryNodeChildren(content[0], 'item')
|
||||
ids.push(...items.map(i => i.attrs.id))
|
||||
}
|
||||
|
||||
const key: proto.IMessageKey = {
|
||||
remoteJid,
|
||||
id: '',
|
||||
fromMe,
|
||||
participant: attrs.participant
|
||||
}
|
||||
const key: proto.IMessageKey = {
|
||||
remoteJid,
|
||||
id: '',
|
||||
fromMe,
|
||||
participant: attrs.participant
|
||||
}
|
||||
|
||||
const status = getStatusFromReceiptType(attrs.type)
|
||||
if(
|
||||
typeof status !== 'undefined' &&
|
||||
const status = getStatusFromReceiptType(attrs.type)
|
||||
if(
|
||||
typeof status !== 'undefined' &&
|
||||
(
|
||||
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
||||
// or another device of ours has read some messages
|
||||
status > proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK ||
|
||||
// basically, we only want to know when a message from us has been delivered to/read by the other person
|
||||
// or another device of ours has read some messages
|
||||
status > proto.WebMessageInfo.WebMessageInfoStatus.DELIVERY_ACK ||
|
||||
!isNodeFromMe
|
||||
)
|
||||
) {
|
||||
ev.emit('messages.update', ids.map(id => ({
|
||||
key: { ...key, id },
|
||||
update: { status }
|
||||
})))
|
||||
}
|
||||
) {
|
||||
ev.emit('messages.update', ids.map(id => ({
|
||||
key: { ...key, id },
|
||||
update: { status }
|
||||
})))
|
||||
}
|
||||
|
||||
if(attrs.type === 'retry') {
|
||||
// correctly set who is asking for the retry
|
||||
key.participant = key.participant || attrs.from
|
||||
if(key.fromMe) {
|
||||
try {
|
||||
logger.debug({ attrs }, 'recv retry request')
|
||||
await sendMessagesAgain(key, ids)
|
||||
} catch(error) {
|
||||
logger.error({ key, ids, trace: error.stack }, 'error in sending message again')
|
||||
shouldAck = false
|
||||
}
|
||||
} else {
|
||||
logger.info({ attrs, key }, 'recv retry for not fromMe message')
|
||||
}
|
||||
}
|
||||
if(attrs.type === 'retry') {
|
||||
// correctly set who is asking for the retry
|
||||
key.participant = key.participant || attrs.from
|
||||
if(key.fromMe) {
|
||||
try {
|
||||
logger.debug({ attrs }, 'recv retry request')
|
||||
await sendMessagesAgain(key, ids)
|
||||
} catch(error) {
|
||||
logger.error({ key, ids, trace: error.stack }, 'error in sending message again')
|
||||
shouldAck = false
|
||||
}
|
||||
} else {
|
||||
logger.info({ attrs, key }, 'recv retry for not fromMe message')
|
||||
}
|
||||
}
|
||||
|
||||
if(shouldAck) {
|
||||
await sendMessageAck(node, { class: 'receipt', type: attrs.type })
|
||||
}
|
||||
if(shouldAck) {
|
||||
await sendMessageAck(node, { class: 'receipt', type: attrs.type })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ws.on('CB:receipt', handleReceipt)
|
||||
ws.on('CB:receipt', handleReceipt)
|
||||
|
||||
ws.on('CB:notification', async(node: BinaryNode) => {
|
||||
await sendMessageAck(node, { class: 'notification', type: node.attrs.type })
|
||||
ws.on('CB:notification', async(node: BinaryNode) => {
|
||||
await sendMessageAck(node, { class: 'notification', type: node.attrs.type })
|
||||
|
||||
const msg = processNotification(node)
|
||||
if(msg) {
|
||||
const fromMe = areJidsSameUser(node.attrs.participant || node.attrs.from, authState.creds.me!.id)
|
||||
msg.key = {
|
||||
remoteJid: node.attrs.from,
|
||||
fromMe,
|
||||
participant: node.attrs.participant,
|
||||
id: node.attrs.id,
|
||||
...(msg.key || {})
|
||||
}
|
||||
msg.messageTimestamp = +node.attrs.t
|
||||
const msg = processNotification(node)
|
||||
if(msg) {
|
||||
const fromMe = areJidsSameUser(node.attrs.participant || node.attrs.from, authState.creds.me!.id)
|
||||
msg.key = {
|
||||
remoteJid: node.attrs.from,
|
||||
fromMe,
|
||||
participant: node.attrs.participant,
|
||||
id: node.attrs.id,
|
||||
...(msg.key || {})
|
||||
}
|
||||
msg.messageTimestamp = +node.attrs.t
|
||||
|
||||
const fullMsg = proto.WebMessageInfo.fromObject(msg)
|
||||
ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' })
|
||||
}
|
||||
})
|
||||
const fullMsg = proto.WebMessageInfo.fromObject(msg)
|
||||
ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' })
|
||||
}
|
||||
})
|
||||
|
||||
ev.on('messages.upsert', async({ messages, type }) => {
|
||||
if(type === 'notify' || type === 'append') {
|
||||
const chat: Partial<Chat> = { id: messages[0].key.remoteJid }
|
||||
const contactNameUpdates: { [_: string]: string } = { }
|
||||
for(const msg of messages) {
|
||||
if(!!msg.pushName) {
|
||||
const jid = msg.key.fromMe ? jidNormalizedUser(authState.creds.me!.id) : (msg.key.participant || msg.key.remoteJid)
|
||||
contactNameUpdates[jid] = msg.pushName
|
||||
// update our pushname too
|
||||
if(msg.key.fromMe && authState.creds.me?.name !== msg.pushName) {
|
||||
ev.emit('creds.update', { me: { ...authState.creds.me!, name: msg.pushName! } })
|
||||
}
|
||||
}
|
||||
ev.on('messages.upsert', async({ messages, type }) => {
|
||||
if(type === 'notify' || type === 'append') {
|
||||
const chat: Partial<Chat> = { id: messages[0].key.remoteJid }
|
||||
const contactNameUpdates: { [_: string]: string } = { }
|
||||
for(const msg of messages) {
|
||||
if(!!msg.pushName) {
|
||||
const jid = msg.key.fromMe ? jidNormalizedUser(authState.creds.me!.id) : (msg.key.participant || msg.key.remoteJid)
|
||||
contactNameUpdates[jid] = msg.pushName
|
||||
// update our pushname too
|
||||
if(msg.key.fromMe && authState.creds.me?.name !== msg.pushName) {
|
||||
ev.emit('creds.update', { me: { ...authState.creds.me!, name: msg.pushName! } })
|
||||
}
|
||||
}
|
||||
|
||||
await processMessage(msg, chat)
|
||||
if(!!msg.message && !msg.message!.protocolMessage) {
|
||||
chat.conversationTimestamp = toNumber(msg.messageTimestamp)
|
||||
if(!msg.key.fromMe) {
|
||||
chat.unreadCount = (chat.unreadCount || 0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Object.keys(chat).length > 1) {
|
||||
ev.emit('chats.update', [ chat ])
|
||||
}
|
||||
if(Object.keys(contactNameUpdates).length) {
|
||||
ev.emit('contacts.update', Object.keys(contactNameUpdates).map(
|
||||
id => ({ id, notify: contactNameUpdates[id] })
|
||||
))
|
||||
}
|
||||
}
|
||||
})
|
||||
await processMessage(msg, chat)
|
||||
if(!!msg.message && !msg.message!.protocolMessage) {
|
||||
chat.conversationTimestamp = toNumber(msg.messageTimestamp)
|
||||
if(!msg.key.fromMe) {
|
||||
chat.unreadCount = (chat.unreadCount || 0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Object.keys(chat).length > 1) {
|
||||
ev.emit('chats.update', [ chat ])
|
||||
}
|
||||
|
||||
if(Object.keys(contactNameUpdates).length) {
|
||||
ev.emit('contacts.update', Object.keys(contactNameUpdates).map(
|
||||
id => ({ id, notify: contactNameUpdates[id] })
|
||||
))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return { ...sock, processMessage, sendMessageAck, sendRetryRequest }
|
||||
}
|
||||
|
||||
@@ -1,439 +1,452 @@
|
||||
|
||||
import { SocketConfig, MediaConnInfo, AnyMessageContent, MiscMessageGenerationOptions, WAMediaUploadFunction, MessageRelayOptions } from "../Types"
|
||||
import { encodeWAMessage, generateMessageID, generateWAMessage, encryptSenderKeyMsgSignalProto, encryptSignalProto, extractDeviceJids, jidToSignalProtocolAddress, parseAndInjectE2ESessions, getWAUploadToServer } from "../Utils"
|
||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET, BinaryNodeAttributes, JidWithDevice, reduceBinaryNodeToDictionary } from '../WABinary'
|
||||
import { proto } from "../../WAProto"
|
||||
import { WA_DEFAULT_EPHEMERAL } from "../Defaults"
|
||||
import { makeGroupsSocket } from "./groups"
|
||||
import NodeCache from "node-cache"
|
||||
import NodeCache from 'node-cache'
|
||||
import { proto } from '../../WAProto'
|
||||
import { WA_DEFAULT_EPHEMERAL } from '../Defaults'
|
||||
import { AnyMessageContent, MediaConnInfo, MessageRelayOptions, MiscMessageGenerationOptions, SocketConfig } from '../Types'
|
||||
import { encodeWAMessage, encryptSenderKeyMsgSignalProto, encryptSignalProto, extractDeviceJids, generateMessageID, generateWAMessage, getWAUploadToServer, jidToSignalProtocolAddress, parseAndInjectE2ESessions } from '../Utils'
|
||||
import { BinaryNode, BinaryNodeAttributes, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidDecode, jidEncode, jidNormalizedUser, JidWithDevice, reduceBinaryNodeToDictionary, S_WHATSAPP_NET } from '../WABinary'
|
||||
import { makeGroupsSocket } from './groups'
|
||||
|
||||
export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const { logger } = config
|
||||
const sock = makeGroupsSocket(config)
|
||||
const {
|
||||
ev,
|
||||
authState,
|
||||
query,
|
||||
generateMessageTag,
|
||||
authState,
|
||||
query,
|
||||
generateMessageTag,
|
||||
sendNode,
|
||||
groupMetadata,
|
||||
groupToggleEphemeral
|
||||
groupMetadata,
|
||||
groupToggleEphemeral
|
||||
} = sock
|
||||
|
||||
const userDevicesCache = config.userDevicesCache || new NodeCache({
|
||||
stdTTL: 300, // 5 minutes
|
||||
useClones: false
|
||||
})
|
||||
let privacySettings: { [_: string]: string } | undefined
|
||||
const userDevicesCache = config.userDevicesCache || new NodeCache({
|
||||
stdTTL: 300, // 5 minutes
|
||||
useClones: false
|
||||
})
|
||||
let privacySettings: { [_: string]: string } | undefined
|
||||
|
||||
const fetchPrivacySettings = async(force: boolean = false) => {
|
||||
if(!privacySettings || force) {
|
||||
const { content } = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
xmlns: 'privacy',
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'get'
|
||||
},
|
||||
content: [
|
||||
{ tag: 'privacy', attrs: { } }
|
||||
]
|
||||
})
|
||||
privacySettings = reduceBinaryNodeToDictionary(content[0] as BinaryNode, 'category')
|
||||
}
|
||||
return privacySettings
|
||||
}
|
||||
const fetchPrivacySettings = async(force: boolean = false) => {
|
||||
if(!privacySettings || force) {
|
||||
const { content } = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
xmlns: 'privacy',
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'get'
|
||||
},
|
||||
content: [
|
||||
{ tag: 'privacy', attrs: { } }
|
||||
]
|
||||
})
|
||||
privacySettings = reduceBinaryNodeToDictionary(content[0] as BinaryNode, 'category')
|
||||
}
|
||||
|
||||
let mediaConn: Promise<MediaConnInfo>
|
||||
const refreshMediaConn = async(forceGet = false) => {
|
||||
let media = await mediaConn
|
||||
if (!media || forceGet || (new Date().getTime()-media.fetchDate.getTime()) > media.ttl*1000) {
|
||||
return privacySettings
|
||||
}
|
||||
|
||||
let mediaConn: Promise<MediaConnInfo>
|
||||
const refreshMediaConn = async(forceGet = false) => {
|
||||
const media = await mediaConn
|
||||
if(!media || forceGet || (new Date().getTime()-media.fetchDate.getTime()) > media.ttl*1000) {
|
||||
mediaConn = (async() => {
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
type: 'set',
|
||||
xmlns: 'w:m',
|
||||
to: S_WHATSAPP_NET,
|
||||
},
|
||||
content: [ { tag: 'media_conn', attrs: { } } ]
|
||||
})
|
||||
const mediaConnNode = getBinaryNodeChild(result, 'media_conn')
|
||||
const node: MediaConnInfo = {
|
||||
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(
|
||||
item => item.attrs as any
|
||||
),
|
||||
auth: mediaConnNode.attrs.auth,
|
||||
ttl: +mediaConnNode.attrs.ttl,
|
||||
fetchDate: new Date()
|
||||
}
|
||||
logger.debug('fetched media conn')
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
type: 'set',
|
||||
xmlns: 'w:m',
|
||||
to: S_WHATSAPP_NET,
|
||||
},
|
||||
content: [ { tag: 'media_conn', attrs: { } } ]
|
||||
})
|
||||
const mediaConnNode = getBinaryNodeChild(result, 'media_conn')
|
||||
const node: MediaConnInfo = {
|
||||
hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(
|
||||
item => item.attrs as any
|
||||
),
|
||||
auth: mediaConnNode.attrs.auth,
|
||||
ttl: +mediaConnNode.attrs.ttl,
|
||||
fetchDate: new Date()
|
||||
}
|
||||
logger.debug('fetched media conn')
|
||||
return node
|
||||
})()
|
||||
}
|
||||
return mediaConn
|
||||
}
|
||||
/**
|
||||
}
|
||||
|
||||
return mediaConn
|
||||
}
|
||||
|
||||
/**
|
||||
* generic send receipt function
|
||||
* used for receipts of phone call, read, delivery etc.
|
||||
* */
|
||||
const sendReceipt = async(jid: string, participant: string | undefined, messageIds: string[], type: 'read' | 'read-self' | undefined) => {
|
||||
const node: BinaryNode = {
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: messageIds[0],
|
||||
t: Date.now().toString(),
|
||||
to: jid,
|
||||
},
|
||||
}
|
||||
if(type) {
|
||||
node.attrs.type = type
|
||||
}
|
||||
if(participant) {
|
||||
node.attrs.participant = participant
|
||||
}
|
||||
const remainingMessageIds = messageIds.slice(1)
|
||||
if(remainingMessageIds.length) {
|
||||
node.content = [
|
||||
{
|
||||
tag: 'list',
|
||||
attrs: { },
|
||||
content: remainingMessageIds.map(id => ({
|
||||
tag: 'item',
|
||||
attrs: { id }
|
||||
}))
|
||||
}
|
||||
]
|
||||
}
|
||||
const sendReceipt = async(jid: string, participant: string | undefined, messageIds: string[], type: 'read' | 'read-self' | undefined) => {
|
||||
const node: BinaryNode = {
|
||||
tag: 'receipt',
|
||||
attrs: {
|
||||
id: messageIds[0],
|
||||
t: Date.now().toString(),
|
||||
to: jid,
|
||||
},
|
||||
}
|
||||
if(type) {
|
||||
node.attrs.type = type
|
||||
}
|
||||
|
||||
logger.debug({ jid, messageIds, type }, 'sending receipt for messages')
|
||||
await sendNode(node)
|
||||
}
|
||||
if(participant) {
|
||||
node.attrs.participant = participant
|
||||
}
|
||||
|
||||
const sendReadReceipt = async(jid: string, participant: string | undefined, messageIds: string[]) => {
|
||||
const privacySettings = await fetchPrivacySettings()
|
||||
// based on privacy settings, we have to change the read type
|
||||
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self'
|
||||
return sendReceipt(jid, participant, messageIds, readType)
|
||||
}
|
||||
const remainingMessageIds = messageIds.slice(1)
|
||||
if(remainingMessageIds.length) {
|
||||
node.content = [
|
||||
{
|
||||
tag: 'list',
|
||||
attrs: { },
|
||||
content: remainingMessageIds.map(id => ({
|
||||
tag: 'item',
|
||||
attrs: { id }
|
||||
}))
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const getUSyncDevices = async(jids: string[], ignoreZeroDevices: boolean) => {
|
||||
const deviceResults: JidWithDevice[] = []
|
||||
logger.debug({ jid, messageIds, type }, 'sending receipt for messages')
|
||||
await sendNode(node)
|
||||
}
|
||||
|
||||
const sendReadReceipt = async(jid: string, participant: string | undefined, messageIds: string[]) => {
|
||||
const privacySettings = await fetchPrivacySettings()
|
||||
// based on privacy settings, we have to change the read type
|
||||
const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self'
|
||||
return sendReceipt(jid, participant, messageIds, readType)
|
||||
}
|
||||
|
||||
const getUSyncDevices = async(jids: string[], ignoreZeroDevices: boolean) => {
|
||||
const deviceResults: JidWithDevice[] = []
|
||||
|
||||
const users: BinaryNode[] = []
|
||||
jids = Array.from(new Set(jids))
|
||||
for(let jid of jids) {
|
||||
const user = jidDecode(jid).user
|
||||
jid = jidNormalizedUser(jid)
|
||||
if(userDevicesCache.has(user)) {
|
||||
const devices: JidWithDevice[] = userDevicesCache.get(user)
|
||||
deviceResults.push(...devices)
|
||||
const users: BinaryNode[] = []
|
||||
jids = Array.from(new Set(jids))
|
||||
for(let jid of jids) {
|
||||
const user = jidDecode(jid).user
|
||||
jid = jidNormalizedUser(jid)
|
||||
if(userDevicesCache.has(user)) {
|
||||
const devices: JidWithDevice[] = userDevicesCache.get(user)
|
||||
deviceResults.push(...devices)
|
||||
|
||||
logger.trace({ user }, 'using cache for devices')
|
||||
} else {
|
||||
users.push({ tag: 'user', attrs: { jid } })
|
||||
}
|
||||
}
|
||||
logger.trace({ user }, 'using cache for devices')
|
||||
} else {
|
||||
users.push({ tag: 'user', attrs: { jid } })
|
||||
}
|
||||
}
|
||||
|
||||
const iq: BinaryNode = {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'get',
|
||||
xmlns: 'usync',
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'usync',
|
||||
attrs: {
|
||||
sid: generateMessageTag(),
|
||||
mode: 'query',
|
||||
last: 'true',
|
||||
index: '0',
|
||||
context: 'message',
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'query',
|
||||
attrs: { },
|
||||
content: [
|
||||
{
|
||||
tag: 'devices',
|
||||
attrs: { version: '2' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{ tag: 'list', attrs: { }, content: users }
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
const result = await query(iq)
|
||||
const extracted = extractDeviceJids(result, authState.creds.me!.id, ignoreZeroDevices)
|
||||
const deviceMap: { [_: string]: JidWithDevice[] } = {}
|
||||
const iq: BinaryNode = {
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
to: S_WHATSAPP_NET,
|
||||
type: 'get',
|
||||
xmlns: 'usync',
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'usync',
|
||||
attrs: {
|
||||
sid: generateMessageTag(),
|
||||
mode: 'query',
|
||||
last: 'true',
|
||||
index: '0',
|
||||
context: 'message',
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'query',
|
||||
attrs: { },
|
||||
content: [
|
||||
{
|
||||
tag: 'devices',
|
||||
attrs: { version: '2' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{ tag: 'list', attrs: { }, content: users }
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
const result = await query(iq)
|
||||
const extracted = extractDeviceJids(result, authState.creds.me!.id, ignoreZeroDevices)
|
||||
const deviceMap: { [_: string]: JidWithDevice[] } = {}
|
||||
|
||||
for(const item of extracted) {
|
||||
deviceMap[item.user] = deviceMap[item.user] || []
|
||||
deviceMap[item.user].push(item)
|
||||
for(const item of extracted) {
|
||||
deviceMap[item.user] = deviceMap[item.user] || []
|
||||
deviceMap[item.user].push(item)
|
||||
|
||||
deviceResults.push(item)
|
||||
}
|
||||
deviceResults.push(item)
|
||||
}
|
||||
|
||||
for(const key in deviceMap) {
|
||||
userDevicesCache.set(key, deviceMap[key])
|
||||
}
|
||||
for(const key in deviceMap) {
|
||||
userDevicesCache.set(key, deviceMap[key])
|
||||
}
|
||||
|
||||
return deviceResults
|
||||
}
|
||||
return deviceResults
|
||||
}
|
||||
|
||||
const assertSessions = async(jids: string[], force: boolean) => {
|
||||
let jidsRequiringFetch: string[] = []
|
||||
if(force) {
|
||||
jidsRequiringFetch = jids
|
||||
} else {
|
||||
const addrs = jids.map(jid => jidToSignalProtocolAddress(jid).toString())
|
||||
const sessions = await authState.keys.get('session', addrs)
|
||||
for(const jid of jids) {
|
||||
const signalId = jidToSignalProtocolAddress(jid).toString()
|
||||
if(!sessions[signalId]) {
|
||||
jidsRequiringFetch.push(jid)
|
||||
}
|
||||
}
|
||||
}
|
||||
const assertSessions = async(jids: string[], force: boolean) => {
|
||||
let jidsRequiringFetch: string[] = []
|
||||
if(force) {
|
||||
jidsRequiringFetch = jids
|
||||
} else {
|
||||
const addrs = jids.map(jid => jidToSignalProtocolAddress(jid).toString())
|
||||
const sessions = await authState.keys.get('session', addrs)
|
||||
for(const jid of jids) {
|
||||
const signalId = jidToSignalProtocolAddress(jid).toString()
|
||||
if(!sessions[signalId]) {
|
||||
jidsRequiringFetch.push(jid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(jidsRequiringFetch.length) {
|
||||
logger.debug({ jidsRequiringFetch }, `fetching sessions`)
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
xmlns: 'encrypt',
|
||||
type: 'get',
|
||||
to: S_WHATSAPP_NET,
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'key',
|
||||
attrs: { },
|
||||
content: jidsRequiringFetch.map(
|
||||
jid => ({
|
||||
tag: 'user',
|
||||
attrs: { jid, reason: 'identity' },
|
||||
})
|
||||
)
|
||||
}
|
||||
]
|
||||
})
|
||||
await parseAndInjectE2ESessions(result, authState)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if(jidsRequiringFetch.length) {
|
||||
logger.debug({ jidsRequiringFetch }, 'fetching sessions')
|
||||
const result = await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
xmlns: 'encrypt',
|
||||
type: 'get',
|
||||
to: S_WHATSAPP_NET,
|
||||
},
|
||||
content: [
|
||||
{
|
||||
tag: 'key',
|
||||
attrs: { },
|
||||
content: jidsRequiringFetch.map(
|
||||
jid => ({
|
||||
tag: 'user',
|
||||
attrs: { jid, reason: 'identity' },
|
||||
})
|
||||
)
|
||||
}
|
||||
]
|
||||
})
|
||||
await parseAndInjectE2ESessions(result, authState)
|
||||
return true
|
||||
}
|
||||
|
||||
const createParticipantNodes = async(jids: string[], bytes: Buffer) => {
|
||||
await assertSessions(jids, false)
|
||||
return false
|
||||
}
|
||||
|
||||
if(authState.keys.isInTransaction()) {
|
||||
await authState.keys.prefetch(
|
||||
'session',
|
||||
jids.map(jid => jidToSignalProtocolAddress(jid).toString())
|
||||
)
|
||||
}
|
||||
const createParticipantNodes = async(jids: string[], bytes: Buffer) => {
|
||||
await assertSessions(jids, false)
|
||||
|
||||
if(authState.keys.isInTransaction()) {
|
||||
await authState.keys.prefetch(
|
||||
'session',
|
||||
jids.map(jid => jidToSignalProtocolAddress(jid).toString())
|
||||
)
|
||||
}
|
||||
|
||||
const nodes = await Promise.all(
|
||||
jids.map(
|
||||
async jid => {
|
||||
const { type, ciphertext } = await encryptSignalProto(jid, bytes, authState)
|
||||
const node: BinaryNode = {
|
||||
tag: 'to',
|
||||
attrs: { jid },
|
||||
content: [{
|
||||
tag: 'enc',
|
||||
attrs: { v: '2', type },
|
||||
content: ciphertext
|
||||
}]
|
||||
}
|
||||
return node
|
||||
}
|
||||
)
|
||||
)
|
||||
return nodes
|
||||
}
|
||||
const nodes = await Promise.all(
|
||||
jids.map(
|
||||
async jid => {
|
||||
const { type, ciphertext } = await encryptSignalProto(jid, bytes, authState)
|
||||
const node: BinaryNode = {
|
||||
tag: 'to',
|
||||
attrs: { jid },
|
||||
content: [{
|
||||
tag: 'enc',
|
||||
attrs: { v: '2', type },
|
||||
content: ciphertext
|
||||
}]
|
||||
}
|
||||
return node
|
||||
}
|
||||
)
|
||||
)
|
||||
return nodes
|
||||
}
|
||||
|
||||
const relayMessage = async(
|
||||
jid: string,
|
||||
message: proto.IMessage,
|
||||
{ messageId: msgId, participant, additionalAttributes, cachedGroupMetadata }: MessageRelayOptions
|
||||
) => {
|
||||
const meId = authState.creds.me!.id
|
||||
const relayMessage = async(
|
||||
jid: string,
|
||||
message: proto.IMessage,
|
||||
{ messageId: msgId, participant, additionalAttributes, cachedGroupMetadata }: MessageRelayOptions
|
||||
) => {
|
||||
const meId = authState.creds.me!.id
|
||||
|
||||
const { user, server } = jidDecode(jid)
|
||||
const isGroup = server === 'g.us'
|
||||
msgId = msgId || generateMessageID()
|
||||
const { user, server } = jidDecode(jid)
|
||||
const isGroup = server === 'g.us'
|
||||
msgId = msgId || generateMessageID()
|
||||
|
||||
const encodedMsg = encodeWAMessage(message)
|
||||
const participants: BinaryNode[] = []
|
||||
const encodedMsg = encodeWAMessage(message)
|
||||
const participants: BinaryNode[] = []
|
||||
|
||||
const destinationJid = jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net')
|
||||
const destinationJid = jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net')
|
||||
|
||||
const binaryNodeContent: BinaryNode[] = []
|
||||
const binaryNodeContent: BinaryNode[] = []
|
||||
|
||||
const devices: JidWithDevice[] = []
|
||||
if(participant) {
|
||||
const { user, device } = jidDecode(participant)
|
||||
devices.push({ user, device })
|
||||
}
|
||||
const devices: JidWithDevice[] = []
|
||||
if(participant) {
|
||||
const { user, device } = jidDecode(participant)
|
||||
devices.push({ user, device })
|
||||
}
|
||||
|
||||
await authState.keys.transaction(
|
||||
async() => {
|
||||
if(isGroup) {
|
||||
const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, meId, authState)
|
||||
await authState.keys.transaction(
|
||||
async() => {
|
||||
if(isGroup) {
|
||||
const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, meId, authState)
|
||||
|
||||
const [groupData, senderKeyMap] = await Promise.all([
|
||||
(async() => {
|
||||
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
||||
if(!groupData) groupData = await groupMetadata(jid)
|
||||
return groupData
|
||||
})(),
|
||||
(async() => {
|
||||
const result = await authState.keys.get('sender-key-memory', [jid])
|
||||
return result[jid] || { }
|
||||
})()
|
||||
])
|
||||
const [groupData, senderKeyMap] = await Promise.all([
|
||||
(async() => {
|
||||
let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
||||
if(!groupData) {
|
||||
groupData = await groupMetadata(jid)
|
||||
}
|
||||
|
||||
return groupData
|
||||
})(),
|
||||
(async() => {
|
||||
const result = await authState.keys.get('sender-key-memory', [jid])
|
||||
return result[jid] || { }
|
||||
})()
|
||||
])
|
||||
|
||||
if(!participant) {
|
||||
const participantsList = groupData.participants.map(p => p.id)
|
||||
const additionalDevices = await getUSyncDevices(participantsList, false)
|
||||
devices.push(...additionalDevices)
|
||||
}
|
||||
if(!participant) {
|
||||
const participantsList = groupData.participants.map(p => p.id)
|
||||
const additionalDevices = await getUSyncDevices(participantsList, false)
|
||||
devices.push(...additionalDevices)
|
||||
}
|
||||
|
||||
const senderKeyJids: string[] = []
|
||||
// ensure a connection is established with every device
|
||||
for(const {user, device} of devices) {
|
||||
const jid = jidEncode(user, 's.whatsapp.net', device)
|
||||
if(!senderKeyMap[jid]) {
|
||||
senderKeyJids.push(jid)
|
||||
// store that this person has had the sender keys sent to them
|
||||
senderKeyMap[jid] = true
|
||||
}
|
||||
}
|
||||
// if there are some participants with whom the session has not been established
|
||||
// if there are, we re-send the senderkey
|
||||
if(senderKeyJids.length) {
|
||||
logger.debug({ senderKeyJids }, 'sending new sender key')
|
||||
const senderKeyJids: string[] = []
|
||||
// ensure a connection is established with every device
|
||||
for(const { user, device } of devices) {
|
||||
const jid = jidEncode(user, 's.whatsapp.net', device)
|
||||
if(!senderKeyMap[jid]) {
|
||||
senderKeyJids.push(jid)
|
||||
// store that this person has had the sender keys sent to them
|
||||
senderKeyMap[jid] = true
|
||||
}
|
||||
}
|
||||
|
||||
// if there are some participants with whom the session has not been established
|
||||
// if there are, we re-send the senderkey
|
||||
if(senderKeyJids.length) {
|
||||
logger.debug({ senderKeyJids }, 'sending new sender key')
|
||||
|
||||
const encSenderKeyMsg = encodeWAMessage({
|
||||
senderKeyDistributionMessage: {
|
||||
axolotlSenderKeyDistributionMessage: senderKeyDistributionMessageKey,
|
||||
groupId: destinationJid
|
||||
}
|
||||
})
|
||||
const encSenderKeyMsg = encodeWAMessage({
|
||||
senderKeyDistributionMessage: {
|
||||
axolotlSenderKeyDistributionMessage: senderKeyDistributionMessageKey,
|
||||
groupId: destinationJid
|
||||
}
|
||||
})
|
||||
|
||||
participants.push(
|
||||
...(await createParticipantNodes(senderKeyJids, encSenderKeyMsg))
|
||||
)
|
||||
}
|
||||
participants.push(
|
||||
...(await createParticipantNodes(senderKeyJids, encSenderKeyMsg))
|
||||
)
|
||||
}
|
||||
|
||||
binaryNodeContent.push({
|
||||
tag: 'enc',
|
||||
attrs: { v: '2', type: 'skmsg' },
|
||||
content: ciphertext
|
||||
})
|
||||
binaryNodeContent.push({
|
||||
tag: 'enc',
|
||||
attrs: { v: '2', type: 'skmsg' },
|
||||
content: ciphertext
|
||||
})
|
||||
|
||||
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
|
||||
} else {
|
||||
const { user: meUser } = jidDecode(meId)
|
||||
await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
|
||||
} else {
|
||||
const { user: meUser } = jidDecode(meId)
|
||||
|
||||
const encodedMeMsg = encodeWAMessage({
|
||||
deviceSentMessage: {
|
||||
destinationJid,
|
||||
message
|
||||
}
|
||||
})
|
||||
const encodedMeMsg = encodeWAMessage({
|
||||
deviceSentMessage: {
|
||||
destinationJid,
|
||||
message
|
||||
}
|
||||
})
|
||||
|
||||
if(!participant) {
|
||||
devices.push({ user })
|
||||
devices.push({ user: meUser })
|
||||
if(!participant) {
|
||||
devices.push({ user })
|
||||
devices.push({ user: meUser })
|
||||
|
||||
const additionalDevices = await getUSyncDevices([ meId, jid ], true)
|
||||
devices.push(...additionalDevices)
|
||||
}
|
||||
const additionalDevices = await getUSyncDevices([ meId, jid ], true)
|
||||
devices.push(...additionalDevices)
|
||||
}
|
||||
|
||||
const meJids: string[] = []
|
||||
const otherJids: string[] = []
|
||||
for(const { user, device } of devices) {
|
||||
const jid = jidEncode(user, 's.whatsapp.net', device)
|
||||
const isMe = user === meUser
|
||||
if(isMe) meJids.push(jid)
|
||||
else otherJids.push(jid)
|
||||
}
|
||||
const meJids: string[] = []
|
||||
const otherJids: string[] = []
|
||||
for(const { user, device } of devices) {
|
||||
const jid = jidEncode(user, 's.whatsapp.net', device)
|
||||
const isMe = user === meUser
|
||||
if(isMe) {
|
||||
meJids.push(jid)
|
||||
} else {
|
||||
otherJids.push(jid)
|
||||
}
|
||||
}
|
||||
|
||||
const [meNodes, otherNodes] = await Promise.all([
|
||||
createParticipantNodes(meJids, encodedMeMsg),
|
||||
createParticipantNodes(otherJids, encodedMsg)
|
||||
])
|
||||
participants.push(...meNodes)
|
||||
participants.push(...otherNodes)
|
||||
}
|
||||
const [meNodes, otherNodes] = await Promise.all([
|
||||
createParticipantNodes(meJids, encodedMeMsg),
|
||||
createParticipantNodes(otherJids, encodedMsg)
|
||||
])
|
||||
participants.push(...meNodes)
|
||||
participants.push(...otherNodes)
|
||||
}
|
||||
|
||||
if(participants.length) {
|
||||
binaryNodeContent.push({
|
||||
tag: 'participants',
|
||||
attrs: { },
|
||||
content: participants
|
||||
})
|
||||
}
|
||||
if(participants.length) {
|
||||
binaryNodeContent.push({
|
||||
tag: 'participants',
|
||||
attrs: { },
|
||||
content: participants
|
||||
})
|
||||
}
|
||||
|
||||
const stanza: BinaryNode = {
|
||||
tag: 'message',
|
||||
attrs: {
|
||||
id: msgId,
|
||||
type: 'text',
|
||||
to: destinationJid,
|
||||
...(additionalAttributes || {})
|
||||
},
|
||||
content: binaryNodeContent
|
||||
}
|
||||
const stanza: BinaryNode = {
|
||||
tag: 'message',
|
||||
attrs: {
|
||||
id: msgId,
|
||||
type: 'text',
|
||||
to: destinationJid,
|
||||
...(additionalAttributes || {})
|
||||
},
|
||||
content: binaryNodeContent
|
||||
}
|
||||
|
||||
const shouldHaveIdentity = !!participants.find(
|
||||
participant => (participant.content! as BinaryNode[]).find(n => n.attrs.type === 'pkmsg')
|
||||
)
|
||||
const shouldHaveIdentity = !!participants.find(
|
||||
participant => (participant.content! as BinaryNode[]).find(n => n.attrs.type === 'pkmsg')
|
||||
)
|
||||
|
||||
if(shouldHaveIdentity) {
|
||||
(stanza.content as BinaryNode[]).push({
|
||||
tag: 'device-identity',
|
||||
attrs: { },
|
||||
content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish()
|
||||
})
|
||||
if(shouldHaveIdentity) {
|
||||
(stanza.content as BinaryNode[]).push({
|
||||
tag: 'device-identity',
|
||||
attrs: { },
|
||||
content: proto.ADVSignedDeviceIdentity.encode(authState.creds.account).finish()
|
||||
})
|
||||
|
||||
logger.debug({ jid }, 'adding device identity')
|
||||
}
|
||||
logger.debug({ jid }, 'adding device identity')
|
||||
}
|
||||
|
||||
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
||||
logger.debug({ msgId }, `sending message to ${participants.length} devices`)
|
||||
|
||||
await sendNode(stanza)
|
||||
}
|
||||
)
|
||||
await sendNode(stanza)
|
||||
}
|
||||
)
|
||||
|
||||
return msgId
|
||||
}
|
||||
return msgId
|
||||
}
|
||||
|
||||
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn)
|
||||
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn)
|
||||
|
||||
return {
|
||||
...sock,
|
||||
assertSessions,
|
||||
relayMessage,
|
||||
sendReceipt,
|
||||
sendReadReceipt,
|
||||
refreshMediaConn,
|
||||
assertSessions,
|
||||
relayMessage,
|
||||
sendReceipt,
|
||||
sendReadReceipt,
|
||||
refreshMediaConn,
|
||||
waUploadToServer,
|
||||
fetchPrivacySettings,
|
||||
sendMessage: async(
|
||||
fetchPrivacySettings,
|
||||
sendMessage: async(
|
||||
jid: string,
|
||||
content: AnyMessageContent,
|
||||
options: MiscMessageGenerationOptions = { }
|
||||
) => {
|
||||
const userJid = authState.creds.me!.id
|
||||
const userJid = authState.creds.me!.id
|
||||
if(
|
||||
typeof content === 'object' &&
|
||||
'disappearingMessagesInChat' in content &&
|
||||
@@ -442,9 +455,9 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
) {
|
||||
const { disappearingMessagesInChat } = content
|
||||
const value = typeof disappearingMessagesInChat === 'boolean' ?
|
||||
(disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
|
||||
disappearingMessagesInChat
|
||||
await groupToggleEphemeral(jid, value)
|
||||
(disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
|
||||
disappearingMessagesInChat
|
||||
await groupToggleEphemeral(jid, value)
|
||||
} else {
|
||||
const fullMsg = await generateWAMessage(
|
||||
jid,
|
||||
@@ -452,28 +465,29 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
{
|
||||
logger,
|
||||
userJid,
|
||||
// multi-device does not have this yet
|
||||
// multi-device does not have this yet
|
||||
//getUrlInfo: generateUrlInfo,
|
||||
upload: waUploadToServer,
|
||||
mediaCache: config.mediaCache,
|
||||
mediaCache: config.mediaCache,
|
||||
...options,
|
||||
}
|
||||
)
|
||||
const isDeleteMsg = 'delete' in content && !!content.delete
|
||||
const additionalAttributes: BinaryNodeAttributes = { }
|
||||
// required for delete
|
||||
if(isDeleteMsg) {
|
||||
additionalAttributes.edit = '7'
|
||||
}
|
||||
const isDeleteMsg = 'delete' in content && !!content.delete
|
||||
const additionalAttributes: BinaryNodeAttributes = { }
|
||||
// required for delete
|
||||
if(isDeleteMsg) {
|
||||
additionalAttributes.edit = '7'
|
||||
}
|
||||
|
||||
await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id!, additionalAttributes })
|
||||
if(config.emitOwnEvents) {
|
||||
process.nextTick(() => {
|
||||
ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' })
|
||||
})
|
||||
}
|
||||
if(config.emitOwnEvents) {
|
||||
process.nextTick(() => {
|
||||
ev.emit('messages.upsert', { messages: [fullMsg], type: 'append' })
|
||||
})
|
||||
}
|
||||
|
||||
return fullMsg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user