@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.
Files changed (2) hide show
  1. package/dist/index.js +47 -15
  2. 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=\"phone\" message_id=\"id\">.",
138
- "Use wahooks_reply to respond to the sender. Use wahooks_send to message any phone.",
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(phone) {
257
- const digits = phone.replace(/\D/g, "");
258
- return digits.includes("@") ? digits : `${digits}@s.whatsapp.net`;
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
- const from = (payload.from ?? "")
346
- .replace("@c.us", "")
347
- .replace("@s.whatsapp.net", "")
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
- if (ALLOW_LIST.size > 0 && !ALLOW_LIST.has(from)) {
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: text,
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wahooks/channel",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "WhatsApp channel for Claude Code — chat with Claude via WhatsApp",
5
5
  "type": "module",
6
6
  "bin": {