chore: release 1.2.1

This commit is contained in:
Peter Steinberger
2025-11-28 08:11:07 +01:00
parent f63bdda628
commit c11abc1134
6 changed files with 50 additions and 32 deletions

View File

@@ -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.

View File

@@ -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",

View File

@@ -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<string, string> = {
@@ -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";
}

View File

@@ -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<typeof import("node:os")>("node:os");
const HOME = path.join(realOs.tmpdir(), "warelay-home-test");

View File

@@ -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<string, string>,
): 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 };

View File

@@ -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<typeof import("@whiskeysockets/baileys")>(
"@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();