From 3e4d66ca2737ef738e05df2f376924fa52cd2be5 Mon Sep 17 00:00:00 2001 From: devlikepro Date: Thu, 20 Mar 2025 16:52:55 +0700 Subject: [PATCH] [core] Reply to button fix #848 --- src/api/chatting.controller.ts | 11 + src/core/abc/session.abc.ts | 5 + src/core/engines/gows/grpc/gows.ts | 175 +++++++++++ src/core/engines/gows/grpc/gows_grpc_pb.js | 22 ++ src/core/engines/gows/grpc/gows_pb.js | 293 +++++++++++++++++++ src/core/engines/gows/session.gows.core.ts | 1 + src/core/engines/webjs/session.webjs.core.ts | 7 +- src/structures/base.dto.ts | 1 + src/structures/chatting.dto.ts | 16 + 9 files changed, 530 insertions(+), 1 deletion(-) diff --git a/src/api/chatting.controller.ts b/src/api/chatting.controller.ts index eb138232..3322e3e1 100644 --- a/src/api/chatting.controller.ts +++ b/src/api/chatting.controller.ts @@ -18,6 +18,7 @@ import { ChatRequest, CheckNumberStatusQuery, GetMessageQuery, + MessageButtonReply, MessageContactVcardRequest, MessageFileRequest, MessageForwardRequest, @@ -177,6 +178,16 @@ export class ChattingController { return whatsapp.sendContactVCard(request); } + @Post('/send/buttons/reply') + @ApiOperation({ + summary: 'Reply on a button message', + }) + @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) + async sendButtonsReply(@Body() request: MessageButtonReply) { + const whatsapp = await this.manager.getWorkingSession(request.session); + return whatsapp.sendButtonsReply(request); + } + @Get('/sendText') @ApiOperation({ summary: 'Send a text message', deprecated: true }) async sendTextGet(@Query() query: MessageTextQuery) { diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index 96378e42..e80de946 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -46,6 +46,7 @@ import { ChatRequest, CheckNumberStatusQuery, EditMessageRequest, + MessageButtonReply, MessageContactVcardRequest, MessageFileRequest, MessageForwardRequest, @@ -431,6 +432,10 @@ export abstract class WhatsappSession { throw new NotImplementedByEngineError(); } + sendButtonsReply(request: MessageButtonReply) { + throw new NotImplementedByEngineError(); + } + abstract reply(request: MessageReplyRequest); abstract sendSeen(chat: SendSeenRequest); diff --git a/src/core/engines/gows/grpc/gows.ts b/src/core/engines/gows/grpc/gows.ts index 4348ec3f..69592294 100644 --- a/src/core/engines/gows/grpc/gows.ts +++ b/src/core/engines/gows/grpc/gows.ts @@ -3236,6 +3236,168 @@ export namespace messages { return ProfilePictureResponse.deserialize(bytes); } } + export class ButtonReplyRequest extends pb_1.Message { + #one_of_decls: number[][] = []; + constructor(data?: any[] | { + session?: Session; + jid?: string; + selectedDisplayText?: string; + selectedButtonID?: string; + replyTo?: 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 ("jid" in data && data.jid != undefined) { + this.jid = data.jid; + } + if ("selectedDisplayText" in data && data.selectedDisplayText != undefined) { + this.selectedDisplayText = data.selectedDisplayText; + } + if ("selectedButtonID" in data && data.selectedButtonID != undefined) { + this.selectedButtonID = data.selectedButtonID; + } + if ("replyTo" in data && data.replyTo != undefined) { + this.replyTo = data.replyTo; + } + } + } + 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 jid() { + return pb_1.Message.getFieldWithDefault(this, 2, "") as string; + } + set jid(value: string) { + pb_1.Message.setField(this, 2, value); + } + get selectedDisplayText() { + return pb_1.Message.getFieldWithDefault(this, 3, "") as string; + } + set selectedDisplayText(value: string) { + pb_1.Message.setField(this, 3, value); + } + get selectedButtonID() { + return pb_1.Message.getFieldWithDefault(this, 4, "") as string; + } + set selectedButtonID(value: string) { + pb_1.Message.setField(this, 4, value); + } + get replyTo() { + return pb_1.Message.getFieldWithDefault(this, 5, "") as string; + } + set replyTo(value: string) { + pb_1.Message.setField(this, 5, value); + } + static fromObject(data: { + session?: ReturnType; + jid?: string; + selectedDisplayText?: string; + selectedButtonID?: string; + replyTo?: string; + }): ButtonReplyRequest { + const message = new ButtonReplyRequest({}); + if (data.session != null) { + message.session = Session.fromObject(data.session); + } + if (data.jid != null) { + message.jid = data.jid; + } + if (data.selectedDisplayText != null) { + message.selectedDisplayText = data.selectedDisplayText; + } + if (data.selectedButtonID != null) { + message.selectedButtonID = data.selectedButtonID; + } + if (data.replyTo != null) { + message.replyTo = data.replyTo; + } + return message; + } + toObject() { + const data: { + session?: ReturnType; + jid?: string; + selectedDisplayText?: string; + selectedButtonID?: string; + replyTo?: string; + } = {}; + if (this.session != null) { + data.session = this.session.toObject(); + } + if (this.jid != null) { + data.jid = this.jid; + } + if (this.selectedDisplayText != null) { + data.selectedDisplayText = this.selectedDisplayText; + } + if (this.selectedButtonID != null) { + data.selectedButtonID = this.selectedButtonID; + } + if (this.replyTo != null) { + data.replyTo = this.replyTo; + } + 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.jid.length) + writer.writeString(2, this.jid); + if (this.selectedDisplayText.length) + writer.writeString(3, this.selectedDisplayText); + if (this.selectedButtonID.length) + writer.writeString(4, this.selectedButtonID); + if (this.replyTo.length) + writer.writeString(5, this.replyTo); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): ButtonReplyRequest { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new ButtonReplyRequest(); + 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.jid = reader.readString(); + break; + case 3: + message.selectedDisplayText = reader.readString(); + break; + case 4: + message.selectedButtonID = reader.readString(); + break; + case 5: + message.replyTo = reader.readString(); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): ButtonReplyRequest { + return ButtonReplyRequest.deserialize(bytes); + } + } export class PresenceRequest extends pb_1.Message { #one_of_decls: number[][] = []; constructor(data?: any[] | { @@ -7327,6 +7489,15 @@ export namespace messages { responseSerialize: (message: MessageResponse) => Buffer.from(message.serialize()), responseDeserialize: (bytes: Buffer) => MessageResponse.deserialize(new Uint8Array(bytes)) }, + SendButtonReply: { + path: "/messages.MessageService/SendButtonReply", + requestStream: false, + responseStream: false, + requestSerialize: (message: ButtonReplyRequest) => Buffer.from(message.serialize()), + requestDeserialize: (bytes: Buffer) => ButtonReplyRequest.deserialize(new Uint8Array(bytes)), + responseSerialize: (message: MessageResponse) => Buffer.from(message.serialize()), + responseDeserialize: (bytes: Buffer) => MessageResponse.deserialize(new Uint8Array(bytes)) + }, GetSubscribedNewsletters: { path: "/messages.MessageService/GetSubscribedNewsletters", requestStream: false, @@ -7488,6 +7659,7 @@ export namespace messages { abstract MarkRead(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract EditMessage(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract RevokeMessage(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; + abstract SendButtonReply(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetSubscribedNewsletters(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetNewsletterInfo(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetNewsletterMessagesByInvite(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; @@ -7606,6 +7778,9 @@ export namespace messages { RevokeMessage: GrpcUnaryServiceInterface = (message: RevokeMessageRequest, 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.RevokeMessage(message, metadata, options, callback); }; + SendButtonReply: GrpcUnaryServiceInterface = (message: ButtonReplyRequest, 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.SendButtonReply(message, metadata, options, callback); + }; GetSubscribedNewsletters: GrpcUnaryServiceInterface = (message: NewsletterListRequest, 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.GetSubscribedNewsletters(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 a5a0bb7e..f756b029 100644 --- a/src/core/engines/gows/grpc/gows_grpc_pb.js +++ b/src/core/engines/gows/grpc/gows_grpc_pb.js @@ -4,6 +4,17 @@ var grpc = require('@grpc/grpc-js'); var gows_pb = require('./gows_pb.js'); +function serialize_messages_ButtonReplyRequest(arg) { + if (!(arg instanceof gows_pb.ButtonReplyRequest)) { + throw new Error('Expected argument of type messages.ButtonReplyRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_messages_ButtonReplyRequest(buffer_arg) { + return gows_pb.ButtonReplyRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_messages_ChatPresenceRequest(arg) { if (!(arg instanceof gows_pb.ChatPresenceRequest)) { throw new Error('Expected argument of type messages.ChatPresenceRequest'); @@ -954,6 +965,17 @@ sendMessage: { responseSerialize: serialize_messages_MessageResponse, responseDeserialize: deserialize_messages_MessageResponse, }, + sendButtonReply: { + path: '/messages.MessageService/SendButtonReply', + requestStream: false, + responseStream: false, + requestType: gows_pb.ButtonReplyRequest, + responseType: gows_pb.MessageResponse, + requestSerialize: serialize_messages_ButtonReplyRequest, + requestDeserialize: deserialize_messages_ButtonReplyRequest, + responseSerialize: serialize_messages_MessageResponse, + responseDeserialize: deserialize_messages_MessageResponse, + }, // // Newsletters // diff --git a/src/core/engines/gows/grpc/gows_pb.js b/src/core/engines/gows/grpc/gows_pb.js index 39cb022c..95743991 100644 --- a/src/core/engines/gows/grpc/gows_pb.js +++ b/src/core/engines/gows/grpc/gows_pb.js @@ -22,6 +22,7 @@ var global = (function() { }.call(null)); goog.exportSymbol('proto.messages.AudioInfo', null, global); +goog.exportSymbol('proto.messages.ButtonReplyRequest', null, global); goog.exportSymbol('proto.messages.ChatPresence', null, global); goog.exportSymbol('proto.messages.ChatPresenceRequest', null, global); goog.exportSymbol('proto.messages.CheckPhonesRequest', null, global); @@ -765,6 +766,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.messages.ProfilePictureResponse.displayName = 'proto.messages.ProfilePictureResponse'; } +/** + * 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.ButtonReplyRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.messages.ButtonReplyRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.messages.ButtonReplyRequest.displayName = 'proto.messages.ButtonReplyRequest'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -7543,6 +7565,277 @@ proto.messages.ProfilePictureResponse.prototype.setUrl = function(value) { +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.ButtonReplyRequest.prototype.toObject = function(opt_includeInstance) { + return proto.messages.ButtonReplyRequest.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.ButtonReplyRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.ButtonReplyRequest.toObject = function(includeInstance, msg) { + var f, obj = { + session: (f = msg.getSession()) && proto.messages.Session.toObject(includeInstance, f), + jid: jspb.Message.getFieldWithDefault(msg, 2, ""), + selecteddisplaytext: jspb.Message.getFieldWithDefault(msg, 3, ""), + selectedbuttonid: jspb.Message.getFieldWithDefault(msg, 4, ""), + replyto: jspb.Message.getFieldWithDefault(msg, 5, "") + }; + + 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.ButtonReplyRequest} + */ +proto.messages.ButtonReplyRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.messages.ButtonReplyRequest; + return proto.messages.ButtonReplyRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.messages.ButtonReplyRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.messages.ButtonReplyRequest} + */ +proto.messages.ButtonReplyRequest.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.setJid(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSelecteddisplaytext(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setSelectedbuttonid(value); + break; + case 5: + var value = /** @type {string} */ (reader.readString()); + msg.setReplyto(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.messages.ButtonReplyRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.messages.ButtonReplyRequest.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.ButtonReplyRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.ButtonReplyRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSession(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.messages.Session.serializeBinaryToWriter + ); + } + f = message.getJid(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSelecteddisplaytext(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getSelectedbuttonid(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } + f = message.getReplyto(); + if (f.length > 0) { + writer.writeString( + 5, + f + ); + } +}; + + +/** + * optional Session session = 1; + * @return {?proto.messages.Session} + */ +proto.messages.ButtonReplyRequest.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.ButtonReplyRequest} returns this +*/ +proto.messages.ButtonReplyRequest.prototype.setSession = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.messages.ButtonReplyRequest} returns this + */ +proto.messages.ButtonReplyRequest.prototype.clearSession = function() { + return this.setSession(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.messages.ButtonReplyRequest.prototype.hasSession = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string jid = 2; + * @return {string} + */ +proto.messages.ButtonReplyRequest.prototype.getJid = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.ButtonReplyRequest} returns this + */ +proto.messages.ButtonReplyRequest.prototype.setJid = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string selectedDisplayText = 3; + * @return {string} + */ +proto.messages.ButtonReplyRequest.prototype.getSelecteddisplaytext = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.ButtonReplyRequest} returns this + */ +proto.messages.ButtonReplyRequest.prototype.setSelecteddisplaytext = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string selectedButtonID = 4; + * @return {string} + */ +proto.messages.ButtonReplyRequest.prototype.getSelectedbuttonid = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.ButtonReplyRequest} returns this + */ +proto.messages.ButtonReplyRequest.prototype.setSelectedbuttonid = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional string replyTo = 5; + * @return {string} + */ +proto.messages.ButtonReplyRequest.prototype.getReplyto = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.ButtonReplyRequest} returns this + */ +proto.messages.ButtonReplyRequest.prototype.setReplyto = function(value) { + return jspb.Message.setProto3StringField(this, 5, value); +}; + + + + + if (jspb.Message.GENERATE_TO_OBJECT) { /** * Creates an object representation of this proto. diff --git a/src/core/engines/gows/session.gows.core.ts b/src/core/engines/gows/session.gows.core.ts index 3e71b101..4d0ca838 100644 --- a/src/core/engines/gows/session.gows.core.ts +++ b/src/core/engines/gows/session.gows.core.ts @@ -63,6 +63,7 @@ import { ChatRequest, CheckNumberStatusQuery, EditMessageRequest, + MessageButtonReply, MessageFileRequest, MessageForwardRequest, MessageImageRequest, diff --git a/src/core/engines/webjs/session.webjs.core.ts b/src/core/engines/webjs/session.webjs.core.ts index ba938507..0b5c7fc6 100644 --- a/src/core/engines/webjs/session.webjs.core.ts +++ b/src/core/engines/webjs/session.webjs.core.ts @@ -41,6 +41,7 @@ import { ChatRequest, CheckNumberStatusQuery, EditMessageRequest, + MessageButtonReply, MessageFileRequest, MessageForwardRequest, MessageImageRequest, @@ -540,6 +541,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { throw new AvailableInPlusVersion(); } + sendButtonsReply(request: MessageButtonReply) { + throw new AvailableInPlusVersion(); + } + async sendLocation(request: MessageLocationRequest) { const location = new Location(request.latitude, request.longitude, { name: request.title, @@ -1443,7 +1448,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { let mentions = request.mentions; mentions = mentions ? mentions.map(this.ensureSuffix) : undefined; - const quotedMessageId = request.reply_to; + const quotedMessageId = request.reply_to || request.replyTo; return { mentions: mentions, diff --git a/src/structures/base.dto.ts b/src/structures/base.dto.ts index a841edd2..0aa44472 100644 --- a/src/structures/base.dto.ts +++ b/src/structures/base.dto.ts @@ -16,6 +16,7 @@ export class SessionQuery { */ export class SessionBaseRequest { + @IsString() session: string = WHATSAPP_DEFAULT_SESSION_NAME; } diff --git a/src/structures/chatting.dto.ts b/src/structures/chatting.dto.ts index 75f4b6af..1035d421 100644 --- a/src/structures/chatting.dto.ts +++ b/src/structures/chatting.dto.ts @@ -64,6 +64,7 @@ export class GetPresenceQuery extends ChatQuery {} */ export class ChatRequest extends SessionBaseRequest { @ChatIdProperty() + @IsString() chatId: string; } @@ -333,3 +334,18 @@ export class MessageDestination { from: string; fromMe: boolean; } + +export class MessageButtonReply extends ChatRequest { + @ReplyToProperty() + @IsString() + @IsNotEmpty() + replyTo?: string; + + @IsString() + @IsNotEmpty() + selectedDisplayText: string; + + @IsString() + @IsNotEmpty() + selectedButtonID: string; +}