scoootscooob 16505718e8
refactor: move WhatsApp channel implementation to extensions/ (#45725)
* refactor: move WhatsApp channel from src/web/ to extensions/whatsapp/

Move all WhatsApp implementation code (77 source/test files + 9 channel
plugin files) from src/web/ and src/channels/plugins/*/whatsapp* to
extensions/whatsapp/src/.

- Leave thin re-export shims at all original locations so cross-cutting
  imports continue to resolve
- Update plugin-sdk/whatsapp.ts to only re-export generic framework
  utilities; channel-specific functions imported locally by the extension
- Update vi.mock paths in 15 cross-cutting test files
- Rename outbound.ts -> send.ts to match extension naming conventions
  and avoid false positive in cfg-threading guard test
- Widen tsconfig.plugin-sdk.dts.json rootDir to support shim->extension
  cross-directory references

Part of the core-channels-to-extensions migration (PR 6/10).

* style: format WhatsApp extension files

* fix: correct stale import paths in WhatsApp extension tests

Fix vi.importActual, test mock, and hardcoded source paths that weren't
updated during the file move:
- media.test.ts: vi.importActual path
- onboarding.test.ts: vi.importActual path
- test-helpers.ts: test/mocks/baileys.js path
- monitor-inbox.test-harness.ts: incomplete media/store mock
- login.test.ts: hardcoded source file path
- message-action-runner.media.test.ts: vi.mock/importActual path
2026-03-14 02:44:55 -07:00

114 lines
3.6 KiB
TypeScript

import type { AnyMessageContent, WAPresence } from "@whiskeysockets/baileys";
import { recordChannelActivity } from "../../../../src/infra/channel-activity.js";
import { toWhatsappJid } from "../../../../src/utils.js";
import type { ActiveWebSendOptions } from "../active-listener.js";
function recordWhatsAppOutbound(accountId: string) {
recordChannelActivity({
channel: "whatsapp",
accountId,
direction: "outbound",
});
}
function resolveOutboundMessageId(result: unknown): string {
return typeof result === "object" && result && "key" in result
? String((result as { key?: { id?: string } }).key?.id ?? "unknown")
: "unknown";
}
export function createWebSendApi(params: {
sock: {
sendMessage: (jid: string, content: AnyMessageContent) => Promise<unknown>;
sendPresenceUpdate: (presence: WAPresence, jid?: string) => Promise<unknown>;
};
defaultAccountId: string;
}) {
return {
sendMessage: async (
to: string,
text: string,
mediaBuffer?: Buffer,
mediaType?: string,
sendOptions?: ActiveWebSendOptions,
): Promise<{ messageId: string }> => {
const jid = toWhatsappJid(to);
let payload: AnyMessageContent;
if (mediaBuffer && mediaType) {
if (mediaType.startsWith("image/")) {
payload = {
image: mediaBuffer,
caption: text || undefined,
mimetype: mediaType,
};
} else if (mediaType.startsWith("audio/")) {
payload = { audio: mediaBuffer, ptt: true, mimetype: mediaType };
} else if (mediaType.startsWith("video/")) {
const gifPlayback = sendOptions?.gifPlayback;
payload = {
video: mediaBuffer,
caption: text || undefined,
mimetype: mediaType,
...(gifPlayback ? { gifPlayback: true } : {}),
};
} else {
const fileName = sendOptions?.fileName?.trim() || "file";
payload = {
document: mediaBuffer,
fileName,
caption: text || undefined,
mimetype: mediaType,
};
}
} else {
payload = { text };
}
const result = await params.sock.sendMessage(jid, payload);
const accountId = sendOptions?.accountId ?? params.defaultAccountId;
recordWhatsAppOutbound(accountId);
const messageId = resolveOutboundMessageId(result);
return { messageId };
},
sendPoll: async (
to: string,
poll: { question: string; options: string[]; maxSelections?: number },
): Promise<{ messageId: string }> => {
const jid = toWhatsappJid(to);
const result = await params.sock.sendMessage(jid, {
poll: {
name: poll.question,
values: poll.options,
selectableCount: poll.maxSelections ?? 1,
},
} as AnyMessageContent);
recordWhatsAppOutbound(params.defaultAccountId);
const messageId = resolveOutboundMessageId(result);
return { messageId };
},
sendReaction: async (
chatJid: string,
messageId: string,
emoji: string,
fromMe: boolean,
participant?: string,
): Promise<void> => {
const jid = toWhatsappJid(chatJid);
await params.sock.sendMessage(jid, {
react: {
text: emoji,
key: {
remoteJid: jid,
id: messageId,
fromMe,
participant: participant ? toWhatsappJid(participant) : undefined,
},
},
} as AnyMessageContent);
},
sendComposingTo: async (to: string): Promise<void> => {
const jid = toWhatsappJid(to);
await params.sock.sendPresenceUpdate("composing", jid);
},
} as const;
}