|
| 1 | +import type { |
| 2 | + ChannelMessagingAdapter, |
| 3 | + ChannelOutboundAdapter, |
| 4 | + ChannelPlugin, |
| 5 | +} from "../../channels/plugins/types.js"; |
| 6 | +import type { OpenClawConfig } from "../../config/config.js"; |
| 7 | +import { createTestRegistry } from "../../test-utils/channel-plugins.js"; |
| 8 | + |
| 9 | +function parseTelegramTargetForTest(raw: string): { |
| 10 | + chatId: string; |
| 11 | + messageThreadId?: number; |
| 12 | + chatType: "direct" | "group" | "unknown"; |
| 13 | +} { |
| 14 | + const trimmed = raw.trim(); |
| 15 | + const withoutPrefix = trimmed.replace(/^telegram:/i, "").trim(); |
| 16 | + const topicMatch = withoutPrefix.match(/^(.*):topic:(\d+)$/i); |
| 17 | + const chatId = topicMatch?.[1]?.trim() || withoutPrefix; |
| 18 | + const messageThreadId = topicMatch?.[2] ? Number.parseInt(topicMatch[2], 10) : undefined; |
| 19 | + const numericId = chatId.startsWith("-") ? chatId.slice(1) : chatId; |
| 20 | + const chatType = |
| 21 | + /^\d+$/.test(numericId) && !chatId.startsWith("-100") |
| 22 | + ? "direct" |
| 23 | + : chatId.startsWith("-") |
| 24 | + ? "group" |
| 25 | + : "unknown"; |
| 26 | + return { chatId, messageThreadId, chatType }; |
| 27 | +} |
| 28 | + |
| 29 | +function normalizeWhatsAppTargetForTest(raw: string): string | null { |
| 30 | + const trimmed = raw.trim().replace(/^whatsapp:/i, "").trim(); |
| 31 | + if (!trimmed) { |
| 32 | + return null; |
| 33 | + } |
| 34 | + const lowered = trimmed.toLowerCase(); |
| 35 | + if (/@g\.us$/u.test(lowered)) { |
| 36 | + const normalized = lowered.replace(/\s+/gu, ""); |
| 37 | + return /^\d+@g\.us$/u.test(normalized) ? normalized : null; |
| 38 | + } |
| 39 | + const digits = trimmed.replace(/\D/gu, ""); |
| 40 | + const normalized = digits ? `+${digits}` : ""; |
| 41 | + return /^\+\d{7,15}$/u.test(normalized) ? normalized : null; |
| 42 | +} |
| 43 | + |
| 44 | +function createWhatsAppResolveTarget( |
| 45 | + label = "WhatsApp", |
| 46 | +): ChannelOutboundAdapter["resolveTarget"] { |
| 47 | + return ({ to }) => { |
| 48 | + const normalized = to ? normalizeWhatsAppTargetForTest(to) : null; |
| 49 | + if (!normalized) { |
| 50 | + return { ok: false, error: new Error(`${label} target is required`) }; |
| 51 | + } |
| 52 | + return { ok: true, to: normalized }; |
| 53 | + }; |
| 54 | +} |
| 55 | + |
| 56 | +function createTelegramResolveTarget(label = "Telegram"): ChannelOutboundAdapter["resolveTarget"] { |
| 57 | + return ({ to }) => { |
| 58 | + const trimmed = to?.trim(); |
| 59 | + if (!trimmed) { |
| 60 | + return { ok: false, error: new Error(`${label} target is required`) }; |
| 61 | + } |
| 62 | + return { ok: true, to: parseTelegramTargetForTest(trimmed).chatId }; |
| 63 | + }; |
| 64 | +} |
| 65 | + |
| 66 | +export const telegramMessagingForTest: ChannelMessagingAdapter = { |
| 67 | + parseExplicitTarget: ({ raw }) => { |
| 68 | + const target = parseTelegramTargetForTest(raw); |
| 69 | + return { |
| 70 | + to: target.chatId, |
| 71 | + threadId: target.messageThreadId, |
| 72 | + chatType: target.chatType === "unknown" ? undefined : target.chatType, |
| 73 | + }; |
| 74 | + }, |
| 75 | + inferTargetChatType: ({ to }) => { |
| 76 | + const target = parseTelegramTargetForTest(to); |
| 77 | + return target.chatType === "unknown" ? undefined : target.chatType; |
| 78 | + }, |
| 79 | +}; |
| 80 | + |
| 81 | +export const whatsappMessagingForTest: ChannelMessagingAdapter = { |
| 82 | + inferTargetChatType: ({ to }) => { |
| 83 | + const normalized = normalizeWhatsAppTargetForTest(to); |
| 84 | + if (!normalized) { |
| 85 | + return undefined; |
| 86 | + } |
| 87 | + return normalized.endsWith("@g.us") ? "group" : "direct"; |
| 88 | + }, |
| 89 | + targetResolver: { |
| 90 | + hint: "<E.164|group JID>", |
| 91 | + }, |
| 92 | +}; |
| 93 | + |
| 94 | +export function createTestChannelPlugin(params: { |
| 95 | + id: ChannelPlugin["id"]; |
| 96 | + label?: string; |
| 97 | + outbound?: ChannelOutboundAdapter; |
| 98 | + messaging?: ChannelMessagingAdapter; |
| 99 | + resolveDefaultTo?: (params: { cfg: OpenClawConfig }) => string | undefined; |
| 100 | +}): ChannelPlugin { |
| 101 | + return { |
| 102 | + id: params.id, |
| 103 | + meta: { |
| 104 | + id: params.id, |
| 105 | + label: params.label ?? String(params.id), |
| 106 | + selectionLabel: params.label ?? String(params.id), |
| 107 | + docsPath: `/channels/${params.id}`, |
| 108 | + blurb: "test stub.", |
| 109 | + }, |
| 110 | + capabilities: { chatTypes: ["direct", "group"] }, |
| 111 | + config: { |
| 112 | + listAccountIds: () => [], |
| 113 | + resolveAccount: () => ({}), |
| 114 | + ...(params.resolveDefaultTo |
| 115 | + ? { |
| 116 | + resolveDefaultTo: params.resolveDefaultTo, |
| 117 | + } |
| 118 | + : {}), |
| 119 | + }, |
| 120 | + ...(params.outbound ? { outbound: params.outbound } : {}), |
| 121 | + ...(params.messaging ? { messaging: params.messaging } : {}), |
| 122 | + }; |
| 123 | +} |
| 124 | + |
| 125 | +export function createTelegramTestPlugin(): ChannelPlugin { |
| 126 | + return createTestChannelPlugin({ |
| 127 | + id: "telegram", |
| 128 | + label: "Telegram", |
| 129 | + outbound: { |
| 130 | + deliveryMode: "direct", |
| 131 | + sendText: async () => ({ channel: "telegram", messageId: "telegram-msg" }), |
| 132 | + resolveTarget: createTelegramResolveTarget(), |
| 133 | + }, |
| 134 | + messaging: telegramMessagingForTest, |
| 135 | + resolveDefaultTo: ({ cfg }) => |
| 136 | + typeof cfg.channels?.telegram?.defaultTo === "string" ? cfg.channels.telegram.defaultTo : undefined, |
| 137 | + }); |
| 138 | +} |
| 139 | + |
| 140 | +export function createWhatsAppTestPlugin(): ChannelPlugin { |
| 141 | + return createTestChannelPlugin({ |
| 142 | + id: "whatsapp", |
| 143 | + label: "WhatsApp", |
| 144 | + outbound: { |
| 145 | + deliveryMode: "direct", |
| 146 | + sendText: async () => ({ channel: "whatsapp", messageId: "whatsapp-msg" }), |
| 147 | + resolveTarget: createWhatsAppResolveTarget(), |
| 148 | + }, |
| 149 | + messaging: whatsappMessagingForTest, |
| 150 | + resolveDefaultTo: ({ cfg }) => |
| 151 | + typeof cfg.channels?.whatsapp?.defaultTo === "string" ? cfg.channels.whatsapp.defaultTo : undefined, |
| 152 | + }); |
| 153 | +} |
| 154 | + |
| 155 | +export function createNoopOutboundChannelPlugin( |
| 156 | + id: "discord" | "imessage" | "slack", |
| 157 | +): ChannelPlugin { |
| 158 | + return createTestChannelPlugin({ |
| 159 | + id, |
| 160 | + outbound: { |
| 161 | + deliveryMode: "direct", |
| 162 | + sendText: async () => ({ channel: id, messageId: `${id}-msg` }), |
| 163 | + }, |
| 164 | + }); |
| 165 | +} |
| 166 | + |
| 167 | +export function createTargetsTestRegistry( |
| 168 | + plugins: ChannelPlugin[] = [createWhatsAppTestPlugin(), createTelegramTestPlugin()], |
| 169 | +) { |
| 170 | + return createTestRegistry( |
| 171 | + plugins.map((plugin) => ({ |
| 172 | + pluginId: plugin.id, |
| 173 | + plugin, |
| 174 | + source: "test", |
| 175 | + })), |
| 176 | + ); |
| 177 | +} |
0 commit comments