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:
@@ -1,8 +1,8 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import EventEmitter from "events"
|
||||
import { LegacyBaileysEventEmitter, LegacySocketConfig, CurveKeyPair, WAInitResponse, ConnectionState, DisconnectReason } from "../Types"
|
||||
import { newLegacyAuthCreds, bindWaitForConnectionUpdate, computeChallengeResponse, validateNewConnection, Curve, printQRIfNecessaryListener } from "../Utils"
|
||||
import { makeSocket } from "./socket"
|
||||
import EventEmitter from 'events'
|
||||
import { ConnectionState, CurveKeyPair, DisconnectReason, LegacyBaileysEventEmitter, LegacySocketConfig, WAInitResponse } from '../Types'
|
||||
import { bindWaitForConnectionUpdate, computeChallengeResponse, Curve, newLegacyAuthCreds, printQRIfNecessaryListener, validateNewConnection } from '../Utils'
|
||||
import { makeSocket } from './socket'
|
||||
|
||||
const makeAuthSocket = (config: LegacySocketConfig) => {
|
||||
const {
|
||||
@@ -60,14 +60,15 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
|
||||
* If connected, invalidates the credentials with the server
|
||||
*/
|
||||
const logout = async() => {
|
||||
if(state.connection === 'open') {
|
||||
await socket.sendNode({
|
||||
if(state.connection === 'open') {
|
||||
await socket.sendNode({
|
||||
json: ['admin', 'Conn', 'disconnect'],
|
||||
tag: 'goodbye'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// will call state update to close connection
|
||||
socket?.end(
|
||||
socket?.end(
|
||||
new Boom('Logged Out', { statusCode: DisconnectReason.loggedOut })
|
||||
)
|
||||
}
|
||||
@@ -86,13 +87,15 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
|
||||
const qr = [ref, publicKey, authInfo.clientID].join(',')
|
||||
updateState({ qr })
|
||||
|
||||
initTimeout = setTimeout(async () => {
|
||||
if(state.connection !== 'connecting') return
|
||||
initTimeout = setTimeout(async() => {
|
||||
if(state.connection !== 'connecting') {
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug('regenerating QR')
|
||||
try {
|
||||
// request new QR
|
||||
const {ref: newRef, ttl: newTTL} = await socket.query({
|
||||
const { ref: newRef, ttl: newTTL } = await socket.query({
|
||||
json: ['admin', 'Conn', 'reref'],
|
||||
expect200: true,
|
||||
longTag: true,
|
||||
@@ -100,94 +103,104 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
|
||||
})
|
||||
ttl = newTTL
|
||||
ref = newRef
|
||||
} catch (error) {
|
||||
logger.error({ error }, `error in QR gen`)
|
||||
if (error.output?.statusCode === 429) { // too many QR requests
|
||||
} catch(error) {
|
||||
logger.error({ error }, 'error in QR gen')
|
||||
if(error.output?.statusCode === 429) { // too many QR requests
|
||||
socket.end(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
qrGens += 1
|
||||
qrLoop(ttl)
|
||||
}, ttl || 20_000) // default is 20s, on the off-chance ttl is not present
|
||||
}
|
||||
|
||||
qrLoop(ttl)
|
||||
}
|
||||
|
||||
const onOpen = async() => {
|
||||
const canDoLogin = canLogin()
|
||||
const initQuery = (async () => {
|
||||
const {ref, ttl} = await socket.query({
|
||||
json: ['admin', 'init', version, browser, authInfo.clientID, true],
|
||||
expect200: true,
|
||||
longTag: true,
|
||||
requiresPhoneConnection: false
|
||||
}) as WAInitResponse
|
||||
const initQuery = (async() => {
|
||||
const { ref, ttl } = await socket.query({
|
||||
json: ['admin', 'init', version, browser, authInfo.clientID, true],
|
||||
expect200: true,
|
||||
longTag: true,
|
||||
requiresPhoneConnection: false
|
||||
}) as WAInitResponse
|
||||
|
||||
if (!canDoLogin) {
|
||||
generateKeysForAuth(ref, ttl)
|
||||
}
|
||||
})();
|
||||
let loginTag: string
|
||||
if(canDoLogin) {
|
||||
if(!canDoLogin) {
|
||||
generateKeysForAuth(ref, ttl)
|
||||
}
|
||||
})()
|
||||
let loginTag: string
|
||||
if(canDoLogin) {
|
||||
updateEncKeys()
|
||||
// if we have the info to restore a closed session
|
||||
const json = [
|
||||
// if we have the info to restore a closed session
|
||||
const json = [
|
||||
'admin',
|
||||
'login',
|
||||
authInfo.clientToken,
|
||||
authInfo.serverToken,
|
||||
authInfo.clientID,
|
||||
'login',
|
||||
authInfo.clientToken,
|
||||
authInfo.serverToken,
|
||||
authInfo.clientID,
|
||||
'takeover'
|
||||
]
|
||||
loginTag = socket.generateMessageTag(true)
|
||||
// send login every 10s
|
||||
const sendLoginReq = () => {
|
||||
if(state.connection === 'open') {
|
||||
logger.warn('Received login timeout req when state=open, ignoring...')
|
||||
return
|
||||
}
|
||||
logger.info('sending login request')
|
||||
socket.sendNode({
|
||||
]
|
||||
loginTag = socket.generateMessageTag(true)
|
||||
// send login every 10s
|
||||
const sendLoginReq = () => {
|
||||
if(state.connection === 'open') {
|
||||
logger.warn('Received login timeout req when state=open, ignoring...')
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('sending login request')
|
||||
socket.sendNode({
|
||||
json,
|
||||
tag: loginTag
|
||||
})
|
||||
initTimeout = setTimeout(sendLoginReq, 10_000)
|
||||
}
|
||||
sendLoginReq()
|
||||
}
|
||||
await initQuery
|
||||
initTimeout = setTimeout(sendLoginReq, 10_000)
|
||||
}
|
||||
|
||||
// wait for response with tag "s1"
|
||||
let response = await Promise.race(
|
||||
[
|
||||
sendLoginReq()
|
||||
}
|
||||
|
||||
await initQuery
|
||||
|
||||
// wait for response with tag "s1"
|
||||
let response = await Promise.race(
|
||||
[
|
||||
socket.waitForMessage('s1', false, undefined).promise,
|
||||
...(loginTag ? [socket.waitForMessage(loginTag, false, connectTimeoutMs).promise] : [])
|
||||
]
|
||||
)
|
||||
initTimeout && clearTimeout(initTimeout)
|
||||
initTimeout = undefined
|
||||
)
|
||||
initTimeout && clearTimeout(initTimeout)
|
||||
initTimeout = undefined
|
||||
|
||||
if(response.status && response.status !== 200) {
|
||||
throw new Boom(`Unexpected error in login`, { data: response, statusCode: response.status })
|
||||
}
|
||||
// if its a challenge request (we get it when logging in)
|
||||
if(response[1]?.challenge) {
|
||||
if(response.status && response.status !== 200) {
|
||||
throw new Boom('Unexpected error in login', { data: response, statusCode: response.status })
|
||||
}
|
||||
|
||||
// if its a challenge request (we get it when logging in)
|
||||
if(response[1]?.challenge) {
|
||||
const json = computeChallengeResponse(response[1].challenge, authInfo)
|
||||
logger.info('resolving login challenge')
|
||||
|
||||
await socket.query({ json, expect200: true, timeoutMs: connectTimeoutMs })
|
||||
|
||||
response = await socket.waitForMessage('s2', true).promise
|
||||
}
|
||||
}
|
||||
|
||||
if(!response || !response[1]) {
|
||||
throw new Boom('Received unexpected login response', { data: response })
|
||||
}
|
||||
|
||||
if(response[1].type === 'upgrade_md_prod') {
|
||||
throw new Boom('Require multi-device edition', { statusCode: DisconnectReason.multideviceMismatch })
|
||||
}
|
||||
// validate the new connection
|
||||
const {user, auth} = validateNewConnection(response[1], authInfo, curveKeys)// validate the connection
|
||||
const isNewLogin = user.id !== state.legacy!.user?.id
|
||||
|
||||
// validate the new connection
|
||||
const { user, auth } = validateNewConnection(response[1], authInfo, curveKeys)// validate the connection
|
||||
const isNewLogin = user.id !== state.legacy!.user?.id
|
||||
|
||||
Object.assign(authInfo, auth)
|
||||
updateEncKeys()
|
||||
@@ -206,6 +219,7 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
|
||||
qr: undefined
|
||||
})
|
||||
}
|
||||
|
||||
ws.once('open', async() => {
|
||||
try {
|
||||
await onOpen()
|
||||
@@ -219,10 +233,10 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
|
||||
process.nextTick(() => {
|
||||
ev.emit('connection.update', {
|
||||
ev.emit('connection.update', {
|
||||
...state
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
...socket,
|
||||
@@ -231,8 +245,9 @@ const makeAuthSocket = (config: LegacySocketConfig) => {
|
||||
ev,
|
||||
canLogin,
|
||||
logout,
|
||||
/** Waits for the connection to WA to reach a state */
|
||||
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev)
|
||||
/** Waits for the connection to WA to reach a state */
|
||||
waitForConnectionUpdate: bindWaitForConnectionUpdate(ev)
|
||||
}
|
||||
}
|
||||
|
||||
export default makeAuthSocket
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BinaryNode, jidNormalizedUser } from "../WABinary";
|
||||
import { Chat, Contact, WAPresence, PresenceData, LegacySocketConfig, WAFlag, WAMetric, WABusinessProfile, ChatModification, WAMessageKey, WAMessageUpdate, BaileysEventMap } from "../Types";
|
||||
import { debouncedTimeout, unixTimestampSeconds } from "../Utils/generics";
|
||||
import makeAuthSocket from "./auth";
|
||||
import { BaileysEventMap, Chat, ChatModification, Contact, LegacySocketConfig, PresenceData, WABusinessProfile, WAFlag, WAMessageKey, WAMessageUpdate, WAMetric, WAPresence } from '../Types'
|
||||
import { debouncedTimeout, unixTimestampSeconds } from '../Utils/generics'
|
||||
import { BinaryNode, jidNormalizedUser } from '../WABinary'
|
||||
import makeAuthSocket from './auth'
|
||||
|
||||
const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
const { logger } = config
|
||||
@@ -22,7 +22,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
sendNode({
|
||||
json: {
|
||||
tag: 'query',
|
||||
attrs: {type: 'chat', epoch: epoch.toString()}
|
||||
attrs: { type: 'chat', epoch: epoch.toString() }
|
||||
},
|
||||
binaryTag: [ WAMetric.queryChat, WAFlag.ignore ]
|
||||
})
|
||||
@@ -43,62 +43,64 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
const updateType = attributes.type
|
||||
const jid = jidNormalizedUser(attributes?.jid)
|
||||
|
||||
switch(updateType) {
|
||||
case 'delete':
|
||||
ev.emit('chats.delete', [jid])
|
||||
break
|
||||
case 'clear':
|
||||
if(node.content) {
|
||||
const ids = (node.content as BinaryNode[]).map(
|
||||
({ attrs }) => attrs.index
|
||||
)
|
||||
ev.emit('messages.delete', { keys: ids.map(id => ({ id, remoteJid: jid })) })
|
||||
} else {
|
||||
ev.emit('messages.delete', { jid, all: true })
|
||||
}
|
||||
break
|
||||
case 'archive':
|
||||
ev.emit('chats.update', [ { id: jid, archive: true } ])
|
||||
break
|
||||
case 'unarchive':
|
||||
ev.emit('chats.update', [ { id: jid, archive: false } ])
|
||||
break
|
||||
case 'pin':
|
||||
ev.emit('chats.update', [ { id: jid, pin: attributes.pin ? +attributes.pin : null } ])
|
||||
break
|
||||
case 'star':
|
||||
case 'unstar':
|
||||
const starred = updateType === 'star'
|
||||
const updates: WAMessageUpdate[] = (node.content as BinaryNode[]).map(
|
||||
({ attrs }) => ({
|
||||
key: {
|
||||
remoteJid: jid,
|
||||
id: attrs.index,
|
||||
fromMe: attrs.owner === 'true'
|
||||
},
|
||||
update: { starred }
|
||||
})
|
||||
switch (updateType) {
|
||||
case 'delete':
|
||||
ev.emit('chats.delete', [jid])
|
||||
break
|
||||
case 'clear':
|
||||
if(node.content) {
|
||||
const ids = (node.content as BinaryNode[]).map(
|
||||
({ attrs }) => attrs.index
|
||||
)
|
||||
ev.emit('messages.update', updates)
|
||||
break
|
||||
case 'mute':
|
||||
if(attributes.mute === '0') {
|
||||
ev.emit('chats.update', [{ id: jid, mute: null }])
|
||||
} else {
|
||||
ev.emit('chats.update', [{ id: jid, mute: +attributes.mute }])
|
||||
}
|
||||
break
|
||||
default:
|
||||
logger.warn({ node }, `received unrecognized chat update`)
|
||||
break
|
||||
ev.emit('messages.delete', { keys: ids.map(id => ({ id, remoteJid: jid })) })
|
||||
} else {
|
||||
ev.emit('messages.delete', { jid, all: true })
|
||||
}
|
||||
|
||||
break
|
||||
case 'archive':
|
||||
ev.emit('chats.update', [ { id: jid, archive: true } ])
|
||||
break
|
||||
case 'unarchive':
|
||||
ev.emit('chats.update', [ { id: jid, archive: false } ])
|
||||
break
|
||||
case 'pin':
|
||||
ev.emit('chats.update', [ { id: jid, pin: attributes.pin ? +attributes.pin : null } ])
|
||||
break
|
||||
case 'star':
|
||||
case 'unstar':
|
||||
const starred = updateType === 'star'
|
||||
const updates: WAMessageUpdate[] = (node.content as BinaryNode[]).map(
|
||||
({ attrs }) => ({
|
||||
key: {
|
||||
remoteJid: jid,
|
||||
id: attrs.index,
|
||||
fromMe: attrs.owner === 'true'
|
||||
},
|
||||
update: { starred }
|
||||
})
|
||||
)
|
||||
ev.emit('messages.update', updates)
|
||||
break
|
||||
case 'mute':
|
||||
if(attributes.mute === '0') {
|
||||
ev.emit('chats.update', [{ id: jid, mute: null }])
|
||||
} else {
|
||||
ev.emit('chats.update', [{ id: jid, mute: +attributes.mute }])
|
||||
}
|
||||
|
||||
break
|
||||
default:
|
||||
logger.warn({ node }, 'received unrecognized chat update')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const applyingPresenceUpdate = (update: BinaryNode['attrs']): BaileysEventMap<any>['presence.update'] => {
|
||||
const id = jidNormalizedUser(update.id)
|
||||
const participant = jidNormalizedUser(update.participant || update.id)
|
||||
const participant = jidNormalizedUser(update.participant || update.id)
|
||||
|
||||
const presence: PresenceData = {
|
||||
const presence: PresenceData = {
|
||||
lastSeen: update.t ? +update.t : undefined,
|
||||
lastKnownPresence: update.type as WAPresence
|
||||
}
|
||||
@@ -126,27 +128,30 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
|
||||
ev.on('connection.update', async({ connection }) => {
|
||||
if(connection !== 'open') return
|
||||
if(connection !== 'open') {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
sendNode({
|
||||
json: { tag: 'query', attrs: {type: 'contacts', epoch: '1'} },
|
||||
json: { tag: 'query', attrs: { type: 'contacts', epoch: '1' } },
|
||||
binaryTag: [ WAMetric.queryContact, WAFlag.ignore ]
|
||||
}),
|
||||
sendNode({
|
||||
json: { tag: 'query', attrs: {type: 'status', epoch: '1'} },
|
||||
json: { tag: 'query', attrs: { type: 'status', epoch: '1' } },
|
||||
binaryTag: [ WAMetric.queryStatus, WAFlag.ignore ]
|
||||
}),
|
||||
sendNode({
|
||||
json: { tag: 'query', attrs: {type: 'quick_reply', epoch: '1'} },
|
||||
json: { tag: 'query', attrs: { type: 'quick_reply', epoch: '1' } },
|
||||
binaryTag: [ WAMetric.queryQuickReply, WAFlag.ignore ]
|
||||
}),
|
||||
sendNode({
|
||||
json: { tag: 'query', attrs: {type: 'label', epoch: '1'} },
|
||||
json: { tag: 'query', attrs: { type: 'label', epoch: '1' } },
|
||||
binaryTag: [ WAMetric.queryLabel, WAFlag.ignore ]
|
||||
}),
|
||||
sendNode({
|
||||
json: { tag: 'query', attrs: {type: 'emoji', epoch: '1'} },
|
||||
json: { tag: 'query', attrs: { type: 'emoji', epoch: '1' } },
|
||||
binaryTag: [ WAMetric.queryEmoji, WAFlag.ignore ]
|
||||
}),
|
||||
sendNode({
|
||||
@@ -154,7 +159,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
tag: 'action',
|
||||
attrs: { type: 'set', epoch: '1' },
|
||||
content: [
|
||||
{ tag: 'presence', attrs: {type: 'available'} }
|
||||
{ tag: 'presence', attrs: { type: 'available' } }
|
||||
]
|
||||
},
|
||||
binaryTag: [ WAMetric.presence, WAFlag.available ]
|
||||
@@ -167,7 +172,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
logger.error(`error in sending init queries: ${error}`)
|
||||
}
|
||||
})
|
||||
socketEvents.on('CB:response,type:chat', async ({ content: data }: BinaryNode) => {
|
||||
socketEvents.on('CB:response,type:chat', async({ content: data }: BinaryNode) => {
|
||||
chatsDebounceTimeout.cancel()
|
||||
if(Array.isArray(data)) {
|
||||
const contacts: Contact[] = []
|
||||
@@ -176,6 +181,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
if(attrs.name) {
|
||||
contacts.push({ id, name: attrs.name })
|
||||
}
|
||||
|
||||
return {
|
||||
id: jidNormalizedUser(attrs.jid),
|
||||
conversationTimestamp: attrs.t ? +attrs.t : undefined,
|
||||
@@ -196,7 +202,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
})
|
||||
// got all contacts from phone
|
||||
socketEvents.on('CB:response,type:contacts', async ({ content: data }: BinaryNode) => {
|
||||
socketEvents.on('CB:response,type:contacts', async({ content: data }: BinaryNode) => {
|
||||
if(Array.isArray(data)) {
|
||||
const contacts = data.map(({ attrs }): Contact => {
|
||||
return {
|
||||
@@ -225,15 +231,18 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
})
|
||||
// read updates
|
||||
socketEvents.on ('CB:action,,read', async ({ content }: BinaryNode) => {
|
||||
socketEvents.on ('CB:action,,read', async({ content }: BinaryNode) => {
|
||||
if(Array.isArray(content)) {
|
||||
const { attrs } = content[0]
|
||||
|
||||
const update: Partial<Chat> = {
|
||||
id: jidNormalizedUser(attrs.jid)
|
||||
}
|
||||
if(attrs.type === 'false') update.unreadCount = -1
|
||||
else update.unreadCount = 0
|
||||
if(attrs.type === 'false') {
|
||||
update.unreadCount = -1
|
||||
} else {
|
||||
update.unreadCount = 0
|
||||
}
|
||||
|
||||
ev.emit('chats.update', [update])
|
||||
}
|
||||
@@ -295,7 +304,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
* @param jid the ID of the person/group you are modifiying
|
||||
*/
|
||||
chatModify: async(modification: ChatModification, jid: string, chatInfo: Pick<Chat, 'mute' | 'pin'>, timestampNow?: number) => {
|
||||
let chatAttrs: BinaryNode['attrs'] = { jid: jid }
|
||||
const chatAttrs: BinaryNode['attrs'] = { jid: jid }
|
||||
let data: BinaryNode[] | undefined = undefined
|
||||
|
||||
timestampNow = timestampNow || unixTimestampSeconds()
|
||||
@@ -356,6 +365,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
// apply it and emit events
|
||||
executeChatModification(node)
|
||||
}
|
||||
|
||||
return response
|
||||
},
|
||||
/**
|
||||
@@ -381,7 +391,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
* @param jid the ID of the person/group who you are updating
|
||||
* @param type your presence
|
||||
*/
|
||||
sendPresenceUpdate: ( type: WAPresence, jid: string | undefined) => (
|
||||
sendPresenceUpdate: (type: WAPresence, jid: string | undefined) => (
|
||||
sendNode({
|
||||
binaryTag: [WAMetric.presence, WAFlag[type]], // weird stuff WA does
|
||||
json: {
|
||||
@@ -400,7 +410,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
* Request updates on the presence of a user
|
||||
* this returns nothing, you'll receive updates in chats.update event
|
||||
* */
|
||||
presenceSubscribe: async (jid: string) => (
|
||||
presenceSubscribe: async(jid: string) => (
|
||||
sendNode({ json: ['action', 'presence', 'subscribe', jid] })
|
||||
),
|
||||
/** Query the status of the person (see groupMetadata() for groups) */
|
||||
@@ -423,11 +433,12 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
},
|
||||
/** Updates business profile. */
|
||||
updateBusinessProfile: async(profile: WABusinessProfile) => {
|
||||
if (profile.business_hours?.config) {
|
||||
if(profile.business_hours?.config) {
|
||||
profile.business_hours.business_config = profile.business_hours.config
|
||||
delete profile.business_hours.config
|
||||
}
|
||||
const json = ['action', "editBusinessProfile", {...profile, v: 2}]
|
||||
|
||||
const json = ['action', 'editBusinessProfile', { ...profile, v: 2 }]
|
||||
await query({ json, expect200: true, requiresPhoneConnection: true })
|
||||
},
|
||||
updateProfileName: async(name: string) => {
|
||||
@@ -447,6 +458,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
} })
|
||||
ev.emit('contacts.update', [{ id: user.id, name }])
|
||||
}
|
||||
|
||||
return response
|
||||
},
|
||||
/**
|
||||
@@ -454,7 +466,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
* @param jid
|
||||
* @param img
|
||||
*/
|
||||
async updateProfilePicture (jid: string, img: Buffer) {
|
||||
async updateProfilePicture(jid: string, img: Buffer) {
|
||||
jid = jidNormalizedUser (jid)
|
||||
const data = { img: Buffer.from([]), preview: Buffer.from([]) } //await generateProfilePicture(img) TODO
|
||||
const tag = this.generateMessageTag ()
|
||||
@@ -480,6 +492,7 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ev.emit('contacts.update', [ { id: jid, imgUrl: eurl } ])
|
||||
}
|
||||
},
|
||||
@@ -513,8 +526,8 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
}]
|
||||
} = await query({
|
||||
json: [
|
||||
"query", "businessProfile",
|
||||
[ { "wid": jid.replace('@s.whatsapp.net', '@c.us') } ],
|
||||
'query', 'businessProfile',
|
||||
[ { 'wid': jid.replace('@s.whatsapp.net', '@c.us') } ],
|
||||
84
|
||||
],
|
||||
expect200: true,
|
||||
@@ -528,4 +541,5 @@ const makeChatsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default makeChatsSocket
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BinaryNode, jidNormalizedUser } from "../WABinary";
|
||||
import { LegacySocketConfig, GroupModificationResponse, ParticipantAction, GroupMetadata, WAFlag, WAMetric, WAGroupCreateResponse, GroupParticipant } from "../Types";
|
||||
import { generateMessageID, unixTimestampSeconds } from "../Utils/generics";
|
||||
import makeMessagesSocket from "./messages";
|
||||
import { GroupMetadata, GroupModificationResponse, GroupParticipant, LegacySocketConfig, ParticipantAction, WAFlag, WAGroupCreateResponse, WAMetric } from '../Types'
|
||||
import { generateMessageID, unixTimestampSeconds } from '../Utils/generics'
|
||||
import { BinaryNode, jidNormalizedUser } from '../WABinary'
|
||||
import makeMessagesSocket from './messages'
|
||||
|
||||
const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
const { logger } = config
|
||||
@@ -17,9 +17,9 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
} = sock
|
||||
|
||||
/** Generic function for group queries */
|
||||
const groupQuery = async(type: string, jid?: string, subject?: string, participants?: string[], additionalNodes?: BinaryNode[]) => {
|
||||
const tag = generateMessageTag()
|
||||
const result = await setQuery ([
|
||||
const groupQuery = async(type: string, jid?: string, subject?: string, participants?: string[], additionalNodes?: BinaryNode[]) => {
|
||||
const tag = generateMessageTag()
|
||||
const result = await setQuery ([
|
||||
{
|
||||
tag: 'group',
|
||||
attrs: {
|
||||
@@ -36,12 +36,12 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
additionalNodes
|
||||
}
|
||||
], [WAMetric.group, 136], tag)
|
||||
return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Get the metadata of the group from WA */
|
||||
const groupMetadataFull = async (jid: string) => {
|
||||
const metadata = await query({
|
||||
/** Get the metadata of the group from WA */
|
||||
const groupMetadataFull = async(jid: string) => {
|
||||
const metadata = await query({
|
||||
json: ['query', 'GroupMetadata', jid],
|
||||
expect200: true
|
||||
})
|
||||
@@ -62,13 +62,14 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
/** Get the metadata (works after you've left the group also) */
|
||||
const groupMetadataMinimal = async (jid: string) => {
|
||||
const { attrs, content }:BinaryNode = await query({
|
||||
}
|
||||
|
||||
/** Get the metadata (works after you've left the group also) */
|
||||
const groupMetadataMinimal = async(jid: string) => {
|
||||
const { attrs, content }:BinaryNode = await query({
|
||||
json: {
|
||||
tag: 'query',
|
||||
attrs: {type: 'group', jid: jid, epoch: currentEpoch().toString()}
|
||||
attrs: { type: 'group', jid: jid, epoch: currentEpoch().toString() }
|
||||
},
|
||||
binaryTag: [WAMetric.group, WAFlag.ignore],
|
||||
expect200: true
|
||||
@@ -89,16 +90,17 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
const meta: GroupMetadata = {
|
||||
id: jid,
|
||||
owner: attrs?.creator,
|
||||
creation: +attrs?.create,
|
||||
subject: null,
|
||||
desc,
|
||||
participants
|
||||
}
|
||||
|
||||
const meta: GroupMetadata = {
|
||||
id: jid,
|
||||
owner: attrs?.creator,
|
||||
creation: +attrs?.create,
|
||||
subject: null,
|
||||
desc,
|
||||
participants
|
||||
}
|
||||
return meta
|
||||
}
|
||||
}
|
||||
|
||||
socketEvents.on('CB:Chat,cmd:action', (json: BinaryNode) => {
|
||||
/*const data = json[1].data
|
||||
@@ -129,8 +131,11 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
groupMetadata: async(jid: string, minimal: boolean) => {
|
||||
let result: GroupMetadata
|
||||
|
||||
if(minimal) result = await groupMetadataMinimal(jid)
|
||||
else result = await groupMetadataFull(jid)
|
||||
if(minimal) {
|
||||
result = await groupMetadataMinimal(jid)
|
||||
} else {
|
||||
result = await groupMetadataFull(jid)
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
@@ -139,13 +144,13 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
* @param title like, the title of the group
|
||||
* @param participants people to include in the group
|
||||
*/
|
||||
groupCreate: async (title: string, participants: string[]) => {
|
||||
groupCreate: async(title: string, participants: string[]) => {
|
||||
const response = await groupQuery('create', null, title, participants) as WAGroupCreateResponse
|
||||
const gid = response.gid
|
||||
let metadata: GroupMetadata
|
||||
try {
|
||||
metadata = await groupMetadataFull(gid)
|
||||
} catch (error) {
|
||||
} catch(error) {
|
||||
logger.warn (`error in group creation: ${error}, switching gid & checking`)
|
||||
// if metadata is not available
|
||||
const comps = gid.replace ('@g.us', '').split ('-')
|
||||
@@ -154,6 +159,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
metadata = await groupMetadataFull(gid)
|
||||
logger.warn (`group ID switched from ${gid} to ${response.gid}`)
|
||||
}
|
||||
|
||||
ev.emit('chats.upsert', [
|
||||
{
|
||||
id: response.gid!,
|
||||
@@ -168,7 +174,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
* Leave a group
|
||||
* @param jid the ID of the group
|
||||
*/
|
||||
groupLeave: async (id: string) => {
|
||||
groupLeave: async(id: string) => {
|
||||
await groupQuery('leave', id)
|
||||
ev.emit('chats.update', [ { id, readOnly: true } ])
|
||||
},
|
||||
@@ -177,7 +183,7 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
* @param {string} jid the ID of the group
|
||||
* @param {string} title the new title of the group
|
||||
*/
|
||||
groupUpdateSubject: async (id: string, title: string) => {
|
||||
groupUpdateSubject: async(id: string, title: string) => {
|
||||
await groupQuery('subject', id, title)
|
||||
ev.emit('chats.update', [ { id, name: title } ])
|
||||
ev.emit('contacts.update', [ { id, name: title } ])
|
||||
@@ -188,11 +194,11 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
* @param {string} jid the ID of the group
|
||||
* @param {string} title the new title of the group
|
||||
*/
|
||||
groupUpdateDescription: async (jid: string, description: string) => {
|
||||
groupUpdateDescription: async(jid: string, description: string) => {
|
||||
const metadata = await groupMetadataFull(jid)
|
||||
const node: BinaryNode = {
|
||||
tag: 'description',
|
||||
attrs: {id: generateMessageID(), prev: metadata?.descId},
|
||||
attrs: { id: generateMessageID(), prev: metadata?.descId },
|
||||
content: Buffer.from(description, 'utf-8')
|
||||
}
|
||||
|
||||
@@ -247,4 +253,5 @@ const makeGroupsSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default makeGroupsSocket
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LegacySocketConfig } from '../Types'
|
||||
import { DEFAULT_LEGACY_CONNECTION_CONFIG } from '../Defaults'
|
||||
import { LegacySocketConfig } from '../Types'
|
||||
import _makeLegacySocket from './groups'
|
||||
// export the last socket layer
|
||||
const makeLegacySocket = (config: Partial<LegacySocketConfig>) => (
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { BinaryNode, getBinaryNodeMessages, isJidGroup, jidNormalizedUser, areJidsSameUser } from "../WABinary";
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { Chat, WAMessageCursor, WAMessage, LegacySocketConfig, WAMessageKey, ParticipantAction, WAMessageStatus, WAMessageStubType, GroupMetadata, AnyMessageContent, MiscMessageGenerationOptions, WAFlag, WAMetric, WAUrlInfo, MediaConnInfo, MessageUpdateType, MessageInfo, MessageInfoUpdate, WAMessageUpdate } from "../Types";
|
||||
import { toNumber, generateWAMessage, decryptMediaMessageBuffer, extractMessageContent, getWAUploadToServer } from "../Utils";
|
||||
import makeChatsSocket from "./chats";
|
||||
import { WA_DEFAULT_EPHEMERAL } from "../Defaults";
|
||||
import { proto } from "../../WAProto";
|
||||
import { proto } from '../../WAProto'
|
||||
import { WA_DEFAULT_EPHEMERAL } from '../Defaults'
|
||||
import { AnyMessageContent, Chat, GroupMetadata, LegacySocketConfig, MediaConnInfo, MessageInfo, MessageInfoUpdate, MessageUpdateType, MiscMessageGenerationOptions, ParticipantAction, WAFlag, WAMessage, WAMessageCursor, WAMessageKey, WAMessageStatus, WAMessageStubType, WAMessageUpdate, WAMetric, WAUrlInfo } from '../Types'
|
||||
import { decryptMediaMessageBuffer, extractMessageContent, generateWAMessage, getWAUploadToServer, toNumber } from '../Utils'
|
||||
import { areJidsSameUser, BinaryNode, getBinaryNodeMessages, isJidGroup, jidNormalizedUser } from '../WABinary'
|
||||
import makeChatsSocket from './chats'
|
||||
|
||||
const STATUS_MAP = {
|
||||
read: WAMessageStatus.READ,
|
||||
message: WAMessageStatus.DELIVERY_ACK,
|
||||
error: WAMessageStatus.ERROR
|
||||
error: WAMessageStatus.ERROR
|
||||
} as { [_: string]: WAMessageStatus }
|
||||
|
||||
const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
@@ -27,10 +27,10 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
|
||||
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) {
|
||||
const media = await mediaConn
|
||||
if(!media || forceGet || (new Date().getTime()-media.fetchDate.getTime()) > media.ttl*1000) {
|
||||
mediaConn = (async() => {
|
||||
const {media_conn} = await query({
|
||||
const { media_conn } = await query({
|
||||
json: ['query', 'mediaConn'],
|
||||
requiresPhoneConnection: false,
|
||||
expect200: true
|
||||
@@ -38,9 +38,10 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
media_conn.fetchDate = new Date()
|
||||
return media_conn as MediaConnInfo
|
||||
})()
|
||||
}
|
||||
return mediaConn
|
||||
}
|
||||
}
|
||||
|
||||
return mediaConn
|
||||
}
|
||||
|
||||
const fetchMessagesFromWA = async(
|
||||
jid: string,
|
||||
@@ -51,7 +52,8 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
if(cursor) {
|
||||
key = 'before' in cursor ? cursor.before : cursor.after
|
||||
}
|
||||
const { content }:BinaryNode = await query({
|
||||
|
||||
const { content }:BinaryNode = await query({
|
||||
json: {
|
||||
tag: 'query',
|
||||
attrs: {
|
||||
@@ -71,15 +73,18 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
if(Array.isArray(content)) {
|
||||
return content.map(data => proto.WebMessageInfo.decode(data.content as Buffer))
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const updateMediaMessage = async(message: WAMessage) => {
|
||||
const content = message.message?.audioMessage || message.message?.videoMessage || message.message?.imageMessage || message.message?.stickerMessage || message.message?.documentMessage
|
||||
if (!content) throw new Boom(
|
||||
`given message ${message.key.id} is not a media message`,
|
||||
{ statusCode: 400, data: message }
|
||||
)
|
||||
if(!content) {
|
||||
throw new Boom(
|
||||
`given message ${message.key.id} is not a media message`,
|
||||
{ statusCode: 400, data: message }
|
||||
)
|
||||
}
|
||||
|
||||
const response: BinaryNode = await query ({
|
||||
json: {
|
||||
@@ -133,35 +138,36 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
}
|
||||
|
||||
const protocolMessage = message.message?.protocolMessage || message.message?.ephemeralMessage?.message?.protocolMessage
|
||||
// if it's a message to delete another message
|
||||
if (protocolMessage) {
|
||||
switch (protocolMessage.type) {
|
||||
case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
|
||||
const key = protocolMessage.key
|
||||
const messageStubType = WAMessageStubType.REVOKE
|
||||
ev.emit('messages.update', [
|
||||
{
|
||||
// the key of the deleted message is updated
|
||||
update: { message: null, key: message.key, messageStubType },
|
||||
key
|
||||
}
|
||||
])
|
||||
return
|
||||
case proto.ProtocolMessage.ProtocolMessageType.EPHEMERAL_SETTING:
|
||||
chatUpdate.ephemeralSettingTimestamp = message.messageTimestamp
|
||||
// if it's a message to delete another message
|
||||
if(protocolMessage) {
|
||||
switch (protocolMessage.type) {
|
||||
case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
|
||||
const key = protocolMessage.key
|
||||
const messageStubType = WAMessageStubType.REVOKE
|
||||
ev.emit('messages.update', [
|
||||
{
|
||||
// the key of the deleted message is updated
|
||||
update: { message: null, key: message.key, messageStubType },
|
||||
key
|
||||
}
|
||||
])
|
||||
return
|
||||
case proto.ProtocolMessage.ProtocolMessageType.EPHEMERAL_SETTING:
|
||||
chatUpdate.ephemeralSettingTimestamp = message.messageTimestamp
|
||||
chatUpdate.ephemeralExpiration = protocolMessage.ephemeralExpiration
|
||||
|
||||
if(isJidGroup(jid)) {
|
||||
emitGroupUpdate({ ephemeralDuration: protocolMessage.ephemeralExpiration || null })
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if(isJidGroup(jid)) {
|
||||
emitGroupUpdate({ ephemeralDuration: protocolMessage.ephemeralExpiration || null })
|
||||
}
|
||||
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// check if the message is an action
|
||||
if (message.messageStubType) {
|
||||
if(message.messageStubType) {
|
||||
const { user } = state.legacy!
|
||||
//let actor = jidNormalizedUser (message.participant)
|
||||
let participants: string[]
|
||||
@@ -170,44 +176,47 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
)
|
||||
|
||||
switch (message.messageStubType) {
|
||||
case WAMessageStubType.CHANGE_EPHEMERAL_SETTING:
|
||||
chatUpdate.ephemeralSettingTimestamp = message.messageTimestamp
|
||||
chatUpdate.ephemeralExpiration = +message.messageStubParameters[0]
|
||||
if(isJidGroup(jid)) {
|
||||
emitGroupUpdate({ ephemeralDuration: +message.messageStubParameters[0] || null })
|
||||
}
|
||||
break
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
|
||||
participants = message.messageStubParameters.map (jidNormalizedUser)
|
||||
emitParticipantsUpdate('remove')
|
||||
// mark the chat read only if you left the group
|
||||
if (participants.includes(user.id)) {
|
||||
chatUpdate.readOnly = true
|
||||
}
|
||||
break
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
|
||||
participants = message.messageStubParameters.map (jidNormalizedUser)
|
||||
if (participants.includes(user.id)) {
|
||||
chatUpdate.readOnly = null
|
||||
}
|
||||
emitParticipantsUpdate('add')
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_ANNOUNCE:
|
||||
const announce = message.messageStubParameters[0] === 'on'
|
||||
emitGroupUpdate({ announce })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_RESTRICT:
|
||||
const restrict = message.messageStubParameters[0] === 'on'
|
||||
emitGroupUpdate({ restrict })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_SUBJECT:
|
||||
case WAMessageStubType.GROUP_CREATE:
|
||||
chatUpdate.name = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ subject: chatUpdate.name })
|
||||
break
|
||||
case WAMessageStubType.CHANGE_EPHEMERAL_SETTING:
|
||||
chatUpdate.ephemeralSettingTimestamp = message.messageTimestamp
|
||||
chatUpdate.ephemeralExpiration = +message.messageStubParameters[0]
|
||||
if(isJidGroup(jid)) {
|
||||
emitGroupUpdate({ ephemeralDuration: +message.messageStubParameters[0] || null })
|
||||
}
|
||||
|
||||
break
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
|
||||
participants = message.messageStubParameters.map (jidNormalizedUser)
|
||||
emitParticipantsUpdate('remove')
|
||||
// mark the chat read only if you left the group
|
||||
if(participants.includes(user.id)) {
|
||||
chatUpdate.readOnly = true
|
||||
}
|
||||
|
||||
break
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
|
||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
|
||||
participants = message.messageStubParameters.map (jidNormalizedUser)
|
||||
if(participants.includes(user.id)) {
|
||||
chatUpdate.readOnly = null
|
||||
}
|
||||
|
||||
emitParticipantsUpdate('add')
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_ANNOUNCE:
|
||||
const announce = message.messageStubParameters[0] === 'on'
|
||||
emitGroupUpdate({ announce })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_RESTRICT:
|
||||
const restrict = message.messageStubParameters[0] === 'on'
|
||||
emitGroupUpdate({ restrict })
|
||||
break
|
||||
case WAMessageStubType.GROUP_CHANGE_SUBJECT:
|
||||
case WAMessageStubType.GROUP_CREATE:
|
||||
chatUpdate.name = message.messageStubParameters[0]
|
||||
emitGroupUpdate({ subject: chatUpdate.name })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +230,8 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn)
|
||||
|
||||
/** Query a string to check if it has a url, if it does, return WAUrlInfo */
|
||||
const generateUrlInfo = async(text: string) => {
|
||||
const response: BinaryNode = await query({
|
||||
const generateUrlInfo = async(text: string) => {
|
||||
const response: BinaryNode = await query({
|
||||
json: {
|
||||
tag: 'query',
|
||||
attrs: {
|
||||
@@ -236,14 +245,15 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
requiresPhoneConnection: false
|
||||
})
|
||||
const urlInfo = { ...response.attrs } as any as WAUrlInfo
|
||||
if(response && response.content) {
|
||||
urlInfo.jpegThumbnail = response.content as Buffer
|
||||
}
|
||||
return urlInfo
|
||||
}
|
||||
if(response && response.content) {
|
||||
urlInfo.jpegThumbnail = response.content as Buffer
|
||||
}
|
||||
|
||||
return urlInfo
|
||||
}
|
||||
|
||||
/** Relay (send) a WAMessage; more advanced functionality to send a built WA Message, you may want to stick with sendMessage() */
|
||||
const relayMessage = async(message: WAMessage, { waitForAck } = { waitForAck: true }) => {
|
||||
const relayMessage = async(message: WAMessage, { waitForAck } = { waitForAck: true }) => {
|
||||
const json: BinaryNode = {
|
||||
tag: 'action',
|
||||
attrs: { epoch: currentEpoch().toString(), type: 'relay' },
|
||||
@@ -256,35 +266,37 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
]
|
||||
}
|
||||
const isMsgToMe = areJidsSameUser(message.key.remoteJid, state.legacy.user?.id || '')
|
||||
const flag = isMsgToMe ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself
|
||||
const mID = message.key.id
|
||||
const flag = isMsgToMe ? WAFlag.acknowledge : WAFlag.ignore // acknowledge when sending message to oneself
|
||||
const mID = message.key.id
|
||||
const finalState = isMsgToMe ? WAMessageStatus.READ : WAMessageStatus.SERVER_ACK
|
||||
|
||||
message.status = WAMessageStatus.PENDING
|
||||
const promise = query({
|
||||
json,
|
||||
binaryTag: [WAMetric.message, flag],
|
||||
tag: mID,
|
||||
expect200: true,
|
||||
requiresPhoneConnection: true
|
||||
})
|
||||
message.status = WAMessageStatus.PENDING
|
||||
const promise = query({
|
||||
json,
|
||||
binaryTag: [WAMetric.message, flag],
|
||||
tag: mID,
|
||||
expect200: true,
|
||||
requiresPhoneConnection: true
|
||||
})
|
||||
|
||||
if(waitForAck) {
|
||||
await promise
|
||||
if(waitForAck) {
|
||||
await promise
|
||||
message.status = finalState
|
||||
} else {
|
||||
const emitUpdate = (status: WAMessageStatus) => {
|
||||
message.status = status
|
||||
ev.emit('messages.update', [ { key: message.key, update: { status } } ])
|
||||
}
|
||||
promise
|
||||
} else {
|
||||
const emitUpdate = (status: WAMessageStatus) => {
|
||||
message.status = status
|
||||
ev.emit('messages.update', [ { key: message.key, update: { status } } ])
|
||||
}
|
||||
|
||||
promise
|
||||
.then(() => emitUpdate(finalState))
|
||||
.catch(() => emitUpdate(WAMessageStatus.ERROR))
|
||||
}
|
||||
}
|
||||
|
||||
if(config.emitOwnEvents) {
|
||||
onMessage(message, 'append')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// messages received
|
||||
const messagesUpdate = (node: BinaryNode, isLatest: boolean) => {
|
||||
@@ -330,26 +342,30 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
logger.warn({ content, key }, 'got unknown status update for message')
|
||||
}
|
||||
}
|
||||
|
||||
ev.emit('messages.update', updates)
|
||||
}
|
||||
}
|
||||
const onMessageInfoUpdate = ([,attributes]: [string,{[_: string]: any}]) => {
|
||||
|
||||
const onMessageInfoUpdate = ([, attributes]: [string, {[_: string]: any}]) => {
|
||||
let ids = attributes.id as string[] | string
|
||||
if(typeof ids === 'string') {
|
||||
ids = [ids]
|
||||
}
|
||||
|
||||
let updateKey: keyof MessageInfoUpdate['update']
|
||||
switch(attributes.ack.toString()) {
|
||||
case '2':
|
||||
updateKey = 'deliveries'
|
||||
break
|
||||
case '3':
|
||||
updateKey = 'reads'
|
||||
break
|
||||
default:
|
||||
logger.warn({ attributes }, `received unknown message info update`)
|
||||
return
|
||||
switch (attributes.ack.toString()) {
|
||||
case '2':
|
||||
updateKey = 'deliveries'
|
||||
break
|
||||
case '3':
|
||||
updateKey = 'reads'
|
||||
break
|
||||
default:
|
||||
logger.warn({ attributes }, 'received unknown message info update')
|
||||
return
|
||||
}
|
||||
|
||||
const keyPartial = {
|
||||
remoteJid: jidNormalizedUser(attributes.to),
|
||||
fromMe: areJidsSameUser(attributes.from, state.legacy?.user?.id || ''),
|
||||
@@ -406,38 +422,43 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
const [{ attrs }] = (innerData as BinaryNode[])
|
||||
const jid = jidNormalizedUser(attrs.jid)
|
||||
const date = new Date(+attrs.t * 1000)
|
||||
switch(tag) {
|
||||
case 'read':
|
||||
info.reads[jid] = date
|
||||
break
|
||||
case 'delivery':
|
||||
info.deliveries[jid] = date
|
||||
break
|
||||
switch (tag) {
|
||||
case 'read':
|
||||
info.reads[jid] = date
|
||||
break
|
||||
case 'delivery':
|
||||
info.deliveries[jid] = date
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
},
|
||||
downloadMediaMessage: async(message: WAMessage, type: 'buffer' | 'stream' = 'buffer') => {
|
||||
const downloadMediaMessage = async () => {
|
||||
let mContent = extractMessageContent(message.message)
|
||||
if (!mContent) throw new Boom('No message present', { statusCode: 400, data: message })
|
||||
const downloadMediaMessage = async() => {
|
||||
const mContent = extractMessageContent(message.message)
|
||||
if(!mContent) {
|
||||
throw new Boom('No message present', { statusCode: 400, data: message })
|
||||
}
|
||||
|
||||
const stream = await decryptMediaMessageBuffer(mContent)
|
||||
if(type === 'buffer') {
|
||||
let buffer = Buffer.from([])
|
||||
for await(const chunk of stream) {
|
||||
for await (const chunk of stream) {
|
||||
buffer = Buffer.concat([buffer, chunk])
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await downloadMediaMessage()
|
||||
return result
|
||||
} catch (error) {
|
||||
} catch(error) {
|
||||
if(error.message.includes('404')) { // media needs to be updated
|
||||
logger.info (`updating media of message: ${message.key.id}`)
|
||||
|
||||
@@ -446,6 +467,7 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
const result = await downloadMediaMessage()
|
||||
return result
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
},
|
||||
@@ -453,15 +475,15 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
fetchMessagesFromWA,
|
||||
/** Load a single message specified by the ID */
|
||||
loadMessageFromWA: async(jid: string, id: string) => {
|
||||
let message: WAMessage
|
||||
|
||||
// load the message before the given message
|
||||
let messages = (await fetchMessagesFromWA(jid, 1, { before: {id, fromMe: true} }))
|
||||
if(!messages[0]) messages = (await fetchMessagesFromWA(jid, 1, { before: {id, fromMe: false} }))
|
||||
let messages = (await fetchMessagesFromWA(jid, 1, { before: { id, fromMe: true } }))
|
||||
if(!messages[0]) {
|
||||
messages = (await fetchMessagesFromWA(jid, 1, { before: { id, fromMe: false } }))
|
||||
}
|
||||
|
||||
// the message after the loaded message is the message required
|
||||
const [actual] = await fetchMessagesFromWA(jid, 1, { after: messages[0] && messages[0].key })
|
||||
message = actual
|
||||
return message
|
||||
return actual
|
||||
},
|
||||
searchMessages: async(txt: string, inJid: string | null, count: number, page: number) => {
|
||||
const node: BinaryNode = await query({
|
||||
@@ -499,8 +521,8 @@ const makeMessagesSocket = (config: LegacySocketConfig) => {
|
||||
) {
|
||||
const { disappearingMessagesInChat } = content
|
||||
const value = typeof disappearingMessagesInChat === 'boolean' ?
|
||||
(disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
|
||||
disappearingMessagesInChat
|
||||
(disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0) :
|
||||
disappearingMessagesInChat
|
||||
const tag = generateMessageTag(true)
|
||||
await setQuery([
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Boom } from '@hapi/boom'
|
||||
import { STATUS_CODES } from "http"
|
||||
import { promisify } from "util"
|
||||
import WebSocket from "ws"
|
||||
import { BinaryNode, encodeBinaryNodeLegacy } from "../WABinary"
|
||||
import { DisconnectReason, LegacySocketConfig, SocketQueryOptions, SocketSendMessageOptions, WAFlag, WAMetric, WATag } from "../Types"
|
||||
import { aesEncrypt, hmacSign, promiseTimeout, unixTimestampSeconds, decodeWAMessage } from "../Utils"
|
||||
import { DEFAULT_ORIGIN, DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, PHONE_CONNECTION_CB } from "../Defaults"
|
||||
import { STATUS_CODES } from 'http'
|
||||
import { promisify } from 'util'
|
||||
import WebSocket from 'ws'
|
||||
import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, DEFAULT_ORIGIN, PHONE_CONNECTION_CB } from '../Defaults'
|
||||
import { DisconnectReason, LegacySocketConfig, SocketQueryOptions, SocketSendMessageOptions, WAFlag, WAMetric, WATag } from '../Types'
|
||||
import { aesEncrypt, decodeWAMessage, hmacSign, promiseTimeout, unixTimestampSeconds } from '../Utils'
|
||||
import { BinaryNode, encodeBinaryNodeLegacy } from '../WABinary'
|
||||
|
||||
/**
|
||||
* Connects to WA servers and performs:
|
||||
@@ -14,13 +14,13 @@ import { DEFAULT_ORIGIN, DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, PHONE_CONNECTION_C
|
||||
* - query phone connection
|
||||
*/
|
||||
export const makeSocket = ({
|
||||
waWebSocketUrl,
|
||||
connectTimeoutMs,
|
||||
phoneResponseTimeMs,
|
||||
logger,
|
||||
agent,
|
||||
keepAliveIntervalMs,
|
||||
expectResponseTimeout,
|
||||
waWebSocketUrl,
|
||||
connectTimeoutMs,
|
||||
phoneResponseTimeMs,
|
||||
logger,
|
||||
agent,
|
||||
keepAliveIntervalMs,
|
||||
expectResponseTimeout,
|
||||
}: LegacySocketConfig) => {
|
||||
// for generating tags
|
||||
const referenceDateSeconds = unixTimestampSeconds(new Date())
|
||||
@@ -37,33 +37,35 @@ export const makeSocket = ({
|
||||
'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
|
||||
}
|
||||
})
|
||||
ws.setMaxListeners(0)
|
||||
ws.setMaxListeners(0)
|
||||
let lastDateRecv: Date
|
||||
let epoch = 0
|
||||
let authInfo: { encKey: Buffer, macKey: Buffer }
|
||||
let keepAliveReq: NodeJS.Timeout
|
||||
|
||||
let phoneCheckInterval: NodeJS.Timeout
|
||||
let phoneCheckListeners = 0
|
||||
let phoneCheckInterval: NodeJS.Timeout
|
||||
let phoneCheckListeners = 0
|
||||
|
||||
const phoneConnectionChanged = (value: boolean) => {
|
||||
ws.emit('phone-connection', { value })
|
||||
}
|
||||
const phoneConnectionChanged = (value: boolean) => {
|
||||
ws.emit('phone-connection', { value })
|
||||
}
|
||||
|
||||
const sendPromise = promisify(ws.send)
|
||||
/** generate message tag and increment epoch */
|
||||
const generateMessageTag = (longTag: boolean = false) => {
|
||||
const tag = `${longTag ? referenceDateSeconds : (referenceDateSeconds%1000)}.--${epoch}`
|
||||
epoch += 1 // increment message count, it makes the 'epoch' field when sending binary messages
|
||||
return tag
|
||||
}
|
||||
const sendRawMessage = (data: Buffer | string) => {
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
const tag = `${longTag ? referenceDateSeconds : (referenceDateSeconds%1000)}.--${epoch}`
|
||||
epoch += 1 // increment message count, it makes the 'epoch' field when sending binary messages
|
||||
return tag
|
||||
}
|
||||
|
||||
const sendRawMessage = (data: Buffer | string) => {
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
throw new Boom('Connection Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
|
||||
return sendPromise.call(ws, data) as Promise<void>
|
||||
}
|
||||
|
||||
return sendPromise.call(ws, data) as Promise<void>
|
||||
}
|
||||
/**
|
||||
* Send a message to the WA servers
|
||||
* @returns the tag attached in the message
|
||||
@@ -73,17 +75,19 @@ export const makeSocket = ({
|
||||
) => {
|
||||
tag = tag || generateMessageTag(longTag)
|
||||
let data: Buffer | string
|
||||
if(logger.level === 'trace') {
|
||||
logger.trace({ tag, fromMe: true, json, binaryTag }, 'communication')
|
||||
}
|
||||
if(logger.level === 'trace') {
|
||||
logger.trace({ tag, fromMe: true, json, binaryTag }, 'communication')
|
||||
}
|
||||
|
||||
if(binaryTag) {
|
||||
if(Array.isArray(json)) {
|
||||
throw new Boom('Expected BinaryNode with binary code', { statusCode: 400 })
|
||||
}
|
||||
if(Array.isArray(json)) {
|
||||
throw new Boom('Expected BinaryNode with binary code', { statusCode: 400 })
|
||||
}
|
||||
|
||||
if(!authInfo) {
|
||||
throw new Boom('No encryption/mac keys to encrypt node with', { statusCode: 400 })
|
||||
}
|
||||
|
||||
const binary = encodeBinaryNodeLegacy(json) // encode the JSON to the WhatsApp binary format
|
||||
|
||||
const buff = aesEncrypt(binary, authInfo.encKey) // encrypt it using AES and our encKey
|
||||
@@ -98,242 +102,268 @@ export const makeSocket = ({
|
||||
} else {
|
||||
data = `${tag},${JSON.stringify(json)}`
|
||||
}
|
||||
|
||||
await sendRawMessage(data)
|
||||
return tag
|
||||
}
|
||||
|
||||
const end = (error: Error | undefined) => {
|
||||
logger.info({ error }, 'connection closed')
|
||||
logger.info({ error }, 'connection closed')
|
||||
|
||||
ws.removeAllListeners('close')
|
||||
ws.removeAllListeners('error')
|
||||
ws.removeAllListeners('open')
|
||||
ws.removeAllListeners('message')
|
||||
ws.removeAllListeners('close')
|
||||
ws.removeAllListeners('error')
|
||||
ws.removeAllListeners('open')
|
||||
ws.removeAllListeners('message')
|
||||
|
||||
phoneCheckListeners = 0
|
||||
clearInterval(keepAliveReq)
|
||||
clearPhoneCheckInterval()
|
||||
phoneCheckListeners = 0
|
||||
clearInterval(keepAliveReq)
|
||||
clearPhoneCheckInterval()
|
||||
|
||||
if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
|
||||
try { ws.close() } catch { }
|
||||
}
|
||||
if(ws.readyState !== ws.CLOSED && ws.readyState !== ws.CLOSING) {
|
||||
try {
|
||||
ws.close()
|
||||
} catch{ }
|
||||
}
|
||||
|
||||
ws.emit('ws-close', error)
|
||||
ws.removeAllListeners('ws-close')
|
||||
ws.emit('ws-close', error)
|
||||
ws.removeAllListeners('ws-close')
|
||||
}
|
||||
|
||||
const onMessageRecieved = (message: string | Buffer) => {
|
||||
if(message[0] === '!' || message[0] === '!'.charCodeAt(0)) {
|
||||
// when the first character in the message is an '!', the server is sending a pong frame
|
||||
const timestamp = message.slice(1, message.length).toString()
|
||||
lastDateRecv = new Date(parseInt(timestamp))
|
||||
ws.emit('received-pong')
|
||||
} else {
|
||||
let messageTag: string
|
||||
let json: any
|
||||
try {
|
||||
const dec = decodeWAMessage(message, authInfo)
|
||||
messageTag = dec[0]
|
||||
json = dec[1]
|
||||
if (!json) return
|
||||
} catch (error) {
|
||||
end(error)
|
||||
if(message[0] === '!' || message[0] === '!'.charCodeAt(0)) {
|
||||
// when the first character in the message is an '!', the server is sending a pong frame
|
||||
const timestamp = message.slice(1, message.length).toString()
|
||||
lastDateRecv = new Date(parseInt(timestamp))
|
||||
ws.emit('received-pong')
|
||||
} else {
|
||||
let messageTag: string
|
||||
let json: any
|
||||
try {
|
||||
const dec = decodeWAMessage(message, authInfo)
|
||||
messageTag = dec[0]
|
||||
json = dec[1]
|
||||
if(!json) {
|
||||
return
|
||||
}
|
||||
} catch(error) {
|
||||
end(error)
|
||||
return
|
||||
}
|
||||
//if (this.shouldLogMessages) this.messageLog.push ({ tag: messageTag, json: JSON.stringify(json), fromMe: false })
|
||||
}
|
||||
//if (this.shouldLogMessages) this.messageLog.push ({ tag: messageTag, json: JSON.stringify(json), fromMe: false })
|
||||
|
||||
if (logger.level === 'trace') {
|
||||
logger.trace({ tag: messageTag, fromMe: false, json }, 'communication')
|
||||
}
|
||||
if(logger.level === 'trace') {
|
||||
logger.trace({ tag: messageTag, fromMe: false, json }, 'communication')
|
||||
}
|
||||
|
||||
let anyTriggered = false
|
||||
/* Check if this is a response to a message we sent */
|
||||
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${messageTag}`, json)
|
||||
/* Check if this is a response to a message we are expecting */
|
||||
const l0 = json.tag || json[0] || ''
|
||||
const l1 = json?.attrs || json?.[1] || { }
|
||||
const l2 = json?.content?.[0]?.tag || json[2]?.[0] || ''
|
||||
let anyTriggered = false
|
||||
/* Check if this is a response to a message we sent */
|
||||
anyTriggered = ws.emit(`${DEF_TAG_PREFIX}${messageTag}`, json)
|
||||
/* Check if this is a response to a message we are expecting */
|
||||
const l0 = json.tag || json[0] || ''
|
||||
const l1 = json?.attrs || json?.[1] || { }
|
||||
const l2 = json?.content?.[0]?.tag || json[2]?.[0] || ''
|
||||
|
||||
Object.keys(l1).forEach(key => {
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, json) || anyTriggered
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, json) || anyTriggered
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}`, json) || anyTriggered
|
||||
})
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},,${l2}`, json) || anyTriggered
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0}`, json) || anyTriggered
|
||||
Object.keys(l1).forEach(key => {
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]},${l2}`, json) || anyTriggered
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}:${l1[key]}`, json) || anyTriggered
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},${key}`, json) || anyTriggered
|
||||
})
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0},,${l2}`, json) || anyTriggered
|
||||
anyTriggered = ws.emit(`${DEF_CALLBACK_PREFIX}${l0}`, json) || anyTriggered
|
||||
|
||||
if (!anyTriggered && logger.level === 'debug') {
|
||||
logger.debug({ unhandled: true, tag: messageTag, fromMe: false, json }, 'communication recv')
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!anyTriggered && logger.level === 'debug') {
|
||||
logger.debug({ unhandled: true, tag: messageTag, fromMe: false, json }, 'communication recv')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Exits a query if the phone connection is active and no response is still found */
|
||||
const exitQueryIfResponseNotExpected = (tag: string, cancel: (error: Boom) => void) => {
|
||||
let timeout: NodeJS.Timeout
|
||||
const listener = ([, connected]) => {
|
||||
if(connected) {
|
||||
timeout = setTimeout(() => {
|
||||
logger.info({ tag }, `cancelling wait for message as a response is no longer expected from the phone`)
|
||||
const exitQueryIfResponseNotExpected = (tag: string, cancel: (error: Boom) => void) => {
|
||||
let timeout: NodeJS.Timeout
|
||||
const listener = ([, connected]) => {
|
||||
if(connected) {
|
||||
timeout = setTimeout(() => {
|
||||
logger.info({ tag }, 'cancelling wait for message as a response is no longer expected from the phone')
|
||||
cancel(new Boom('Not expecting a response', { statusCode: 422 }))
|
||||
}, expectResponseTimeout)
|
||||
ws.off(PHONE_CONNECTION_CB, listener)
|
||||
}
|
||||
}
|
||||
ws.on(PHONE_CONNECTION_CB, listener)
|
||||
return () => {
|
||||
ws.off(PHONE_CONNECTION_CB, listener)
|
||||
timeout && clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
/** interval is started when a query takes too long to respond */
|
||||
const startPhoneCheckInterval = () => {
|
||||
phoneCheckListeners += 1
|
||||
if (!phoneCheckInterval) {
|
||||
// if its been a long time and we haven't heard back from WA, send a ping
|
||||
phoneCheckInterval = setInterval(() => {
|
||||
if(phoneCheckListeners <= 0) {
|
||||
logger.warn('phone check called without listeners')
|
||||
return
|
||||
}
|
||||
logger.info('checking phone connection...')
|
||||
sendAdminTest()
|
||||
}, expectResponseTimeout)
|
||||
ws.off(PHONE_CONNECTION_CB, listener)
|
||||
}
|
||||
}
|
||||
|
||||
ws.on(PHONE_CONNECTION_CB, listener)
|
||||
return () => {
|
||||
ws.off(PHONE_CONNECTION_CB, listener)
|
||||
timeout && clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/** interval is started when a query takes too long to respond */
|
||||
const startPhoneCheckInterval = () => {
|
||||
phoneCheckListeners += 1
|
||||
if(!phoneCheckInterval) {
|
||||
// if its been a long time and we haven't heard back from WA, send a ping
|
||||
phoneCheckInterval = setInterval(() => {
|
||||
if(phoneCheckListeners <= 0) {
|
||||
logger.warn('phone check called without listeners')
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('checking phone connection...')
|
||||
sendAdminTest()
|
||||
|
||||
phoneConnectionChanged(false)
|
||||
}, phoneResponseTimeMs)
|
||||
}
|
||||
}
|
||||
|
||||
const clearPhoneCheckInterval = () => {
|
||||
phoneCheckListeners -= 1
|
||||
if(phoneCheckListeners <= 0) {
|
||||
clearInterval(phoneCheckInterval)
|
||||
phoneCheckInterval = undefined
|
||||
phoneCheckListeners = 0
|
||||
}
|
||||
}
|
||||
|
||||
phoneConnectionChanged(false)
|
||||
}, phoneResponseTimeMs)
|
||||
}
|
||||
}
|
||||
const clearPhoneCheckInterval = () => {
|
||||
phoneCheckListeners -= 1
|
||||
if (phoneCheckListeners <= 0) {
|
||||
clearInterval(phoneCheckInterval)
|
||||
phoneCheckInterval = undefined
|
||||
phoneCheckListeners = 0
|
||||
}
|
||||
}
|
||||
/** checks for phone connection */
|
||||
const sendAdminTest = () => sendNode({ json: ['admin', 'test'] })
|
||||
/**
|
||||
const sendAdminTest = () => sendNode({ json: ['admin', 'test'] })
|
||||
/**
|
||||
* Wait for a message with a certain tag to be received
|
||||
* @param tag the message tag to await
|
||||
* @param json query that was sent
|
||||
* @param timeoutMs timeout after which the promise will reject
|
||||
*/
|
||||
const waitForMessage = (tag: string, requiresPhoneConnection: boolean, timeoutMs?: number) => {
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
throw new Boom('Connection not open', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
if(ws.readyState !== ws.OPEN) {
|
||||
throw new Boom('Connection not open', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
|
||||
let cancelToken = () => { }
|
||||
let cancelToken = () => { }
|
||||
|
||||
return {
|
||||
promise: (async() => {
|
||||
let onRecv: (json) => void
|
||||
let onErr: (err) => void
|
||||
let cancelPhoneChecker: () => void
|
||||
try {
|
||||
const result = await promiseTimeout(timeoutMs,
|
||||
(resolve, reject) => {
|
||||
onRecv = resolve
|
||||
onErr = err => {
|
||||
reject(err || new Boom('Intentional Close', { statusCode: DisconnectReason.connectionClosed }))
|
||||
}
|
||||
cancelToken = () => onErr(new Boom('Cancelled', { statusCode: 500 }))
|
||||
return {
|
||||
promise: (async() => {
|
||||
let onRecv: (json) => void
|
||||
let onErr: (err) => void
|
||||
let cancelPhoneChecker: () => void
|
||||
try {
|
||||
const result = await promiseTimeout(timeoutMs,
|
||||
(resolve, reject) => {
|
||||
onRecv = resolve
|
||||
onErr = err => {
|
||||
reject(err || new Boom('Intentional Close', { statusCode: DisconnectReason.connectionClosed }))
|
||||
}
|
||||
|
||||
cancelToken = () => onErr(new Boom('Cancelled', { statusCode: 500 }))
|
||||
|
||||
if(requiresPhoneConnection) {
|
||||
startPhoneCheckInterval()
|
||||
cancelPhoneChecker = exitQueryIfResponseNotExpected(tag, onErr)
|
||||
}
|
||||
if(requiresPhoneConnection) {
|
||||
startPhoneCheckInterval()
|
||||
cancelPhoneChecker = exitQueryIfResponseNotExpected(tag, onErr)
|
||||
}
|
||||
|
||||
ws.on(`TAG:${tag}`, onRecv)
|
||||
ws.on('ws-close', onErr) // if the socket closes, you'll never receive the message
|
||||
},
|
||||
)
|
||||
return result as any
|
||||
} finally {
|
||||
requiresPhoneConnection && clearPhoneCheckInterval()
|
||||
cancelPhoneChecker && cancelPhoneChecker()
|
||||
ws.on(`TAG:${tag}`, onRecv)
|
||||
ws.on('ws-close', onErr) // if the socket closes, you'll never receive the message
|
||||
},
|
||||
)
|
||||
return result as any
|
||||
} finally {
|
||||
requiresPhoneConnection && clearPhoneCheckInterval()
|
||||
cancelPhoneChecker && cancelPhoneChecker()
|
||||
|
||||
ws.off(`TAG:${tag}`, onRecv)
|
||||
ws.off('ws-close', onErr) // if the socket closes, you'll never receive the message
|
||||
}
|
||||
})(),
|
||||
cancelToken: () => { cancelToken() }
|
||||
}
|
||||
}
|
||||
/**
|
||||
ws.off(`TAG:${tag}`, onRecv)
|
||||
ws.off('ws-close', onErr) // if the socket closes, you'll never receive the message
|
||||
}
|
||||
})(),
|
||||
cancelToken: () => {
|
||||
cancelToken()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query something from the WhatsApp servers
|
||||
* @param json the query itself
|
||||
* @param binaryTags the tags to attach if the query is supposed to be sent encoded in binary
|
||||
* @param timeoutMs timeout after which the query will be failed (set to null to disable a timeout)
|
||||
* @param tag the tag to attach to the message
|
||||
*/
|
||||
const query = async(
|
||||
{ json, timeoutMs, expect200, tag, longTag, binaryTag, requiresPhoneConnection }: SocketQueryOptions
|
||||
) => {
|
||||
const query = async(
|
||||
{ json, timeoutMs, expect200, tag, longTag, binaryTag, requiresPhoneConnection }: SocketQueryOptions
|
||||
) => {
|
||||
tag = tag || generateMessageTag(longTag)
|
||||
const { promise, cancelToken } = waitForMessage(tag, requiresPhoneConnection, timeoutMs)
|
||||
try {
|
||||
await sendNode({ json, tag, binaryTag })
|
||||
} catch(error) {
|
||||
cancelToken()
|
||||
// swallow error
|
||||
await promise.catch(() => { })
|
||||
// throw back the error
|
||||
throw error
|
||||
}
|
||||
const { promise, cancelToken } = waitForMessage(tag, requiresPhoneConnection, timeoutMs)
|
||||
try {
|
||||
await sendNode({ json, tag, binaryTag })
|
||||
} catch(error) {
|
||||
cancelToken()
|
||||
// swallow error
|
||||
await promise.catch(() => { })
|
||||
// throw back the error
|
||||
throw error
|
||||
}
|
||||
|
||||
const response = await promise
|
||||
const responseStatusCode = +(response.status ? response.status : 200) // default status
|
||||
// read here: http://getstatuscode.com/599
|
||||
if(responseStatusCode === 599) { // the connection has gone bad
|
||||
end(new Boom('WA server overloaded', { statusCode: 599, data: { query: json, response } }))
|
||||
}
|
||||
if(expect200 && Math.floor(responseStatusCode/100) !== 2) {
|
||||
const message = STATUS_CODES[responseStatusCode] || 'unknown'
|
||||
throw new Boom(
|
||||
`Unexpected status in '${Array.isArray(json) ? json[0] : (json?.tag || 'query')}': ${message}(${responseStatusCode})`,
|
||||
{ data: { query: json, response }, statusCode: response.status }
|
||||
)
|
||||
}
|
||||
return response
|
||||
}
|
||||
const response = await promise
|
||||
const responseStatusCode = +(response.status ? response.status : 200) // default status
|
||||
// read here: http://getstatuscode.com/599
|
||||
if(responseStatusCode === 599) { // the connection has gone bad
|
||||
end(new Boom('WA server overloaded', { statusCode: 599, data: { query: json, response } }))
|
||||
}
|
||||
|
||||
if(expect200 && Math.floor(responseStatusCode/100) !== 2) {
|
||||
const message = STATUS_CODES[responseStatusCode] || 'unknown'
|
||||
throw new Boom(
|
||||
`Unexpected status in '${Array.isArray(json) ? json[0] : (json?.tag || 'query')}': ${message}(${responseStatusCode})`,
|
||||
{ data: { query: json, response }, statusCode: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
const startKeepAliveRequest = () => (
|
||||
keepAliveReq = setInterval(() => {
|
||||
if (!lastDateRecv) lastDateRecv = new Date()
|
||||
const diff = Date.now() - lastDateRecv.getTime()
|
||||
/*
|
||||
keepAliveReq = setInterval(() => {
|
||||
if(!lastDateRecv) {
|
||||
lastDateRecv = new Date()
|
||||
}
|
||||
|
||||
const diff = Date.now() - lastDateRecv.getTime()
|
||||
/*
|
||||
check if it's been a suspicious amount of time since the server responded with our last seen
|
||||
it could be that the network is down
|
||||
*/
|
||||
if (diff > keepAliveIntervalMs+5000) {
|
||||
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
||||
} else if(ws.readyState === ws.OPEN) {
|
||||
sendRawMessage('?,,') // if its all good, send a keep alive request
|
||||
} else {
|
||||
logger.warn('keep alive called when WS not open')
|
||||
}
|
||||
}, keepAliveIntervalMs)
|
||||
)
|
||||
if(diff > keepAliveIntervalMs+5000) {
|
||||
end(new Boom('Connection was lost', { statusCode: DisconnectReason.connectionLost }))
|
||||
} else if(ws.readyState === ws.OPEN) {
|
||||
sendRawMessage('?,,') // if its all good, send a keep alive request
|
||||
} else {
|
||||
logger.warn('keep alive called when WS not open')
|
||||
}
|
||||
}, keepAliveIntervalMs)
|
||||
)
|
||||
|
||||
const waitForSocketOpen = async() => {
|
||||
if(ws.readyState === ws.OPEN) return
|
||||
if(ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
|
||||
throw new Boom('Connection Already Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
let onOpen: () => void
|
||||
let onClose: (err: Error) => void
|
||||
await new Promise((resolve, reject) => {
|
||||
onOpen = () => resolve(undefined)
|
||||
onClose = reject
|
||||
ws.on('open', onOpen)
|
||||
ws.on('close', onClose)
|
||||
ws.on('error', onClose)
|
||||
})
|
||||
.finally(() => {
|
||||
ws.off('open', onOpen)
|
||||
ws.off('close', onClose)
|
||||
ws.off('error', onClose)
|
||||
})
|
||||
}
|
||||
const waitForSocketOpen = async() => {
|
||||
if(ws.readyState === ws.OPEN) {
|
||||
return
|
||||
}
|
||||
|
||||
if(ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING) {
|
||||
throw new Boom('Connection Already Closed', { statusCode: DisconnectReason.connectionClosed })
|
||||
}
|
||||
|
||||
let onOpen: () => void
|
||||
let onClose: (err: Error) => void
|
||||
await new Promise((resolve, reject) => {
|
||||
onOpen = () => resolve(undefined)
|
||||
onClose = reject
|
||||
ws.on('open', onOpen)
|
||||
ws.on('close', onClose)
|
||||
ws.on('error', onClose)
|
||||
})
|
||||
.finally(() => {
|
||||
ws.off('open', onOpen)
|
||||
ws.off('close', onClose)
|
||||
ws.off('error', onClose)
|
||||
})
|
||||
}
|
||||
|
||||
ws.on('message', onMessageRecieved)
|
||||
ws.on('open', () => {
|
||||
@@ -343,57 +373,58 @@ export const makeSocket = ({
|
||||
ws.on('error', end)
|
||||
ws.on('close', () => end(new Boom('Connection Terminated', { statusCode: DisconnectReason.connectionLost })))
|
||||
|
||||
ws.on(PHONE_CONNECTION_CB, json => {
|
||||
if (!json[1]) {
|
||||
end(new Boom('Connection terminated by phone', { statusCode: DisconnectReason.connectionLost }))
|
||||
logger.info('Connection terminated by phone, closing...')
|
||||
} else {
|
||||
phoneConnectionChanged(true)
|
||||
}
|
||||
})
|
||||
ws.on('CB:Cmd,type:disconnect', json => {
|
||||
const {kind} = json[1]
|
||||
let reason: DisconnectReason
|
||||
switch(kind) {
|
||||
case 'replaced':
|
||||
reason = DisconnectReason.connectionReplaced
|
||||
break
|
||||
default:
|
||||
reason = DisconnectReason.connectionLost
|
||||
break
|
||||
}
|
||||
end(new Boom(
|
||||
`Connection terminated by server: "${kind || 'unknown'}"`,
|
||||
{ statusCode: reason }
|
||||
))
|
||||
})
|
||||
ws.on(PHONE_CONNECTION_CB, json => {
|
||||
if(!json[1]) {
|
||||
end(new Boom('Connection terminated by phone', { statusCode: DisconnectReason.connectionLost }))
|
||||
logger.info('Connection terminated by phone, closing...')
|
||||
} else {
|
||||
phoneConnectionChanged(true)
|
||||
}
|
||||
})
|
||||
ws.on('CB:Cmd,type:disconnect', json => {
|
||||
const { kind } = json[1]
|
||||
let reason: DisconnectReason
|
||||
switch (kind) {
|
||||
case 'replaced':
|
||||
reason = DisconnectReason.connectionReplaced
|
||||
break
|
||||
default:
|
||||
reason = DisconnectReason.connectionLost
|
||||
break
|
||||
}
|
||||
|
||||
end(new Boom(
|
||||
`Connection terminated by server: "${kind || 'unknown'}"`,
|
||||
{ statusCode: reason }
|
||||
))
|
||||
})
|
||||
|
||||
return {
|
||||
type: 'legacy' as 'legacy',
|
||||
ws,
|
||||
sendAdminTest,
|
||||
updateKeys: (info: { encKey: Buffer, macKey: Buffer }) => authInfo = info,
|
||||
waitForSocketOpen,
|
||||
type: 'legacy' as 'legacy',
|
||||
ws,
|
||||
sendAdminTest,
|
||||
updateKeys: (info: { encKey: Buffer, macKey: Buffer }) => authInfo = info,
|
||||
waitForSocketOpen,
|
||||
sendNode,
|
||||
generateMessageTag,
|
||||
waitForMessage,
|
||||
query,
|
||||
/** Generic function for action, set queries */
|
||||
setQuery: async(nodes: BinaryNode[], binaryTag: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) => {
|
||||
const json: BinaryNode = {
|
||||
tag: 'action',
|
||||
attrs: { epoch: epoch.toString(), type: 'set' },
|
||||
content: nodes
|
||||
}
|
||||
waitForMessage,
|
||||
query,
|
||||
/** Generic function for action, set queries */
|
||||
setQuery: async(nodes: BinaryNode[], binaryTag: WATag = [WAMetric.group, WAFlag.ignore], tag?: string) => {
|
||||
const json: BinaryNode = {
|
||||
tag: 'action',
|
||||
attrs: { epoch: epoch.toString(), type: 'set' },
|
||||
content: nodes
|
||||
}
|
||||
|
||||
return query({
|
||||
json,
|
||||
binaryTag,
|
||||
tag,
|
||||
expect200: true,
|
||||
requiresPhoneConnection: true
|
||||
}) as Promise<{ status: number }>
|
||||
},
|
||||
return query({
|
||||
json,
|
||||
binaryTag,
|
||||
tag,
|
||||
expect200: true,
|
||||
requiresPhoneConnection: true
|
||||
}) as Promise<{ status: number }>
|
||||
},
|
||||
currentEpoch: () => epoch,
|
||||
end
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user