@diegoaltoworks/chatter 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -117,6 +117,23 @@ export interface TelegramApi {
117
117
  }): Promise<void>;
118
118
  sendMedia(chatId: string, payload: unknown): Promise<void>;
119
119
  setMessageReaction(chatId: string, messageId: number, emoji: string): Promise<void>;
120
+ /**
121
+ * Registers `url` as this bot's webhook, switching Telegram from queuing
122
+ * updates for `getUpdates` to POSTing them there instead. `secretToken`,
123
+ * when given, is echoed back on every webhook POST as
124
+ * `X-Telegram-Bot-Api-Secret-Token` — see `./webhook`'s fail-closed check.
125
+ */
126
+ setWebhook(url: string, options?: {
127
+ secretToken?: string;
128
+ }): Promise<void>;
129
+ /**
130
+ * Clears the registered webhook so `getUpdates` starts working again: a
131
+ * registered webhook suppresses long-polling entirely, so switching a bot
132
+ * from webhook mode back to `createTelegramChannel` needs this called
133
+ * first — the channel itself never calls it, since it has no way to know
134
+ * a webhook was ever registered.
135
+ */
136
+ deleteWebhook(): Promise<void>;
120
137
  }
121
138
  export declare function createTelegramApi(config: TelegramApiConfig): TelegramApi;
122
139
  //# sourceMappingURL=api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/api.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,10 @@
10
10
  * per bot token. It is deliberately self-contained — `start(deps)` has
11
11
  * everything it needs, so there is no `customRoutes` wiring step (contrast
12
12
  * `./channels/whatsapp/inbound.ts`, whose transport and interpretation are
13
- * separate modules).
13
+ * separate modules). `./webhook` is the alternative transport for hosts that
14
+ * would rather receive updates over HTTPS than long-poll; the two share
15
+ * their update-handling logic via `./handler` so gate behaviour never
16
+ * diverges between them — see docs/telegram.md on choosing a mode.
14
17
  *
15
18
  * One caveat worth knowing before choosing this over a user-mode client: a bot
16
19
  * cannot start a conversation (the user must message it first), and in groups
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,OAAO,EAAqB,KAAK,WAAW,EAAuB,MAAM,OAAO,CAAC;AAOjF,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,8GAA8G;IAC9G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,0IAA0I;IAC1I,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iJAAiJ;IACjJ,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kIAAkI;IAClI,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kIAAkI;IAClI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4GAA4G;IAC5G,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,kFAAkF;IAClF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf;;;;WAIG;QACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACpE,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gHAAgH;IAChH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,kBAAkB;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0GAA0G;IAC1G,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,mGAAmG;IACnG,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qJAAqJ;IACrJ,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,uGAAuG;IACvG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAoI5E"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The per-update handling logic shared by both Telegram transports: the
3
+ * long-poll loop (`./channel`, via `./poll`) and the webhook route
4
+ * (`./webhook`). Kept in one place so addressing, allowlist logging and
5
+ * pipeline dispatch are defined exactly once — a second copy would drift the
6
+ * moment one transport's gate behaviour changed and the other didn't.
7
+ */
8
+ import type { Logger } from "../../core/logger";
9
+ import type { InboundPipeline } from "../pipeline";
10
+ import type { ChannelSender } from "../senders";
11
+ import type { TelegramApi, TelegramUpdate } from "./api";
12
+ import { type TelegramBotIdentity } from "./updates";
13
+ export interface TelegramUpdateHandlerDeps {
14
+ api: TelegramApi;
15
+ me: TelegramBotIdentity;
16
+ pipeline: InboundPipeline;
17
+ /** Group chats eligible for a reply. Empty = every group. */
18
+ allowedChats: string[];
19
+ logger: Logger;
20
+ /** Log-line prefix, e.g. `Telegram[mybot]`. */
21
+ label: string;
22
+ }
23
+ /**
24
+ * One update -> one pipeline turn. Builds its own allowlist-log dedup set, so
25
+ * each transport instance gets its own "skipped chat" throttling rather than
26
+ * sharing (or fighting over) state with another.
27
+ */
28
+ export declare function createTelegramUpdateHandler(deps: TelegramUpdateHandlerDeps): (update: TelegramUpdate) => Promise<void>;
29
+ /** The `ChannelSender` every Telegram transport registers — text, media and reactions over the same `TelegramApi`, regardless of how updates arrive. */
30
+ export declare function createTelegramSender(api: TelegramApi): ChannelSender;
31
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,KAAK,mBAAmB,EAAuC,MAAM,WAAW,CAAC;AAE1F,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,WAAW,CAAC;IACjB,EAAE,EAAE,mBAAmB,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;IAC1B,6DAA6D;IAC7D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,yBAAyB,GAC9B,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CA8B3C;AAED,wJAAwJ;AACxJ,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAOpE"}
@@ -17,10 +17,17 @@
17
17
  * docs/telegram.md for configuration and docs/build-a-channel.md for the SPI
18
18
  * this implements.
19
19
  *
20
+ * `createTelegramWebhookRoute` is the alternative to `createTelegramChannel`:
21
+ * updates over an HTTPS webhook instead of long-polling, mounted via
22
+ * `ChatterConfig.customRoutes` — see docs/telegram.md for choosing between
23
+ * the two.
24
+ *
20
25
  * @packageDocumentation
21
26
  */
22
27
  export { createTelegramApi, redactToken, splitTelegramText, TELEGRAM_API_BASE, TELEGRAM_TEXT_LIMIT, type TelegramApi, type TelegramApiConfig, TelegramApiError, type TelegramChat, type TelegramMediaPayload, type TelegramMessage, type TelegramMessageEntity, type TelegramUpdate, type TelegramUser, toTelegramMediaRequest, } from "./api";
23
28
  export { createTelegramChannel, type TelegramChannelConfig } from "./channel";
29
+ export { createTelegramSender, createTelegramUpdateHandler, type TelegramUpdateHandlerDeps, } from "./handler";
24
30
  export { type LongPollDeps, pollBackoffMs, retryDelayMs, runLongPoll } from "./poll";
25
31
  export { mentionsBot, messageEntities, messageText, nextOffset, type TelegramBotIdentity, telegramSenderKey, toChannelMessage, } from "./updates";
32
+ export { createTelegramWebhookRoute, type TelegramWebhookConfig } from "./webhook";
26
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,sBAAsB,GACvB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC9E,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrF,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,sBAAsB,GACvB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,qBAAqB,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC9E,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,yBAAyB,GAC/B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrF,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,0BAA0B,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC"}
@@ -25,6 +25,9 @@ __export(index_exports, {
25
25
  TelegramApiError: () => TelegramApiError,
26
26
  createTelegramApi: () => createTelegramApi,
27
27
  createTelegramChannel: () => createTelegramChannel,
28
+ createTelegramSender: () => createTelegramSender,
29
+ createTelegramUpdateHandler: () => createTelegramUpdateHandler,
30
+ createTelegramWebhookRoute: () => createTelegramWebhookRoute,
28
31
  mentionsBot: () => mentionsBot,
29
32
  messageEntities: () => messageEntities,
30
33
  messageText: () => messageText,
@@ -188,6 +191,17 @@ function createTelegramApi(config) {
188
191
  message_id: messageId,
189
192
  reaction: [{ type: "emoji", emoji }]
190
193
  });
194
+ },
195
+ async setWebhook(url, options) {
196
+ await call("setWebhook", {
197
+ url,
198
+ // Only what this channel interprets — see the matching comment on getUpdates.
199
+ allowed_updates: ["message"],
200
+ ...options?.secretToken ? { secret_token: options.secretToken } : {}
201
+ });
202
+ },
203
+ async deleteWebhook() {
204
+ await call("deleteWebhook");
191
205
  }
192
206
  };
193
207
  }
@@ -208,56 +222,6 @@ function createConsoleLogger(level = "info") {
208
222
  };
209
223
  }
210
224
 
211
- // src/channels/gates.ts
212
- function matches(re, text) {
213
- return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
214
- }
215
- function isBlockedByAllowlist(msg, config) {
216
- return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
217
- }
218
- function decideChannelAction(msg, config) {
219
- if (msg.fromBot || !msg.text.trim()) return "ignore";
220
- if (!msg.isDirectMessage) {
221
- if (isBlockedByAllowlist(msg, config)) return "ignore";
222
- if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
223
- if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
224
- if (config.mutedChats.has(msg.chatId)) return "ignore";
225
- return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
226
- }
227
- return "reply";
228
- }
229
- function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
230
- const log = /* @__PURE__ */ new Map();
231
- let lastSweep = now();
232
- function sweep(current) {
233
- if (current - lastSweep < windowMs) return;
234
- lastSweep = current;
235
- const cutoff = current - windowMs;
236
- for (const [key, entries] of log) {
237
- if (!entries.some((t) => t > cutoff)) log.delete(key);
238
- }
239
- }
240
- function allow(key) {
241
- const current = now();
242
- sweep(current);
243
- const cutoff = current - windowMs;
244
- const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
245
- if (entries.length >= maxPerWindow) {
246
- if (entries.length > 0) log.set(key, entries);
247
- else log.delete(key);
248
- return false;
249
- }
250
- entries.push(current);
251
- log.set(key, entries);
252
- return true;
253
- }
254
- allow.size = () => log.size;
255
- return allow;
256
- }
257
- function underReplyRateLimit(msg, limiters) {
258
- return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
259
- }
260
-
261
225
  // src/core/guardrails.ts
262
226
  function detectLeakage(text) {
263
227
  const flags = [
@@ -429,6 +393,56 @@ ${ctx.join("\n\n")}`
429
393
  return { system, messages, context: ctx };
430
394
  }
431
395
 
396
+ // src/channels/gates.ts
397
+ function matches(re, text) {
398
+ return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
399
+ }
400
+ function isBlockedByAllowlist(msg, config) {
401
+ return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
402
+ }
403
+ function decideChannelAction(msg, config) {
404
+ if (msg.fromBot || !msg.text.trim()) return "ignore";
405
+ if (!msg.isDirectMessage) {
406
+ if (isBlockedByAllowlist(msg, config)) return "ignore";
407
+ if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
408
+ if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
409
+ if (config.mutedChats.has(msg.chatId)) return "ignore";
410
+ return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
411
+ }
412
+ return "reply";
413
+ }
414
+ function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
415
+ const log = /* @__PURE__ */ new Map();
416
+ let lastSweep = now();
417
+ function sweep(current) {
418
+ if (current - lastSweep < windowMs) return;
419
+ lastSweep = current;
420
+ const cutoff = current - windowMs;
421
+ for (const [key, entries] of log) {
422
+ if (!entries.some((t) => t > cutoff)) log.delete(key);
423
+ }
424
+ }
425
+ function allow(key) {
426
+ const current = now();
427
+ sweep(current);
428
+ const cutoff = current - windowMs;
429
+ const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
430
+ if (entries.length >= maxPerWindow) {
431
+ if (entries.length > 0) log.set(key, entries);
432
+ else log.delete(key);
433
+ return false;
434
+ }
435
+ entries.push(current);
436
+ log.set(key, entries);
437
+ return true;
438
+ }
439
+ allow.size = () => log.size;
440
+ return allow;
441
+ }
442
+ function underReplyRateLimit(msg, limiters) {
443
+ return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
444
+ }
445
+
432
446
  // src/channels/pipeline.ts
433
447
  var DEFAULT_HISTORY_LIMIT = 20;
434
448
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
@@ -581,6 +595,41 @@ function telegramSenderKey(senderId) {
581
595
  return `tg:${senderId}`;
582
596
  }
583
597
 
598
+ // src/channels/telegram/handler.ts
599
+ function createTelegramUpdateHandler(deps) {
600
+ const { api, me, pipeline, allowedChats, logger, label } = deps;
601
+ const loggedUnallowedChats = /* @__PURE__ */ new Set();
602
+ return async function handleUpdate(update) {
603
+ const message = update.message;
604
+ const msg = toChannelMessage(update, me);
605
+ if (!msg || !message) return;
606
+ if (isBlockedByAllowlist(msg, { allowedChats })) {
607
+ if (!loggedUnallowedChats.has(msg.chatId)) {
608
+ loggedUnallowedChats.add(msg.chatId);
609
+ logger.warn(`${label}: skipped chat ${msg.chatId} - not in allowedChats`);
610
+ }
611
+ }
612
+ const reply = {
613
+ // Threaded onto the incoming message: in a busy group an untethered
614
+ // answer reads as a non-sequitur.
615
+ sendAnswer: (chatId, text) => api.sendMessage(chatId, text, { replyToMessageId: message.message_id }),
616
+ sendGateReply: (chatId, text) => api.sendMessage(chatId, text)
617
+ };
618
+ await pipeline(msg, {
619
+ reply,
620
+ conversationId: msg.chatId,
621
+ sender: telegramSenderKey(msg.senderId)
622
+ });
623
+ };
624
+ }
625
+ function createTelegramSender(api) {
626
+ return {
627
+ sendText: (chatId, text) => api.sendMessage(chatId, text),
628
+ sendMedia: (chatId, payload) => api.sendMedia(chatId, payload),
629
+ sendReaction: (chatId, messageRef, emoji) => api.setMessageReaction(chatId, Number(messageRef), emoji)
630
+ };
631
+ }
632
+
584
633
  // src/channels/telegram/poll.ts
585
634
  var BASE_DELAY_MS = 1e3;
586
635
  var MAX_DELAY_MS = 6e4;
@@ -651,12 +700,7 @@ function createTelegramChannel(config) {
651
700
  });
652
701
  const me = await api.getMe();
653
702
  const label = `Telegram[${me.username ?? me.id}]`;
654
- const sender = {
655
- sendText: (chatId, text) => api.sendMessage(chatId, text),
656
- sendMedia: (chatId, payload) => api.sendMedia(chatId, payload),
657
- sendReaction: (chatId, messageRef, emoji) => api.setMessageReaction(chatId, Number(messageRef), emoji)
658
- };
659
- deps.senders.register(channelName, sender);
703
+ deps.senders.register(channelName, createTelegramSender(api));
660
704
  senders = deps.senders;
661
705
  registered = true;
662
706
  const pipeline = createInboundPipeline(
@@ -683,29 +727,14 @@ function createTelegramChannel(config) {
683
727
  logger: log
684
728
  }
685
729
  );
686
- const loggedUnallowedChats = /* @__PURE__ */ new Set();
687
- async function handleUpdate(update) {
688
- const message = update.message;
689
- const msg = toChannelMessage(update, me);
690
- if (!msg || !message) return;
691
- if (isBlockedByAllowlist(msg, { allowedChats })) {
692
- if (!loggedUnallowedChats.has(msg.chatId)) {
693
- loggedUnallowedChats.add(msg.chatId);
694
- log.warn(`${label}: skipped chat ${msg.chatId} - not in allowedChats`);
695
- }
696
- }
697
- const reply = {
698
- // Threaded onto the incoming message: in a busy group an untethered
699
- // answer reads as a non-sequitur.
700
- sendAnswer: (chatId, text) => api.sendMessage(chatId, text, { replyToMessageId: message.message_id }),
701
- sendGateReply: (chatId, text) => api.sendMessage(chatId, text)
702
- };
703
- await pipeline(msg, {
704
- reply,
705
- conversationId: msg.chatId,
706
- sender: telegramSenderKey(msg.senderId)
707
- });
708
- }
730
+ const handleUpdate = createTelegramUpdateHandler({
731
+ api,
732
+ me,
733
+ pipeline,
734
+ allowedChats,
735
+ logger: log,
736
+ label
737
+ });
709
738
  abort = new AbortController();
710
739
  const signal = abort.signal;
711
740
  void runLongPoll({
@@ -730,6 +759,196 @@ function createTelegramChannel(config) {
730
759
  }
731
760
  };
732
761
  }
762
+
763
+ // src/channels/telegram/webhook.ts
764
+ var import_node_crypto = require("node:crypto");
765
+
766
+ // node_modules/hono/dist/http-exception.js
767
+ var HTTPException = class extends Error {
768
+ res;
769
+ status;
770
+ /**
771
+ * Creates an instance of `HTTPException`.
772
+ * @param status - HTTP status code for the exception. Defaults to 500.
773
+ * @param options - Additional options for the exception.
774
+ */
775
+ constructor(status = 500, options) {
776
+ super(options?.message, { cause: options?.cause });
777
+ this.res = options?.res;
778
+ this.status = status;
779
+ }
780
+ /**
781
+ * Returns the response object associated with the exception.
782
+ * If a response object is not provided, a new response is created with the error message and status code.
783
+ * @returns The response object.
784
+ */
785
+ getResponse() {
786
+ if (this.res) {
787
+ const newResponse = new Response(this.res.body, {
788
+ status: this.status,
789
+ headers: this.res.headers
790
+ });
791
+ return newResponse;
792
+ }
793
+ return new Response(this.message, {
794
+ status: this.status
795
+ });
796
+ }
797
+ };
798
+
799
+ // node_modules/hono/dist/middleware/body-limit/index.js
800
+ var ERROR_MESSAGE = "Payload Too Large";
801
+ var bodyLimit = (options) => {
802
+ const onError = options.onError || (() => {
803
+ const res = new Response(ERROR_MESSAGE, {
804
+ status: 413
805
+ });
806
+ throw new HTTPException(413, { res });
807
+ });
808
+ const maxSize = options.maxSize;
809
+ return async function bodyLimit2(c, next) {
810
+ if (!c.req.raw.body) {
811
+ return next();
812
+ }
813
+ const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
814
+ const hasContentLength = c.req.raw.headers.has("content-length");
815
+ if (hasContentLength && !hasTransferEncoding) {
816
+ const contentLength = parseInt(c.req.raw.headers.get("content-length") || "0", 10);
817
+ return contentLength > maxSize ? onError(c) : next();
818
+ }
819
+ let size = 0;
820
+ const chunks = [];
821
+ const rawReader = c.req.raw.body.getReader();
822
+ for (; ; ) {
823
+ const { done, value } = await rawReader.read();
824
+ if (done) {
825
+ break;
826
+ }
827
+ size += value.length;
828
+ if (size > maxSize) {
829
+ return onError(c);
830
+ }
831
+ chunks.push(value);
832
+ }
833
+ const requestInit = {
834
+ body: new ReadableStream({
835
+ start(controller) {
836
+ for (const chunk of chunks) {
837
+ controller.enqueue(chunk);
838
+ }
839
+ controller.close();
840
+ }
841
+ }),
842
+ duplex: "half"
843
+ };
844
+ c.req.raw = new Request(c.req.raw, requestInit);
845
+ return next();
846
+ };
847
+ };
848
+
849
+ // src/middleware/bodyLimit.ts
850
+ var DEFAULT_MAX_REQUEST_BYTES = 262144;
851
+ function chatBodyLimit(maxSize = DEFAULT_MAX_REQUEST_BYTES, toError = (message) => ({ error: message })) {
852
+ return bodyLimit({
853
+ maxSize,
854
+ onError: (c) => c.json(toError("Request body too large"), 413)
855
+ });
856
+ }
857
+
858
+ // src/channels/telegram/webhook.ts
859
+ var SECRET_TOKEN_HEADER = "x-telegram-bot-api-secret-token";
860
+ function secureCompare(a, b) {
861
+ const bufA = Buffer.from(a);
862
+ const bufB = Buffer.from(b);
863
+ return bufA.length === bufB.length && (0, import_node_crypto.timingSafeEqual)(bufA, bufB);
864
+ }
865
+ var SECRET_TOKEN_PATTERN = /^[A-Za-z0-9_-]{1,256}$/;
866
+ function createTelegramWebhookRoute(config) {
867
+ const webhookSecret = config.webhookSecret;
868
+ if (!webhookSecret?.trim()) {
869
+ throw new Error("Telegram webhook requires webhookSecret (it fails closed without one)");
870
+ }
871
+ if (!SECRET_TOKEN_PATTERN.test(webhookSecret)) {
872
+ throw new Error(
873
+ "Telegram webhookSecret must match /^[A-Za-z0-9_-]{1,256}$/ (Telegram's own secret_token charset)"
874
+ );
875
+ }
876
+ const channelName = config.name ?? "telegram";
877
+ const allowedChats = config.allowedChats ?? [];
878
+ const path = config.path ?? "/webhooks/telegram";
879
+ return async (app, deps) => {
880
+ const log = config.logger ?? deps.logger ?? createConsoleLogger();
881
+ const api = config.api ?? createTelegramApi({
882
+ botToken: config.botToken,
883
+ fetch: config.fetch,
884
+ baseUrl: config.apiBaseUrl
885
+ });
886
+ const me = await api.getMe();
887
+ const label = `Telegram[${me.username ?? me.id}] (webhook)`;
888
+ deps.senders.register(channelName, createTelegramSender(api));
889
+ const pipeline = createInboundPipeline(
890
+ { client: deps.client, store: deps.store, prompts: deps.prompts },
891
+ {
892
+ channel: channelName,
893
+ answerFn: config.answerFn ?? deps.config.answerFn,
894
+ bucketsFor: config.bucketsFor ?? deps.config.bucketsFor,
895
+ rewriteQuery: config.rewriteQuery ?? deps.config.rewriteQuery,
896
+ rerankContext: config.rerankContext ?? deps.config.rerankContext,
897
+ transformReply: config.transformReply ?? deps.config.transformReply,
898
+ model: config.model,
899
+ channelHint: config.channelHint ?? "Channel: Telegram.",
900
+ personaResolver: config.personaResolver,
901
+ history: config.history,
902
+ allowedChats,
903
+ muteRegex: config.muteRegex,
904
+ unmuteRegex: config.unmuteRegex,
905
+ muteReply: config.muteReply,
906
+ unmuteReply: config.unmuteReply,
907
+ dmRateLimit: config.dmRateLimit,
908
+ groupRateLimit: config.groupRateLimit,
909
+ now: config.now,
910
+ logger: log
911
+ }
912
+ );
913
+ const handleUpdate = createTelegramUpdateHandler({
914
+ api,
915
+ me,
916
+ pipeline,
917
+ allowedChats,
918
+ logger: log,
919
+ label
920
+ });
921
+ app.post(
922
+ path,
923
+ chatBodyLimit(deps.config.server?.maxRequestBytes),
924
+ // Reject oversized bodies first
925
+ async (c) => {
926
+ const provided = c.req.header(SECRET_TOKEN_HEADER);
927
+ if (!provided || !secureCompare(provided, webhookSecret)) {
928
+ log.warn(`${label}: rejected webhook POST - missing or invalid secret token`);
929
+ return c.text("", 403);
930
+ }
931
+ let body;
932
+ try {
933
+ body = await c.req.json();
934
+ } catch {
935
+ return c.text("", 400);
936
+ }
937
+ if (typeof body !== "object" || body === null) {
938
+ return c.text("", 400);
939
+ }
940
+ const update = body;
941
+ try {
942
+ await handleUpdate(update);
943
+ } catch (error) {
944
+ log.warn(`${label}: update handling failed:`, error);
945
+ }
946
+ return c.text("", 200);
947
+ }
948
+ );
949
+ log.info(`${label}: webhook mounted at ${path} as @${me.username ?? me.id}`);
950
+ };
951
+ }
733
952
  // Annotate the CommonJS export names for ESM import in node:
734
953
  0 && (module.exports = {
735
954
  TELEGRAM_API_BASE,
@@ -737,6 +956,9 @@ function createTelegramChannel(config) {
737
956
  TelegramApiError,
738
957
  createTelegramApi,
739
958
  createTelegramChannel,
959
+ createTelegramSender,
960
+ createTelegramUpdateHandler,
961
+ createTelegramWebhookRoute,
740
962
  mentionsBot,
741
963
  messageEntities,
742
964
  messageText,
@@ -146,6 +146,17 @@ function createTelegramApi(config) {
146
146
  message_id: messageId,
147
147
  reaction: [{ type: "emoji", emoji }]
148
148
  });
149
+ },
150
+ async setWebhook(url, options) {
151
+ await call("setWebhook", {
152
+ url,
153
+ // Only what this channel interprets — see the matching comment on getUpdates.
154
+ allowed_updates: ["message"],
155
+ ...options?.secretToken ? { secret_token: options.secretToken } : {}
156
+ });
157
+ },
158
+ async deleteWebhook() {
159
+ await call("deleteWebhook");
149
160
  }
150
161
  };
151
162
  }
@@ -166,56 +177,6 @@ function createConsoleLogger(level = "info") {
166
177
  };
167
178
  }
168
179
 
169
- // src/channels/gates.ts
170
- function matches(re, text) {
171
- return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
172
- }
173
- function isBlockedByAllowlist(msg, config) {
174
- return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
175
- }
176
- function decideChannelAction(msg, config) {
177
- if (msg.fromBot || !msg.text.trim()) return "ignore";
178
- if (!msg.isDirectMessage) {
179
- if (isBlockedByAllowlist(msg, config)) return "ignore";
180
- if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
181
- if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
182
- if (config.mutedChats.has(msg.chatId)) return "ignore";
183
- return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
184
- }
185
- return "reply";
186
- }
187
- function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
188
- const log = /* @__PURE__ */ new Map();
189
- let lastSweep = now();
190
- function sweep(current) {
191
- if (current - lastSweep < windowMs) return;
192
- lastSweep = current;
193
- const cutoff = current - windowMs;
194
- for (const [key, entries] of log) {
195
- if (!entries.some((t) => t > cutoff)) log.delete(key);
196
- }
197
- }
198
- function allow(key) {
199
- const current = now();
200
- sweep(current);
201
- const cutoff = current - windowMs;
202
- const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
203
- if (entries.length >= maxPerWindow) {
204
- if (entries.length > 0) log.set(key, entries);
205
- else log.delete(key);
206
- return false;
207
- }
208
- entries.push(current);
209
- log.set(key, entries);
210
- return true;
211
- }
212
- allow.size = () => log.size;
213
- return allow;
214
- }
215
- function underReplyRateLimit(msg, limiters) {
216
- return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
217
- }
218
-
219
180
  // src/core/guardrails.ts
220
181
  function detectLeakage(text) {
221
182
  const flags = [
@@ -387,6 +348,56 @@ ${ctx.join("\n\n")}`
387
348
  return { system, messages, context: ctx };
388
349
  }
389
350
 
351
+ // src/channels/gates.ts
352
+ function matches(re, text) {
353
+ return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
354
+ }
355
+ function isBlockedByAllowlist(msg, config) {
356
+ return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
357
+ }
358
+ function decideChannelAction(msg, config) {
359
+ if (msg.fromBot || !msg.text.trim()) return "ignore";
360
+ if (!msg.isDirectMessage) {
361
+ if (isBlockedByAllowlist(msg, config)) return "ignore";
362
+ if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
363
+ if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
364
+ if (config.mutedChats.has(msg.chatId)) return "ignore";
365
+ return msg.mentionsBot || msg.isReplyToBot ? "reply" : "ignore";
366
+ }
367
+ return "reply";
368
+ }
369
+ function createSlidingWindowRateLimiter(maxPerWindow, windowMs, now = Date.now) {
370
+ const log = /* @__PURE__ */ new Map();
371
+ let lastSweep = now();
372
+ function sweep(current) {
373
+ if (current - lastSweep < windowMs) return;
374
+ lastSweep = current;
375
+ const cutoff = current - windowMs;
376
+ for (const [key, entries] of log) {
377
+ if (!entries.some((t) => t > cutoff)) log.delete(key);
378
+ }
379
+ }
380
+ function allow(key) {
381
+ const current = now();
382
+ sweep(current);
383
+ const cutoff = current - windowMs;
384
+ const entries = (log.get(key) ?? []).filter((t) => t > cutoff);
385
+ if (entries.length >= maxPerWindow) {
386
+ if (entries.length > 0) log.set(key, entries);
387
+ else log.delete(key);
388
+ return false;
389
+ }
390
+ entries.push(current);
391
+ log.set(key, entries);
392
+ return true;
393
+ }
394
+ allow.size = () => log.size;
395
+ return allow;
396
+ }
397
+ function underReplyRateLimit(msg, limiters) {
398
+ return msg.isDirectMessage ? limiters.dm(msg.chatId) : limiters.group(msg.chatId);
399
+ }
400
+
390
401
  // src/channels/pipeline.ts
391
402
  var DEFAULT_HISTORY_LIMIT = 20;
392
403
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
@@ -539,6 +550,41 @@ function telegramSenderKey(senderId) {
539
550
  return `tg:${senderId}`;
540
551
  }
541
552
 
553
+ // src/channels/telegram/handler.ts
554
+ function createTelegramUpdateHandler(deps) {
555
+ const { api, me, pipeline, allowedChats, logger, label } = deps;
556
+ const loggedUnallowedChats = /* @__PURE__ */ new Set();
557
+ return async function handleUpdate(update) {
558
+ const message = update.message;
559
+ const msg = toChannelMessage(update, me);
560
+ if (!msg || !message) return;
561
+ if (isBlockedByAllowlist(msg, { allowedChats })) {
562
+ if (!loggedUnallowedChats.has(msg.chatId)) {
563
+ loggedUnallowedChats.add(msg.chatId);
564
+ logger.warn(`${label}: skipped chat ${msg.chatId} - not in allowedChats`);
565
+ }
566
+ }
567
+ const reply = {
568
+ // Threaded onto the incoming message: in a busy group an untethered
569
+ // answer reads as a non-sequitur.
570
+ sendAnswer: (chatId, text) => api.sendMessage(chatId, text, { replyToMessageId: message.message_id }),
571
+ sendGateReply: (chatId, text) => api.sendMessage(chatId, text)
572
+ };
573
+ await pipeline(msg, {
574
+ reply,
575
+ conversationId: msg.chatId,
576
+ sender: telegramSenderKey(msg.senderId)
577
+ });
578
+ };
579
+ }
580
+ function createTelegramSender(api) {
581
+ return {
582
+ sendText: (chatId, text) => api.sendMessage(chatId, text),
583
+ sendMedia: (chatId, payload) => api.sendMedia(chatId, payload),
584
+ sendReaction: (chatId, messageRef, emoji) => api.setMessageReaction(chatId, Number(messageRef), emoji)
585
+ };
586
+ }
587
+
542
588
  // src/channels/telegram/poll.ts
543
589
  var BASE_DELAY_MS = 1e3;
544
590
  var MAX_DELAY_MS = 6e4;
@@ -609,12 +655,7 @@ function createTelegramChannel(config) {
609
655
  });
610
656
  const me = await api.getMe();
611
657
  const label = `Telegram[${me.username ?? me.id}]`;
612
- const sender = {
613
- sendText: (chatId, text) => api.sendMessage(chatId, text),
614
- sendMedia: (chatId, payload) => api.sendMedia(chatId, payload),
615
- sendReaction: (chatId, messageRef, emoji) => api.setMessageReaction(chatId, Number(messageRef), emoji)
616
- };
617
- deps.senders.register(channelName, sender);
658
+ deps.senders.register(channelName, createTelegramSender(api));
618
659
  senders = deps.senders;
619
660
  registered = true;
620
661
  const pipeline = createInboundPipeline(
@@ -641,29 +682,14 @@ function createTelegramChannel(config) {
641
682
  logger: log
642
683
  }
643
684
  );
644
- const loggedUnallowedChats = /* @__PURE__ */ new Set();
645
- async function handleUpdate(update) {
646
- const message = update.message;
647
- const msg = toChannelMessage(update, me);
648
- if (!msg || !message) return;
649
- if (isBlockedByAllowlist(msg, { allowedChats })) {
650
- if (!loggedUnallowedChats.has(msg.chatId)) {
651
- loggedUnallowedChats.add(msg.chatId);
652
- log.warn(`${label}: skipped chat ${msg.chatId} - not in allowedChats`);
653
- }
654
- }
655
- const reply = {
656
- // Threaded onto the incoming message: in a busy group an untethered
657
- // answer reads as a non-sequitur.
658
- sendAnswer: (chatId, text) => api.sendMessage(chatId, text, { replyToMessageId: message.message_id }),
659
- sendGateReply: (chatId, text) => api.sendMessage(chatId, text)
660
- };
661
- await pipeline(msg, {
662
- reply,
663
- conversationId: msg.chatId,
664
- sender: telegramSenderKey(msg.senderId)
665
- });
666
- }
685
+ const handleUpdate = createTelegramUpdateHandler({
686
+ api,
687
+ me,
688
+ pipeline,
689
+ allowedChats,
690
+ logger: log,
691
+ label
692
+ });
667
693
  abort = new AbortController();
668
694
  const signal = abort.signal;
669
695
  void runLongPoll({
@@ -688,12 +714,205 @@ function createTelegramChannel(config) {
688
714
  }
689
715
  };
690
716
  }
717
+
718
+ // src/channels/telegram/webhook.ts
719
+ import { timingSafeEqual } from "node:crypto";
720
+
721
+ // node_modules/hono/dist/http-exception.js
722
+ var HTTPException = class extends Error {
723
+ res;
724
+ status;
725
+ /**
726
+ * Creates an instance of `HTTPException`.
727
+ * @param status - HTTP status code for the exception. Defaults to 500.
728
+ * @param options - Additional options for the exception.
729
+ */
730
+ constructor(status = 500, options) {
731
+ super(options?.message, { cause: options?.cause });
732
+ this.res = options?.res;
733
+ this.status = status;
734
+ }
735
+ /**
736
+ * Returns the response object associated with the exception.
737
+ * If a response object is not provided, a new response is created with the error message and status code.
738
+ * @returns The response object.
739
+ */
740
+ getResponse() {
741
+ if (this.res) {
742
+ const newResponse = new Response(this.res.body, {
743
+ status: this.status,
744
+ headers: this.res.headers
745
+ });
746
+ return newResponse;
747
+ }
748
+ return new Response(this.message, {
749
+ status: this.status
750
+ });
751
+ }
752
+ };
753
+
754
+ // node_modules/hono/dist/middleware/body-limit/index.js
755
+ var ERROR_MESSAGE = "Payload Too Large";
756
+ var bodyLimit = (options) => {
757
+ const onError = options.onError || (() => {
758
+ const res = new Response(ERROR_MESSAGE, {
759
+ status: 413
760
+ });
761
+ throw new HTTPException(413, { res });
762
+ });
763
+ const maxSize = options.maxSize;
764
+ return async function bodyLimit2(c, next) {
765
+ if (!c.req.raw.body) {
766
+ return next();
767
+ }
768
+ const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
769
+ const hasContentLength = c.req.raw.headers.has("content-length");
770
+ if (hasContentLength && !hasTransferEncoding) {
771
+ const contentLength = parseInt(c.req.raw.headers.get("content-length") || "0", 10);
772
+ return contentLength > maxSize ? onError(c) : next();
773
+ }
774
+ let size = 0;
775
+ const chunks = [];
776
+ const rawReader = c.req.raw.body.getReader();
777
+ for (; ; ) {
778
+ const { done, value } = await rawReader.read();
779
+ if (done) {
780
+ break;
781
+ }
782
+ size += value.length;
783
+ if (size > maxSize) {
784
+ return onError(c);
785
+ }
786
+ chunks.push(value);
787
+ }
788
+ const requestInit = {
789
+ body: new ReadableStream({
790
+ start(controller) {
791
+ for (const chunk of chunks) {
792
+ controller.enqueue(chunk);
793
+ }
794
+ controller.close();
795
+ }
796
+ }),
797
+ duplex: "half"
798
+ };
799
+ c.req.raw = new Request(c.req.raw, requestInit);
800
+ return next();
801
+ };
802
+ };
803
+
804
+ // src/middleware/bodyLimit.ts
805
+ var DEFAULT_MAX_REQUEST_BYTES = 262144;
806
+ function chatBodyLimit(maxSize = DEFAULT_MAX_REQUEST_BYTES, toError = (message) => ({ error: message })) {
807
+ return bodyLimit({
808
+ maxSize,
809
+ onError: (c) => c.json(toError("Request body too large"), 413)
810
+ });
811
+ }
812
+
813
+ // src/channels/telegram/webhook.ts
814
+ var SECRET_TOKEN_HEADER = "x-telegram-bot-api-secret-token";
815
+ function secureCompare(a, b) {
816
+ const bufA = Buffer.from(a);
817
+ const bufB = Buffer.from(b);
818
+ return bufA.length === bufB.length && timingSafeEqual(bufA, bufB);
819
+ }
820
+ var SECRET_TOKEN_PATTERN = /^[A-Za-z0-9_-]{1,256}$/;
821
+ function createTelegramWebhookRoute(config) {
822
+ const webhookSecret = config.webhookSecret;
823
+ if (!webhookSecret?.trim()) {
824
+ throw new Error("Telegram webhook requires webhookSecret (it fails closed without one)");
825
+ }
826
+ if (!SECRET_TOKEN_PATTERN.test(webhookSecret)) {
827
+ throw new Error(
828
+ "Telegram webhookSecret must match /^[A-Za-z0-9_-]{1,256}$/ (Telegram's own secret_token charset)"
829
+ );
830
+ }
831
+ const channelName = config.name ?? "telegram";
832
+ const allowedChats = config.allowedChats ?? [];
833
+ const path = config.path ?? "/webhooks/telegram";
834
+ return async (app, deps) => {
835
+ const log = config.logger ?? deps.logger ?? createConsoleLogger();
836
+ const api = config.api ?? createTelegramApi({
837
+ botToken: config.botToken,
838
+ fetch: config.fetch,
839
+ baseUrl: config.apiBaseUrl
840
+ });
841
+ const me = await api.getMe();
842
+ const label = `Telegram[${me.username ?? me.id}] (webhook)`;
843
+ deps.senders.register(channelName, createTelegramSender(api));
844
+ const pipeline = createInboundPipeline(
845
+ { client: deps.client, store: deps.store, prompts: deps.prompts },
846
+ {
847
+ channel: channelName,
848
+ answerFn: config.answerFn ?? deps.config.answerFn,
849
+ bucketsFor: config.bucketsFor ?? deps.config.bucketsFor,
850
+ rewriteQuery: config.rewriteQuery ?? deps.config.rewriteQuery,
851
+ rerankContext: config.rerankContext ?? deps.config.rerankContext,
852
+ transformReply: config.transformReply ?? deps.config.transformReply,
853
+ model: config.model,
854
+ channelHint: config.channelHint ?? "Channel: Telegram.",
855
+ personaResolver: config.personaResolver,
856
+ history: config.history,
857
+ allowedChats,
858
+ muteRegex: config.muteRegex,
859
+ unmuteRegex: config.unmuteRegex,
860
+ muteReply: config.muteReply,
861
+ unmuteReply: config.unmuteReply,
862
+ dmRateLimit: config.dmRateLimit,
863
+ groupRateLimit: config.groupRateLimit,
864
+ now: config.now,
865
+ logger: log
866
+ }
867
+ );
868
+ const handleUpdate = createTelegramUpdateHandler({
869
+ api,
870
+ me,
871
+ pipeline,
872
+ allowedChats,
873
+ logger: log,
874
+ label
875
+ });
876
+ app.post(
877
+ path,
878
+ chatBodyLimit(deps.config.server?.maxRequestBytes),
879
+ // Reject oversized bodies first
880
+ async (c) => {
881
+ const provided = c.req.header(SECRET_TOKEN_HEADER);
882
+ if (!provided || !secureCompare(provided, webhookSecret)) {
883
+ log.warn(`${label}: rejected webhook POST - missing or invalid secret token`);
884
+ return c.text("", 403);
885
+ }
886
+ let body;
887
+ try {
888
+ body = await c.req.json();
889
+ } catch {
890
+ return c.text("", 400);
891
+ }
892
+ if (typeof body !== "object" || body === null) {
893
+ return c.text("", 400);
894
+ }
895
+ const update = body;
896
+ try {
897
+ await handleUpdate(update);
898
+ } catch (error) {
899
+ log.warn(`${label}: update handling failed:`, error);
900
+ }
901
+ return c.text("", 200);
902
+ }
903
+ );
904
+ log.info(`${label}: webhook mounted at ${path} as @${me.username ?? me.id}`);
905
+ };
906
+ }
691
907
  export {
692
908
  TELEGRAM_API_BASE,
693
909
  TELEGRAM_TEXT_LIMIT,
694
910
  TelegramApiError,
695
911
  createTelegramApi,
696
912
  createTelegramChannel,
913
+ createTelegramSender,
914
+ createTelegramUpdateHandler,
915
+ createTelegramWebhookRoute,
697
916
  mentionsBot,
698
917
  messageEntities,
699
918
  messageText,
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Telegram Bot API transport: a `CustomRoutes` mount that receives updates
3
+ * over HTTPS instead of long-polling `getUpdates` (see `./channel` for that
4
+ * transport). Update handling is identical between the two — both build on
5
+ * `./handler`'s shared `createTelegramUpdateHandler`/`createTelegramSender` —
6
+ * so choosing one over the other is purely an infrastructure decision: a
7
+ * webhook needs a publicly reachable HTTPS endpoint and no open connection,
8
+ * long-polling needs neither but holds one request open at a time. See
9
+ * docs/telegram.md for the full comparison and `TelegramApi.setWebhook` for
10
+ * registering the URL with Telegram once this route is mounted.
11
+ *
12
+ * ```ts
13
+ * await createServer({
14
+ * ...,
15
+ * customRoutes: createTelegramWebhookRoute({
16
+ * botToken: process.env.TELEGRAM_BOT_TOKEN as string,
17
+ * webhookSecret: process.env.TELEGRAM_WEBHOOK_SECRET as string,
18
+ * }),
19
+ * });
20
+ * ```
21
+ */
22
+ import type { AnswerFn, TransformReply } from "../../core/answer";
23
+ import type { BucketsFor } from "../../core/buckets";
24
+ import { type Logger } from "../../core/logger";
25
+ import type { RerankContext, RewriteQuery } from "../../core/pipeline";
26
+ import type { HistoryStore } from "../../history/types";
27
+ import type { CustomRoutes } from "../../types";
28
+ import { type TelegramApi } from "./api";
29
+ export interface TelegramWebhookConfig {
30
+ /** From @BotFather. A credential — pass it from the environment, never commit it. */
31
+ botToken: string;
32
+ /**
33
+ * Shared secret Telegram is told about via `setWebhook({ secretToken })`
34
+ * and then echoes back on every POST as `X-Telegram-Bot-Api-Secret-Token`.
35
+ * Required: without one there is no way to tell a genuine Telegram POST
36
+ * from a forged one, so this fails closed by construction — a missing or
37
+ * blank value throws rather than falling back to accepting every request.
38
+ */
39
+ webhookSecret: string;
40
+ /** Where the route is mounted. @default "/webhooks/telegram" */
41
+ path?: string;
42
+ /** Channel and sender-registry name. Override to run more than one bot in one process. @default "telegram" */
43
+ name?: string;
44
+ /** Group chats eligible for a reply. Empty (default) = every group. Has no effect on DMs, which always reply. */
45
+ allowedChats?: string[];
46
+ answerFn?: AnswerFn;
47
+ bucketsFor?: BucketsFor;
48
+ rewriteQuery?: RewriteQuery;
49
+ rerankContext?: RerankContext;
50
+ transformReply?: TransformReply;
51
+ model?: string;
52
+ /** Extra system-prompt section describing the delivery channel; passed through to `prepareChat`. @default "Channel: Telegram." */
53
+ channelHint?: string;
54
+ /** A throw/rejection is treated as "no persona" for that turn. `sender` is the namespaced `tg:<id>` key. */
55
+ personaResolver?: (ctx: {
56
+ sender: string;
57
+ text: string;
58
+ }) => string | undefined | Promise<string | undefined>;
59
+ /** Off by default — the channel stays single-turn until a store is configured. */
60
+ history?: {
61
+ store: HistoryStore;
62
+ /** Most recent turns to load per reply. @default 20 */
63
+ limit?: number;
64
+ historyEnabledFor?: (sender: string) => boolean | Promise<boolean>;
65
+ };
66
+ muteRegex?: RegExp;
67
+ unmuteRegex?: RegExp;
68
+ muteReply?: string;
69
+ unmuteReply?: string;
70
+ dmRateLimit?: {
71
+ max: number;
72
+ windowMs: number;
73
+ };
74
+ groupRateLimit?: {
75
+ max: number;
76
+ windowMs: number;
77
+ };
78
+ now?: () => number;
79
+ /** Overridable for tests and for hosts routing through a proxy; defaults to `globalThis.fetch`. */
80
+ fetch?: typeof fetch;
81
+ /** Self-hosted Bot API server origin. */
82
+ apiBaseUrl?: string;
83
+ /** Overridable for tests, which fake the Bot API instead of calling it; defaults to a `fetch` client over {@link TelegramWebhookConfig.botToken}. */
84
+ api?: TelegramApi;
85
+ logger?: Logger;
86
+ }
87
+ /**
88
+ * Builds a `CustomRoutes` mount that receives Telegram updates over HTTPS.
89
+ * Throws synchronously if `webhookSecret` is missing, blank, or outside the
90
+ * charset Telegram accepts for it — before the route ever exists, rather
91
+ * than mounting an endpoint that would silently accept everything or reject
92
+ * every request.
93
+ */
94
+ export declare function createTelegramWebhookRoute(config: TelegramWebhookConfig): CustomRoutes;
95
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/channels/telegram/webhook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAqB,KAAK,WAAW,EAAuB,MAAM,OAAO,CAAC;AAMjF,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iHAAiH;IACjH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kIAAkI;IAClI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4GAA4G;IAC5G,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,kFAAkF;IAClF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,YAAY,CAAC;QACpB,uDAAuD;QACvD,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACpE,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,mGAAmG;IACnG,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qJAAqJ;IACrJ,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,qBAAqB,GAAG,YAAY,CAkHtF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.58.0",
3
+ "version": "0.59.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",