From 9ff8eed6ba0d67a82cd2bff31440f8d0157f5dd0 Mon Sep 17 00:00:00 2001 From: devlikepro Date: Tue, 22 Jul 2025 17:43:39 +0700 Subject: [PATCH] [core] Add, edit, and remove contact API fix #1124 --- src/contacts.session.controller.ts | 38 +++ src/core/abc/session.abc.ts | 10 +- src/core/app.module.core.ts | 2 + src/core/engines/gows/grpc/gows.ts | 204 ++++++++++++-- src/core/engines/gows/grpc/gows_grpc_pb.js | 69 +++-- src/core/engines/gows/grpc/gows_pb.js | 263 +++++++++++++++++++ src/core/engines/gows/session.gows.core.ts | 15 +- src/core/engines/noweb/session.noweb.core.ts | 28 +- src/core/engines/webjs/session.webjs.core.ts | 16 +- src/structures/contacts.dto.ts | 18 ++ yarn.lock | 4 +- 11 files changed, 613 insertions(+), 54 deletions(-) create mode 100644 src/contacts.session.controller.ts diff --git a/src/contacts.session.controller.ts b/src/contacts.session.controller.ts new file mode 100644 index 00000000..e5f483fc --- /dev/null +++ b/src/contacts.session.controller.ts @@ -0,0 +1,38 @@ +import { Body, Controller, Delete, Param, Put, UsePipes } from '@nestjs/common'; +import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger'; +import { WhatsappSession } from '@waha/core/abc/session.abc'; +import { ChatIdApiParam } from '@waha/nestjs/params/ChatIdApiParam'; +import { + SessionApiParam, + WorkingSessionParam, +} from '@waha/nestjs/params/SessionApiParam'; +import { WAHAValidationPipe } from '@waha/nestjs/pipes/WAHAValidationPipe'; + +import { SessionManager } from './core/abc/manager.abc'; +import { Result } from './structures/base.dto'; +import { ContactUpdateBody } from './structures/contacts.dto'; + +@ApiSecurity('api_key') +@Controller('api/:session/contacts') +@ApiTags('👤 Contacts') +export class ContactsSessionController { + constructor(private manager: SessionManager) {} + + @Put('/:chatId') + @SessionApiParam + @ChatIdApiParam + @ApiOperation({ + summary: 'Create or update contact', + description: + 'Create or update contact on the phone address book. May not work if you have installed many WhatsApp apps on the same phone', + }) + @UsePipes(new WAHAValidationPipe()) + async put( + @WorkingSessionParam session: WhatsappSession, + @Param('chatId') chatId: string, + @Body() body: ContactUpdateBody, + ): Promise { + await session.upsertContact(chatId, body); + return { success: true }; + } +} diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index 1e4250f6..c7ad8cb4 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -77,7 +77,11 @@ import { MessageVoiceRequest, SendSeenRequest, } from '../../structures/chatting.dto'; -import { ContactQuery, ContactRequest } from '../../structures/contacts.dto'; +import { + ContactQuery, + ContactRequest, + ContactUpdateBody, +} from '../../structures/contacts.dto'; import { WAHAEngine, WAHAEvents, @@ -639,6 +643,10 @@ export abstract class WhatsappSession { /** * Contacts methods */ + public upsertContact(chatId: string, body: ContactUpdateBody): Promise { + throw new NotImplementedByEngineError(); + } + public getContact(query: ContactQuery) { throw new NotImplementedByEngineError(); } diff --git a/src/core/app.module.core.ts b/src/core/app.module.core.ts index 52afc4c5..b480baf1 100644 --- a/src/core/app.module.core.ts +++ b/src/core/app.module.core.ts @@ -14,6 +14,7 @@ import { ServerController } from '@waha/api/server.controller'; import { ServerDebugController } from '@waha/api/server.debug.controller'; import { WebsocketGatewayCore } from '@waha/api/websocket.gateway.core'; import { AppsModuleExports } from '@waha/apps/apps.module'; +import { ContactsSessionController } from '@waha/contacts.session.controller'; import { ApiKeyStrategy } from '@waha/core/auth/apiKey.strategy'; import { IApiKeyAuth } from '@waha/core/auth/auth'; import { AuthMiddleware } from '@waha/core/auth/auth.middleware'; @@ -146,6 +147,7 @@ export const CONTROLLERS = [ StatusController, LabelsController, ContactsController, + ContactsSessionController, LidsController, GroupsController, PresenceController, diff --git a/src/core/engines/gows/grpc/gows.ts b/src/core/engines/gows/grpc/gows.ts index 7dfe6504..64c6c23e 100644 --- a/src/core/engines/gows/grpc/gows.ts +++ b/src/core/engines/gows/grpc/gows.ts @@ -7941,6 +7941,145 @@ export namespace messages { return GetMessagesRequest.deserialize(bytes); } } + export class UpdateContactRequest extends pb_1.Message { + #one_of_decls: number[][] = []; + constructor(data?: any[] | { + session?: Session; + jid?: string; + firstName?: string; + lastName?: 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 ("firstName" in data && data.firstName != undefined) { + this.firstName = data.firstName; + } + if ("lastName" in data && data.lastName != undefined) { + this.lastName = data.lastName; + } + } + } + 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 firstName() { + return pb_1.Message.getFieldWithDefault(this, 3, "") as string; + } + set firstName(value: string) { + pb_1.Message.setField(this, 3, value); + } + get lastName() { + return pb_1.Message.getFieldWithDefault(this, 4, "") as string; + } + set lastName(value: string) { + pb_1.Message.setField(this, 4, value); + } + static fromObject(data: { + session?: ReturnType; + jid?: string; + firstName?: string; + lastName?: string; + }): UpdateContactRequest { + const message = new UpdateContactRequest({}); + if (data.session != null) { + message.session = Session.fromObject(data.session); + } + if (data.jid != null) { + message.jid = data.jid; + } + if (data.firstName != null) { + message.firstName = data.firstName; + } + if (data.lastName != null) { + message.lastName = data.lastName; + } + return message; + } + toObject() { + const data: { + session?: ReturnType; + jid?: string; + firstName?: string; + lastName?: string; + } = {}; + if (this.session != null) { + data.session = this.session.toObject(); + } + if (this.jid != null) { + data.jid = this.jid; + } + if (this.firstName != null) { + data.firstName = this.firstName; + } + if (this.lastName != null) { + data.lastName = this.lastName; + } + 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.firstName.length) + writer.writeString(3, this.firstName); + if (this.lastName.length) + writer.writeString(4, this.lastName); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): UpdateContactRequest { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new UpdateContactRequest(); + 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.firstName = reader.readString(); + break; + case 4: + message.lastName = reader.readString(); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): UpdateContactRequest { + return UpdateContactRequest.deserialize(bytes); + } + } export class GetContactsRequest extends pb_1.Message { #one_of_decls: number[][] = []; constructor(data?: any[] | { @@ -9603,6 +9742,33 @@ export namespace messages { responseSerialize: (message: JsonList) => Buffer.from(message.serialize()), responseDeserialize: (bytes: Buffer) => JsonList.deserialize(new Uint8Array(bytes)) }, + UpdateContact: { + path: "/messages.MessageService/UpdateContact", + requestStream: false, + responseStream: false, + requestSerialize: (message: UpdateContactRequest) => Buffer.from(message.serialize()), + requestDeserialize: (bytes: Buffer) => UpdateContactRequest.deserialize(new Uint8Array(bytes)), + responseSerialize: (message: Empty) => Buffer.from(message.serialize()), + responseDeserialize: (bytes: Buffer) => Empty.deserialize(new Uint8Array(bytes)) + }, + GetContacts: { + path: "/messages.MessageService/GetContacts", + requestStream: false, + responseStream: false, + requestSerialize: (message: GetContactsRequest) => Buffer.from(message.serialize()), + requestDeserialize: (bytes: Buffer) => GetContactsRequest.deserialize(new Uint8Array(bytes)), + responseSerialize: (message: JsonList) => Buffer.from(message.serialize()), + responseDeserialize: (bytes: Buffer) => JsonList.deserialize(new Uint8Array(bytes)) + }, + GetContactById: { + path: "/messages.MessageService/GetContactById", + requestStream: false, + responseStream: false, + requestSerialize: (message: EntityByIdRequest) => Buffer.from(message.serialize()), + requestDeserialize: (bytes: Buffer) => EntityByIdRequest.deserialize(new Uint8Array(bytes)), + responseSerialize: (message: Json) => Buffer.from(message.serialize()), + responseDeserialize: (bytes: Buffer) => Json.deserialize(new Uint8Array(bytes)) + }, CancelEventMessage: { path: "/messages.MessageService/CancelEventMessage", requestStream: false, @@ -9639,24 +9805,6 @@ export namespace messages { responseSerialize: (message: JsonList) => Buffer.from(message.serialize()), responseDeserialize: (bytes: Buffer) => JsonList.deserialize(new Uint8Array(bytes)) }, - GetContacts: { - path: "/messages.MessageService/GetContacts", - requestStream: false, - responseStream: false, - requestSerialize: (message: GetContactsRequest) => Buffer.from(message.serialize()), - requestDeserialize: (bytes: Buffer) => GetContactsRequest.deserialize(new Uint8Array(bytes)), - responseSerialize: (message: JsonList) => Buffer.from(message.serialize()), - responseDeserialize: (bytes: Buffer) => JsonList.deserialize(new Uint8Array(bytes)) - }, - GetContactById: { - path: "/messages.MessageService/GetContactById", - requestStream: false, - responseStream: false, - requestSerialize: (message: EntityByIdRequest) => Buffer.from(message.serialize()), - requestDeserialize: (bytes: Buffer) => EntityByIdRequest.deserialize(new Uint8Array(bytes)), - responseSerialize: (message: Json) => Buffer.from(message.serialize()), - responseDeserialize: (bytes: Buffer) => Json.deserialize(new Uint8Array(bytes)) - }, GetChats: { path: "/messages.MessageService/GetChats", requestStream: false, @@ -9722,12 +9870,13 @@ export namespace messages { abstract RemoveChatLabel(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetLabelsByJid(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetChatsByLabelId(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; + abstract UpdateContact(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; + abstract GetContacts(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; + 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 GetMessageById(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetMessages(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; - abstract GetContacts(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; - abstract GetContactById(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; abstract GetChats(call: grpc_1.ServerUnaryCall, callback: grpc_1.sendUnaryData): void; } export class MessageServiceClient extends grpc_1.makeGenericClientConstructor(UnimplementedMessageServiceService.definition, "MessageService", {}) { @@ -9896,6 +10045,15 @@ export namespace messages { GetChatsByLabelId: 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.GetChatsByLabelId(message, metadata, options, callback); }; + UpdateContact: GrpcUnaryServiceInterface = (message: UpdateContactRequest, 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.UpdateContact(message, metadata, options, callback); + }; + GetContacts: GrpcUnaryServiceInterface = (message: GetContactsRequest, 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.GetContacts(message, metadata, options, callback); + }; + GetContactById: 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.GetContactById(message, metadata, options, callback); + }; CancelEventMessage: GrpcUnaryServiceInterface = (message: CancelEventMessageRequest, 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.CancelEventMessage(message, metadata, options, callback); }; @@ -9908,12 +10066,6 @@ export namespace messages { GetMessages: GrpcUnaryServiceInterface = (message: GetMessagesRequest, 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.GetMessages(message, metadata, options, callback); }; - GetContacts: GrpcUnaryServiceInterface = (message: GetContactsRequest, 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.GetContacts(message, metadata, options, callback); - }; - GetContactById: 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.GetContactById(message, metadata, options, callback); - }; GetChats: GrpcUnaryServiceInterface = (message: GetChatsRequest, 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.GetChats(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 2bf3fbfe..c2df03ba 100644 --- a/src/core/engines/gows/grpc/gows_grpc_pb.js +++ b/src/core/engines/gows/grpc/gows_grpc_pb.js @@ -631,6 +631,17 @@ function deserialize_messages_SubscribePresenceRequest(buffer_arg) { return gows_pb.SubscribePresenceRequest.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_messages_UpdateContactRequest(arg) { + if (!(arg instanceof gows_pb.UpdateContactRequest)) { + throw new Error('Expected argument of type messages.UpdateContactRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_messages_UpdateContactRequest(buffer_arg) { + return gows_pb.UpdateContactRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_messages_UpdateParticipantsRequest(arg) { if (!(arg instanceof gows_pb.UpdateParticipantsRequest)) { throw new Error('Expected argument of type messages.UpdateParticipantsRequest'); @@ -1294,6 +1305,42 @@ getLabels: { responseDeserialize: deserialize_messages_JsonList, }, // +// Contacts +// +updateContact: { + path: '/messages.MessageService/UpdateContact', + requestStream: false, + responseStream: false, + requestType: gows_pb.UpdateContactRequest, + responseType: gows_pb.Empty, + requestSerialize: serialize_messages_UpdateContactRequest, + requestDeserialize: deserialize_messages_UpdateContactRequest, + responseSerialize: serialize_messages_Empty, + responseDeserialize: deserialize_messages_Empty, + }, + getContacts: { + path: '/messages.MessageService/GetContacts', + requestStream: false, + responseStream: false, + requestType: gows_pb.GetContactsRequest, + responseType: gows_pb.JsonList, + requestSerialize: serialize_messages_GetContactsRequest, + requestDeserialize: deserialize_messages_GetContactsRequest, + responseSerialize: serialize_messages_JsonList, + responseDeserialize: deserialize_messages_JsonList, + }, + getContactById: { + path: '/messages.MessageService/GetContactById', + requestStream: false, + responseStream: false, + requestType: gows_pb.EntityByIdRequest, + responseType: gows_pb.Json, + requestSerialize: serialize_messages_EntityByIdRequest, + requestDeserialize: deserialize_messages_EntityByIdRequest, + responseSerialize: serialize_messages_Json, + responseDeserialize: deserialize_messages_Json, + }, + // // Events // cancelEventMessage: { @@ -1346,28 +1393,6 @@ getMessageById: { responseSerialize: serialize_messages_JsonList, responseDeserialize: deserialize_messages_JsonList, }, - getContacts: { - path: '/messages.MessageService/GetContacts', - requestStream: false, - responseStream: false, - requestType: gows_pb.GetContactsRequest, - responseType: gows_pb.JsonList, - requestSerialize: serialize_messages_GetContactsRequest, - requestDeserialize: deserialize_messages_GetContactsRequest, - responseSerialize: serialize_messages_JsonList, - responseDeserialize: deserialize_messages_JsonList, - }, - getContactById: { - path: '/messages.MessageService/GetContactById', - requestStream: false, - responseStream: false, - requestType: gows_pb.EntityByIdRequest, - responseType: gows_pb.Json, - requestSerialize: serialize_messages_EntityByIdRequest, - requestDeserialize: deserialize_messages_EntityByIdRequest, - responseSerialize: serialize_messages_Json, - responseDeserialize: deserialize_messages_Json, - }, getChats: { path: '/messages.MessageService/GetChats', requestStream: false, diff --git a/src/core/engines/gows/grpc/gows_pb.js b/src/core/engines/gows/grpc/gows_pb.js index 6fedf0e7..14959791 100644 --- a/src/core/engines/gows/grpc/gows_pb.js +++ b/src/core/engines/gows/grpc/gows_pb.js @@ -107,6 +107,7 @@ goog.exportSymbol('proto.messages.SortBy', null, global); goog.exportSymbol('proto.messages.SortBy.Order', null, global); goog.exportSymbol('proto.messages.StartSessionRequest', null, global); goog.exportSymbol('proto.messages.SubscribePresenceRequest', null, global); +goog.exportSymbol('proto.messages.UpdateContactRequest', null, global); goog.exportSymbol('proto.messages.UpdateParticipantsRequest', null, global); goog.exportSymbol('proto.messages.UpsertLabelRequest', null, global); goog.exportSymbol('proto.messages.vCardContact', null, global); @@ -1601,6 +1602,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.messages.GetMessagesRequest.displayName = 'proto.messages.GetMessagesRequest'; } +/** + * 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.UpdateContactRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.messages.UpdateContactRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.messages.UpdateContactRequest.displayName = 'proto.messages.UpdateContactRequest'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -16661,6 +16683,247 @@ proto.messages.GetMessagesRequest.prototype.hasPagination = 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.UpdateContactRequest.prototype.toObject = function(opt_includeInstance) { + return proto.messages.UpdateContactRequest.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.UpdateContactRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.UpdateContactRequest.toObject = function(includeInstance, msg) { + var f, obj = { + session: (f = msg.getSession()) && proto.messages.Session.toObject(includeInstance, f), + jid: jspb.Message.getFieldWithDefault(msg, 2, ""), + firstname: jspb.Message.getFieldWithDefault(msg, 3, ""), + lastname: jspb.Message.getFieldWithDefault(msg, 4, "") + }; + + 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.UpdateContactRequest} + */ +proto.messages.UpdateContactRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.messages.UpdateContactRequest; + return proto.messages.UpdateContactRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.messages.UpdateContactRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.messages.UpdateContactRequest} + */ +proto.messages.UpdateContactRequest.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.setFirstname(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setLastname(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.messages.UpdateContactRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.messages.UpdateContactRequest.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.UpdateContactRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.messages.UpdateContactRequest.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.getFirstname(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getLastname(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } +}; + + +/** + * optional Session session = 1; + * @return {?proto.messages.Session} + */ +proto.messages.UpdateContactRequest.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.UpdateContactRequest} returns this +*/ +proto.messages.UpdateContactRequest.prototype.setSession = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.messages.UpdateContactRequest} returns this + */ +proto.messages.UpdateContactRequest.prototype.clearSession = function() { + return this.setSession(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.messages.UpdateContactRequest.prototype.hasSession = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string jid = 2; + * @return {string} + */ +proto.messages.UpdateContactRequest.prototype.getJid = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.UpdateContactRequest} returns this + */ +proto.messages.UpdateContactRequest.prototype.setJid = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string firstName = 3; + * @return {string} + */ +proto.messages.UpdateContactRequest.prototype.getFirstname = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.UpdateContactRequest} returns this + */ +proto.messages.UpdateContactRequest.prototype.setFirstname = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string lastName = 4; + * @return {string} + */ +proto.messages.UpdateContactRequest.prototype.getLastname = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.messages.UpdateContactRequest} returns this + */ +proto.messages.UpdateContactRequest.prototype.setLastname = function(value) { + return jspb.Message.setProto3StringField(this, 4, 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 a7cc4f9c..e45e83a5 100644 --- a/src/core/engines/gows/session.gows.core.ts +++ b/src/core/engines/gows/session.gows.core.ts @@ -1,5 +1,6 @@ import { aggregateMessageKeysNotFromMe, + Contact, getContentType, getUrlFromDirectPath, isJidGroup, @@ -86,7 +87,7 @@ import { SendSeenRequest, WANumberExistResult, } from '@waha/structures/chatting.dto'; -import { ContactQuery } from '@waha/structures/contacts.dto'; +import { ContactQuery, ContactUpdateBody } from '@waha/structures/contacts.dto'; import { ACK_UNKNOWN, WAHAEvents, @@ -1464,6 +1465,18 @@ export class WhatsappSessionGoWSCore extends WhatsappSession { * Contacts methods */ + public async upsertContact(chatId: string, body: ContactUpdateBody) { + const jid = toJID(chatId); + const request = new messages.UpdateContactRequest({ + session: this.session, + jid: jid, + firstName: body.firstName || '', + lastName: body.lastName || '', + }); + const response = await promisify(this.client.UpdateContact)(request); + response.toObject(); + } + protected toWAContact(contact) { return { id: toCusFormat(contact.Jid), diff --git a/src/core/engines/noweb/session.noweb.core.ts b/src/core/engines/noweb/session.noweb.core.ts index 53e89d27..fc1aea9b 100644 --- a/src/core/engines/noweb/session.noweb.core.ts +++ b/src/core/engines/noweb/session.noweb.core.ts @@ -121,7 +121,11 @@ import { SendSeenRequest, WANumberExistResult, } from '@waha/structures/chatting.dto'; -import { ContactQuery, ContactRequest } from '@waha/structures/contacts.dto'; +import { + ContactQuery, + ContactRequest, + ContactUpdateBody, +} from '@waha/structures/contacts.dto'; import { ACK_UNKNOWN, SECOND, @@ -1322,6 +1326,28 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { /** * Contacts methods */ + + public async upsertContact(chatId: string, body: ContactUpdateBody) { + const jid = toJID(chatId); + let fullName = body.firstName; + if (body.lastName) { + fullName = `${body.firstName} ${body.lastName}`; + } + const action = { + fullName: fullName, + firstName: body.firstName, + saveOnPrimaryAddressbook: true, + }; + await this.sock.addOrEditContact(jid, action); + const updates: Partial[] = [ + { + id: jid, + name: fullName, + }, + ]; + this.sock.ev.emit('contacts.update', updates); + } + async getContact(query: ContactQuery) { const jid = toJID(query.contactId); const contact = await this.store.getContactById(jid); diff --git a/src/core/engines/webjs/session.webjs.core.ts b/src/core/engines/webjs/session.webjs.core.ts index 4db4af16..cc116e26 100644 --- a/src/core/engines/webjs/session.webjs.core.ts +++ b/src/core/engines/webjs/session.webjs.core.ts @@ -80,7 +80,11 @@ import { SendSeenRequest, WANumberExistResult, } from '@waha/structures/chatting.dto'; -import { ContactQuery, ContactRequest } from '@waha/structures/contacts.dto'; +import { + ContactQuery, + ContactRequest, + ContactUpdateBody, +} from '@waha/structures/contacts.dto'; import { ACK_UNKNOWN, SECOND, @@ -953,6 +957,16 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { /** * Contacts methods */ + public async upsertContact(chatId: string, body: ContactUpdateBody) { + const phoneNumber = chatId.split('@')[0]; + await this.whatsapp.saveOrEditAddressbookContact( + phoneNumber, + body.firstName, + body.lastName, + true, + ); + } + getContact(query: ContactQuery) { return this.whatsapp .getContactById(this.ensureSuffix(query.contactId)) diff --git a/src/structures/contacts.dto.ts b/src/structures/contacts.dto.ts index 0909e292..da00376e 100644 --- a/src/structures/contacts.dto.ts +++ b/src/structures/contacts.dto.ts @@ -54,3 +54,21 @@ export class ContactRequest extends SessionBaseRequest { @IsString() contactId: string; } + +export class ContactUpdateBody { + @ApiProperty({ + description: 'Contact First Name', + example: 'John', + required: true, + }) + @IsString() + firstName: string = 'John'; + + @ApiProperty({ + description: 'Contact Last Name', + example: 'Doe', + required: true, + }) + @IsString() + lastName: string = 'Doe'; +} diff --git a/yarn.lock b/yarn.lock index e717fed9..1d13d1dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,7 @@ __metadata: "@adiwajshing/baileys@github:devlikeapro/Baileys#fork-master-2025-07-04": version: 6.7.18 - resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=599231e9f18be6ebe6e8eb48a5c57ec9f587ee5e" + resolution: "@adiwajshing/baileys@https://github.com/devlikeapro/Baileys.git#commit=f70c2b715e4b5d0319d831612aab88f65ad9f3bb" dependencies: "@cacheable/node-cache": ^1.4.0 "@hapi/boom": ^9.1.3 @@ -33,7 +33,7 @@ __metadata: optional: true sharp: optional: true - checksum: 4a0d0221ea30efbbe50dd3c639c45bed677da6688d550787b7c4fa2b3a1bb3992e3d3ebb920be2429113996c02e87a0801f94e6d6609f4ceaf8637b7490691ee + checksum: 095b00f611b65ed84fc347b6c5d20a7bab057806706c5cfe6ac74d43d1ec1d5086c7eff220b0ab1d426983e9696e63c14a8a1d02bdb60401047388c2aefa68b5 languageName: node linkType: hard