whatsapp-pi 1.0.60 → 1.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/services/whatsapp.service.ts +44 -26
- package/src/ui/message-reply.view.ts +32 -21
- package/whatsapp-pi.ts +40 -30
package/package.json
CHANGED
|
@@ -185,11 +185,27 @@ export class WhatsAppService {
|
|
|
185
185
|
return value;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
private normalizeRecipientJid(jid: string): string {
|
|
189
|
-
if (jid.includes('@')) return jid;
|
|
190
|
-
const digits = jid.startsWith('+') ? jid.slice(1) : jid;
|
|
191
|
-
return `${digits}@s.whatsapp.net`;
|
|
192
|
-
}
|
|
188
|
+
private normalizeRecipientJid(jid: string): string {
|
|
189
|
+
if (jid.includes('@')) return jid;
|
|
190
|
+
const digits = jid.startsWith('+') ? jid.slice(1) : jid;
|
|
191
|
+
return `${digits}@s.whatsapp.net`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public resolveOutboundRecipientJid(recipient: string): string {
|
|
195
|
+
if (SessionManager.isGroupJid(recipient)) {
|
|
196
|
+
return recipient;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const senderNumber = this.normalizeContactNumber(recipient.split('@')[0]);
|
|
200
|
+
const allowedContact = this.sessionManager.getAllowedContact(recipient)
|
|
201
|
+
?? this.sessionManager.getAllowedContact(senderNumber);
|
|
202
|
+
|
|
203
|
+
if (allowedContact?.sendNumber) {
|
|
204
|
+
return this.normalizeRecipientJid(allowedContact.sendNumber);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return this.normalizeRecipientJid(recipient);
|
|
208
|
+
}
|
|
193
209
|
|
|
194
210
|
private normalizeJidForComparison(jid: string): string {
|
|
195
211
|
const [localPart, domain = ''] = jid.split('@');
|
|
@@ -653,27 +669,29 @@ export class WhatsAppService {
|
|
|
653
669
|
}
|
|
654
670
|
}
|
|
655
671
|
|
|
656
|
-
async sendMessage(jid: string, text: string) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
672
|
+
async sendMessage(jid: string, text: string) {
|
|
673
|
+
const recipientJid = this.resolveOutboundRecipientJid(jid);
|
|
674
|
+
|
|
675
|
+
// Ensure we show the typing indicator before sending
|
|
676
|
+
await this.sendPresence(recipientJid, 'composing');
|
|
677
|
+
|
|
678
|
+
const result = await this.messageSender.send({
|
|
679
|
+
recipientJid,
|
|
680
|
+
text: text
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// After sending, we can stop the typing indicator
|
|
684
|
+
await this.sendPresence(recipientJid, 'paused');
|
|
685
|
+
|
|
686
|
+
if (!result.success) {
|
|
687
|
+
console.error(t('service.whatsapp.failedSendMessage', { jid: recipientJid, error: result.error ?? t('message.sender.unknownError') }));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async sendMenuMessage(jid: string, text: string) {
|
|
694
|
+
const normalizedJid = this.resolveOutboundRecipientJid(jid);
|
|
677
695
|
const socket = this.getActiveSocket();
|
|
678
696
|
|
|
679
697
|
if (!socket) {
|
|
@@ -41,13 +41,21 @@ const buildReplyWidget = (selectedMessage: SelectedMessageContext): string[] =>
|
|
|
41
41
|
];
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const buildReplyTitle = (selectedMessage: SelectedMessageContext): string => {
|
|
44
|
+
const buildReplyTitle = (selectedMessage: SelectedMessageContext): string => {
|
|
45
45
|
const sender = selectedMessage.senderName
|
|
46
46
|
? `${selectedMessage.senderName} (${selectedMessage.senderNumber})`
|
|
47
47
|
: selectedMessage.senderNumber;
|
|
48
48
|
|
|
49
|
-
return truncateToWidth(`${t('message.reply.title')} ${sender}`, 120);
|
|
50
|
-
};
|
|
49
|
+
return truncateToWidth(`${t('message.reply.title')} ${sender}`, 120);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const toRecentSenderNumber = (recipientJid: string): string => {
|
|
53
|
+
if (recipientJid.endsWith('@g.us')) {
|
|
54
|
+
return recipientJid;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return `+${recipientJid.split('@')[0]}`;
|
|
58
|
+
};
|
|
51
59
|
|
|
52
60
|
export async function showMessageReplyView(
|
|
53
61
|
ctx: MessageReplyContext,
|
|
@@ -70,24 +78,27 @@ export async function showMessageReplyView(
|
|
|
70
78
|
continue;
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
const draft: ReplyDraft = {
|
|
74
|
-
text,
|
|
75
|
-
targetMessageId: props.selectedMessage.messageId,
|
|
76
|
-
targetConversation: props.selectedMessage.senderNumber
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
const draft: ReplyDraft = {
|
|
82
|
+
text,
|
|
83
|
+
targetMessageId: props.selectedMessage.messageId,
|
|
84
|
+
targetConversation: props.selectedMessage.senderNumber
|
|
85
|
+
};
|
|
86
|
+
const recipientJid = props.whatsappService.resolveOutboundRecipientJid(
|
|
87
|
+
props.selectedMessage.senderNumber
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const result: ReplySendResult = await props.whatsappService.sendMenuMessage(
|
|
91
|
+
recipientJid,
|
|
92
|
+
draft.text
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (result.success) {
|
|
96
|
+
await props.recentsService.recordMessage({
|
|
97
|
+
messageId: result.messageId ?? `${Date.now()}`,
|
|
98
|
+
senderNumber: toRecentSenderNumber(recipientJid),
|
|
99
|
+
senderName: props.selectedMessage.senderName,
|
|
100
|
+
text: draft.text,
|
|
101
|
+
direction: 'outgoing',
|
|
91
102
|
timestamp: Date.now()
|
|
92
103
|
});
|
|
93
104
|
ctx.ui.notify(t('message.reply.sent', { preview: buildPreview(props.selectedMessage.text) }), 'info');
|
package/whatsapp-pi.ts
CHANGED
|
@@ -205,8 +205,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
205
205
|
ctx.ui.notify('WhatsApp: Session reset via /new is now fully supported.', 'info');
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
// Track whether send_wa_message tool already sent a reply this turn
|
|
209
|
-
let toolSentToJid: string | null = null;
|
|
208
|
+
// Track whether send_wa_message tool already sent a reply this turn
|
|
209
|
+
let toolSentToJid: string | null = null;
|
|
210
|
+
|
|
211
|
+
const toRecentSenderNumber = (recipientJid: string): string => {
|
|
212
|
+
if (recipientJid.endsWith('@g.us')) {
|
|
213
|
+
return recipientJid;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return `+${recipientJid.split('@')[0]}`;
|
|
217
|
+
};
|
|
210
218
|
|
|
211
219
|
// Handle incoming messages by injecting them as user prompts
|
|
212
220
|
whatsappService.setMessageCallback(async (m) => {
|
|
@@ -318,16 +326,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
318
326
|
formattedMessage
|
|
319
327
|
].join('\n'));
|
|
320
328
|
|
|
321
|
-
const
|
|
329
|
+
const outboundJid = whatsappService.resolveOutboundRecipientJid(resolvedJid);
|
|
330
|
+
const result = await whatsappService.sendMessage(outboundJid, params.message);
|
|
322
331
|
|
|
323
332
|
if (result.success) {
|
|
324
333
|
// Mark that tool already sent to this JID — prevents message_end from re-sending
|
|
325
|
-
toolSentToJid =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
messageId: result.messageId!,
|
|
330
|
-
senderNumber,
|
|
334
|
+
toolSentToJid = outboundJid;
|
|
335
|
+
await recentsService.recordMessage({
|
|
336
|
+
messageId: result.messageId!,
|
|
337
|
+
senderNumber: toRecentSenderNumber(outboundJid),
|
|
331
338
|
text: params.message,
|
|
332
339
|
direction: 'outgoing',
|
|
333
340
|
timestamp: Date.now()
|
|
@@ -378,10 +385,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
378
385
|
// Handle outgoing messages (Agent -> WhatsApp)
|
|
379
386
|
pi.on("agent_start", async (_event, _ctx) => {
|
|
380
387
|
if (sessionManager.getStatus() !== 'connected') return;
|
|
381
|
-
const lastJid = whatsappService.getLastRemoteJid();
|
|
382
|
-
if (lastJid) {
|
|
383
|
-
await whatsappService.sendPresence(lastJid, 'composing');
|
|
384
|
-
}
|
|
388
|
+
const lastJid = whatsappService.getLastRemoteJid();
|
|
389
|
+
if (lastJid) {
|
|
390
|
+
await whatsappService.sendPresence(whatsappService.resolveOutboundRecipientJid(lastJid), 'composing');
|
|
391
|
+
}
|
|
385
392
|
});
|
|
386
393
|
|
|
387
394
|
pi.on("message_end", async (event, ctx) => {
|
|
@@ -389,23 +396,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
389
396
|
|
|
390
397
|
const { message } = event;
|
|
391
398
|
// Only reply if it's the assistant and we have a valid target
|
|
392
|
-
if (message.role === "assistant") {
|
|
393
|
-
const lastJid = whatsappService.getLastRemoteJid();
|
|
394
|
-
const text = message.content.filter(c => c.type === "text").map(c => c.text).join("\n");
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
399
|
+
if (message.role === "assistant") {
|
|
400
|
+
const lastJid = whatsappService.getLastRemoteJid();
|
|
401
|
+
const text = message.content.filter(c => c.type === "text").map(c => c.text).join("\n");
|
|
402
|
+
const outboundJid = lastJid
|
|
403
|
+
? whatsappService.resolveOutboundRecipientJid(lastJid)
|
|
404
|
+
: null;
|
|
405
|
+
|
|
406
|
+
// Skip if send_wa_message tool already sent a reply to this JID
|
|
407
|
+
if (toolSentToJid === outboundJid) {
|
|
408
|
+
toolSentToJid = null;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (outboundJid && text) {
|
|
413
|
+
try {
|
|
414
|
+
const result = await whatsappService.sendMessage(outboundJid, text);
|
|
415
|
+
if (result.success) {
|
|
416
|
+
await recentsService.recordMessage({
|
|
417
|
+
messageId: result.messageId ?? `${Date.now()}`,
|
|
418
|
+
senderNumber: toRecentSenderNumber(outboundJid),
|
|
409
419
|
text,
|
|
410
420
|
direction: 'outgoing',
|
|
411
421
|
timestamp: Date.now()
|