feat: handle call events

This commit is contained in:
Adhiraj Singh
2022-05-09 15:00:53 +05:30
parent 72aa8f9ea7
commit ae4aa67950
7 changed files with 119 additions and 11 deletions

View File

@@ -57,6 +57,7 @@ const startSock = async() => {
await sock.sendMessage(jid, msg)
}
sock.ev.on('call', item => console.log('recv call event', item))
sock.ev.on('chats.set', item => console.log(`recv ${item.chats.length} chats (is latest: ${item.isLatest})`))
sock.ev.on('messages.set', item => console.log(`recv ${item.messages.length} messages (is latest: ${item.isLatest})`))
sock.ev.on('contacts.set', item => console.log(`recv ${item.contacts.length} contacts`))

View File

@@ -226,6 +226,8 @@ export type BaileysEventMap = {
'blocklist.set': { blocklist: string[] }
'blocklist.update': { blocklist: string[], type: 'add' | 'remove' }
/** Receive an update on a call, including when the call was received, rejected, accepted */
'call': WACallEvent[]
}
```

View File

@@ -1,8 +1,8 @@
import { proto } from '../../WAProto'
import { KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults'
import { BaileysEventMap, MessageReceiptType, MessageUserReceipt, SocketConfig, WAMessageStubType } from '../Types'
import { debouncedTimeout, decodeMessageStanza, delay, encodeBigEndian, generateSignalPubKey, getNextPreKeys, getStatusFromReceiptType, normalizeMessageContent, xmppPreKey, xmppSignedPreKey } from '../Utils'
import { BaileysEventMap, MessageReceiptType, MessageUserReceipt, SocketConfig, WACallEvent, WAMessageStubType } from '../Types'
import { debouncedTimeout, decodeMessageStanza, delay, encodeBigEndian, generateSignalPubKey, getCallStatusFromNode, getNextPreKeys, getStatusFromReceiptType, normalizeMessageContent, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils'
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex'
import processMessage from '../Utils/process-message'
import { areJidsSameUser, BinaryNode, BinaryNodeAttributes, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary'
@@ -42,6 +42,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
)
const msgRetryMap = config.msgRetryCounterMap || { }
const callOfferData: { [id: string]: WACallEvent } = { }
const historyCache = new Set<string>()
@@ -518,15 +519,43 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
})
ws.on('CB:call', async(node: BinaryNode) => {
logger.info({ node }, 'recv call')
const [child] = getAllBinaryNodeChildren(node)
if(!!child?.tag) {
sendMessageAck(node, { class: 'call', type: child.tag })
.catch(
error => onUnexpectedError(error, 'ack call')
)
const { attrs } = node
const [infoChild] = getAllBinaryNodeChildren(node)
const callId = infoChild.attrs['call-id']
const from = infoChild.attrs.from || infoChild.attrs['call-creator']
const status = getCallStatusFromNode(infoChild)
const call: WACallEvent = {
chatId: attrs.from,
from,
id: callId,
date: new Date(+attrs.t * 1000),
offline: !!attrs.offline,
status,
}
if(status === 'offer') {
call.isVideo = !!getBinaryNodeChild(infoChild, 'video')
call.isGroup = infoChild.attrs.type === 'group'
callOfferData[call.id] = call
}
// use existing call info to populate this event
if(callOfferData[call.id]) {
call.isVideo = callOfferData[call.id].isVideo
call.isGroup = callOfferData[call.id].isGroup
}
// delete data once call has ended
if(status === 'reject' || status === 'accept' || status === 'timeout') {
delete callOfferData[call.id]
}
ev.emit('call', [call])
await sendMessageAck(node, { class: 'call', type: infoChild.tag })
.catch(
error => onUnexpectedError(error, 'ack call')
)
})
ws.on('CB:receipt', node => {
@@ -552,6 +581,35 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
)
})
ev.on('call', ([ call ]) => {
// missed call + group call notification message generation
if(call.status === 'timeout' || (call.status === 'offer' && call.isGroup)) {
const msg: proto.IWebMessageInfo = {
key: {
remoteJid: call.chatId,
id: call.id,
fromMe: false
},
messageTimestamp: unixTimestampSeconds(call.date),
}
if(call.status === 'timeout') {
if(call.isGroup) {
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_GROUP_VIDEO : WAMessageStubType.CALL_MISSED_GROUP_VOICE
} else {
msg.messageStubType = call.isVideo ? WAMessageStubType.CALL_MISSED_VIDEO : WAMessageStubType.CALL_MISSED_VOICE
}
} else {
msg.message = { call: { callKey: Buffer.from(call.id) } }
}
const protoMsg = proto.WebMessageInfo.fromObject(msg)
ev.emit(
'messages.upsert',
{ messages: [protoMsg], type: call.offline ? 'append' : 'notify' }
)
}
})
return {
...sock,
processMessage: processMessageLocal,

14
src/Types/Call.ts Normal file
View File

@@ -0,0 +1,14 @@
export type WACallUpdateType = 'offer' | 'ringing' | 'timeout' | 'reject' | 'accept'
export type WACallEvent = {
chatId: string
from: string
isGroup?: boolean
id: string
date: Date
isVideo?: boolean
status: WACallUpdateType
offline: boolean
latencyMs?: number
}

View File

@@ -1,6 +1,7 @@
import type EventEmitter from 'events'
import { proto } from '../../WAProto'
import { AuthenticationCreds } from './Auth'
import { WACallEvent } from './Call'
import { Chat, PresenceData } from './Chat'
import { Contact } from './Contact'
import { GroupMetadata, ParticipantAction } from './GroupMetadata'
@@ -48,6 +49,8 @@ export type BaileysEventMap<T> = {
'blocklist.set': { blocklist: string[] }
'blocklist.update': { blocklist: string[], type: 'add' | 'remove' }
/** Receive an update on a call, including when the call was received, rejected, accepted */
'call': WACallEvent[]
}
export interface CommonBaileysEventEmitter<Creds> extends EventEmitter {

View File

@@ -8,6 +8,7 @@ export * from './Legacy'
export * from './Socket'
export * from './Events'
export * from './Product'
export * from './Call'
import type NodeCache from 'node-cache'
import { proto } from '../../WAProto'

View File

@@ -5,7 +5,7 @@ import { platform, release } from 'os'
import { Logger } from 'pino'
import { proto } from '../../WAProto'
import { version as baileysVersion } from '../Defaults/baileys-version.json'
import { CommonBaileysEventEmitter, ConnectionState, DisconnectReason, WAVersion } from '../Types'
import { CommonBaileysEventEmitter, ConnectionState, DisconnectReason, WACallUpdateType, WAVersion } from '../Types'
import { BinaryNode, getAllBinaryNodeChildren } from '../WABinary'
const PLATFORM_MAP = {
@@ -287,4 +287,33 @@ export const getErrorCodeFromStreamError = (node: BinaryNode) => {
reason,
statusCode
}
}
export const getCallStatusFromNode = ({ tag, attrs }: BinaryNode) => {
let status: WACallUpdateType
switch (tag) {
case 'offer':
case 'offer_notice':
status = 'offer'
break
case 'terminate':
if(attrs.reason === 'timeout') {
status = 'timeout'
} else {
status = 'reject'
}
break
case 'reject':
status = 'reject'
break
case 'accept':
status = 'accept'
break
default:
status = 'ringing'
break
}
return status
}