diff --git a/CHANGELOG.md b/CHANGELOG.md index e5d643e0f..372c4d4f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 1.2.1 — Unreleased +## 1.2.1 — 2025-11-28 ### Changes - **Manual heartbeat sends:** `warelay heartbeat` now accepts `--message/--body` with `--provider web|twilio` to push real outbound messages through the same plumbing; `--dry-run` previews payloads without sending. diff --git a/package.json b/package.json index 7188331b8..b27346f79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "warelay", - "version": "1.2.0", + "version": "1.2.1", "description": "WhatsApp relay CLI (send, monitor, webhook, auto-reply) using Twilio", "type": "module", "main": "dist/index.js", diff --git a/src/media/mime.ts b/src/media/mime.ts index 20ed050f6..9b3457a02 100644 --- a/src/media/mime.ts +++ b/src/media/mime.ts @@ -1,6 +1,6 @@ import path from "node:path"; -import { mediaKindFromMime, type MediaKind } from "./constants.js"; +import { type MediaKind, mediaKindFromMime } from "./constants.js"; // Map common mimes to preferred file extensions. const EXT_BY_MIME: Record = { @@ -82,7 +82,10 @@ function sniffMime(buffer?: Buffer): string | undefined { } // MP4: "ftyp" at offset 4. - if (buffer.length >= 12 && buffer.subarray(4, 8).toString("ascii") === "ftyp") { + if ( + buffer.length >= 12 && + buffer.subarray(4, 8).toString("ascii") === "ftyp" + ) { return "video/mp4"; } diff --git a/src/media/store.test.ts b/src/media/store.test.ts index f2038ee67..e95a08b0c 100644 --- a/src/media/store.test.ts +++ b/src/media/store.test.ts @@ -1,8 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; - -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import sharp from "sharp"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; const realOs = await vi.importActual("node:os"); const HOME = path.join(realOs.tmpdir(), "warelay-home-test"); diff --git a/src/media/store.ts b/src/media/store.ts index 42802b19d..291d048e8 100644 --- a/src/media/store.ts +++ b/src/media/store.ts @@ -41,15 +41,14 @@ function looksLikeUrl(src: string) { return /^https?:\/\//i.test(src); } +/** + * Download media to disk while capturing the first few KB for mime sniffing. + */ async function downloadToFile( url: string, dest: string, headers?: Record, -): Promise<{ headerMime?: string; sniffBuffer: Buffer; size: number }> -/** - * Download media to disk while capturing the first few KB for mime sniffing. - */ -{ +): Promise<{ headerMime?: string; sniffBuffer: Buffer; size: number }> { return await new Promise((resolve, reject) => { const req = request(url, { headers }, (res) => { if (!res.statusCode || res.statusCode >= 400) { @@ -72,9 +71,14 @@ async function downloadToFile( }); pipeline(res, out) .then(() => { - const sniffBuffer = Buffer.concat(sniffChunks, Math.min(sniffLen, 16384)); + const sniffBuffer = Buffer.concat( + sniffChunks, + Math.min(sniffLen, 16384), + ); const rawHeader = res.headers["content-type"]; - const headerMime = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader; + const headerMime = Array.isArray(rawHeader) + ? rawHeader[0] + : rawHeader; resolve({ headerMime, sniffBuffer, @@ -116,7 +120,8 @@ export async function saveMediaSource( headerMime, filePath: source, }); - const ext = extensionForMime(mime) ?? path.extname(new URL(source).pathname); + const ext = + extensionForMime(mime) ?? path.extname(new URL(source).pathname); const finalDest = path.join(dir, ext ? `${id}${ext}` : id); await fs.rename(tempDest, finalDest); return { id, path: finalDest, size, contentType: mime }; diff --git a/src/web/inbound.media.test.ts b/src/web/inbound.media.test.ts index fd6f240ca..84994cdd6 100644 --- a/src/web/inbound.media.test.ts +++ b/src/web/inbound.media.test.ts @@ -5,22 +5,30 @@ import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -const HOME = path.join(os.tmpdir(), `warelay-inbound-media-${crypto.randomUUID()}`); +const HOME = path.join( + os.tmpdir(), + `warelay-inbound-media-${crypto.randomUUID()}`, +); process.env.HOME = HOME; vi.mock("@whiskeysockets/baileys", async () => { - const actual = await vi.importActual( - "@whiskeysockets/baileys", - ); + const actual = await vi.importActual< + typeof import("@whiskeysockets/baileys") + >("@whiskeysockets/baileys"); const jpegBuffer = Buffer.from([ - 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, - 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, - 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, - 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc0, 0x00, 0x11, 0x08, - 0x00, 0x01, 0x00, 0x01, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x14, 0x00, - 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, - 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, - 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xff, 0xd9, + 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, + 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, + 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, + 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, + 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 0x03, 0x01, 0x11, 0x00, + 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x14, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, + 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, + 0xff, 0xd9, ]); return { ...actual, @@ -63,9 +71,11 @@ describe("web inbound media saves with extension", () => { const onMessage = vi.fn(); const listener = await monitorWebInbox({ verbose: false, onMessage }); const { createWaSocket } = await import("./session.js"); - const realSock = await (createWaSocket as unknown as () => Promise<{ - ev: import("node:events").EventEmitter; - }>)(); + const realSock = await ( + createWaSocket as unknown as () => Promise<{ + ev: import("node:events").EventEmitter; + }> + )(); const upsert = { type: "notify", @@ -83,9 +93,10 @@ describe("web inbound media saves with extension", () => { expect(onMessage).toHaveBeenCalledTimes(1); const msg = onMessage.mock.calls[0][0]; - expect(msg.mediaPath).toBeDefined(); - expect(path.extname(msg.mediaPath!)).toBe(".jpg"); - const stat = await fs.stat(msg.mediaPath!); + const mediaPath = msg.mediaPath; + expect(mediaPath).toBeDefined(); + expect(path.extname(mediaPath as string)).toBe(".jpg"); + const stat = await fs.stat(mediaPath as string); expect(stat.size).toBeGreaterThan(0); await listener.close();