From 7fa2b2941806c1686507a52bf8490bb9fa8206a3 Mon Sep 17 00:00:00 2001 From: devlikepro Date: Sun, 24 Aug 2025 15:18:32 +0700 Subject: [PATCH] [core] GOWS - /api/sendPollVote fix #1077 --- src/api/chatting.controller.ts | 12 + src/core/abc/session.abc.ts | 5 + src/core/engines/gows/grpc/gows.ts | 152 ++++++++++ src/core/engines/gows/grpc/gows_pb.js | 309 ++++++++++++++++++++- src/core/engines/gows/session.gows.core.ts | 6 + src/structures/chatting.dto.ts | 33 +++ 6 files changed, 516 insertions(+), 1 deletion(-) diff --git a/src/api/chatting.controller.ts b/src/api/chatting.controller.ts index 4bc847f0..d3368c1d 100644 --- a/src/api/chatting.controller.ts +++ b/src/api/chatting.controller.ts @@ -32,6 +32,7 @@ import { MessageLinkPreviewRequest, MessageLocationRequest, MessagePollRequest, + MessagePollVoteRequest, MessageReactionRequest, MessageReplyRequest, MessageStarRequest, @@ -206,6 +207,17 @@ export class ChattingController { return whatsapp.sendPoll(request); } + @Post('/sendPollVote') + @ApiOperation({ + summary: 'Vote on a poll', + description: 'Cast vote(s) on an existing poll message', + }) + @UsePipes(new WAHAValidationPipe()) + async sendPollVote(@Body() request: MessagePollVoteRequest) { + const whatsapp = await this.manager.getWorkingSession(request.session); + return whatsapp.sendPollVote(request); + } + @Post('/sendLocation') async sendLocation(@Body() request: MessageLocationRequest) { const whatsapp = await this.manager.getWorkingSession(request.session); diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index 3fcc626b..df9f8e71 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -70,6 +70,7 @@ import { MessageLinkPreviewRequest, MessageLocationRequest, MessagePollRequest, + MessagePollVoteRequest, MessageReactionRequest, MessageReplyRequest, MessageStarRequest, @@ -452,6 +453,10 @@ export abstract class WhatsappSession { throw new NotImplementedByEngineError(); } + sendPollVote(request: MessagePollVoteRequest) { + throw new NotImplementedByEngineError(); + } + abstract sendLocation(request: MessageLocationRequest); sendLinkPreview(request: MessageLinkPreviewRequest) { diff --git a/src/core/engines/gows/grpc/gows.ts b/src/core/engines/gows/grpc/gows.ts index ce7c39ef..81f36dd0 100644 --- a/src/core/engines/gows/grpc/gows.ts +++ b/src/core/engines/gows/grpc/gows.ts @@ -3391,6 +3391,7 @@ export namespace messages { poll?: PollMessage; list?: ListMessage; location?: Location; + pollVote?: PollVoteMessage; }) { super(); pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [11, 13], this.#one_of_decls); @@ -3446,6 +3447,9 @@ export namespace messages { if ("location" in data && data.location != undefined) { this.location = data.location; } + if ("pollVote" in data && data.pollVote != undefined) { + this.pollVote = data.pollVote; + } } } get session() { @@ -3577,6 +3581,15 @@ export namespace messages { get has_location() { return pb_1.Message.getField(this, 17) != null; } + get pollVote() { + return pb_1.Message.getWrapperField(this, PollVoteMessage, 18) as PollVoteMessage; + } + set pollVote(value: PollVoteMessage) { + pb_1.Message.setWrapperField(this, 18, value); + } + get has_pollVote() { + return pb_1.Message.getField(this, 18) != null; + } static fromObject(data: { session?: ReturnType; jid?: string; @@ -3595,6 +3608,7 @@ export namespace messages { poll?: ReturnType; list?: ReturnType; location?: ReturnType; + pollVote?: ReturnType; }): MessageRequest { const message = new MessageRequest({}); if (data.session != null) { @@ -3648,6 +3662,9 @@ export namespace messages { if (data.location != null) { message.location = Location.fromObject(data.location); } + if (data.pollVote != null) { + message.pollVote = PollVoteMessage.fromObject(data.pollVote); + } return message; } toObject() { @@ -3669,6 +3686,7 @@ export namespace messages { poll?: ReturnType; list?: ReturnType; location?: ReturnType; + pollVote?: ReturnType; } = {}; if (this.session != null) { data.session = this.session.toObject(); @@ -3721,6 +3739,9 @@ export namespace messages { if (this.location != null) { data.location = this.location.toObject(); } + if (this.pollVote != null) { + data.pollVote = this.pollVote.toObject(); + } return data; } serialize(): Uint8Array; @@ -3761,6 +3782,8 @@ export namespace messages { writer.writeMessage(16, this.list, () => this.list.serialize(writer)); if (this.has_location) writer.writeMessage(17, this.location, () => this.location.serialize(writer)); + if (this.has_pollVote) + writer.writeMessage(18, this.pollVote, () => this.pollVote.serialize(writer)); if (!w) return writer.getResultBuffer(); } @@ -3821,6 +3844,9 @@ export namespace messages { case 17: reader.readMessage(message.location, () => message.location = Location.deserialize(reader)); break; + case 18: + reader.readMessage(message.pollVote, () => message.pollVote = PollVoteMessage.deserialize(reader)); + break; default: reader.skipField(); } } @@ -9786,6 +9812,132 @@ export namespace messages { return PollMessage.deserialize(bytes); } } + export class PollVoteMessage extends pb_1.Message { + #one_of_decls: number[][] = [[2]]; + constructor(data?: any[] | ({ + pollMessageId?: string; + options?: string[]; + } & (({ + pollServerId?: number; + })))) { + super(); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [3], this.#one_of_decls); + if (!Array.isArray(data) && typeof data == "object") { + if ("pollMessageId" in data && data.pollMessageId != undefined) { + this.pollMessageId = data.pollMessageId; + } + if ("pollServerId" in data && data.pollServerId != undefined) { + this.pollServerId = data.pollServerId; + } + if ("options" in data && data.options != undefined) { + this.options = data.options; + } + } + } + get pollMessageId() { + return pb_1.Message.getFieldWithDefault(this, 1, "") as string; + } + set pollMessageId(value: string) { + pb_1.Message.setField(this, 1, value); + } + get pollServerId() { + return pb_1.Message.getFieldWithDefault(this, 2, 0) as number; + } + set pollServerId(value: number) { + pb_1.Message.setOneofField(this, 2, this.#one_of_decls[0], value); + } + get has_pollServerId() { + return pb_1.Message.getField(this, 2) != null; + } + get options() { + return pb_1.Message.getFieldWithDefault(this, 3, []) as string[]; + } + set options(value: string[]) { + pb_1.Message.setField(this, 3, value); + } + get _pollServerId() { + const cases: { + [index: number]: "none" | "pollServerId"; + } = { + 0: "none", + 2: "pollServerId" + }; + return cases[pb_1.Message.computeOneofCase(this, [2])]; + } + static fromObject(data: { + pollMessageId?: string; + pollServerId?: number; + options?: string[]; + }): PollVoteMessage { + const message = new PollVoteMessage({}); + if (data.pollMessageId != null) { + message.pollMessageId = data.pollMessageId; + } + if (data.pollServerId != null) { + message.pollServerId = data.pollServerId; + } + if (data.options != null) { + message.options = data.options; + } + return message; + } + toObject() { + const data: { + pollMessageId?: string; + pollServerId?: number; + options?: string[]; + } = {}; + if (this.pollMessageId != null) { + data.pollMessageId = this.pollMessageId; + } + if (this.pollServerId != null) { + data.pollServerId = this.pollServerId; + } + if (this.options != null) { + data.options = this.options; + } + 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.pollMessageId.length) + writer.writeString(1, this.pollMessageId); + if (this.has_pollServerId) + writer.writeInt64(2, this.pollServerId); + if (this.options.length) + writer.writeRepeatedString(3, this.options); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): PollVoteMessage { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new PollVoteMessage(); + while (reader.nextField()) { + if (reader.isEndGroup()) + break; + switch (reader.getFieldNumber()) { + case 1: + message.pollMessageId = reader.readString(); + break; + case 2: + message.pollServerId = reader.readInt64(); + break; + case 3: + pb_1.Message.addToRepeatedField(message, 3, reader.readString()); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): PollVoteMessage { + return PollVoteMessage.deserialize(bytes); + } + } interface GrpcUnaryServiceInterface { (message: P, metadata: grpc_1.Metadata, options: grpc_1.CallOptions, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; (message: P, metadata: grpc_1.Metadata, callback: grpc_1.requestCallback): grpc_1.ClientUnaryCall; diff --git a/src/core/engines/gows/grpc/gows_pb.js b/src/core/engines/gows/grpc/gows_pb.js index 3cdc6c97..3d2c205b 100644 --- a/src/core/engines/gows/grpc/gows_pb.js +++ b/src/core/engines/gows/grpc/gows_pb.js @@ -85,6 +85,7 @@ goog.exportSymbol('proto.messages.PairCodeResponse', null, global); goog.exportSymbol('proto.messages.ParticipantAction', null, global); goog.exportSymbol('proto.messages.PhoneInfo', null, global); goog.exportSymbol('proto.messages.PollMessage', null, global); +goog.exportSymbol('proto.messages.PollVoteMessage', null, global); goog.exportSymbol('proto.messages.Presence', null, global); goog.exportSymbol('proto.messages.PresenceRequest', null, global); goog.exportSymbol('proto.messages.ProfileNameRequest', null, global); @@ -1942,6 +1943,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.messages.PollMessage.displayName = 'proto.messages.PollMessage'; } +/** + * 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.PollVoteMessage = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.messages.PollVoteMessage.repeatedFields_, null); +}; +goog.inherits(proto.messages.PollVoteMessage, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.messages.PollVoteMessage.displayName = 'proto.messages.PollVoteMessage'; +} @@ -8268,7 +8290,8 @@ proto.messages.MessageRequest.toObject = function(includeInstance, msg) { event: (f = msg.getEvent()) && proto.messages.EventMessage.toObject(includeInstance, f), poll: (f = msg.getPoll()) && proto.messages.PollMessage.toObject(includeInstance, f), list: (f = msg.getList()) && proto.messages.ListMessage.toObject(includeInstance, f), - location: (f = msg.getLocation()) && proto.messages.Location.toObject(includeInstance, f) + location: (f = msg.getLocation()) && proto.messages.Location.toObject(includeInstance, f), + pollvote: (f = msg.getPollvote()) && proto.messages.PollVoteMessage.toObject(includeInstance, f) }; if (includeInstance) { @@ -8383,6 +8406,11 @@ proto.messages.MessageRequest.deserializeBinaryFromReader = function(msg, reader reader.readMessage(value,proto.messages.Location.deserializeBinaryFromReader); msg.setLocation(value); break; + case 18: + var value = new proto.messages.PollVoteMessage; + reader.readMessage(value,proto.messages.PollVoteMessage.deserializeBinaryFromReader); + msg.setPollvote(value); + break; default: reader.skipField(); break; @@ -8541,6 +8569,14 @@ proto.messages.MessageRequest.serializeBinaryToWriter = function(message, writer proto.messages.Location.serializeBinaryToWriter ); } + f = message.getPollvote(); + if (f != null) { + writer.writeMessage( + 18, + f, + proto.messages.PollVoteMessage.serializeBinaryToWriter + ); + } }; @@ -9060,6 +9096,43 @@ proto.messages.MessageRequest.prototype.hasLocation = function() { }; +/** + * optional PollVoteMessage pollVote = 18; + * @return {?proto.messages.PollVoteMessage} + */ +proto.messages.MessageRequest.prototype.getPollvote = function() { + return /** @type{?proto.messages.PollVoteMessage} */ ( + jspb.Message.getWrapperField(this, proto.messages.PollVoteMessage, 18)); +}; + + +/** + * @param {?proto.messages.PollVoteMessage|undefined} value + * @return {!proto.messages.MessageRequest} returns this +*/ +proto.messages.MessageRequest.prototype.setPollvote = function(value) { + return jspb.Message.setWrapperField(this, 18, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.messages.MessageRequest} returns this + */ +proto.messages.MessageRequest.prototype.clearPollvote = function() { + return this.setPollvote(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.messages.MessageRequest.prototype.hasPollvote = function() { + return jspb.Message.getField(this, 18) != null; +}; + + @@ -20280,6 +20353,240 @@ proto.messages.PollMessage.prototype.setMultipleanswers = function(value) { }; + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.messages.PollVoteMessage.repeatedFields_ = [3]; + + + +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.PollVoteMessage.prototype.toObject = function(opt_includeInstance) { + return proto.messages.PollVoteMessage.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.PollVoteMessage} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.PollVoteMessage.toObject = function(includeInstance, msg) { + var f, obj = { + pollmessageid: jspb.Message.getFieldWithDefault(msg, 1, ""), + pollserverid: jspb.Message.getFieldWithDefault(msg, 2, 0), + optionsList: (f = jspb.Message.getRepeatedField(msg, 3)) == null ? undefined : f + }; + + 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.PollVoteMessage} + */ +proto.messages.PollVoteMessage.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.messages.PollVoteMessage; + return proto.messages.PollVoteMessage.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.messages.PollVoteMessage} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.messages.PollVoteMessage} + */ +proto.messages.PollVoteMessage.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setPollmessageid(value); + break; + case 2: + var value = /** @type {number} */ (reader.readInt64()); + msg.setPollserverid(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.addOptions(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.messages.PollVoteMessage.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.messages.PollVoteMessage.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.PollVoteMessage} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.PollVoteMessage.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getPollmessageid(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = /** @type {number} */ (jspb.Message.getField(message, 2)); + if (f != null) { + writer.writeInt64( + 2, + f + ); + } + f = message.getOptionsList(); + if (f.length > 0) { + writer.writeRepeatedString( + 3, + f + ); + } +}; + + +/** + * optional string pollMessageId = 1; + * @return {string} + */ +proto.messages.PollVoteMessage.prototype.getPollmessageid = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.PollVoteMessage} returns this + */ +proto.messages.PollVoteMessage.prototype.setPollmessageid = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional int64 pollServerId = 2; + * @return {number} + */ +proto.messages.PollVoteMessage.prototype.getPollserverid = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.messages.PollVoteMessage} returns this + */ +proto.messages.PollVoteMessage.prototype.setPollserverid = function(value) { + return jspb.Message.setField(this, 2, value); +}; + + +/** + * Clears the field making it undefined. + * @return {!proto.messages.PollVoteMessage} returns this + */ +proto.messages.PollVoteMessage.prototype.clearPollserverid = function() { + return jspb.Message.setField(this, 2, undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.messages.PollVoteMessage.prototype.hasPollserverid = function() { + return jspb.Message.getField(this, 2) != null; +}; + + +/** + * repeated string options = 3; + * @return {!Array} + */ +proto.messages.PollVoteMessage.prototype.getOptionsList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 3)); +}; + + +/** + * @param {!Array} value + * @return {!proto.messages.PollVoteMessage} returns this + */ +proto.messages.PollVoteMessage.prototype.setOptionsList = function(value) { + return jspb.Message.setField(this, 3, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.messages.PollVoteMessage} returns this + */ +proto.messages.PollVoteMessage.prototype.addOptions = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 3, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.messages.PollVoteMessage} returns this + */ +proto.messages.PollVoteMessage.prototype.clearOptionsList = function() { + return this.setOptionsList([]); +}; + + /** * @enum {number} */ diff --git a/src/core/engines/gows/session.gows.core.ts b/src/core/engines/gows/session.gows.core.ts index d095794f..6f27160f 100644 --- a/src/core/engines/gows/session.gows.core.ts +++ b/src/core/engines/gows/session.gows.core.ts @@ -81,6 +81,7 @@ import { MessageLinkCustomPreviewRequest, MessageLocationRequest, MessagePollRequest, + MessagePollVoteRequest, MessageReactionRequest, MessageReplyRequest, MessageTextRequest, @@ -539,6 +540,7 @@ export class WhatsappSessionGoWSCore extends WhatsappSession { onlyEvent(WhatsMeowEvent.POLL_VOTE_EVENT), map(this.toPollVotePayload.bind(this)), filter(Boolean), + share(), ); // Split into successful and failed responses @@ -795,6 +797,10 @@ export class WhatsappSessionGoWSCore extends WhatsappSession { return this.messageResponse(jid, data); } + sendPollVote(request: MessagePollVoteRequest) { + throw new AvailableInPlusVersion('Poll voting'); + } + sendList(request: SendListRequest): Promise { throw new AvailableInPlusVersion(); } diff --git a/src/structures/chatting.dto.ts b/src/structures/chatting.dto.ts index ff424abd..6ad38ca9 100644 --- a/src/structures/chatting.dto.ts +++ b/src/structures/chatting.dto.ts @@ -8,8 +8,10 @@ import { IsFileType } from '@waha/nestjs/validation/IsFileType'; import { GetChatMessagesQuery } from '@waha/structures/chats.dto'; import { plainToInstance, Transform, Type } from 'class-transformer'; import { + IsArray, IsBoolean, IsNotEmpty, + IsNumber, IsOptional, IsString, IsUrl, @@ -456,3 +458,34 @@ export class NewMessageIDResponse { }) id: string; } + +export class MessagePollVoteRequest extends ChatRequest { + @ApiProperty({ + description: + 'The ID of the poll message. Format: {fromMe}_{chatID}_{messageId}[_{participant}] or just ID for GOWS', + example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA', + required: true, + }) + @IsString() + @IsNotEmpty() + pollMessageId: string; + + @ApiProperty({ + description: + 'Only for Channels - server message id (if known); if omitted, API may look it up in the storage', + required: false, + example: null, + }) + @IsOptional() + @IsNumber() + pollServerId?: number; + + @ApiProperty({ + description: 'Poll options you are voting for', + example: 'Awesome!', + isArray: true, + }) + @IsArray() + @IsString({ each: true }) + votes: string[]; +}