whatsapp-pi 1.0.48 → 1.0.49

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/README.md CHANGED
@@ -8,15 +8,15 @@ A WhatsApp integration extension for the **[Pi Coding Agent](https://github.com/
8
8
 
9
9
  [![GitHub](https://img.shields.io/badge/github-repo-black.svg?style=flat-square&logo=github)](https://github.com/RaphaCastelloes/whatsapp-pi)
10
10
 
11
- Pi is a powerful agentic AI coding assistant that operates in your terminal. This extension allows you to chat and pair-program with your Pi agent directly through WhatsApp, featuring message filtering, allow-listing, recents/history browsing, message detail/reply, group-only binding, and reliable message delivery.
11
+ Pi is a powerful agentic AI coding assistant that operates in your terminal. This extension lets you chat and pair-program with your Pi agent through WhatsApp, with message filtering, allowed contacts/groups, recents/history browsing, message detail/reply, group-only binding, and reliable message delivery.
12
12
 
13
13
  ## Features
14
14
 
15
15
  - **Manual WhatsApp Connection**: QR code-based authentication with session persistence
16
- - **Allow List**: Control which numbers can interact with Pi
16
+ - **Allowed Contacts**: Control which phone numbers can interact with Pi
17
17
  - Add contacts with optional names for easy identification
18
- - View ignored numbers (not in allow list) and add them when needed
19
- - Manage aliases and print allowed numbers from the menu
18
+ - View ignored numbers (not yet allowed) and add them when needed
19
+ - Manage aliases and print allowed contacts from the menu
20
20
  - **Allowed Groups**: Control which WhatsApp groups can interact with Pi
21
21
  - Add group JIDs with optional aliases
22
22
  - Only groups in Allowed Groups are processed by the agent
@@ -48,18 +48,25 @@ To enable PDF reading capabilities (required for the agent to process documents)
48
48
  pi install npm:whatsapp-pi
49
49
  ```
50
50
 
51
- 2. Start Pi (the extension will load automatically once installed):
51
+ 2. Start Pi:
52
52
  ```bash
53
53
  pi
54
54
  ```
55
55
 
56
- After connecting WhatsApp once from the menu and scanning the QR code, you can start Pi with auto-connect enabled:
56
+ 3. Open `/whatsapp` and choose **Connect / Reconnect WhatsApp**.
57
+ - QR appears only on first pair or after logoff.
58
+
59
+ 4. Add the chat you will use with Pi to **Allowed Contacts** or **Allowed Groups**.
60
+
61
+ 5. Send a message from that allowed chat to Pi.
62
+ - Pi replies in same thread.
63
+ - Use **Recents** only to browse history or reply manually.
64
+
65
+ After first pairing, you can start Pi with auto-connect enabled:
57
66
  ```bash
58
67
  pi --whatsapp-pi-online
59
68
  ```
60
69
 
61
- 3. Use the menu to connect WhatsApp and manage allowed numbers and groups
62
-
63
70
  ## Development / Testing
64
71
 
65
72
  If you are developing or testing the extension locally, you can clone the repository from [GitHub](https://github.com/RaphaCastelloes/whatsapp-pi):
@@ -86,6 +93,20 @@ To test startup auto-connect locally after you have already paired WhatsApp:
86
93
  pi -e whatsapp-pi.ts --whatsapp-pi-online
87
94
  ```
88
95
 
96
+ ## How It Works
97
+
98
+ - Pi processes **incoming** messages only from allowed contacts or allowed groups.
99
+ - **Recents** is history browser, not trigger.
100
+ - **Send Message** and `send_wa_message` are outbound only.
101
+ - If you message yourself, WhatsApp may show sent/read ticks, but that does not guarantee Pi will treat it as a trigger.
102
+
103
+ ## WhatsApp Numbers and JIDs
104
+
105
+ - Contacts use phone format in UI: `+5511999999999`
106
+ - Internally, contacts map to JIDs like `5511999999999@s.whatsapp.net`
107
+ - Groups use JIDs like `120363012345@g.us`
108
+ - Recents may show normalized values from WhatsApp, so use **Print Contact** / **Print Group JID** and aliases to avoid confusion.
109
+
89
110
  ## Commands
90
111
 
91
112
  - `/whatsapp` - Open the WhatsApp management menu
@@ -95,12 +116,12 @@ pi -e whatsapp-pi.ts --whatsapp-pi-online
95
116
  - **Disconnect WhatsApp** - Stop WhatsApp connection
96
117
  - **Logoff (Delete Session)** - Remove all credentials and session data
97
118
  - **Recents** - Open recent conversations, view history, and reply
98
- - **Allowed Numbers** - Manage contacts that can interact with Pi
119
+ - **Allowed Contacts** - Manage contacts that can interact with Pi
99
120
  - **Allowed Groups** - Manage WhatsApp groups that can interact with Pi
100
121
 
101
- ### Allowed Numbers Management
102
- - **Add Number** - Add a new contact to the allow list (format: +5511999999999)
103
- - **Select a contact** - Open a submenu with **History**, **Send Message**, **Print Number**, alias actions, **Remove Number**, and **Back**
122
+ ### Allowed Contacts Management
123
+ - **Add Contact** - Add a new contact to the allowed contacts list (format: +5511999999999)
124
+ - **Select a contact** - Open a submenu with **History**, **Send Message**, **Print Contact**, alias actions, **Remove Contact**, and **Back**
104
125
  - **Back** - Return to main menu
105
126
 
106
127
  ### Allowed Groups Management
@@ -112,7 +133,7 @@ pi -e whatsapp-pi.ts --whatsapp-pi-online
112
133
  - **History** - Open full message history for that conversation
113
134
  - **Send Message** - Send a new message without Pi suffix
114
135
  - **Reply** - Open message detail, then press `R` to reply
115
- - **Allow Number / Allow Group** - Move a recent sender into the appropriate allow list
136
+ - **Allow Contact / Allow Group** - Move a recent sender into the appropriate allowed list
116
137
  - **Remove Alias** - Clear saved alias for that sender
117
138
  - **Back** - Return to main menu
118
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-pi",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "type": "module",
5
5
  "description": "WhatsApp integration extension for Pi",
6
6
  "main": "whatsapp-pi.ts",
package/src/i18n.ts CHANGED
@@ -53,7 +53,7 @@ const fallback = {
53
53
  "service.whatsapp.failedMarkRead": "Failed to mark message as read:",
54
54
  "menu.whatsapp.title": "WhatsApp (Status: {status})",
55
55
  "menu.root.recents": "Recents",
56
- "menu.root.allowedNumbers": "Allowed Numbers",
56
+ "menu.root.allowedNumbers": "Allowed Contacts",
57
57
  "menu.root.allowedGroups": "Allowed Groups",
58
58
  "menu.root.disconnectWhatsApp": "Disconnect WhatsApp",
59
59
  "menu.root.connectWhatsApp": "Connect WhatsApp",
@@ -67,25 +67,25 @@ const fallback = {
67
67
  "menu.root.logoffTitle": "Logoff",
68
68
  "menu.root.logoffConfirmMessage": "Delete all credentials?",
69
69
  "menu.root.loggedOffAndDeleted": "Logged off and credentials deleted",
70
- "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Allowed numbers",
71
- "menu.allowed.title": "Allowed Numbers",
72
- "menu.allowed.addNumber": "Add Number",
73
- "menu.allowed.enterNumber": "Enter number (e.g. +5511999999999):",
74
- "menu.allowed.invalidNumber": "Invalid number format",
75
- "menu.allowed.contact.title": "Allowed Number • {displayName}",
70
+ "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Allowed contacts",
71
+ "menu.allowed.title": "Allowed Contacts",
72
+ "menu.allowed.addNumber": "Add Contact",
73
+ "menu.allowed.enterNumber": "Enter contact (e.g. +5511999999999):",
74
+ "menu.allowed.invalidNumber": "Invalid contact format",
75
+ "menu.allowed.contact.title": "Allowed Contact • {displayName}",
76
76
  "menu.allowed.contact.history": "History",
77
77
  "menu.allowed.contact.sendMessage": "Send Message",
78
- "menu.allowed.contact.printNumber": "Print Number",
78
+ "menu.allowed.contact.printNumber": "Print Contact",
79
79
  "menu.allowed.contact.removeAlias": "Remove Alias",
80
80
  "menu.allowed.contact.addAlias": "Add Alias",
81
- "menu.allowed.contact.removeNumber": "Remove Number",
81
+ "menu.allowed.contact.removeNumber": "Remove Contact",
82
82
  "menu.allowed.contact.back": "Back",
83
83
  "menu.allowed.enterAlias": "Enter alias for {number}:",
84
84
  "menu.allowed.pleaseEnterAlias": "Please enter an alias.",
85
85
  "menu.allowed.aliasAdded": "Alias added for {number}",
86
86
  "menu.allowed.aliasRemoved": "Alias removed for {number}",
87
- "menu.allowed.removeConfirmTitle": "Remove Number",
88
- "menu.allowed.removeConfirmMessage": "Remove {displayName} from Allowed Numbers?",
87
+ "menu.allowed.removeConfirmTitle": "Remove Contact",
88
+ "menu.allowed.removeConfirmMessage": "Remove {displayName} from Allowed Contacts?",
89
89
  "menu.allowed.removed": "Removed {displayName}",
90
90
  "menu.allowed.alreadyAllowed": "{number} is already in the allow list",
91
91
  "menu.allowed.addedToAllowList": "Added {number} to the allow list",
@@ -118,7 +118,7 @@ const fallback = {
118
118
  "menu.recents.empty": "No recent individual conversations yet.",
119
119
  "menu.recents.contact.title": "Recents • {displayName}",
120
120
  "menu.recents.contact.history": "History",
121
- "menu.recents.contact.allowNumber": "Allow Number",
121
+ "menu.recents.contact.allowNumber": "Allow Contact",
122
122
  "menu.recents.contact.allowGroup": "Allow Group",
123
123
  "menu.recents.contact.sendMessage": "Send Message",
124
124
  "menu.recents.contact.removeAlias": "Remove Alias",
@@ -255,7 +255,7 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
255
255
  "service.whatsapp.failedMarkRead": "Falha ao marcar mensagem como lida:",
256
256
  "menu.whatsapp.title": "WhatsApp (Status: {status})",
257
257
  "menu.root.recents": "Recentes",
258
- "menu.root.allowedNumbers": "Números permitidos",
258
+ "menu.root.allowedNumbers": "Contactos permitidos",
259
259
  "menu.root.allowedGroups": "Grupos permitidos",
260
260
  "menu.root.disconnectWhatsApp": "Desconectar WhatsApp",
261
261
  "menu.root.connectWhatsApp": "Conectar WhatsApp",
@@ -269,25 +269,25 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
269
269
  "menu.root.logoffTitle": "Fazer logoff",
270
270
  "menu.root.logoffConfirmMessage": "Excluir todas as credenciais?",
271
271
  "menu.root.loggedOffAndDeleted": "Logout feito e credenciais excluídas",
272
- "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Números permitidos",
273
- "menu.allowed.title": "Números permitidos",
274
- "menu.allowed.addNumber": "Adicionar número",
275
- "menu.allowed.enterNumber": "Digite o número (ex.: +5511999999999):",
276
- "menu.allowed.invalidNumber": "Formato de número inválido",
277
- "menu.allowed.contact.title": "Número permitido • {displayName}",
272
+ "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Contactos permitidos",
273
+ "menu.allowed.title": "Contactos permitidos",
274
+ "menu.allowed.addNumber": "Adicionar contato",
275
+ "menu.allowed.enterNumber": "Digite o contato (ex.: +5511999999999):",
276
+ "menu.allowed.invalidNumber": "Formato de contacto inválido",
277
+ "menu.allowed.contact.title": "Contacto permitido • {displayName}",
278
278
  "menu.allowed.contact.history": "Histórico",
279
279
  "menu.allowed.contact.sendMessage": "Enviar mensagem",
280
- "menu.allowed.contact.printNumber": "Mostrar número",
280
+ "menu.allowed.contact.printNumber": "Mostrar contacto",
281
281
  "menu.allowed.contact.removeAlias": "Remover apelido",
282
282
  "menu.allowed.contact.addAlias": "Adicionar apelido",
283
- "menu.allowed.contact.removeNumber": "Remover número",
283
+ "menu.allowed.contact.removeNumber": "Remover contato",
284
284
  "menu.allowed.contact.back": "Voltar",
285
285
  "menu.allowed.enterAlias": "Digite apelido para {number}:",
286
286
  "menu.allowed.pleaseEnterAlias": "Digite um apelido.",
287
287
  "menu.allowed.aliasAdded": "Apelido adicionado para {number}",
288
288
  "menu.allowed.aliasRemoved": "Apelido removido para {number}",
289
- "menu.allowed.removeConfirmTitle": "Remover número",
290
- "menu.allowed.removeConfirmMessage": "Remover {displayName} dos Números permitidos?",
289
+ "menu.allowed.removeConfirmTitle": "Remover contato",
290
+ "menu.allowed.removeConfirmMessage": "Remover {displayName} dos Contactos permitidos?",
291
291
  "menu.allowed.removed": "Removido {displayName}",
292
292
  "menu.allowed.alreadyAllowed": "{number} já está na lista permitida",
293
293
  "menu.allowed.addedToAllowList": "Adicionado {number} à lista permitida",
@@ -320,7 +320,7 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
320
320
  "menu.recents.empty": "Ainda não há conversas individuais recentes.",
321
321
  "menu.recents.contact.title": "Recentes • {displayName}",
322
322
  "menu.recents.contact.history": "Histórico",
323
- "menu.recents.contact.allowNumber": "Permitir número",
323
+ "menu.recents.contact.allowNumber": "Permitir contacto",
324
324
  "menu.recents.contact.allowGroup": "Permitir grupo",
325
325
  "menu.recents.contact.sendMessage": "Enviar mensagem",
326
326
  "menu.recents.contact.removeAlias": "Remover apelido",
@@ -420,7 +420,7 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
420
420
  "service.whatsapp.failedMarkRead": "Error al marcar mensaje como leído:",
421
421
  "menu.whatsapp.title": "WhatsApp (Estado: {status})",
422
422
  "menu.root.recents": "Recientes",
423
- "menu.root.allowedNumbers": "Números permitidos",
423
+ "menu.root.allowedNumbers": "Contactos permitidos",
424
424
  "menu.root.allowedGroups": "Grupos permitidos",
425
425
  "menu.root.disconnectWhatsApp": "Desconectar WhatsApp",
426
426
  "menu.root.connectWhatsApp": "Conectar WhatsApp",
@@ -434,25 +434,25 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
434
434
  "menu.root.logoffTitle": "Cerrar sesión",
435
435
  "menu.root.logoffConfirmMessage": "¿Eliminar todas las credenciales?",
436
436
  "menu.root.loggedOffAndDeleted": "Sesión cerrada y credenciales eliminadas",
437
- "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Números permitidos",
438
- "menu.allowed.title": "Números permitidos",
439
- "menu.allowed.addNumber": "Agregar número",
440
- "menu.allowed.enterNumber": "Ingresa el número (p. ej. +5511999999999):",
441
- "menu.allowed.invalidNumber": "Formato de número inválido",
442
- "menu.allowed.contact.title": "Número permitido • {displayName}",
437
+ "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Contactos permitidos",
438
+ "menu.allowed.title": "Contactos permitidos",
439
+ "menu.allowed.addNumber": "Agregar contacto",
440
+ "menu.allowed.enterNumber": "Ingresa el contacto (p. ej. +5511999999999):",
441
+ "menu.allowed.invalidNumber": "Formato de contacto inválido",
442
+ "menu.allowed.contact.title": "Contacto permitido • {displayName}",
443
443
  "menu.allowed.contact.history": "Historial",
444
444
  "menu.allowed.contact.sendMessage": "Enviar mensaje",
445
- "menu.allowed.contact.printNumber": "Mostrar número",
445
+ "menu.allowed.contact.printNumber": "Mostrar contacto",
446
446
  "menu.allowed.contact.removeAlias": "Quitar alias",
447
447
  "menu.allowed.contact.addAlias": "Agregar alias",
448
- "menu.allowed.contact.removeNumber": "Quitar número",
448
+ "menu.allowed.contact.removeNumber": "Quitar contacto",
449
449
  "menu.allowed.contact.back": "Volver",
450
450
  "menu.allowed.enterAlias": "Ingresa alias para {number}:",
451
451
  "menu.allowed.pleaseEnterAlias": "Ingresa un alias.",
452
452
  "menu.allowed.aliasAdded": "Alias agregado para {number}",
453
453
  "menu.allowed.aliasRemoved": "Alias quitado para {number}",
454
- "menu.allowed.removeConfirmTitle": "Quitar número",
455
- "menu.allowed.removeConfirmMessage": "¿Quitar {displayName} de Números permitidos?",
454
+ "menu.allowed.removeConfirmTitle": "Quitar contacto",
455
+ "menu.allowed.removeConfirmMessage": "¿Quitar {displayName} de Contactos permitidos?",
456
456
  "menu.allowed.removed": "Quitado {displayName}",
457
457
  "menu.allowed.alreadyAllowed": "{number} ya está en la lista permitida",
458
458
  "menu.allowed.addedToAllowList": "Agregado {number} a la lista permitida",
@@ -464,7 +464,7 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
464
464
  "menu.recents.empty": "Todavía no hay conversaciones individuales recientes.",
465
465
  "menu.recents.contact.title": "Recientes • {displayName}",
466
466
  "menu.recents.contact.history": "Historial",
467
- "menu.recents.contact.allowNumber": "Permitir número",
467
+ "menu.recents.contact.allowNumber": "Permitir contacto",
468
468
  "menu.recents.contact.sendMessage": "Enviar mensaje",
469
469
  "menu.recents.contact.removeAlias": "Quitar alias",
470
470
  "menu.recents.contact.back": "Volver",
@@ -547,7 +547,7 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
547
547
  "service.whatsapp.failedMarkRead": "Échec du marquage du message comme lu :",
548
548
  "menu.whatsapp.title": "WhatsApp (Statut: {status})",
549
549
  "menu.root.recents": "Récents",
550
- "menu.root.allowedNumbers": "Numéros autorisés",
550
+ "menu.root.allowedNumbers": "Contacts autorisés",
551
551
  "menu.root.allowedGroups": "Groupes autorisés",
552
552
  "menu.root.disconnectWhatsApp": "Déconnecter WhatsApp",
553
553
  "menu.root.connectWhatsApp": "Connecter WhatsApp",
@@ -561,25 +561,25 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
561
561
  "menu.root.logoffTitle": "Déconnexion",
562
562
  "menu.root.logoffConfirmMessage": "Supprimer toutes les informations d’identification ?",
563
563
  "menu.root.loggedOffAndDeleted": "Déconnexion effectuée et identifiants supprimés",
564
- "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Numéros autorisés",
565
- "menu.allowed.title": "Numéros autorisés",
566
- "menu.allowed.addNumber": "Ajouter un numéro",
567
- "menu.allowed.enterNumber": "Entrez le numéro (ex. +5511999999999) :",
568
- "menu.allowed.invalidNumber": "Format de numéro invalide",
569
- "menu.allowed.contact.title": "Numéro autorisé • {displayName}",
564
+ "menu.allowed.printAllowedNumbersTitle": "[WhatsApp-Pi] Contacts autorisés",
565
+ "menu.allowed.title": "Contacts autorisés",
566
+ "menu.allowed.addNumber": "Ajouter un contact",
567
+ "menu.allowed.enterNumber": "Entrez le contact (ex. +5511999999999) :",
568
+ "menu.allowed.invalidNumber": "Format de contact invalide",
569
+ "menu.allowed.contact.title": "Contact autorisé • {displayName}",
570
570
  "menu.allowed.contact.history": "Historique",
571
571
  "menu.allowed.contact.sendMessage": "Envoyer un message",
572
- "menu.allowed.contact.printNumber": "Afficher le numéro",
572
+ "menu.allowed.contact.printNumber": "Afficher le contact",
573
573
  "menu.allowed.contact.removeAlias": "Supprimer l’alias",
574
574
  "menu.allowed.contact.addAlias": "Ajouter un alias",
575
- "menu.allowed.contact.removeNumber": "Supprimer le numéro",
575
+ "menu.allowed.contact.removeNumber": "Supprimer le contact",
576
576
  "menu.allowed.contact.back": "Retour",
577
577
  "menu.allowed.enterAlias": "Entrez un alias pour {number} :",
578
578
  "menu.allowed.pleaseEnterAlias": "Veuillez saisir un alias.",
579
579
  "menu.allowed.aliasAdded": "Alias ajouté pour {number}",
580
580
  "menu.allowed.aliasRemoved": "Alias supprimé pour {number}",
581
- "menu.allowed.removeConfirmTitle": "Supprimer le numéro",
582
- "menu.allowed.removeConfirmMessage": "Supprimer {displayName} des Numéros autorisés ?",
581
+ "menu.allowed.removeConfirmTitle": "Supprimer le contact",
582
+ "menu.allowed.removeConfirmMessage": "Supprimer {displayName} des Contacts autorisés ?",
583
583
  "menu.allowed.removed": "Supprimé {displayName}",
584
584
  "menu.allowed.alreadyAllowed": "{number} est déjà dans la liste autorisée",
585
585
  "menu.allowed.addedToAllowList": "Ajouté {number} à la liste autorisée",
@@ -591,7 +591,7 @@ const translations: Record<Locale, Partial<Record<Key, string>>> = {
591
591
  "menu.recents.empty": "Aucune conversation individuelle récente.",
592
592
  "menu.recents.contact.title": "Récents • {displayName}",
593
593
  "menu.recents.contact.history": "Historique",
594
- "menu.recents.contact.allowNumber": "Autoriser le numéro",
594
+ "menu.recents.contact.allowNumber": "Autoriser le contact",
595
595
  "menu.recents.contact.sendMessage": "Envoyer un message",
596
596
  "menu.recents.contact.removeAlias": "Supprimer l’alias",
597
597
  "menu.recents.contact.back": "Retour",
@@ -1,150 +1,150 @@
1
- import { t } from '../i18n.js';
2
- import { extractMessageContent } from 'baileys';
3
-
4
- export type IncomingResolution =
5
- | { kind: 'text'; text: string }
6
- | { kind: 'audio'; text: string; audioMessage: any }
7
- | { kind: 'image'; text: string; imageMessage: any }
8
- | { kind: 'document'; text: string; documentMessage: any }
9
- | { kind: 'contact'; text: string }
10
- | { kind: 'location'; text: string }
11
- | { kind: 'system'; text: string }
12
- | { kind: 'unsupported'; text: string };
13
-
14
- const protocolTypes: Record<number, keyof typeof protocolLabels> = {
15
- 0: 'messageDeleted',
16
- 3: 'disappearingMessagesUpdated',
17
- 4: 'disappearingMessageSyncResponse',
18
- 5: 'historySyncNotification',
19
- 6: 'appStateSyncKeyShare',
20
- 7: 'appStateSyncKeyRequest',
21
- 8: 'messageBackfillRequest',
22
- 9: 'securityNotificationSync',
23
- 10: 'fatalAppStateSyncNotification',
24
- 11: 'phoneNumberShared',
25
- 14: 'messageEdited',
26
- 16: 'peerDataRequest',
27
- 17: 'peerDataResponse',
28
- 18: 'welcomeMessageRequest',
29
- 19: 'botFeedback',
30
- 20: 'mediaNotification'
31
- };
32
-
33
- const protocolLabels = {
34
- messageDeleted: t('incoming.protocol.messageDeleted'),
35
- disappearingMessagesUpdated: t('incoming.protocol.disappearingMessagesUpdated'),
36
- disappearingMessageSyncResponse: t('incoming.protocol.disappearingMessageSyncResponse'),
37
- historySyncNotification: t('incoming.protocol.historySyncNotification'),
38
- appStateSyncKeyShare: t('incoming.protocol.appStateSyncKeyShare'),
39
- appStateSyncKeyRequest: t('incoming.protocol.appStateSyncKeyRequest'),
40
- messageBackfillRequest: t('incoming.protocol.messageBackfillRequest'),
41
- securityNotificationSync: t('incoming.protocol.securityNotificationSync'),
42
- fatalAppStateSyncNotification: t('incoming.protocol.fatalAppStateSyncNotification'),
43
- phoneNumberShared: t('incoming.protocol.phoneNumberShared'),
44
- messageEdited: t('incoming.protocol.messageEdited'),
45
- peerDataRequest: t('incoming.protocol.peerDataRequest'),
46
- peerDataResponse: t('incoming.protocol.peerDataResponse'),
47
- welcomeMessageRequest: t('incoming.protocol.welcomeMessageRequest'),
48
- botFeedback: t('incoming.protocol.botFeedback'),
49
- mediaNotification: t('incoming.protocol.mediaNotification')
50
- } as const;
51
-
52
- const unwrapMessageContent = (content: any): any => extractMessageContent(content) ?? content;
53
-
54
- const getTypeName = (payload: any): string => {
55
- if (!payload || typeof payload !== 'object') return 'unknown';
56
- return Object.keys(payload)[0] || 'unknown';
57
- };
58
-
59
- const formatProtocolMessage = (protocolMessage: any): string => {
60
- const typeLabelKey = protocolTypes[Number(protocolMessage?.type)];
61
- const typeLabel = typeLabelKey ? protocolLabels[typeLabelKey] : t('incoming.protocol.systemUpdate');
62
- const editedText = protocolMessage?.editedMessage?.conversation
63
- || protocolMessage?.editedMessage?.extendedTextMessage?.text;
64
-
65
- if (editedText) {
66
- return `[${typeLabel}: ${editedText}]`;
67
- }
68
-
69
- return `[${typeLabel}]`;
70
- };
71
-
72
- export const extractIncomingText = (message: any): IncomingResolution => {
73
- const content = unwrapMessageContent(message);
74
- const inner = content?.ephemeralMessage?.message
75
- || content?.viewOnceMessage?.message
76
- || content?.viewOnceMessageV2?.message
77
- || content?.viewOnceMessageV2Extension?.message
78
- || content?.message;
79
-
80
- const resolved = inner ? unwrapMessageContent(inner) : content;
81
- const typeName = getTypeName(resolved);
82
- const protocolMessage = resolved?.protocolMessage
83
- || (typeName === 'protocolMessage' ? resolved : undefined)
84
- || content?.protocolMessage;
85
-
86
- if (protocolMessage) {
87
- return { kind: 'system', text: formatProtocolMessage(protocolMessage) };
88
- }
89
-
90
- if (resolved?.conversation) {
91
- return { kind: 'text', text: resolved.conversation };
92
- }
93
-
94
- if (resolved?.extendedTextMessage?.text) {
95
- return { kind: 'text', text: resolved.extendedTextMessage.text };
96
- }
97
-
98
- if (resolved?.imageMessage) {
99
- return {
100
- kind: 'image',
101
- text: resolved.imageMessage.caption || t('incoming.media.image'),
102
- imageMessage: resolved.imageMessage
103
- };
104
- }
105
-
106
- if (resolved?.videoMessage) {
107
- return {
108
- kind: 'text',
109
- text: resolved.videoMessage.caption || t('incoming.media.video')
110
- };
111
- }
112
-
113
- if (resolved?.audioMessage) {
114
- return {
115
- kind: 'audio',
116
- text: t('incoming.media.audio'),
117
- audioMessage: resolved.audioMessage
118
- };
119
- }
120
-
121
- if (resolved?.documentMessage) {
122
- return {
123
- kind: 'document',
124
- text: resolved.documentMessage.caption || t('incoming.media.document'),
125
- documentMessage: resolved.documentMessage
126
- };
127
- }
128
-
129
- if (resolved?.contactMessage || resolved?.contactsArrayMessage) {
130
- return { kind: 'contact', text: t('incoming.media.contact') };
131
- }
132
-
133
- if (resolved?.locationMessage) {
134
- return { kind: 'location', text: t('incoming.media.location') };
135
- }
136
-
137
- if (resolved?.buttonsResponseMessage?.selectedDisplayText) {
138
- return { kind: 'text', text: resolved.buttonsResponseMessage.selectedDisplayText };
139
- }
140
-
141
- if (resolved?.listResponseMessage?.title) {
142
- return { kind: 'text', text: resolved.listResponseMessage.title };
143
- }
144
-
145
- if (resolved?.templateButtonReplyMessage?.selectedDisplayText) {
146
- return { kind: 'text', text: resolved.templateButtonReplyMessage.selectedDisplayText };
147
- }
148
-
149
- return { kind: 'unsupported', text: t('incoming.media.unsupported', { typeName }) };
150
- };
1
+ import { t } from '../i18n.js';
2
+ import { extractMessageContent } from 'baileys';
3
+
4
+ export type IncomingResolution =
5
+ | { kind: 'text'; text: string }
6
+ | { kind: 'audio'; text: string; audioMessage: any }
7
+ | { kind: 'image'; text: string; imageMessage: any }
8
+ | { kind: 'document'; text: string; documentMessage: any }
9
+ | { kind: 'contact'; text: string }
10
+ | { kind: 'location'; text: string }
11
+ | { kind: 'system'; text: string }
12
+ | { kind: 'unsupported'; text: string };
13
+
14
+ const protocolTypes: Record<number, keyof typeof protocolLabels> = {
15
+ 0: 'messageDeleted',
16
+ 3: 'disappearingMessagesUpdated',
17
+ 4: 'disappearingMessageSyncResponse',
18
+ 5: 'historySyncNotification',
19
+ 6: 'appStateSyncKeyShare',
20
+ 7: 'appStateSyncKeyRequest',
21
+ 8: 'messageBackfillRequest',
22
+ 9: 'securityNotificationSync',
23
+ 10: 'fatalAppStateSyncNotification',
24
+ 11: 'phoneNumberShared',
25
+ 14: 'messageEdited',
26
+ 16: 'peerDataRequest',
27
+ 17: 'peerDataResponse',
28
+ 18: 'welcomeMessageRequest',
29
+ 19: 'botFeedback',
30
+ 20: 'mediaNotification'
31
+ };
32
+
33
+ const protocolLabels = {
34
+ messageDeleted: t('incoming.protocol.messageDeleted'),
35
+ disappearingMessagesUpdated: t('incoming.protocol.disappearingMessagesUpdated'),
36
+ disappearingMessageSyncResponse: t('incoming.protocol.disappearingMessageSyncResponse'),
37
+ historySyncNotification: t('incoming.protocol.historySyncNotification'),
38
+ appStateSyncKeyShare: t('incoming.protocol.appStateSyncKeyShare'),
39
+ appStateSyncKeyRequest: t('incoming.protocol.appStateSyncKeyRequest'),
40
+ messageBackfillRequest: t('incoming.protocol.messageBackfillRequest'),
41
+ securityNotificationSync: t('incoming.protocol.securityNotificationSync'),
42
+ fatalAppStateSyncNotification: t('incoming.protocol.fatalAppStateSyncNotification'),
43
+ phoneNumberShared: t('incoming.protocol.phoneNumberShared'),
44
+ messageEdited: t('incoming.protocol.messageEdited'),
45
+ peerDataRequest: t('incoming.protocol.peerDataRequest'),
46
+ peerDataResponse: t('incoming.protocol.peerDataResponse'),
47
+ welcomeMessageRequest: t('incoming.protocol.welcomeMessageRequest'),
48
+ botFeedback: t('incoming.protocol.botFeedback'),
49
+ mediaNotification: t('incoming.protocol.mediaNotification')
50
+ } as const;
51
+
52
+ const unwrapMessageContent = (content: any): any => extractMessageContent(content) ?? content;
53
+
54
+ const getTypeName = (payload: any): string => {
55
+ if (!payload || typeof payload !== 'object') return 'unknown';
56
+ return Object.keys(payload)[0] || 'unknown';
57
+ };
58
+
59
+ const formatProtocolMessage = (protocolMessage: any): string => {
60
+ const typeLabelKey = protocolTypes[Number(protocolMessage?.type)];
61
+ const typeLabel = typeLabelKey ? protocolLabels[typeLabelKey] : t('incoming.protocol.systemUpdate');
62
+ const editedText = protocolMessage?.editedMessage?.conversation
63
+ || protocolMessage?.editedMessage?.extendedTextMessage?.text;
64
+
65
+ if (editedText) {
66
+ return `[${typeLabel}: ${editedText}]`;
67
+ }
68
+
69
+ return `[${typeLabel}]`;
70
+ };
71
+
72
+ export const extractIncomingText = (message: any): IncomingResolution => {
73
+ const content = unwrapMessageContent(message);
74
+ const inner = content?.ephemeralMessage?.message
75
+ || content?.viewOnceMessage?.message
76
+ || content?.viewOnceMessageV2?.message
77
+ || content?.viewOnceMessageV2Extension?.message
78
+ || content?.message;
79
+
80
+ const resolved = inner ? unwrapMessageContent(inner) : content;
81
+ const typeName = getTypeName(resolved);
82
+ const protocolMessage = resolved?.protocolMessage
83
+ || (typeName === 'protocolMessage' ? resolved : undefined)
84
+ || content?.protocolMessage;
85
+
86
+ if (protocolMessage) {
87
+ return { kind: 'system', text: formatProtocolMessage(protocolMessage) };
88
+ }
89
+
90
+ if (resolved?.conversation) {
91
+ return { kind: 'text', text: resolved.conversation };
92
+ }
93
+
94
+ if (resolved?.extendedTextMessage?.text) {
95
+ return { kind: 'text', text: resolved.extendedTextMessage.text };
96
+ }
97
+
98
+ if (resolved?.imageMessage) {
99
+ return {
100
+ kind: 'image',
101
+ text: resolved.imageMessage.caption || t('incoming.media.image'),
102
+ imageMessage: resolved.imageMessage
103
+ };
104
+ }
105
+
106
+ if (resolved?.videoMessage) {
107
+ return {
108
+ kind: 'text',
109
+ text: resolved.videoMessage.caption || t('incoming.media.video')
110
+ };
111
+ }
112
+
113
+ if (resolved?.audioMessage) {
114
+ return {
115
+ kind: 'audio',
116
+ text: t('incoming.media.audio'),
117
+ audioMessage: resolved.audioMessage
118
+ };
119
+ }
120
+
121
+ if (resolved?.documentMessage) {
122
+ return {
123
+ kind: 'document',
124
+ text: resolved.documentMessage.caption || t('incoming.media.document'),
125
+ documentMessage: resolved.documentMessage
126
+ };
127
+ }
128
+
129
+ if (resolved?.contactMessage || resolved?.contactsArrayMessage) {
130
+ return { kind: 'contact', text: t('incoming.media.contact') };
131
+ }
132
+
133
+ if (resolved?.locationMessage) {
134
+ return { kind: 'location', text: t('incoming.media.location') };
135
+ }
136
+
137
+ if (resolved?.buttonsResponseMessage?.selectedDisplayText) {
138
+ return { kind: 'text', text: resolved.buttonsResponseMessage.selectedDisplayText };
139
+ }
140
+
141
+ if (resolved?.listResponseMessage?.title) {
142
+ return { kind: 'text', text: resolved.listResponseMessage.title };
143
+ }
144
+
145
+ if (resolved?.templateButtonReplyMessage?.selectedDisplayText) {
146
+ return { kind: 'text', text: resolved.templateButtonReplyMessage.selectedDisplayText };
147
+ }
148
+
149
+ return { kind: 'unsupported', text: t('incoming.media.unsupported', { typeName }) };
150
+ };