119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
|
|
/**
|
|||
|
|
* Outbound 模块 - 消息发送
|
|||
|
|
* @module outbound
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import type { SendResult } from "./types.js";
|
|||
|
|
import { chunkText } from "./utils/chunker.js";
|
|||
|
|
import { createLogger } from "./utils/logger.js";
|
|||
|
|
|
|||
|
|
const MODULE = "outbound";
|
|||
|
|
|
|||
|
|
// 外部 Gateway 管理器 - 由 channel.ts 注入
|
|||
|
|
let gatewayMap: Map<string, {
|
|||
|
|
sendText: (to: string, content: string, replyTo?: string) => Promise<{ success: boolean; messageId?: string; error?: string }>;
|
|||
|
|
sendMedia: (to: string, mediaUrl: string, text?: string, replyTo?: string) => Promise<{ success: boolean; messageId?: string; error?: string }>;
|
|||
|
|
}> | null = null;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置 Gateway 映射(由 channel.ts 调用)
|
|||
|
|
*/
|
|||
|
|
export function setGatewayMap(map: Map<string, never>): void {
|
|||
|
|
gatewayMap = map as unknown as typeof gatewayMap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送文本消息
|
|||
|
|
*/
|
|||
|
|
export async function sendText(options: {
|
|||
|
|
to: string;
|
|||
|
|
text: string;
|
|||
|
|
accountId: string;
|
|||
|
|
replyToId?: string;
|
|||
|
|
}): Promise<SendResult> {
|
|||
|
|
const { to, text, accountId, replyToId } = options;
|
|||
|
|
const log = createLogger(MODULE);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const gateway = gatewayMap?.get(accountId);
|
|||
|
|
if (!gateway) {
|
|||
|
|
return { messageId: "", error: "Gateway not available" };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取分块限制
|
|||
|
|
const chunkLimit = 4000;
|
|||
|
|
const chunks = chunkText(text, { limit: chunkLimit, mode: "markdown" });
|
|||
|
|
|
|||
|
|
if (chunks.length === 1) {
|
|||
|
|
const result = await gateway.sendText(to, text, replyToId);
|
|||
|
|
return {
|
|||
|
|
messageId: result.messageId ?? "",
|
|||
|
|
error: result.error,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 多块消息,逐块发送
|
|||
|
|
const messageIds: string[] = [];
|
|||
|
|
let lastError: string | undefined;
|
|||
|
|
|
|||
|
|
for (let i = 0; i < chunks.length; i++) {
|
|||
|
|
const chunk = chunks[i];
|
|||
|
|
const chunkSuffix = chunks.length > 1 ? `\n\n--- [${i + 1}/${chunks.length}] ---` : "";
|
|||
|
|
const result = await gateway.sendText(to, chunk + chunkSuffix, replyToId);
|
|||
|
|
|
|||
|
|
if (result.messageId) {
|
|||
|
|
messageIds.push(result.messageId);
|
|||
|
|
}
|
|||
|
|
if (result.error) {
|
|||
|
|
lastError = result.error;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 块之间添加延迟
|
|||
|
|
if (i < chunks.length - 1) {
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
messageId: messageIds.join(","),
|
|||
|
|
error: lastError,
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
const message = error instanceof Error ? error.message : String(error);
|
|||
|
|
log.error("Failed to send text", { to, error: message });
|
|||
|
|
return { messageId: "", error: message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送媒体消息
|
|||
|
|
*/
|
|||
|
|
export async function sendMedia(options: {
|
|||
|
|
to: string;
|
|||
|
|
text?: string;
|
|||
|
|
mediaUrl: string;
|
|||
|
|
accountId: string;
|
|||
|
|
replyToId?: string;
|
|||
|
|
}): Promise<SendResult> {
|
|||
|
|
const { to, text, mediaUrl, accountId, replyToId } = options;
|
|||
|
|
const log = createLogger(MODULE);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const gateway = gatewayMap?.get(accountId);
|
|||
|
|
if (!gateway) {
|
|||
|
|
return { messageId: "", error: "Gateway not available" };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const result = await gateway.sendMedia(to, mediaUrl, text, replyToId);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
messageId: result.messageId ?? "",
|
|||
|
|
error: result.error,
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
const message = error instanceof Error ? error.message : String(error);
|
|||
|
|
log.error("Failed to send media", { to, mediaUrl, error: message });
|
|||
|
|
return { messageId: "", error: message };
|
|||
|
|
}
|
|||
|
|
}
|