feat: PIT Channel plugin v1.0.0 - complete implementation
This commit is contained in:
118
src/outbound.ts
Normal file
118
src/outbound.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user