@diegoaltoworks/chatter 0.57.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.
- package/dist/channels/index.js +18 -3
- package/dist/channels/index.mjs +18 -3
- package/dist/channels/pipeline.d.ts +8 -0
- package/dist/channels/pipeline.d.ts.map +1 -1
- package/dist/channels/telegram/api.d.ts +17 -0
- package/dist/channels/telegram/api.d.ts.map +1 -1
- package/dist/channels/telegram/channel.d.ts +11 -1
- package/dist/channels/telegram/channel.d.ts.map +1 -1
- package/dist/channels/telegram/handler.d.ts +31 -0
- package/dist/channels/telegram/handler.d.ts.map +1 -0
- package/dist/channels/telegram/index.d.ts +7 -0
- package/dist/channels/telegram/index.d.ts.map +1 -1
- package/dist/channels/telegram/index.js +319 -82
- package/dist/channels/telegram/index.mjs +316 -82
- package/dist/channels/telegram/webhook.d.ts +95 -0
- package/dist/channels/telegram/webhook.d.ts.map +1 -0
- package/dist/channels/whatsapp/inbound.d.ts +6 -0
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +18 -3
- package/dist/channels/whatsapp/index.mjs +18 -3
- package/dist/history/index.d.ts +1 -0
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +23 -2
- package/dist/history/index.mjs +23 -2
- package/dist/history/tursoStore.d.ts +13 -1
- package/dist/history/tursoStore.d.ts.map +1 -1
- package/dist/history/types.d.ts +2 -0
- package/dist/history/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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 };
|
|
@@ -441,6 +455,16 @@ async function resolvePersonaLayer(config, ctx) {
|
|
|
441
455
|
return void 0;
|
|
442
456
|
}
|
|
443
457
|
}
|
|
458
|
+
async function isHistoryEnabled(config, sender) {
|
|
459
|
+
const predicate = config.history?.historyEnabledFor;
|
|
460
|
+
if (!predicate) return true;
|
|
461
|
+
try {
|
|
462
|
+
return await predicate(sender);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
config.logger?.error("historyEnabledFor threw; disabling history for this turn", error);
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
444
468
|
function createInboundPipeline(deps, config) {
|
|
445
469
|
const mutedChats = /* @__PURE__ */ new Set();
|
|
446
470
|
const now = config.now ?? Date.now;
|
|
@@ -480,12 +504,15 @@ function createInboundPipeline(deps, config) {
|
|
|
480
504
|
}
|
|
481
505
|
const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
|
|
482
506
|
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
483
|
-
const
|
|
507
|
+
const historyEnabled = config.history ? await isHistoryEnabled(config, sender) : false;
|
|
508
|
+
const priorTurns = config.history && historyEnabled ? await config.history.store.load(
|
|
484
509
|
conversationId,
|
|
485
510
|
config.history.limit ?? DEFAULT_HISTORY_LIMIT
|
|
486
511
|
) : [];
|
|
487
512
|
const messages = [...priorTurns, { role: "user", content: msg.text }];
|
|
488
|
-
|
|
513
|
+
if (historyEnabled) {
|
|
514
|
+
await config.history?.store.append(conversationId, { role: "user", content: msg.text });
|
|
515
|
+
}
|
|
489
516
|
const { system } = await prepareChat({
|
|
490
517
|
store: deps.store,
|
|
491
518
|
prompts: deps.prompts,
|
|
@@ -517,7 +544,9 @@ function createInboundPipeline(deps, config) {
|
|
|
517
544
|
if (!content) {
|
|
518
545
|
return { action: "ignore" };
|
|
519
546
|
}
|
|
520
|
-
|
|
547
|
+
if (historyEnabled) {
|
|
548
|
+
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
549
|
+
}
|
|
521
550
|
await turn.reply.sendAnswer(msg.chatId, content);
|
|
522
551
|
return { action: "reply", content };
|
|
523
552
|
};
|
|
@@ -566,6 +595,41 @@ function telegramSenderKey(senderId) {
|
|
|
566
595
|
return `tg:${senderId}`;
|
|
567
596
|
}
|
|
568
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
|
+
|
|
569
633
|
// src/channels/telegram/poll.ts
|
|
570
634
|
var BASE_DELAY_MS = 1e3;
|
|
571
635
|
var MAX_DELAY_MS = 6e4;
|
|
@@ -636,12 +700,7 @@ function createTelegramChannel(config) {
|
|
|
636
700
|
});
|
|
637
701
|
const me = await api.getMe();
|
|
638
702
|
const label = `Telegram[${me.username ?? me.id}]`;
|
|
639
|
-
|
|
640
|
-
sendText: (chatId, text) => api.sendMessage(chatId, text),
|
|
641
|
-
sendMedia: (chatId, payload) => api.sendMedia(chatId, payload),
|
|
642
|
-
sendReaction: (chatId, messageRef, emoji) => api.setMessageReaction(chatId, Number(messageRef), emoji)
|
|
643
|
-
};
|
|
644
|
-
deps.senders.register(channelName, sender);
|
|
703
|
+
deps.senders.register(channelName, createTelegramSender(api));
|
|
645
704
|
senders = deps.senders;
|
|
646
705
|
registered = true;
|
|
647
706
|
const pipeline = createInboundPipeline(
|
|
@@ -668,29 +727,14 @@ function createTelegramChannel(config) {
|
|
|
668
727
|
logger: log
|
|
669
728
|
}
|
|
670
729
|
);
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
log.warn(`${label}: skipped chat ${msg.chatId} - not in allowedChats`);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
const reply = {
|
|
683
|
-
// Threaded onto the incoming message: in a busy group an untethered
|
|
684
|
-
// answer reads as a non-sequitur.
|
|
685
|
-
sendAnswer: (chatId, text) => api.sendMessage(chatId, text, { replyToMessageId: message.message_id }),
|
|
686
|
-
sendGateReply: (chatId, text) => api.sendMessage(chatId, text)
|
|
687
|
-
};
|
|
688
|
-
await pipeline(msg, {
|
|
689
|
-
reply,
|
|
690
|
-
conversationId: msg.chatId,
|
|
691
|
-
sender: telegramSenderKey(msg.senderId)
|
|
692
|
-
});
|
|
693
|
-
}
|
|
730
|
+
const handleUpdate = createTelegramUpdateHandler({
|
|
731
|
+
api,
|
|
732
|
+
me,
|
|
733
|
+
pipeline,
|
|
734
|
+
allowedChats,
|
|
735
|
+
logger: log,
|
|
736
|
+
label
|
|
737
|
+
});
|
|
694
738
|
abort = new AbortController();
|
|
695
739
|
const signal = abort.signal;
|
|
696
740
|
void runLongPoll({
|
|
@@ -715,6 +759,196 @@ function createTelegramChannel(config) {
|
|
|
715
759
|
}
|
|
716
760
|
};
|
|
717
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
|
+
}
|
|
718
952
|
// Annotate the CommonJS export names for ESM import in node:
|
|
719
953
|
0 && (module.exports = {
|
|
720
954
|
TELEGRAM_API_BASE,
|
|
@@ -722,6 +956,9 @@ function createTelegramChannel(config) {
|
|
|
722
956
|
TelegramApiError,
|
|
723
957
|
createTelegramApi,
|
|
724
958
|
createTelegramChannel,
|
|
959
|
+
createTelegramSender,
|
|
960
|
+
createTelegramUpdateHandler,
|
|
961
|
+
createTelegramWebhookRoute,
|
|
725
962
|
mentionsBot,
|
|
726
963
|
messageEntities,
|
|
727
964
|
messageText,
|