@wahooks/channel 0.5.1 → 0.6.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.
- package/dist/index.js +47 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -134,11 +134,12 @@ const mcp = new Server({ name: "wahooks-channel", version: "0.1.0" }, {
|
|
|
134
134
|
tools: {},
|
|
135
135
|
},
|
|
136
136
|
instructions: [
|
|
137
|
-
"WhatsApp messages arrive as <channel source=\"wahooks-channel\" from=\"
|
|
138
|
-
"
|
|
137
|
+
"WhatsApp messages arrive as <channel source=\"wahooks-channel\" from=\"sender_id\" message_id=\"id\">.",
|
|
138
|
+
"IMPORTANT: When a WhatsApp message arrives, reply immediately using wahooks_reply with the exact 'from' value from the channel tag. Do NOT ask the local user for permission to reply — just reply directly.",
|
|
139
|
+
"Use wahooks_reply to respond to the sender. Pass the 'from' value exactly as received (do not modify it).",
|
|
140
|
+
"Use wahooks_send to message any phone number.",
|
|
139
141
|
"Media tools: wahooks_send_image, wahooks_send_video, wahooks_send_audio, wahooks_send_document.",
|
|
140
142
|
"Also available: wahooks_send_location (lat/lng) and wahooks_send_contact (name/phone).",
|
|
141
|
-
"For permission requests, the user can reply 'yes XXXXX' or 'no XXXXX' where XXXXX is the request ID.",
|
|
142
143
|
].join(" "),
|
|
143
144
|
});
|
|
144
145
|
// ─── Permission relay ───────────────────────────────────────────────────
|
|
@@ -253,9 +254,15 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
253
254
|
},
|
|
254
255
|
],
|
|
255
256
|
}));
|
|
256
|
-
function toChatId(
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
function toChatId(id) {
|
|
258
|
+
// Already a full chat ID (contains @)
|
|
259
|
+
if (id.includes("@"))
|
|
260
|
+
return id;
|
|
261
|
+
// LID format (long numeric, typically 14+ digits used by WhatsApp linked IDs)
|
|
262
|
+
if (id.length >= 14)
|
|
263
|
+
return `${id}@lid`;
|
|
264
|
+
// Regular phone number
|
|
265
|
+
return `${id.replace(/\D/g, "")}@s.whatsapp.net`;
|
|
259
266
|
}
|
|
260
267
|
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
261
268
|
const args = req.params.arguments;
|
|
@@ -342,16 +349,18 @@ function connectWebSocket() {
|
|
|
342
349
|
// Skip outbound messages (sent by us)
|
|
343
350
|
if (payload.fromMe)
|
|
344
351
|
return;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
.replace("@lid", "");
|
|
352
|
+
// Keep the full from ID (e.g. "1234@s.whatsapp.net" or "5678@lid")
|
|
353
|
+
// so the reply tool can use it directly as a chat ID
|
|
354
|
+
const from = payload.from ?? "";
|
|
349
355
|
const text = payload.body ?? payload.text ?? "";
|
|
356
|
+
const hasMedia = payload.hasMedia === true;
|
|
357
|
+
const media = payload.media;
|
|
350
358
|
const messageId = payload.id?._serialized ?? payload.id ?? `msg_${Date.now()}`;
|
|
351
|
-
if (!from || !text)
|
|
359
|
+
if (!from || (!text && !hasMedia))
|
|
352
360
|
return;
|
|
353
|
-
// Sender gating
|
|
354
|
-
|
|
361
|
+
// Sender gating (compare bare number against allow list)
|
|
362
|
+
const bareNumber = from.replace(/@.*$/, "");
|
|
363
|
+
if (ALLOW_LIST.size > 0 && !ALLOW_LIST.has(bareNumber)) {
|
|
355
364
|
console.error(`[wahooks-channel] Blocked message from ${from} (not in allow list)`);
|
|
356
365
|
return;
|
|
357
366
|
}
|
|
@@ -370,18 +379,41 @@ function connectWebSocket() {
|
|
|
370
379
|
console.error(`[wahooks-channel] Permission verdict: ${permMatch[1]} ${permMatch[2]}`);
|
|
371
380
|
return;
|
|
372
381
|
}
|
|
382
|
+
// Build message content for Claude
|
|
383
|
+
let content = text;
|
|
384
|
+
if (hasMedia && media?.url) {
|
|
385
|
+
// Download media and convert to base64 for Claude to see
|
|
386
|
+
try {
|
|
387
|
+
const mediaRes = await fetch(media.url, {
|
|
388
|
+
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
389
|
+
});
|
|
390
|
+
if (mediaRes.ok) {
|
|
391
|
+
const buf = Buffer.from(await mediaRes.arrayBuffer());
|
|
392
|
+
const b64 = buf.toString("base64");
|
|
393
|
+
const mime = media.mimetype ?? "image/jpeg";
|
|
394
|
+
content = `${text ? text + "\n\n" : ""}[Media: ${mime}]\ndata:${mime};base64,${b64}`;
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
content = `${text ? text + "\n\n" : ""}[Media attached but could not be downloaded: ${media.mimetype ?? "unknown type"}]`;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
content = `${text ? text + "\n\n" : ""}[Media attached: ${media?.mimetype ?? "unknown type"}]`;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
373
404
|
// Forward to Claude Code
|
|
374
405
|
await mcp.notification({
|
|
375
406
|
method: "notifications/claude/channel",
|
|
376
407
|
params: {
|
|
377
|
-
content
|
|
408
|
+
content,
|
|
378
409
|
meta: {
|
|
379
410
|
from,
|
|
380
411
|
message_id: messageId,
|
|
412
|
+
...(hasMedia ? { has_media: "true", media_type: media?.mimetype ?? "unknown" } : {}),
|
|
381
413
|
},
|
|
382
414
|
},
|
|
383
415
|
});
|
|
384
|
-
console.error(`[wahooks-channel] Message from ${from}: ${text.slice(0, 80)}`);
|
|
416
|
+
console.error(`[wahooks-channel] Message from ${from}: ${text.slice(0, 80)}${hasMedia ? " [+media]" : ""}`);
|
|
385
417
|
}
|
|
386
418
|
catch (err) {
|
|
387
419
|
console.error("[wahooks-channel] Event parse error:", err);
|