Repository navigation
feat(chat): add updateContact endpoint to set a contact's name #2758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import { | |
| PrivacySettingDto, | ||
| ReadMessageDto, | ||
| SendPresenceDto, | ||
| UpdateContactDto, | ||
| UpdateMessageDto, | ||
| WhatsAppNumberDto, | ||
| } from '@api/dto/chat.dto'; | ||
|
|
@@ -118,6 +119,7 @@ import makeWASocket, { | |
| isJidBroadcast, | ||
| isJidGroup, | ||
| isJidNewsletter, | ||
| isLidUser, | ||
| isPnUser, | ||
| jidNormalizedUser, | ||
| makeCacheableSignalKeyStore, | ||
|
|
@@ -4541,6 +4543,75 @@ export class BaileysStartupService extends ChannelStartupService { | |
| } | ||
| } | ||
|
|
||
| public async updateContact(data: UpdateContactDto) { | ||
| const jid = createJid(data.number); | ||
|
|
||
| if (isJidGroup(jid) || isJidNewsletter(jid) || isJidBroadcast(jid)) { | ||
| throw new BadRequestException('Only individual contacts can be updated'); | ||
| } | ||
|
|
||
| let pnJid: string | null = null; | ||
| let lidJid: string | null = null; | ||
|
|
||
| if (isLidUser(jid)) { | ||
| lidJid = jid; | ||
| } else { | ||
| const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); | ||
|
|
||
| if (!onWhatsapp?.exists) { | ||
| throw new BadRequestException(onWhatsapp); | ||
| } | ||
|
|
||
| if (isLidUser(onWhatsapp.jid)) { | ||
| lidJid = onWhatsapp.jid; | ||
| } else { | ||
| pnJid = onWhatsapp.jid; | ||
| } | ||
| } | ||
|
|
||
| const lidMapping = this.client.signalRepository.lidMapping; | ||
| if (!pnJid) { | ||
| const pn = await lidMapping.getPNForLID(lidJid); | ||
| pnJid = pn ? jidNormalizedUser(pn) : isPnUser(jid) ? jid : null; | ||
| } | ||
| if (!lidJid) { | ||
| const lid = await lidMapping.getLIDForPN(pnJid); | ||
| lidJid = lid ? jidNormalizedUser(lid) : null; | ||
| } | ||
|
|
||
| if (!pnJid) { | ||
| throw new BadRequestException(`No phone number is known for ${lidJid}`); | ||
| } | ||
|
|
||
| // Same shape the phone uses when it syncs a contact: the phone number JID | ||
| // goes in the patch index and the LID travels inside the action. | ||
| const contact: proto.SyncActionValue.IContactAction = { fullName: data.fullName }; | ||
| if (data.firstName) contact.firstName = data.firstName; | ||
| if (lidJid) contact.lidJid = lidJid; | ||
| if (data.saveOnPrimaryAddressbook !== undefined) { | ||
| contact.saveOnPrimaryAddressbook = data.saveOnPrimaryAddressbook; | ||
| } | ||
|
|
||
| try { | ||
| await this.client.addOrEditContact(pnJid, contact); | ||
| } catch (error) { | ||
| throw new InternalServerErrorException('Error updating contact', error.toString()); | ||
| } | ||
|
|
||
| // emitOwnEvents is off, so Baileys will not echo this change back to us. | ||
| await this.contactHandle['contacts.update']([{ id: pnJid, name: data.fullName }]); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Medium Β· Saved avatars disappear When a contact has a saved avatar and the profile-picture lookup during a name update fails or returns null, Preserve the existing Prompt for AI agentsIn `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts` at line 4602:
**Saved avatars disappear**
When a contact has a saved avatar and the profile-picture lookup during a name update fails or returns null, `contacts.update` writes the null `profilePicture` result into `profilePicUrl`, replacing the saved avatar, so contact listings lose it.
Preserve the existing `profilePicUrl` when the lookup fails or returns null. |
||
|
|
||
| // Chats that arrived addressed to the LID keep their own contact row. | ||
| if (lidJid && this.configService.get<Database>('DATABASE').SAVE_DATA.CONTACTS) { | ||
| await this.prismaRepository.contact.updateMany({ | ||
| where: { remoteJid: lidJid, instanceId: this.instanceId }, | ||
| data: { pushName: data.fullName }, | ||
| }); | ||
| } | ||
|
Comment on lines
+4602
to
+4610
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Medium Β· PN and LID names diverge When the PN update succeeds but the separate LID contact update fails, the PN row is committed while the LID row retains the old name, so LID-based contact reads remain stale and the caller receives an error despite the accepted WhatsApp patch. Make the PN and LID contact updates atomic, or retry/reconcile a failed LID update. Prompt for AI agentsIn `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts` at lines 4602-4610:
**PN and LID names diverge**
When the PN update succeeds but the separate LID contact update fails, the PN row is committed while the LID row retains the old name, so LID-based contact reads remain stale and the caller receives an error despite the accepted WhatsApp patch.
Make the PN and LID contact updates atomic, or retry/reconcile a failed LID update. |
||
|
|
||
| return { update: 'success', remoteJid: pnJid, lid: lidJid, fullName: data.fullName }; | ||
| } | ||
|
|
||
| public async updateProfileStatus(status: string) { | ||
| try { | ||
| await this.client.updateProfileStatus(status); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -319,6 +319,19 @@ export const profileNameSchema: JSONSchema7 = { | |
| ...isNotEmpty('name'), | ||
| }; | ||
|
|
||
| export const updateContactSchema: JSONSchema7 = { | ||
| $id: v4(), | ||
| type: 'object', | ||
| properties: { | ||
| number: { type: 'string' }, | ||
| fullName: { type: 'string' }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Medium Β· Long names fail local persistence When Set a 100-character maximum for Prompt for AI agentsIn `src/validate/chat.schema.ts` at line 327:
**Long names fail local persistence**
When `fullName` exceeds 100 characters and contact persistence is enabled, the schema accepts the name, but the Contact `pushName` column is limited to 100 characters. Baileys accepts the patch before Prisma rejects the local write, so the request fails and the local contact remains stale.
Set a 100-character maximum for `fullName` in `updateContactSchema`. |
||
| firstName: { type: 'string' }, | ||
| saveOnPrimaryAddressbook: { type: 'boolean' }, | ||
| }, | ||
| required: ['number', 'fullName'], | ||
| ...isNotEmpty('number', 'fullName'), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Medium Β· Empty names are accepted When the request includes an unrecognized property or an optional field and Validate that Prompt for AI agentsIn `src/validate/chat.schema.ts` at line 332:
**Empty names are accepted**
When the request includes an unrecognized property or an optional field and `fullName` is empty, `isNotEmpty` skips the `minLength` check when a property name is outside its enum, so the empty `fullName` reaches `addOrEditContact` and leaves the contact with a blank name.
Validate that `fullName` is nonempty independently of the other request properties. |
||
| }; | ||
|
|
||
| export const profileStatusSchema: JSONSchema7 = { | ||
| $id: v4(), | ||
| type: 'object', | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π‘ Medium Β· Accepted updates leave contacts stale
When baileys accepts the patch, then the profile-picture lookup, event adapter, or PN contact write in
contacts.updatefails,updateContactpropagates thecontacts.updatefailure afteraddOrEditContacthas accepted the WhatsApp change, so the API returns an error while the local contact remains stale; a profile-picture lookup failure also prevents the webhook from being sent.Make local synchronization failure-safe after the remote patch, retrying or reconciling failed contact updates instead of leaving the accepted change stale.
Prompt for AI agents