@wahooks/channel 0.7.0 → 0.8.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 +40 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -167,11 +167,11 @@ const mcp = new Server({ name: "wahooks-channel", version: "0.1.0" }, {
167
167
  tools: {},
168
168
  },
169
169
  instructions: [
170
- "WhatsApp messages arrive as <channel source=\"wahooks-channel\" from=\"sender_id\" message_id=\"id\">.",
171
- "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.",
172
- "Use wahooks_reply to respond to the sender. Pass the 'from' value exactly as received (do not modify it).",
173
- "Use wahooks_send to message any phone number.",
174
- "Media tools: wahooks_send_image, wahooks_send_video, wahooks_send_audio, wahooks_send_document.",
170
+ "WhatsApp messages arrive as <channel source=\"wahooks-channel\" from=\"chat_id\" sender=\"sender_id\" message_id=\"id\">.",
171
+ "For group messages, group=\"true\" is set. 'from' is the chat/group to reply to, 'sender' is who sent it.",
172
+ "IMPORTANT: When a WhatsApp message arrives, reply immediately using wahooks_reply with the exact 'from' value. Do NOT ask the local user for permission — just reply directly.",
173
+ "Use wahooks_reply to respond in the same chat. Use wahooks_send to message any phone or group.",
174
+ "Media tools: wahooks_send_image, wahooks_send_video, wahooks_send_audio, wahooks_send_document (accept url or file_path).",
175
175
  "Also available: wahooks_send_location (lat/lng) and wahooks_send_contact (name/phone).",
176
176
  ].join(" "),
177
177
  });
@@ -399,23 +399,23 @@ function connectWebSocket() {
399
399
  // Skip outbound messages (sent by us)
400
400
  if (payload.fromMe)
401
401
  return;
402
- // Keep the full from ID (e.g. "1234@s.whatsapp.net" or "5678@lid")
403
- // so the reply tool can use it directly as a chat ID
404
- const from = payload.from ?? "";
402
+ const chatId = payload.from ?? "";
403
+ const isGroup = chatId.includes("@g.us");
404
+ const sender = payload.participant ?? chatId; // participant for groups, from for DMs
405
405
  const text = payload.body ?? payload.text ?? "";
406
406
  const hasMedia = payload.hasMedia === true;
407
407
  const media = payload.media;
408
408
  const messageId = payload.id?._serialized ?? payload.id ?? `msg_${Date.now()}`;
409
- if (!from || (!text && !hasMedia))
409
+ if (!chatId || (!text && !hasMedia))
410
410
  return;
411
- // Sender gating (compare bare number against allow list)
412
- const bareNumber = from.replace(/@.*$/, "");
411
+ // Sender gating (check the actual sender, not the group)
412
+ const bareNumber = sender.replace(/@.*$/, "");
413
413
  if (ALLOW_LIST.size > 0 && !ALLOW_LIST.has(bareNumber)) {
414
- console.error(`[wahooks-channel] Blocked message from ${from} (not in allow list)`);
414
+ console.error(`[wahooks-channel] Blocked message from ${sender} (not in allow list)`);
415
415
  return;
416
416
  }
417
- // Track last sender for permission relay
418
- lastSender = from;
417
+ // Track last sender for replies
418
+ lastSender = chatId; // reply to the chat (group or DM)
419
419
  // Check if this is a permission verdict
420
420
  const permMatch = PERMISSION_RE.exec(text);
421
421
  if (permMatch) {
@@ -431,9 +431,30 @@ function connectWebSocket() {
431
431
  }
432
432
  // Build message content for Claude
433
433
  let content = text;
434
+ let localMediaPath = "";
434
435
  if (hasMedia && media?.url) {
435
436
  const mime = media.mimetype ?? "unknown";
436
- content = `${text ? text + "\n\n" : ""}[Attached: ${mime}]\nDownload URL: ${media.url}`;
437
+ // Download media locally so Claude can access it directly
438
+ try {
439
+ const mediaRes = await fetch(media.url, {
440
+ headers: { Authorization: `Bearer ${API_KEY}` },
441
+ });
442
+ if (mediaRes.ok) {
443
+ const buf = Buffer.from(await mediaRes.arrayBuffer());
444
+ const ext = mime.split("/")[1]?.split(";")[0] ?? "bin";
445
+ localMediaPath = path.join(os.tmpdir(), `wahooks-media-${Date.now()}.${ext}`);
446
+ fs.writeFileSync(localMediaPath, buf);
447
+ content = `${text ? text + "\n\n" : ""}[Attached: ${mime}] Saved to: ${localMediaPath}`;
448
+ console.error(`[wahooks-channel] Media saved: ${localMediaPath} (${buf.length} bytes)`);
449
+ }
450
+ else {
451
+ content = `${text ? text + "\n\n" : ""}[Attached: ${mime}] (could not download)`;
452
+ }
453
+ }
454
+ catch (err) {
455
+ content = `${text ? text + "\n\n" : ""}[Attached: ${mime}] (download failed)`;
456
+ console.error(`[wahooks-channel] Media download failed: ${err}`);
457
+ }
437
458
  }
438
459
  // Forward to Claude Code
439
460
  await mcp.notification({
@@ -441,13 +462,15 @@ function connectWebSocket() {
441
462
  params: {
442
463
  content,
443
464
  meta: {
444
- from,
465
+ from: chatId,
466
+ sender: isGroup ? sender : chatId,
445
467
  message_id: messageId,
468
+ ...(isGroup ? { group: "true" } : {}),
446
469
  ...(hasMedia ? { has_media: "true", media_type: media?.mimetype ?? "unknown" } : {}),
447
470
  },
448
471
  },
449
472
  });
450
- console.error(`[wahooks-channel] Message from ${from}: ${text.slice(0, 80)}${hasMedia ? " [+media]" : ""}`);
473
+ console.error(`[wahooks-channel] ${isGroup ? "[group] " : ""}Message from ${sender}: ${text.slice(0, 80)}${hasMedia ? " [+media]" : ""}`);
451
474
  }
452
475
  catch (err) {
453
476
  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.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "WhatsApp channel for Claude Code — chat with Claude via WhatsApp",
5
5
  "type": "module",
6
6
  "bin": {