@sjawhar/whatsapp-mcp 1.0.0

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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -0
  3. package/dist/__tests__/connection.test.d.ts +2 -0
  4. package/dist/__tests__/connection.test.d.ts.map +1 -0
  5. package/dist/__tests__/connection.test.js +105 -0
  6. package/dist/__tests__/connection.test.js.map +1 -0
  7. package/dist/__tests__/disconnect.test.d.ts +2 -0
  8. package/dist/__tests__/disconnect.test.d.ts.map +1 -0
  9. package/dist/__tests__/disconnect.test.js +166 -0
  10. package/dist/__tests__/disconnect.test.js.map +1 -0
  11. package/dist/__tests__/download-media.test.d.ts +2 -0
  12. package/dist/__tests__/download-media.test.d.ts.map +1 -0
  13. package/dist/__tests__/download-media.test.js +110 -0
  14. package/dist/__tests__/download-media.test.js.map +1 -0
  15. package/dist/__tests__/failures/connection-failures.test.d.ts +2 -0
  16. package/dist/__tests__/failures/connection-failures.test.d.ts.map +1 -0
  17. package/dist/__tests__/failures/connection-failures.test.js +146 -0
  18. package/dist/__tests__/failures/connection-failures.test.js.map +1 -0
  19. package/dist/__tests__/failures/edge-cases.test.d.ts +2 -0
  20. package/dist/__tests__/failures/edge-cases.test.d.ts.map +1 -0
  21. package/dist/__tests__/failures/edge-cases.test.js +121 -0
  22. package/dist/__tests__/failures/edge-cases.test.js.map +1 -0
  23. package/dist/__tests__/failures/resource-failures.test.d.ts +2 -0
  24. package/dist/__tests__/failures/resource-failures.test.d.ts.map +1 -0
  25. package/dist/__tests__/failures/resource-failures.test.js +136 -0
  26. package/dist/__tests__/failures/resource-failures.test.js.map +1 -0
  27. package/dist/__tests__/failures/security-failures.test.d.ts +2 -0
  28. package/dist/__tests__/failures/security-failures.test.d.ts.map +1 -0
  29. package/dist/__tests__/failures/security-failures.test.js +0 -0
  30. package/dist/__tests__/failures/security-failures.test.js.map +1 -0
  31. package/dist/__tests__/helpers/fake-baileys.d.ts +52 -0
  32. package/dist/__tests__/helpers/fake-baileys.d.ts.map +1 -0
  33. package/dist/__tests__/helpers/fake-baileys.js +60 -0
  34. package/dist/__tests__/helpers/fake-baileys.js.map +1 -0
  35. package/dist/__tests__/helpers/mcp-test-client.d.ts +9 -0
  36. package/dist/__tests__/helpers/mcp-test-client.d.ts.map +1 -0
  37. package/dist/__tests__/helpers/mcp-test-client.js +40 -0
  38. package/dist/__tests__/helpers/mcp-test-client.js.map +1 -0
  39. package/dist/__tests__/helpers/test-db.d.ts +4 -0
  40. package/dist/__tests__/helpers/test-db.d.ts.map +1 -0
  41. package/dist/__tests__/helpers/test-db.js +32 -0
  42. package/dist/__tests__/helpers/test-db.js.map +1 -0
  43. package/dist/__tests__/integration/chat-navigation.test.d.ts +2 -0
  44. package/dist/__tests__/integration/chat-navigation.test.d.ts.map +1 -0
  45. package/dist/__tests__/integration/chat-navigation.test.js +171 -0
  46. package/dist/__tests__/integration/chat-navigation.test.js.map +1 -0
  47. package/dist/__tests__/integration/contacts-flow.test.d.ts +2 -0
  48. package/dist/__tests__/integration/contacts-flow.test.d.ts.map +1 -0
  49. package/dist/__tests__/integration/contacts-flow.test.js +144 -0
  50. package/dist/__tests__/integration/contacts-flow.test.js.map +1 -0
  51. package/dist/__tests__/integration/media-flow.test.d.ts +2 -0
  52. package/dist/__tests__/integration/media-flow.test.d.ts.map +1 -0
  53. package/dist/__tests__/integration/media-flow.test.js +225 -0
  54. package/dist/__tests__/integration/media-flow.test.js.map +1 -0
  55. package/dist/__tests__/integration/search-flow.test.d.ts +2 -0
  56. package/dist/__tests__/integration/search-flow.test.d.ts.map +1 -0
  57. package/dist/__tests__/integration/search-flow.test.js +44 -0
  58. package/dist/__tests__/integration/search-flow.test.js.map +1 -0
  59. package/dist/__tests__/integration/send-message.test.d.ts +2 -0
  60. package/dist/__tests__/integration/send-message.test.d.ts.map +1 -0
  61. package/dist/__tests__/integration/send-message.test.js +160 -0
  62. package/dist/__tests__/integration/send-message.test.js.map +1 -0
  63. package/dist/__tests__/lock-file.test.d.ts +2 -0
  64. package/dist/__tests__/lock-file.test.d.ts.map +1 -0
  65. package/dist/__tests__/lock-file.test.js +63 -0
  66. package/dist/__tests__/lock-file.test.js.map +1 -0
  67. package/dist/__tests__/medium-fixes.test.d.ts +2 -0
  68. package/dist/__tests__/medium-fixes.test.d.ts.map +1 -0
  69. package/dist/__tests__/medium-fixes.test.js +141 -0
  70. package/dist/__tests__/medium-fixes.test.js.map +1 -0
  71. package/dist/__tests__/rate-limit.test.d.ts +2 -0
  72. package/dist/__tests__/rate-limit.test.d.ts.map +1 -0
  73. package/dist/__tests__/rate-limit.test.js +193 -0
  74. package/dist/__tests__/rate-limit.test.js.map +1 -0
  75. package/dist/__tests__/send-file.test.d.ts +2 -0
  76. package/dist/__tests__/send-file.test.d.ts.map +1 -0
  77. package/dist/__tests__/send-file.test.js +237 -0
  78. package/dist/__tests__/send-file.test.js.map +1 -0
  79. package/dist/__tests__/smoke.test.d.ts +2 -0
  80. package/dist/__tests__/smoke.test.d.ts.map +1 -0
  81. package/dist/__tests__/smoke.test.js +28 -0
  82. package/dist/__tests__/smoke.test.js.map +1 -0
  83. package/dist/__tests__/transcribe.test.d.ts +2 -0
  84. package/dist/__tests__/transcribe.test.d.ts.map +1 -0
  85. package/dist/__tests__/transcribe.test.js +71 -0
  86. package/dist/__tests__/transcribe.test.js.map +1 -0
  87. package/dist/__tests__/zombie.test.d.ts +2 -0
  88. package/dist/__tests__/zombie.test.d.ts.map +1 -0
  89. package/dist/__tests__/zombie.test.js +145 -0
  90. package/dist/__tests__/zombie.test.js.map +1 -0
  91. package/dist/db.d.ts +53 -0
  92. package/dist/db.d.ts.map +1 -0
  93. package/dist/db.js +509 -0
  94. package/dist/db.js.map +1 -0
  95. package/dist/import-contacts.d.ts +37 -0
  96. package/dist/import-contacts.d.ts.map +1 -0
  97. package/dist/import-contacts.js +242 -0
  98. package/dist/import-contacts.js.map +1 -0
  99. package/dist/index.d.ts +3 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +62 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/lock.d.ts +16 -0
  104. package/dist/lock.d.ts.map +1 -0
  105. package/dist/lock.js +65 -0
  106. package/dist/lock.js.map +1 -0
  107. package/dist/tools.d.ts +6 -0
  108. package/dist/tools.d.ts.map +1 -0
  109. package/dist/tools.js +339 -0
  110. package/dist/tools.js.map +1 -0
  111. package/dist/transcribe.d.ts +8 -0
  112. package/dist/transcribe.d.ts.map +1 -0
  113. package/dist/transcribe.js +63 -0
  114. package/dist/transcribe.js.map +1 -0
  115. package/dist/utils.d.ts +51 -0
  116. package/dist/utils.d.ts.map +1 -0
  117. package/dist/utils.js +156 -0
  118. package/dist/utils.js.map +1 -0
  119. package/dist/whatsapp.d.ts +50 -0
  120. package/dist/whatsapp.d.ts.map +1 -0
  121. package/dist/whatsapp.js +896 -0
  122. package/dist/whatsapp.js.map +1 -0
  123. package/package.json +52 -0
  124. package/patches/@whiskeysockets+baileys+6.7.21.patch +46 -0
  125. package/patches/libsignal+2.0.1.patch +84 -0
package/dist/db.js ADDED
@@ -0,0 +1,509 @@
1
+ import Database from "better-sqlite3";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fromJid, formatMessageRow } from "./utils.js";
5
+ const __project_root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
6
+ const STORE_DIR = path.join(__project_root, "store");
7
+ const DB_PATH = path.join(STORE_DIR, "whatsapp.db");
8
+ let db;
9
+ // ─── Message helpers (moved from utils.ts) ──────────────────────────
10
+ function getMessageText(msg) {
11
+ const m = msg.message;
12
+ if (!m)
13
+ return null;
14
+ return (m.conversation ||
15
+ m.extendedTextMessage?.text ||
16
+ m.imageMessage?.caption ||
17
+ m.videoMessage?.caption ||
18
+ m.documentMessage?.caption ||
19
+ m.listResponseMessage?.title ||
20
+ m.buttonsResponseMessage?.selectedDisplayText ||
21
+ null);
22
+ }
23
+ function getMessageType(msg) {
24
+ const m = msg.message;
25
+ if (!m)
26
+ return "unknown";
27
+ if (m.conversation || m.extendedTextMessage)
28
+ return "text";
29
+ if (m.imageMessage)
30
+ return "image";
31
+ if (m.videoMessage)
32
+ return "video";
33
+ if (m.audioMessage)
34
+ return m.audioMessage.ptt ? "voice_note" : "audio";
35
+ if (m.documentMessage)
36
+ return "document";
37
+ if (m.stickerMessage)
38
+ return "sticker";
39
+ if (m.contactMessage)
40
+ return "contact";
41
+ if (m.locationMessage)
42
+ return "location";
43
+ if (m.reactionMessage)
44
+ return "reaction";
45
+ if (m.protocolMessage)
46
+ return "protocol";
47
+ return "other";
48
+ }
49
+ function isUserMessage(msg) {
50
+ const m = msg.message;
51
+ if (!m)
52
+ return false;
53
+ if (m.protocolMessage || m.reactionMessage || m.senderKeyDistributionMessage)
54
+ return false;
55
+ return true;
56
+ }
57
+ const MEDIA_TYPES = new Set(["image", "video", "audio", "voice_note", "document", "sticker"]);
58
+ // ─── Schema ─────────────────────────────────────────────────────────
59
+ function createTables() {
60
+ db.exec(`
61
+ PRAGMA journal_mode = WAL;
62
+ PRAGMA foreign_keys = ON;
63
+
64
+ CREATE TABLE IF NOT EXISTS chats (
65
+ jid TEXT PRIMARY KEY,
66
+ name TEXT,
67
+ conversation_ts INTEGER DEFAULT 0,
68
+ unread_count INTEGER DEFAULT 0
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS contacts (
72
+ jid TEXT PRIMARY KEY,
73
+ name TEXT,
74
+ notify TEXT
75
+ );
76
+
77
+ CREATE TABLE IF NOT EXISTS messages (
78
+ id TEXT NOT NULL,
79
+ chat_jid TEXT NOT NULL,
80
+ from_me INTEGER NOT NULL DEFAULT 0,
81
+ sender_jid TEXT,
82
+ sender_name TEXT,
83
+ type TEXT NOT NULL DEFAULT 'text',
84
+ text TEXT,
85
+ timestamp INTEGER NOT NULL DEFAULT 0,
86
+ has_media INTEGER NOT NULL DEFAULT 0,
87
+ message_blob TEXT,
88
+ PRIMARY KEY (chat_jid, id)
89
+ );
90
+
91
+ CREATE INDEX IF NOT EXISTS idx_messages_chat_ts ON messages(chat_jid, timestamp);
92
+ CREATE INDEX IF NOT EXISTS idx_messages_text ON messages(text) WHERE text IS NOT NULL;
93
+
94
+ CREATE TABLE IF NOT EXISTS jid_mapping (
95
+ lid_jid TEXT PRIMARY KEY,
96
+ phone_jid TEXT NOT NULL
97
+ );
98
+ CREATE INDEX IF NOT EXISTS idx_jid_mapping_phone ON jid_mapping(phone_jid);
99
+ `);
100
+ // Migration: add transcription column for voice note caching
101
+ const hasCol = db.prepare(`SELECT COUNT(*) as cnt FROM pragma_table_info('messages') WHERE name = 'transcription'`).get();
102
+ if (hasCol.cnt === 0) {
103
+ db.exec(`ALTER TABLE messages ADD COLUMN transcription TEXT`);
104
+ }
105
+ // Seed JID mappings from name-matched chats (LID ↔ phone number).
106
+ // WhatsApp is migrating from phone JIDs to LID JIDs, so the same contact
107
+ // may have messages under both. This bootstraps the mapping from existing data.
108
+ db.exec(`
109
+ INSERT OR IGNORE INTO jid_mapping (lid_jid, phone_jid)
110
+ SELECT l.jid, p.jid
111
+ FROM chats l
112
+ JOIN chats p ON p.name = l.name AND p.jid LIKE '%@s.whatsapp.net'
113
+ WHERE l.jid LIKE '%@lid'
114
+ AND l.name IS NOT NULL AND l.name != ''
115
+ `);
116
+ }
117
+ // ─── Prepared Statements ────────────────────────────────────────────
118
+ let stmts;
119
+ function prepareStatements() {
120
+ stmts = {
121
+ upsertChat: db.prepare(`
122
+ INSERT INTO chats (jid, name, conversation_ts, unread_count)
123
+ VALUES (@jid, @name, @conversation_ts, @unread_count)
124
+ ON CONFLICT(jid) DO UPDATE SET
125
+ name = COALESCE(@name, chats.name),
126
+ conversation_ts = MAX(COALESCE(@conversation_ts, 0), chats.conversation_ts),
127
+ unread_count = COALESCE(@unread_count, chats.unread_count)
128
+ `),
129
+ upsertContact: db.prepare(`
130
+ INSERT INTO contacts (jid, name, notify)
131
+ VALUES (@jid, @name, @notify)
132
+ ON CONFLICT(jid) DO UPDATE SET
133
+ name = COALESCE(@name, contacts.name),
134
+ notify = COALESCE(@notify, contacts.notify)
135
+ `),
136
+ upsertMessage: db.prepare(`
137
+ INSERT INTO messages (id, chat_jid, from_me, sender_jid, sender_name, type, text, timestamp, has_media, message_blob)
138
+ VALUES (@id, @chat_jid, @from_me, @sender_jid, @sender_name, @type, @text, @timestamp, @has_media, @message_blob)
139
+ ON CONFLICT(chat_jid, id) DO UPDATE SET
140
+ sender_name = COALESCE(@sender_name, messages.sender_name),
141
+ text = COALESCE(@text, messages.text),
142
+ has_media = @has_media,
143
+ message_blob = COALESCE(@message_blob, messages.message_blob)
144
+ `),
145
+ deleteChat: db.prepare(`DELETE FROM chats WHERE jid = ?`),
146
+ deleteMessage: db.prepare(`DELETE FROM messages WHERE chat_jid = ? AND id = ?`),
147
+ getContactName: db.prepare(`
148
+ SELECT COALESCE(name, notify) AS display FROM contacts WHERE jid = ?
149
+ `),
150
+ };
151
+ }
152
+ // ─── Write Operations ───────────────────────────────────────────────
153
+ export function upsertChat(jid, name, conversationTs, unreadCount) {
154
+ stmts.upsertChat.run({
155
+ jid,
156
+ name: name || null,
157
+ conversation_ts: conversationTs ? Number(conversationTs) : null,
158
+ unread_count: unreadCount ?? null,
159
+ });
160
+ }
161
+ export function upsertContact(jid, name, notify) {
162
+ stmts.upsertContact.run({ jid, name: name || null, notify: notify || null });
163
+ }
164
+ function resolveSenderName(senderJid) {
165
+ if (!senderJid)
166
+ return null;
167
+ const row = stmts.getContactName.get(senderJid);
168
+ if (row?.display)
169
+ return row.display;
170
+ return fromJid(senderJid);
171
+ }
172
+ export function upsertMessage(chatJid, msg) {
173
+ if (!isUserMessage(msg))
174
+ return;
175
+ const type = getMessageType(msg);
176
+ const text = getMessageText(msg);
177
+ const fromMe = msg.key.fromMe ? 1 : 0;
178
+ const senderJid = fromMe ? null : (msg.key.participant || msg.key.remoteJid || null);
179
+ const senderName = fromMe ? null : resolveSenderName(senderJid);
180
+ const hasMedia = MEDIA_TYPES.has(type) ? 1 : 0;
181
+ const messageBlob = hasMedia ? JSON.stringify({ key: msg.key, message: msg.message, messageTimestamp: msg.messageTimestamp }) : null;
182
+ stmts.upsertMessage.run({
183
+ id: msg.key.id,
184
+ chat_jid: chatJid,
185
+ from_me: fromMe,
186
+ sender_jid: senderJid,
187
+ sender_name: senderName,
188
+ type,
189
+ text: text || null,
190
+ timestamp: Number(msg.messageTimestamp || 0),
191
+ has_media: hasMedia,
192
+ message_blob: messageBlob,
193
+ });
194
+ }
195
+ export function upsertChats(chats) {
196
+ const run = db.transaction((items) => {
197
+ for (const chat of items) {
198
+ upsertChat(chat.id, chat.name, chat.conversationTimestamp ? Number(chat.conversationTimestamp) : null, chat.unreadCount);
199
+ }
200
+ });
201
+ run(chats);
202
+ }
203
+ export function upsertContacts(contacts) {
204
+ const run = db.transaction((items) => {
205
+ for (const contact of items) {
206
+ upsertContact(contact.id, contact.name, contact.notify);
207
+ }
208
+ });
209
+ run(contacts);
210
+ }
211
+ export function upsertMessages(chatJid, msgs) {
212
+ const run = db.transaction((items) => {
213
+ for (const msg of items) {
214
+ upsertMessage(chatJid, msg);
215
+ }
216
+ });
217
+ run(msgs);
218
+ }
219
+ export function deleteChat(jid) {
220
+ stmts.deleteChat.run(jid);
221
+ }
222
+ export function deleteMessage(chatJid, messageId) {
223
+ const result = stmts.deleteMessage.run(chatJid, messageId);
224
+ return result.changes > 0;
225
+ }
226
+ // ─── JID Mapping (LID ↔ Phone) ──────────────────────────────────────
227
+ export function saveJidMapping(lidJid, phoneJid) {
228
+ db.prepare(`INSERT OR REPLACE INTO jid_mapping (lid_jid, phone_jid) VALUES (?, ?)`).run(lidJid, phoneJid);
229
+ }
230
+ export function getPhoneJid(lidJid) {
231
+ const row = db.prepare(`SELECT phone_jid FROM jid_mapping WHERE lid_jid = ?`).get(lidJid);
232
+ return row?.phone_jid || null;
233
+ }
234
+ export function getLidJid(phoneJid) {
235
+ const row = db.prepare(`SELECT lid_jid FROM jid_mapping WHERE phone_jid = ?`).get(phoneJid);
236
+ return row?.lid_jid || null;
237
+ }
238
+ /**
239
+ * Return all known JIDs for a contact — the input JID plus any mapped counterpart.
240
+ * If a phone JID is given, also returns the LID JID (and vice versa).
241
+ */
242
+ export function getAllJidsFor(jid) {
243
+ const jids = [jid];
244
+ if (jid.endsWith("@lid")) {
245
+ const phone = getPhoneJid(jid);
246
+ if (phone)
247
+ jids.push(phone);
248
+ }
249
+ else if (jid.endsWith("@s.whatsapp.net")) {
250
+ const lid = getLidJid(jid);
251
+ if (lid)
252
+ jids.push(lid);
253
+ }
254
+ return jids;
255
+ }
256
+ export function getMessageFromMe(chatJid, messageId) {
257
+ const row = db.prepare(`SELECT from_me FROM messages WHERE chat_jid = ? AND id = ?`).get(chatJid, messageId);
258
+ if (!row)
259
+ return null;
260
+ return row.from_me === 1;
261
+ }
262
+ // ─── Read Operations ────────────────────────────────────────────────
263
+ export function resolveDisplayName(jid) {
264
+ const row = db.prepare(`
265
+ SELECT COALESCE(ch.name, co.name, co.notify, ch.jid) AS display_name
266
+ FROM chats ch
267
+ LEFT JOIN contacts co ON ch.jid = co.jid
268
+ WHERE ch.jid = ?
269
+ `).get(jid);
270
+ if (row?.display_name)
271
+ return row.display_name;
272
+ // Fallback: check contacts directly (chat may not exist yet)
273
+ const contact = stmts.getContactName.get(jid);
274
+ if (contact?.display)
275
+ return contact.display;
276
+ return fromJid(jid);
277
+ }
278
+ export function getChats(nameFilter, limit = 100) {
279
+ let sql = `
280
+ SELECT ch.jid, COALESCE(ch.name, co.name, co.notify) AS name,
281
+ ch.unread_count, m.max_ts AS effective_ts
282
+ FROM chats ch
283
+ LEFT JOIN contacts co ON ch.jid = co.jid
284
+ INNER JOIN (
285
+ SELECT chat_jid, MAX(timestamp) AS max_ts
286
+ FROM messages
287
+ WHERE timestamp > 0
288
+ GROUP BY chat_jid
289
+ ) m ON ch.jid = m.chat_jid
290
+ WHERE ch.jid NOT LIKE '0@%'
291
+ AND ch.jid != 'status@broadcast'
292
+ `;
293
+ const params = [];
294
+ if (nameFilter) {
295
+ sql += ` AND COALESCE(ch.name, co.name, co.notify, ch.jid) LIKE ?`;
296
+ params.push(`%${nameFilter}%`);
297
+ }
298
+ sql += ` ORDER BY effective_ts DESC LIMIT ?`;
299
+ params.push(limit);
300
+ const rows = db.prepare(sql).all(...params);
301
+ // Merge LID chats into their phone JID counterparts to avoid duplicates.
302
+ // If both a LID and phone JID exist for the same contact, keep the phone JID
303
+ // entry with the latest timestamp from either.
304
+ const merged = new Map();
305
+ for (const r of rows) {
306
+ let canonicalJid = r.jid;
307
+ if (r.jid.endsWith("@lid")) {
308
+ const phone = getPhoneJid(r.jid);
309
+ if (phone)
310
+ canonicalJid = phone;
311
+ }
312
+ const existing = merged.get(canonicalJid);
313
+ if (existing) {
314
+ // Merge: keep the latest timestamp, sum unread, prefer non-null name
315
+ existing.effective_ts = Math.max(existing.effective_ts, r.effective_ts);
316
+ existing.unread_count += r.unread_count || 0;
317
+ if (!existing.name && r.name)
318
+ existing.name = r.name;
319
+ }
320
+ else {
321
+ merged.set(canonicalJid, { jid: canonicalJid, name: r.name, unread_count: r.unread_count, effective_ts: r.effective_ts });
322
+ }
323
+ }
324
+ // Re-sort by effective_ts after merging
325
+ const result = [...merged.values()].sort((a, b) => b.effective_ts - a.effective_ts);
326
+ return result.map((r) => ({
327
+ jid: r.jid,
328
+ name: r.name || (r.jid.endsWith("@lid") ? "Unknown" : fromJid(r.jid)),
329
+ unreadCount: r.unread_count || 0,
330
+ lastMessageTime: r.effective_ts,
331
+ isGroup: r.jid.endsWith("@g.us"),
332
+ }));
333
+ }
334
+ export function getChat(jid) {
335
+ const jids = getAllJidsFor(jid);
336
+ const placeholders = jids.map(() => "?").join(", ");
337
+ // Query all JID variants and pick the one with the best data
338
+ const chats = db.prepare(`
339
+ SELECT ch.jid, COALESCE(ch.name, co.name, co.notify) AS name,
340
+ ch.unread_count, ch.conversation_ts
341
+ FROM chats ch
342
+ LEFT JOIN contacts co ON ch.jid = co.jid
343
+ WHERE ch.jid IN (${placeholders})
344
+ `).all(...jids);
345
+ if (chats.length === 0) {
346
+ throw new Error(`Chat not found: ${jid}`);
347
+ }
348
+ // Merge: use the best name, latest timestamp, sum unread counts
349
+ const name = chats.find(c => c.name)?.name || null;
350
+ const conversationTs = Math.max(...chats.map(c => c.conversation_ts || 0));
351
+ const unreadCount = chats.reduce((sum, c) => sum + (c.unread_count || 0), 0);
352
+ const primaryJid = chats.find(c => c.jid === jid)?.jid || chats[0].jid;
353
+ // getMessages already merges across JID variants
354
+ const recentMessages = getMessages(jid, 5).reverse();
355
+ return {
356
+ jid: primaryJid,
357
+ name: name || fromJid(primaryJid),
358
+ unreadCount,
359
+ lastMessageTime: conversationTs || null,
360
+ isGroup: primaryJid.endsWith("@g.us"),
361
+ recentMessages,
362
+ };
363
+ }
364
+ export function getMessages(jid, limit = 50) {
365
+ const jids = getAllJidsFor(jid);
366
+ const placeholders = jids.map(() => "?").join(", ");
367
+ const rows = db.prepare(`
368
+ SELECT id, chat_jid, from_me, sender_jid, sender_name, type, text, timestamp, has_media
369
+ FROM messages
370
+ WHERE chat_jid IN (${placeholders})
371
+ ORDER BY timestamp DESC
372
+ LIMIT ?
373
+ `).all(...jids, limit);
374
+ return rows.map(formatMessageRow);
375
+ }
376
+ export function searchMessages(query, jid) {
377
+ let sql = `
378
+ SELECT id, chat_jid, from_me, sender_jid, sender_name, type, text, timestamp, has_media
379
+ FROM messages
380
+ WHERE text LIKE ? ESCAPE '\\'
381
+ `;
382
+ const escaped = query.replace(/[%_\\]/g, "\\$&");
383
+ const params = [`%${escaped}%`];
384
+ if (jid) {
385
+ const jids = getAllJidsFor(jid);
386
+ const placeholders = jids.map(() => "?").join(", ");
387
+ sql += ` AND chat_jid IN (${placeholders})`;
388
+ params.push(...jids);
389
+ }
390
+ sql += ` ORDER BY timestamp DESC LIMIT 50`;
391
+ const rows = db.prepare(sql).all(...params);
392
+ return rows.map((r) => ({
393
+ ...formatMessageRow(r),
394
+ chat: r.chat_jid,
395
+ }));
396
+ }
397
+ export function searchContacts(query) {
398
+ const rows = db.prepare(`
399
+ SELECT jid, name, notify
400
+ FROM contacts
401
+ WHERE name LIKE ? OR notify LIKE ? OR jid LIKE ?
402
+ `).all(`%${query}%`, `%${query}%`, `%${query}%`);
403
+ return rows.map((r) => ({
404
+ jid: r.jid,
405
+ name: r.name || r.notify || fromJid(r.jid),
406
+ phone: fromJid(r.jid),
407
+ isGroup: r.jid.endsWith("@g.us"),
408
+ }));
409
+ }
410
+ export function getMessageContext(jid, messageId, count = 5) {
411
+ const jids = getAllJidsFor(jid);
412
+ const placeholders = jids.map(() => "?").join(", ");
413
+ // Find the target message timestamp
414
+ const target = db.prepare(`
415
+ SELECT timestamp FROM messages WHERE chat_jid IN (${placeholders}) AND id = ?
416
+ `).get(...jids, messageId);
417
+ if (!target) {
418
+ throw new Error(`Message ${messageId} not found in chat ${jid}`);
419
+ }
420
+ // Get messages before (inclusive of target)
421
+ const before = db.prepare(`
422
+ SELECT id, chat_jid, from_me, sender_jid, sender_name, type, text, timestamp, has_media
423
+ FROM messages
424
+ WHERE chat_jid IN (${placeholders}) AND timestamp <= ?
425
+ ORDER BY timestamp DESC
426
+ LIMIT ?
427
+ `).all(...jids, target.timestamp, count + 1);
428
+ // Get messages after target
429
+ const after = db.prepare(`
430
+ SELECT id, chat_jid, from_me, sender_jid, sender_name, type, text, timestamp, has_media
431
+ FROM messages
432
+ WHERE chat_jid IN (${placeholders}) AND timestamp > ?
433
+ ORDER BY timestamp ASC
434
+ LIMIT ?
435
+ `).all(...jids, target.timestamp, count);
436
+ const allMessages = [...before.reverse(), ...after];
437
+ const targetIndex = before.length - 1;
438
+ return {
439
+ messages: allMessages.map(formatMessageRow),
440
+ targetIndex,
441
+ };
442
+ }
443
+ export function getMessageBlob(jid, messageId) {
444
+ const jids = getAllJidsFor(jid);
445
+ const placeholders = jids.map(() => "?").join(", ");
446
+ const row = db.prepare(`
447
+ SELECT message_blob FROM messages WHERE chat_jid IN (${placeholders}) AND id = ?
448
+ `).get(...jids, messageId);
449
+ return row?.message_blob || null;
450
+ }
451
+ export function getMessageTypeById(chatJid, messageId) {
452
+ const jids = getAllJidsFor(chatJid);
453
+ const placeholders = jids.map(() => "?").join(", ");
454
+ const row = db.prepare(`SELECT type FROM messages WHERE chat_jid IN (${placeholders}) AND id = ?`).get(...jids, messageId);
455
+ return row?.type || null;
456
+ }
457
+ export function getTranscription(chatJid, messageId) {
458
+ const jids = getAllJidsFor(chatJid);
459
+ const placeholders = jids.map(() => "?").join(", ");
460
+ const row = db.prepare(`SELECT transcription FROM messages WHERE chat_jid IN (${placeholders}) AND id = ?`).get(...jids, messageId);
461
+ return row?.transcription || null;
462
+ }
463
+ export function saveTranscription(chatJid, messageId, transcription) {
464
+ // Update across all JID variants since we don't know which one holds the message
465
+ const jids = getAllJidsFor(chatJid);
466
+ const placeholders = jids.map(() => "?").join(", ");
467
+ db.prepare(`UPDATE messages SET transcription = ? WHERE chat_jid IN (${placeholders}) AND id = ?`).run(transcription, ...jids, messageId);
468
+ }
469
+ export function getLastMessageKey(jid) {
470
+ const row = db.prepare(`
471
+ SELECT id, from_me, chat_jid, timestamp FROM messages
472
+ WHERE chat_jid = ?
473
+ ORDER BY timestamp DESC
474
+ LIMIT 1
475
+ `).get(jid);
476
+ if (!row)
477
+ return null;
478
+ return { id: row.id, fromMe: row.from_me === 1, remoteJid: row.chat_jid, timestamp: row.timestamp };
479
+ }
480
+ export function deleteChatMessages(jid) {
481
+ db.prepare(`DELETE FROM messages WHERE chat_jid = ?`).run(jid);
482
+ }
483
+ export function getChatName(jid) {
484
+ const row = db.prepare(`SELECT name FROM chats WHERE jid = ?`).get(jid);
485
+ return row?.name || null;
486
+ }
487
+ export function getContactName(jid) {
488
+ const row = db.prepare(`SELECT name, notify FROM contacts WHERE jid = ?`).get(jid);
489
+ return row?.name || row?.notify || null;
490
+ }
491
+ /** Return the raw better-sqlite3 instance (for use by import-contacts). */
492
+ export function getDb() {
493
+ return db;
494
+ }
495
+ // ─── Init / Close ───────────────────────────────────────────────────
496
+ export function initDb() {
497
+ fs.mkdirSync(STORE_DIR, { recursive: true });
498
+ db = new Database(DB_PATH);
499
+ createTables();
500
+ prepareStatements();
501
+ console.error(`Database initialized at ${DB_PATH}`);
502
+ }
503
+ export function closeDb() {
504
+ if (db) {
505
+ db.close();
506
+ console.error("Database closed");
507
+ }
508
+ }
509
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Import phone contacts from a .vcf (vCard) file into the WhatsApp MCP database.
4
+ *
5
+ * Usage (standalone CLI):
6
+ * npx tsx src/import-contacts.ts contacts.vcf
7
+ * npx tsx src/import-contacts.ts contacts.vcf --dry-run
8
+ *
9
+ * Also exports `importContactsFromVcf()` for use by the MCP tool.
10
+ *
11
+ * Export your contacts from iPhone:
12
+ * Settings > Contacts > Accounts > iCloud > Contacts (on)
13
+ * Go to iCloud.com > Contacts > Select All > Export vCard
14
+ *
15
+ * Or use any other method that produces a .vcf file.
16
+ */
17
+ import Database from "better-sqlite3";
18
+ export interface ImportResult {
19
+ success: boolean;
20
+ vcfPath: string;
21
+ totalParsed: number;
22
+ exactMatches: number;
23
+ fuzzyMatches: number;
24
+ totalUpdated: number;
25
+ namelessChatsRemaining: number;
26
+ }
27
+ /**
28
+ * Import contacts from a VCF file into the database.
29
+ * Can be called from the MCP tool or standalone CLI.
30
+ *
31
+ * @param dbInstance - An open better-sqlite3 database instance.
32
+ * When called from the MCP server, pass the shared DB.
33
+ * When called from CLI, opens its own connection.
34
+ * @param vcfPath - Path to the .vcf file. Defaults to contacts/contacts.vcf.
35
+ */
36
+ export declare function importContactsFromVcf(dbInstance: Database.Database, vcfPath?: string): ImportResult;
37
+ //# sourceMappingURL=import-contacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-contacts.d.ts","sourceRoot":"","sources":["../src/import-contacts.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAwFtC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CA2GnG"}