From ec7364ddfe144b3950ae655ac193a5d1d267ad20 Mon Sep 17 00:00:00 2001 From: Edgard Date: Sat, 18 Mar 2023 20:36:14 -0300 Subject: [PATCH] feat: Added WPP.call.end function --- src/call/functions/endCall.ts | 106 +++++++++++++++++++++++ src/call/functions/index.ts | 1 + src/call/functions/prepareDestination.ts | 66 ++++++++++++++ src/call/functions/sendCallOffer.ts | 69 ++++++--------- 4 files changed, 199 insertions(+), 43 deletions(-) create mode 100644 src/call/functions/endCall.ts create mode 100644 src/call/functions/prepareDestination.ts diff --git a/src/call/functions/endCall.ts b/src/call/functions/endCall.ts new file mode 100644 index 0000000000..d6a0265d18 --- /dev/null +++ b/src/call/functions/endCall.ts @@ -0,0 +1,106 @@ +/*! + * Copyright 2023 WPPConnect Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { WPPError } from '../../util'; +import { CallModel, CallStore, websocket } from '../../whatsapp'; +import { CALL_STATES } from '../../whatsapp/enums'; + +/** + * End a outcoming call + * + * @example + * ```javascript + * // End any outcoming call + * WPP.call.endCall(); + * + * // End specific call id + * WPP.call.endCall(callId); + * + * // End any outcoming call + * WPP.on('call.outcoming_call', (call) => { + * WPP.call.endCall(call.id); + * }); + * ``` + * + * @param {string} callId The call ID, empty to end the first one + * @return {[type]} [return description] + */ +export async function endCall(callId?: string): Promise { + const callOut = [ + CALL_STATES.ACTIVE, + CALL_STATES.OUTGOING_CALLING, + CALL_STATES.OUTGOING_RING, + ]; + + let call: CallModel | undefined = undefined; + + if (callId) { + call = CallStore.get(callId); + } else { + // First outcoming ring or call group + call = CallStore.findFirst( + (c) => callOut.includes(c.getState()) || c.isGroup + ); + } + + if (!call) { + throw new WPPError( + 'call_not_found', + `Call ${callId || ''} not found`, + { + callId, + } + ); + } + + if (!callOut.includes(call.getState()) && !call.isGroup) { + throw new WPPError( + 'call_is_not_outcoming_calling', + `Call ${callId || ''} is not outcoming calling`, + { + callId, + state: call.getState(), + } + ); + } + + if (!call.peerJid.isGroupCall()) { + await websocket.ensureE2ESessions([call.peerJid]); + } + + const node = websocket.smax( + 'call', + { + to: call.peerJid.toString({ legacy: true }), + id: websocket.generateId(), + }, + [ + websocket.smax( + 'terminate', + { + 'call-id': call.id, + 'call-creator': call.peerJid.toString({ legacy: true }), + // count: '0', + }, + null + ), + ] + ); + + await websocket.sendSmaxStanza(node); + + return true; +} diff --git a/src/call/functions/index.ts b/src/call/functions/index.ts index 0dd3d76cb8..de630804f7 100644 --- a/src/call/functions/index.ts +++ b/src/call/functions/index.ts @@ -14,5 +14,6 @@ * limitations under the License. */ +export { endCall } from './endCall'; export { rejectCall } from './rejectCall'; export { sendCallOffer } from './sendCallOffer'; diff --git a/src/call/functions/prepareDestination.ts b/src/call/functions/prepareDestination.ts new file mode 100644 index 0000000000..c253cdb460 --- /dev/null +++ b/src/call/functions/prepareDestination.ts @@ -0,0 +1,66 @@ +/*! + * Copyright 2023 WPPConnect Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { functions, multidevice, websocket, Wid } from '../../whatsapp'; + +export async function prepareDestionation(wids: Wid[]) { + const fanList = await functions.getFanOutList({ wids }); + await websocket.ensureE2ESessions(fanList); + + let shouldHaveIdentity = false; + const destination = await Promise.all( + fanList.map(async (wid) => { + const encKey = self.crypto.getRandomValues(new Uint8Array(32)).buffer; + + const { type, ciphertext } = await functions.encryptMsgProtobuf(wid, 0, { + call: { + callKey: new Uint8Array(encKey), + }, + }); + + shouldHaveIdentity = shouldHaveIdentity || type === 'pkmsg'; + + return websocket.smax( + 'to', + { + jid: wid.toString({ legacy: true }), + }, + [ + websocket.smax( + 'enc', + { + v: '2', + type: type, + count: '0', + }, + ciphertext + ), + ] + ); + }) + ); + + const content: websocket.WapNode[] = []; + + content.push(websocket.smax('destination', {}, destination)); + + if (shouldHaveIdentity) { + const identity = await multidevice.adv.getADVEncodedIdentity(); + content.push(websocket.smax('device-identity', undefined, identity)); + } + + return content; +} diff --git a/src/call/functions/sendCallOffer.ts b/src/call/functions/sendCallOffer.ts index 9e9d79b210..c0bfe0c473 100644 --- a/src/call/functions/sendCallOffer.ts +++ b/src/call/functions/sendCallOffer.ts @@ -17,12 +17,16 @@ import { assertWid } from '../../assert'; import { WPPError } from '../../util'; import { + CallModel, + CallStore, functions, - multidevice, UserPrefs, websocket, Wid, } from '../../whatsapp'; +import { CALL_STATES } from '../../whatsapp/enums'; +import { unixTime } from '../../whatsapp/functions'; +import { prepareDestionation } from './prepareDestination'; export interface CallOfferOptions { isVideo?: boolean; @@ -99,47 +103,7 @@ export async function sendCallOffer( ] ); - const fanList = await functions.getFanOutList({ wids: [toWid] }); - await websocket.ensureE2ESessions(fanList); - - let shouldHaveIdentity = false; - const destination = await Promise.all( - fanList.map(async (wid) => { - const encKey = self.crypto.getRandomValues(new Uint8Array(32)).buffer; - - const { type, ciphertext } = await functions.encryptMsgProtobuf(wid, 0, { - call: { - callKey: new Uint8Array(encKey), - }, - }); - - shouldHaveIdentity = shouldHaveIdentity || type === 'pkmsg'; - - return websocket.smax( - 'to', - { - jid: wid.toString({ legacy: true }), - }, - [ - websocket.smax( - 'enc', - { - v: '2', - type: type, - count: '0', - }, - ciphertext - ), - ] - ); - }) - ); - content.push(websocket.smax('destination', {}, destination)); - - if (shouldHaveIdentity) { - const identity = await multidevice.adv.getADVEncodedIdentity(); - content.push(websocket.smax('device-identity', undefined, identity)); - } + content.push(...(await prepareDestionation([toWid]))); const node = websocket.smax( 'call', @@ -159,7 +123,26 @@ export async function sendCallOffer( ] ); + const model = new CallModel({ + id: callId, + peerJid: toWid, + isVideo: options.isVideo, + isGroup: false, + outgoing: true, + offerTime: unixTime(), + webClientShouldHandle: false, + canHandleLocally: true, + }); + + CallStore.add(model); + + CallStore.setActiveCall(CallStore.assertGet(callId)); + + model.setState(CALL_STATES.OUTGOING_CALLING); + const response = await websocket.sendSmaxStanza(node); - return response; + console.info(response); + + return model; }