UNPKG

agents

Version:

A home for your AI agents

198 lines (197 loc) • 7.93 kB
import { t as emptyIngressResponse } from "../../../internal-kmw70xwR.js"; import { t as matchesPath } from "../../../ingress-EFbrS547.js"; //#region src/experimental/channels/telegram/index.ts const DEFAULT_TELEGRAM_WEBHOOK_PATH = "/webhooks/telegram"; const INTERACTION_FOOTER = /\n\nReply YES to approve or NO to reject\.\n\n\[channel-interaction:v1:([A-Za-z0-9_-]+)\]$/; function channelIdentity(chatId) { return `telegram:chat:${chatId}`; } function userIdentity(userId) { return `telegram:user:${userId}`; } function messageIdentity(chatId, messageId) { return `${channelIdentity(chatId)}:message:${messageId}`; } function updateIdentity(chatId, updateId) { return `${channelIdentity(chatId)}:update:${updateId}`; } function decodeApprovalId(value) { try { const base64 = value.replace(/-/g, "+").replace(/_/g, "/"); const padding = "=".repeat((4 - base64.length % 4) % 4); const binary = atob(base64 + padding); const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0)); return new TextDecoder("utf-8", { fatal: true }).decode(bytes) || void 0; } catch { return; } } function asTelegramMessage(value) { return value !== null && typeof value === "object" ? value : void 0; } function telegramActor(message) { const sender = message.from; if (sender) { const fullName = [sender.first_name, sender.last_name].filter(Boolean).join(" "); return { id: userIdentity(sender.id), identity: { subject: `user:${sender.id}` }, ...sender.username && { username: sender.username }, ...fullName && { fullName }, isBot: sender.is_bot }; } const senderChat = message.sender_chat; if (!senderChat) return void 0; const fullName = senderChat.title ?? [senderChat.first_name, senderChat.last_name].filter(Boolean).join(" "); return { id: channelIdentity(senderChat.id), identity: { subject: `chat:${senderChat.id}` }, ...senderChat.username && { username: senderChat.username }, ...fullName && { fullName }, isBot: "unknown" }; } function chatLabel(chat) { return `Telegram · ${(chat.title ?? (chat.username ? `@${chat.username}` : void 0) ?? [chat.first_name, chat.last_name].filter(Boolean).join(" ") ?? String(chat.id)) || chat.id}`; } function eventContext(message, eventId, botUserId) { const actor = telegramActor(message); const channelId = channelIdentity(message.chat.id); return { eventId, thread: { id: typeof message.message_thread_id === "number" ? `${channelId}:topic:${message.message_thread_id}` : channelId, isDirectMessage: message.chat.type === "private" ? true : message.chat.type === "group" || message.chat.type === "supergroup" || message.chat.type === "channel" ? false : "unknown" }, replySurface: { version: 1, address: { chatId: String(message.chat.id), ...botUserId && { botUserId }, ...typeof message.message_thread_id === "number" && { messageThreadId: message.message_thread_id }, replyToMessageId: message.message_id }, label: chatLabel(message.chat) }, ...actor && { actor } }; } function telegramTimestamp(value) { if (typeof value !== "number" || !Number.isFinite(value)) return void 0; const date = /* @__PURE__ */ new Date(value * 1e3); return Number.isFinite(date.getTime()) ? date.toISOString() : void 0; } function normalizedMessage(message, eventId, edited, botUserId) { const reply = asTelegramMessage(message.reply_to_message); const sentAt = telegramTimestamp(message.date); const editedAt = telegramTimestamp(message.edit_date); return { type: "message", ...eventContext(message, eventId, botUserId), message: { id: messageIdentity(message.chat.id, message.message_id), text: message.text, ...typeof reply?.message_id === "number" && { reply: { id: messageIdentity(message.chat.id, reply.message_id), ...typeof reply.text === "string" && { text: reply.text } } }, ...(sentAt || edited || editedAt) && { metadata: { ...sentAt && { sentAt }, ...edited && { edited: true }, ...editedAt && { editedAt } } } } }; } function approvalResponse(message, eventId, botUserId) { if (botUserId === void 0) return void 0; if (message.text !== "YES" && message.text !== "NO") return void 0; const reply = asTelegramMessage(message.reply_to_message); if (typeof reply?.message_id !== "number") return void 0; const author = reply.from; if (author?.is_bot !== true || author.id !== botUserId) return void 0; const encodedApprovalId = typeof reply.text === "string" ? reply.text.match(INTERACTION_FOOTER)?.[1] : void 0; const approvalId = encodedApprovalId ? decodeApprovalId(encodedApprovalId) : void 0; if (!approvalId) return void 0; return { type: "approval-response", ...eventContext(message, eventId, botUserId), approvalId, decision: message.text === "YES" ? "approve" : "reject", reference: messageIdentity(message.chat.id, message.message_id) }; } function inboundEvent(update, botUserId) { const edited = update.edited_message !== void 0; const message = asTelegramMessage(edited ? update.edited_message : update.message); if (typeof update.update_id !== "number" || typeof message?.message_id !== "number" || typeof message.text !== "string" || typeof message.chat?.id !== "number") return; const eventId = updateIdentity(message.chat.id, update.update_id); return approvalResponse(message, eventId, botUserId) ?? normalizedMessage(message, eventId, edited, botUserId); } /** Recover this bot's numeric user id from its BotFather token prefix. */ function telegramBotUserId(botToken) { const prefix = botToken.match(/^(\d+):/)?.[1]; if (!prefix) return void 0; const botUserId = Number(prefix); return Number.isSafeInteger(botUserId) && botUserId > 0 ? botUserId : void 0; } /** Create dependency-free Telegram webhook ingress for a destination chat. */ function telegramWebhook(options) { if (!options.secretToken.trim()) throw new Error("secretToken is required to create Telegram webhook ingress"); if (options.botUserId !== void 0 && (!Number.isSafeInteger(options.botUserId) || options.botUserId <= 0)) throw new Error("botUserId must be a positive Telegram user id"); const botUserId = options.botUserId; const path = options.path ?? DEFAULT_TELEGRAM_WEBHOOK_PATH; return { async receive(request) { if (!matchesPath(request, path)) return null; if (request.method !== "POST") return emptyIngressResponse(405); if (request.headers.get("x-telegram-bot-api-secret-token") !== options.secretToken) return emptyIngressResponse(401); let update; try { update = await request.json(); } catch { return emptyIngressResponse(400); } if (update === null || typeof update !== "object") return emptyIngressResponse(400); const raw = update; const event = inboundEvent(raw, botUserId); return { events: event ? [{ event, raw }] : [], response: new Response(null, { status: 200 }) }; } }; } /** Create a configured Telegram ingress Channel. */ function telegram(options) { if (!options.botToken.trim()) throw new Error("botToken is required to create a Telegram channel"); if (options.webhook && !options.participant) throw new Error("participant is required with a Telegram webhook"); const botUserId = telegramBotUserId(options.botToken); const ingress = options.webhook ? telegramWebhook({ ...options.webhook, botUserId }) : void 0; return { ...options.participant && { participant: options.participant }, ...options.route && { route: options.route }, ...ingress && { ingress }, contactSurface(identity) { if ((identity.scope ?? "default") !== "default") return null; const match = identity.subject.match(/^(user|chat):(-?\d+)$/); if (!match) return null; return { version: 1, address: { chatId: match[2], ...botUserId && { botUserId } }, label: `Telegram · ${match[1]} ${match[2]}` }; } }; } //#endregion export { telegram, telegramWebhook }; //# sourceMappingURL=index.js.map