From c5c99136facaba27cb8cd394abcb278ab4792888 Mon Sep 17 00:00:00 2001 From: devlikepro Date: Tue, 2 Dec 2025 11:18:51 +0700 Subject: [PATCH] [core] Reject Calls - WEBJS, NOWEB, GOWS fix #608 fix #1258 --- src/api/calls.controller.ts | 34 +++ src/core/SwaggerConfiguratorCore.ts | 1 + src/core/abc/session.abc.ts | 4 + src/core/app.module.core.ts | 2 + src/core/engines/gows/grpc/gows.ts | 129 ++++++++++ src/core/engines/gows/grpc/gows_grpc_pb.js | 25 ++ src/core/engines/gows/grpc/gows_pb.js | 233 +++++++++++++++++++ src/core/engines/gows/session.gows.core.ts | 9 + src/core/engines/noweb/session.noweb.core.ts | 38 ++- src/core/engines/webjs/session.webjs.core.ts | 13 +- src/structures/calls.dto.ts | 13 ++ 11 files changed, 493 insertions(+), 8 deletions(-) create mode 100644 src/api/calls.controller.ts diff --git a/src/api/calls.controller.ts b/src/api/calls.controller.ts new file mode 100644 index 00000000..47825145 --- /dev/null +++ b/src/api/calls.controller.ts @@ -0,0 +1,34 @@ +import { + Body, + Controller, + Post, + UsePipes, + ValidationPipe, +} from '@nestjs/common'; +import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { + SessionApiParam, + WorkingSessionParam, +} from '@waha/nestjs/params/SessionApiParam'; + +import { SessionManager } from '../core/abc/manager.abc'; +import { WhatsappSession } from '../core/abc/session.abc'; +import { RejectCallRequest } from '../structures/calls.dto'; + +@ApiSecurity('api_key') +@Controller('api/:session/calls') +@ApiTags('📞 Calls') +export class CallsController { + constructor(private manager: SessionManager) {} + + @Post('reject') + @SessionApiParam + @ApiOperation({ summary: 'Reject incoming call' }) + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + rejectCall( + @WorkingSessionParam session: WhatsappSession, + @Body() request: RejectCallRequest, + ) { + return session.rejectCall(request.from, request.id); + } +} diff --git a/src/core/SwaggerConfiguratorCore.ts b/src/core/SwaggerConfiguratorCore.ts index 0e832a9c..4e34076c 100644 --- a/src/core/SwaggerConfiguratorCore.ts +++ b/src/core/SwaggerConfiguratorCore.ts @@ -79,6 +79,7 @@ export class SwaggerConfiguratorCore { .addTag('🆔 Profile', 'Your profile information') .addTag('🖼️ Screenshot', 'Get screenshot of WhatsApp and show QR code') .addTag('📤 Chatting', 'Chatting methods') + .addTag('📞 Calls', 'Call handling methods') .addTag('📢 Channels', 'Channels (newsletters) methods') .addTag('🟢 Status', 'Status (aka stories) methods') .addTag('💬 Chats', `Chats methods`) diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index 3fffc5ef..fc7ceac2 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -664,6 +664,10 @@ export abstract class WhatsappSession { throw new NotImplementedByEngineError(); } + public rejectCall(from: string, id: string): Promise { + throw new NotImplementedByEngineError(); + } + /** * Chats methods */ diff --git a/src/core/app.module.core.ts b/src/core/app.module.core.ts index b480baf1..4ba8799a 100644 --- a/src/core/app.module.core.ts +++ b/src/core/app.module.core.ts @@ -40,6 +40,7 @@ import { join } from 'path'; import { Logger } from 'pino'; import { AuthController } from '../api/auth.controller'; +import { CallsController } from '../api/calls.controller'; import { ChatsController } from '../api/chats.controller'; import { ChattingController } from '../api/chatting.controller'; import { ContactsController } from '../api/contacts.controller'; @@ -143,6 +144,7 @@ export const CONTROLLERS = [ ProfileController, ChattingController, ChatsController, + CallsController, ChannelsController, StatusController, LabelsController, diff --git a/src/core/engines/gows/grpc/gows.ts b/src/core/engines/gows/grpc/gows.ts index 7d71a888..70d6f55b 100644 --- a/src/core/engines/gows/grpc/gows.ts +++ b/src/core/engines/gows/grpc/gows.ts @@ -10105,6 +10105,122 @@ export namespace messages { return GetLidsRequest.deserialize(bytes); } } + export class RejectCallRequest extends pb_1.Message { + #one_of_decls: number[][] = []; + constructor(data?: any[] | { + session?: Session; + from?: string; + id?: string; + }) { + super(); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [], this.#one_of_decls); + if (!Array.isArray(data) && typeof data == "object") { + if ("session" in data && data.session != undefined) { + this.session = data.session; + } + if ("from" in data && data.from != undefined) { + this.from = data.from; + } + if ("id" in data && data.id != undefined) { + this.id = data.id; + } + } + } + get session() { + return pb_1.Message.getWrapperField(this, Session, 1) as Session; + } + set session(value: Session) { + pb_1.Message.setWrapperField(this, 1, value); + } + get has_session() { + return pb_1.Message.getField(this, 1) != null; + } + get from() { + return pb_1.Message.getFieldWithDefault(this, 2, "") as string; + } + set from(value: string) { + pb_1.Message.setField(this, 2, value); + } + get id() { + return pb_1.Message.getFieldWithDefault(this, 3, "") as string; + } + set id(value: string) { + pb_1.Message.setField(this, 3, value); + } + static fromObject(data: { + session?: ReturnType; + from?: string; + id?: string; + }): RejectCallRequest { + const message = new RejectCallRequest({}); + if (data.session != null) { + message.session = Session.fromObject(data.session); + } + if (data.from != null) { + message.from = data.from; + } + if (data.id != null) { + message.id = data.id; + } + return message; + } + toObject() { + const data: { + session?: ReturnType; + from?: string; + id?: string; + } = {}; + if (this.session != null) { + data.session = this.session.toObject(); + } + if (this.from != null) { + data.from = this.from; + } + if (this.id != null) { + data.id = this.id; + } + return data; + } + serialize(): Uint8Array; + serialize(w: pb_1.BinaryWriter): void; + serialize(w?: pb_1.BinaryWriter): Uint8Array | void { + const writer = w || new pb_1.BinaryWriter(); + if (this.has_session) + writer.writeMessage(1, this.session, () => this.session.serialize(writer)); + if (this.from.length) + writer.writeString(2, this.from); + if (this.id.length) + writer.writeString(3, this.id); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): RejectCallRequest { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new RejectCallRequest(); + while (reader.nextField()) { + if (reader.isEndGroup()) + break; + switch (reader.getFieldNumber()) { + case 1: + reader.readMessage(message.session, () => message.session = Session.deserialize(reader)); + break; + case 2: + message.from = reader.readString(); + break; + case 3: + message.id = reader.readString(); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): RejectCallRequest { + return RejectCallRequest.deserialize(bytes); + } + } export class PollMessage extends pb_1.Message { #one_of_decls: number[][] = []; constructor(data?: any[] | { @@ -10933,6 +11049,15 @@ export namespace messages { responseSerialize: (message: DownloadMediaResponse) => Buffer.from(message.serialize()), responseDeserialize: (bytes: Buffer) => DownloadMediaResponse.deserialize(new Uint8Array(bytes)) }, + RejectCall: { + path: "/messages.MessageService/RejectCall", + requestStream: false, + responseStream: false, + requestSerialize: (message: RejectCallRequest) => Buffer.from(message.serialize()), + requestDeserialize: (bytes: Buffer) => RejectCallRequest.deserialize(new Uint8Array(bytes)), + responseSerialize: (message: Empty) => Buffer.from(message.serialize()), + responseDeserialize: (bytes: Buffer) => Empty.deserialize(new Uint8Array(bytes)) + }, GetMessageById: { path: "/messages.MessageService/GetMessageById", requestStream: false, @@ -11022,6 +11147,7 @@ export namespace messages { abstract GetContactById(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract CancelEventMessage(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract DownloadMedia(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; + abstract RejectCall(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetMessageById(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetMessages(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetChats(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; @@ -11210,6 +11336,9 @@ export namespace messages { DownloadMedia: GrpcUnaryServiceInterface = (message: DownloadMediaRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => { return super.DownloadMedia(message, metadata, options, callback); }; + RejectCall: GrpcUnaryServiceInterface = (message: RejectCallRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => { + return super.RejectCall(message, metadata, options, callback); + }; GetMessageById: GrpcUnaryServiceInterface = (message: EntityByIdRequest, metadata: grpc_1.Metadata | grpc_1.CallOptions | grpc_1.requestCallback, options?: grpc_1.CallOptions | grpc_1.requestCallback, callback?: grpc_1.requestCallback): grpc_1.ClientUnaryCall => { return super.GetMessageById(message, metadata, options, callback); }; diff --git a/src/core/engines/gows/grpc/gows_grpc_pb.js b/src/core/engines/gows/grpc/gows_grpc_pb.js index d91448f6..625ad9cb 100644 --- a/src/core/engines/gows/grpc/gows_grpc_pb.js +++ b/src/core/engines/gows/grpc/gows_grpc_pb.js @@ -543,6 +543,17 @@ function deserialize_messages_ProfileStatusRequest(buffer_arg) { return gows_pb.ProfileStatusRequest.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_messages_RejectCallRequest(arg) { + if (!(arg instanceof gows_pb.RejectCallRequest)) { + throw new Error('Expected argument of type messages.RejectCallRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_messages_RejectCallRequest(buffer_arg) { + return gows_pb.RejectCallRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_messages_RevokeMessageRequest(arg) { if (!(arg instanceof gows_pb.RevokeMessageRequest)) { throw new Error('Expected argument of type messages.RevokeMessageRequest'); @@ -1391,6 +1402,20 @@ downloadMedia: { responseDeserialize: deserialize_messages_DownloadMediaResponse, }, // +// Calls +// +rejectCall: { + path: '/messages.MessageService/RejectCall', + requestStream: false, + responseStream: false, + requestType: gows_pb.RejectCallRequest, + responseType: gows_pb.Empty, + requestSerialize: serialize_messages_RejectCallRequest, + requestDeserialize: deserialize_messages_RejectCallRequest, + responseSerialize: serialize_messages_Empty, + responseDeserialize: deserialize_messages_Empty, + }, + // // Storage // getMessageById: { diff --git a/src/core/engines/gows/grpc/gows_pb.js b/src/core/engines/gows/grpc/gows_pb.js index b94cbeb3..575a3e38 100644 --- a/src/core/engines/gows/grpc/gows_pb.js +++ b/src/core/engines/gows/grpc/gows_pb.js @@ -94,6 +94,7 @@ goog.exportSymbol('proto.messages.ProfilePictureRequest', null, global); goog.exportSymbol('proto.messages.ProfilePictureResponse', null, global); goog.exportSymbol('proto.messages.ProfileStatusRequest', null, global); goog.exportSymbol('proto.messages.ReceiptType', null, global); +goog.exportSymbol('proto.messages.RejectCallRequest', null, global); goog.exportSymbol('proto.messages.RevokeMessageRequest', null, global); goog.exportSymbol('proto.messages.Row', null, global); goog.exportSymbol('proto.messages.SearchNewslettersByTextRequest', null, global); @@ -1966,6 +1967,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.messages.GetLidsRequest.displayName = 'proto.messages.GetLidsRequest'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.messages.RejectCallRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.messages.RejectCallRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.messages.RejectCallRequest.displayName = 'proto.messages.RejectCallRequest'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -20854,6 +20876,217 @@ proto.messages.GetLidsRequest.prototype.hasSession = function() { + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.messages.RejectCallRequest.prototype.toObject = function(opt_includeInstance) { + return proto.messages.RejectCallRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.messages.RejectCallRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.RejectCallRequest.toObject = function(includeInstance, msg) { + var f, obj = { + session: (f = msg.getSession()) && proto.messages.Session.toObject(includeInstance, f), + from: jspb.Message.getFieldWithDefault(msg, 2, ""), + id: jspb.Message.getFieldWithDefault(msg, 3, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.messages.RejectCallRequest} + */ +proto.messages.RejectCallRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.messages.RejectCallRequest; + return proto.messages.RejectCallRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.messages.RejectCallRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.messages.RejectCallRequest} + */ +proto.messages.RejectCallRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.messages.Session; + reader.readMessage(value,proto.messages.Session.deserializeBinaryFromReader); + msg.setSession(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setFrom(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setId(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.messages.RejectCallRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.messages.RejectCallRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.messages.RejectCallRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.RejectCallRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSession(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.messages.Session.serializeBinaryToWriter + ); + } + f = message.getFrom(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getId(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } +}; + + +/** + * optional Session session = 1; + * @return {?proto.messages.Session} + */ +proto.messages.RejectCallRequest.prototype.getSession = function() { + return /** @type{?proto.messages.Session} */ ( + jspb.Message.getWrapperField(this, proto.messages.Session, 1)); +}; + + +/** + * @param {?proto.messages.Session|undefined} value + * @return {!proto.messages.RejectCallRequest} returns this +*/ +proto.messages.RejectCallRequest.prototype.setSession = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.messages.RejectCallRequest} returns this + */ +proto.messages.RejectCallRequest.prototype.clearSession = function() { + return this.setSession(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.messages.RejectCallRequest.prototype.hasSession = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string from = 2; + * @return {string} + */ +proto.messages.RejectCallRequest.prototype.getFrom = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.RejectCallRequest} returns this + */ +proto.messages.RejectCallRequest.prototype.setFrom = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string id = 3; + * @return {string} + */ +proto.messages.RejectCallRequest.prototype.getId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.RejectCallRequest} returns this + */ +proto.messages.RejectCallRequest.prototype.setId = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + + /** * List of repeated fields within this message type. * @private {!Array} diff --git a/src/core/engines/gows/session.gows.core.ts b/src/core/engines/gows/session.gows.core.ts index 840f1a44..a9b108e8 100644 --- a/src/core/engines/gows/session.gows.core.ts +++ b/src/core/engines/gows/session.gows.core.ts @@ -862,6 +862,15 @@ export class WhatsappSessionGoWSCore extends WhatsappSession { return data.id; } + async rejectCall(from: string, id: string): Promise { + const request = new messages.RejectCallRequest({ + session: this.session, + from: toJID(this.ensureSuffix(from)), + id: id, + }); + await promisify(this.client.RejectCall)(request); + } + @Activity() async sendText(request: MessageTextRequest) { const jid = toJID(this.ensureSuffix(request.chatId)); diff --git a/src/core/engines/noweb/session.noweb.core.ts b/src/core/engines/noweb/session.noweb.core.ts index 47966449..5a1fbaf9 100644 --- a/src/core/engines/noweb/session.noweb.core.ts +++ b/src/core/engines/noweb/session.noweb.core.ts @@ -186,9 +186,11 @@ import { mergeMap, Observable, partition, + groupBy, share, + tap, } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { debounceTime, map } from 'rxjs/operators'; import { INowebStore } from './store/INowebStore'; import { NowebPersistentStore } from './store/NowebPersistentStore'; @@ -850,6 +852,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { return this.generateMessageID(); } + async rejectCall(from: string, id: string): Promise { + const jid = toJID(this.ensureSuffix(from)); + await this.sock.rejectCall(id, jid); + } + @Activity() async sendText(request: MessageTextRequest) { const chatId = toJID(this.ensureSuffix(request.chatId)); @@ -2123,6 +2130,8 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { ), share(), ); + + const acceptedCallIds = new Set(); this.events2.get(WAHAEvents.CALL_RECEIVED).switch( call$.pipe( filter((call: WACallEvent) => call.status === 'offer'), @@ -2132,18 +2141,33 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { this.events2.get(WAHAEvents.CALL_ACCEPTED).switch( call$.pipe( filter((call: WACallEvent) => call.status === 'accept'), + tap((call: WACallEvent) => acceptedCallIds.add(call.id)), map(this.toCallData.bind(this)), ), ); this.events2.get(WAHAEvents.CALL_REJECTED).switch( - calls$.pipe( - // Filter out if there's any "accept" events. - // Meaning it's been accepted on one device, but rejected on another - exclude((calls) => calls.some((call) => call.status === 'accept')), - mergeAll(), - filter((call: WACallEvent) => call.status === 'reject'), + call$.pipe( + filter( + (call: WACallEvent) => + call.status === 'reject' || call.status === 'terminate', + ), + // Skip rejections when the call was accepted earlier (local or other device) + exclude((call: WACallEvent) => { + const shouldSkip = acceptedCallIds.has(call.id); + if (call.status === 'terminate') { + acceptedCallIds.delete(call.id); + } + return shouldSkip; + }), // We get two "reject" events, one with null isGroup property, ignore it exclude((call: WACallEvent) => call.isGroup == null), + groupBy((call: WACallEvent) => call.id || 'unknown'), + mergeMap((group$) => + group$.pipe( + debounceTime(1_000), + tap((call: WACallEvent) => acceptedCallIds.delete(call.id)), + ), + ), map(this.toCallData.bind(this)), ), ); diff --git a/src/core/engines/webjs/session.webjs.core.ts b/src/core/engines/webjs/session.webjs.core.ts index 28690891..9dc468a1 100644 --- a/src/core/engines/webjs/session.webjs.core.ts +++ b/src/core/engines/webjs/session.webjs.core.ts @@ -150,7 +150,10 @@ import { Reaction, WAState, } from 'whatsapp-web.js'; -import { Message as MessageInstance } from 'whatsapp-web.js/src/structures'; +import { + Message as MessageInstance, + Call as CallInstance, +} from 'whatsapp-web.js/src/structures'; import { WAJSPresenceChatStateType, WebJSPresence } from './types'; import { @@ -651,6 +654,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { /** * Other methods */ + async rejectCall(from: string, id: string): Promise { + const peerJid = normalizeJid(this.ensureSuffix(from)); + const call = new CallInstance(this.whatsapp, null); + call.id = id; + call.from = peerJid; + await call.reject(); + } + @Activity() sendText(request: MessageTextRequest) { const options = this.getMessageOptions(request); diff --git a/src/structures/calls.dto.ts b/src/structures/calls.dto.ts index 01dd27ad..3d8eb188 100644 --- a/src/structures/calls.dto.ts +++ b/src/structures/calls.dto.ts @@ -3,6 +3,7 @@ */ import { ApiProperty } from '@nestjs/swagger'; import { ChatIdProperty } from '@waha/structures/properties.dto'; +import { IsNotEmpty, IsString } from 'class-validator'; function CallIdProperty() { return ApiProperty({ @@ -11,6 +12,18 @@ function CallIdProperty() { }); } +export class RejectCallRequest { + @ChatIdProperty() + @IsString() + @IsNotEmpty() + from: string; + + @CallIdProperty() + @IsString() + @IsNotEmpty() + id: string; +} + export class CallData { @CallIdProperty() id: string;