@wahooks/channel 0.6.0 → 0.7.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 +70 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,6 +22,39 @@ import WebSocket from "ws";
|
|
|
22
22
|
import fs from "node:fs";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import os from "node:os";
|
|
25
|
+
// Minimal mime type detection from extension
|
|
26
|
+
const MIME_MAP = {
|
|
27
|
+
".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png",
|
|
28
|
+
".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml",
|
|
29
|
+
".pdf": "application/pdf", ".doc": "application/msword",
|
|
30
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
31
|
+
".xls": "application/vnd.ms-excel",
|
|
32
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
33
|
+
".mp4": "video/mp4", ".webm": "video/webm", ".mov": "video/quicktime",
|
|
34
|
+
".mp3": "audio/mpeg", ".ogg": "audio/ogg", ".wav": "audio/wav",
|
|
35
|
+
".m4a": "audio/mp4", ".opus": "audio/opus",
|
|
36
|
+
".zip": "application/zip", ".txt": "text/plain", ".csv": "text/csv",
|
|
37
|
+
".json": "application/json",
|
|
38
|
+
};
|
|
39
|
+
function getMimeType(filePath) {
|
|
40
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
41
|
+
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
42
|
+
}
|
|
43
|
+
/** Resolve file_path/data/url into API-ready body fields */
|
|
44
|
+
function resolveMedia(args) {
|
|
45
|
+
if (args.file_path) {
|
|
46
|
+
const fileData = fs.readFileSync(args.file_path);
|
|
47
|
+
return {
|
|
48
|
+
data: fileData.toString("base64"),
|
|
49
|
+
mimetype: args.mimetype ?? getMimeType(args.file_path),
|
|
50
|
+
filename: args.filename ?? path.basename(args.file_path),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (args.data) {
|
|
54
|
+
return { data: args.data, mimetype: args.mimetype };
|
|
55
|
+
}
|
|
56
|
+
return { url: args.url };
|
|
57
|
+
}
|
|
25
58
|
// ─── Config ─────────────────────────────────────────────────────────────
|
|
26
59
|
const CONFIG_DIR = path.join(os.homedir(), ".claude", "channels", "wahooks");
|
|
27
60
|
const ENV_FILE = path.join(CONFIG_DIR, ".env");
|
|
@@ -175,53 +208,66 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
175
208
|
},
|
|
176
209
|
{
|
|
177
210
|
name: "wahooks_send_image",
|
|
178
|
-
description: "Send an image via WhatsApp.",
|
|
211
|
+
description: "Send an image via WhatsApp. Provide url, file_path (local file), or data (base64).",
|
|
179
212
|
inputSchema: {
|
|
180
213
|
type: "object",
|
|
181
214
|
properties: {
|
|
182
|
-
to: { type: "string", description: "
|
|
215
|
+
to: { type: "string", description: "Recipient (phone number or chat ID from channel event)" },
|
|
183
216
|
url: { type: "string", description: "Image URL" },
|
|
217
|
+
file_path: { type: "string", description: "Local file path" },
|
|
218
|
+
data: { type: "string", description: "Base64-encoded image data" },
|
|
219
|
+
mimetype: { type: "string", description: "MIME type (auto-detected from file_path)" },
|
|
184
220
|
caption: { type: "string", description: "Optional caption" },
|
|
185
221
|
},
|
|
186
|
-
required: ["to"
|
|
222
|
+
required: ["to"],
|
|
187
223
|
},
|
|
188
224
|
},
|
|
189
225
|
{
|
|
190
226
|
name: "wahooks_send_document",
|
|
191
|
-
description: "Send a document/file via WhatsApp.",
|
|
227
|
+
description: "Send a document/file via WhatsApp. Provide url, file_path (local file), or data (base64).",
|
|
192
228
|
inputSchema: {
|
|
193
229
|
type: "object",
|
|
194
230
|
properties: {
|
|
195
|
-
to: { type: "string", description: "
|
|
231
|
+
to: { type: "string", description: "Recipient" },
|
|
196
232
|
url: { type: "string", description: "Document URL" },
|
|
197
|
-
|
|
233
|
+
file_path: { type: "string", description: "Local file path" },
|
|
234
|
+
data: { type: "string", description: "Base64-encoded file data" },
|
|
235
|
+
mimetype: { type: "string", description: "MIME type" },
|
|
236
|
+
filename: { type: "string", description: "Filename (auto-detected from file_path)" },
|
|
237
|
+
caption: { type: "string", description: "Optional caption" },
|
|
198
238
|
},
|
|
199
|
-
required: ["to"
|
|
239
|
+
required: ["to"],
|
|
200
240
|
},
|
|
201
241
|
},
|
|
202
242
|
{
|
|
203
243
|
name: "wahooks_send_video",
|
|
204
|
-
description: "Send a video via WhatsApp.",
|
|
244
|
+
description: "Send a video via WhatsApp. Provide url, file_path (local file), or data (base64).",
|
|
205
245
|
inputSchema: {
|
|
206
246
|
type: "object",
|
|
207
247
|
properties: {
|
|
208
|
-
to: { type: "string", description: "
|
|
248
|
+
to: { type: "string", description: "Recipient" },
|
|
209
249
|
url: { type: "string", description: "Video URL" },
|
|
250
|
+
file_path: { type: "string", description: "Local file path" },
|
|
251
|
+
data: { type: "string", description: "Base64-encoded video data" },
|
|
252
|
+
mimetype: { type: "string", description: "MIME type" },
|
|
210
253
|
caption: { type: "string", description: "Optional caption" },
|
|
211
254
|
},
|
|
212
|
-
required: ["to"
|
|
255
|
+
required: ["to"],
|
|
213
256
|
},
|
|
214
257
|
},
|
|
215
258
|
{
|
|
216
259
|
name: "wahooks_send_audio",
|
|
217
|
-
description: "Send an audio/voice message via WhatsApp.",
|
|
260
|
+
description: "Send an audio/voice message via WhatsApp. Provide url, file_path (local file), or data (base64).",
|
|
218
261
|
inputSchema: {
|
|
219
262
|
type: "object",
|
|
220
263
|
properties: {
|
|
221
|
-
to: { type: "string", description: "
|
|
264
|
+
to: { type: "string", description: "Recipient" },
|
|
222
265
|
url: { type: "string", description: "Audio URL" },
|
|
266
|
+
file_path: { type: "string", description: "Local file path" },
|
|
267
|
+
data: { type: "string", description: "Base64-encoded audio data" },
|
|
268
|
+
mimetype: { type: "string", description: "MIME type" },
|
|
223
269
|
},
|
|
224
|
-
required: ["to"
|
|
270
|
+
required: ["to"],
|
|
225
271
|
},
|
|
226
272
|
},
|
|
227
273
|
{
|
|
@@ -276,33 +322,37 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
276
322
|
return { content: [{ type: "text", text: `Sent to ${args.to}` }] };
|
|
277
323
|
}
|
|
278
324
|
case "wahooks_send_image": {
|
|
325
|
+
const media = resolveMedia(args);
|
|
279
326
|
await api("POST", `/connections/${connectionId}/send-image`, {
|
|
280
327
|
chatId: toChatId(args.to),
|
|
281
|
-
|
|
328
|
+
...media,
|
|
282
329
|
caption: args.caption,
|
|
283
330
|
});
|
|
284
331
|
return { content: [{ type: "text", text: `Image sent to ${args.to}` }] };
|
|
285
332
|
}
|
|
286
333
|
case "wahooks_send_document": {
|
|
334
|
+
const media = resolveMedia(args);
|
|
287
335
|
await api("POST", `/connections/${connectionId}/send-document`, {
|
|
288
336
|
chatId: toChatId(args.to),
|
|
289
|
-
|
|
290
|
-
|
|
337
|
+
...media,
|
|
338
|
+
caption: args.caption,
|
|
291
339
|
});
|
|
292
340
|
return { content: [{ type: "text", text: `Document sent to ${args.to}` }] };
|
|
293
341
|
}
|
|
294
342
|
case "wahooks_send_video": {
|
|
343
|
+
const media = resolveMedia(args);
|
|
295
344
|
await api("POST", `/connections/${connectionId}/send-video`, {
|
|
296
345
|
chatId: toChatId(args.to),
|
|
297
|
-
|
|
346
|
+
...media,
|
|
298
347
|
caption: args.caption,
|
|
299
348
|
});
|
|
300
349
|
return { content: [{ type: "text", text: `Video sent to ${args.to}` }] };
|
|
301
350
|
}
|
|
302
351
|
case "wahooks_send_audio": {
|
|
352
|
+
const media = resolveMedia(args);
|
|
303
353
|
await api("POST", `/connections/${connectionId}/send-audio`, {
|
|
304
354
|
chatId: toChatId(args.to),
|
|
305
|
-
|
|
355
|
+
...media,
|
|
306
356
|
});
|
|
307
357
|
return { content: [{ type: "text", text: `Audio sent to ${args.to}` }] };
|
|
308
358
|
}
|
|
@@ -382,24 +432,8 @@ function connectWebSocket() {
|
|
|
382
432
|
// Build message content for Claude
|
|
383
433
|
let content = text;
|
|
384
434
|
if (hasMedia && media?.url) {
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
}
|
|
435
|
+
const mime = media.mimetype ?? "unknown";
|
|
436
|
+
content = `${text ? text + "\n\n" : ""}[Attached: ${mime}]\nDownload URL: ${media.url}`;
|
|
403
437
|
}
|
|
404
438
|
// Forward to Claude Code
|
|
405
439
|
await mcp.notification({
|