diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index ed61863467..910956635d 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -57,11 +57,21 @@ export const registerTelegramHandlers = ({ processMessage, logger, }: RegisterTelegramHandlerParams) => { + const DEFAULT_TEXT_FRAGMENT_MAX_GAP_MS = 1500; const TELEGRAM_TEXT_FRAGMENT_START_THRESHOLD_CHARS = 4000; - const TELEGRAM_TEXT_FRAGMENT_MAX_GAP_MS = 1500; + const TELEGRAM_TEXT_FRAGMENT_MAX_GAP_MS = + typeof opts.testTimings?.textFragmentGapMs === "number" && + Number.isFinite(opts.testTimings.textFragmentGapMs) + ? Math.max(10, Math.floor(opts.testTimings.textFragmentGapMs)) + : DEFAULT_TEXT_FRAGMENT_MAX_GAP_MS; const TELEGRAM_TEXT_FRAGMENT_MAX_ID_GAP = 1; const TELEGRAM_TEXT_FRAGMENT_MAX_PARTS = 12; const TELEGRAM_TEXT_FRAGMENT_MAX_TOTAL_CHARS = 50_000; + const mediaGroupTimeoutMs = + typeof opts.testTimings?.mediaGroupFlushMs === "number" && + Number.isFinite(opts.testTimings.mediaGroupFlushMs) + ? Math.max(10, Math.floor(opts.testTimings.mediaGroupFlushMs)) + : MEDIA_GROUP_TIMEOUT_MS; const mediaGroupBuffer = new Map(); let mediaGroupProcessing: Promise = Promise.resolve(); @@ -859,7 +869,7 @@ export const registerTelegramHandlers = ({ }) .catch(() => undefined); await mediaGroupProcessing; - }, MEDIA_GROUP_TIMEOUT_MS); + }, mediaGroupTimeoutMs); } else { const entry: MediaGroupEntry = { messages: [{ msg, ctx }], @@ -871,7 +881,7 @@ export const registerTelegramHandlers = ({ }) .catch(() => undefined); await mediaGroupProcessing; - }, MEDIA_GROUP_TIMEOUT_MS), + }, mediaGroupTimeoutMs), }; mediaGroupBuffer.set(mediaGroupId, entry); } diff --git a/src/telegram/bot.media.downloads-media-file-path-no-file-download.e2e.test.ts b/src/telegram/bot.media.downloads-media-file-path-no-file-download.e2e.test.ts index 6e2416c4f4..e0440b3a31 100644 --- a/src/telegram/bot.media.downloads-media-file-path-no-file-download.e2e.test.ts +++ b/src/telegram/bot.media.downloads-media-file-path-no-file-download.e2e.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js"; import * as ssrf from "../infra/net/ssrf.js"; -import { MEDIA_GROUP_TIMEOUT_MS } from "./bot-updates.js"; const useSpy = vi.fn(); const middlewareUseSpy = vi.fn(); @@ -14,6 +13,10 @@ const describeStickerImageSpy = vi.fn(); const resolvePinnedHostname = ssrf.resolvePinnedHostname; const lookupMock = vi.fn(); let resolvePinnedHostnameSpy: ReturnType = null; +const TELEGRAM_TEST_TIMINGS = { + mediaGroupFlushMs: 75, + textFragmentGapMs: 120, +} as const; const sleep = async (ms: number) => { await new Promise((resolve) => setTimeout(resolve, ms)); @@ -141,6 +144,7 @@ describe("telegram inbound media", () => { const runtimeError = vi.fn(); createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, runtime: { log: runtimeLog, error: runtimeError, @@ -207,6 +211,7 @@ describe("telegram inbound media", () => { createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, proxyFetch: proxyFetch as unknown as typeof fetch, runtime: { log: runtimeLog, @@ -254,6 +259,7 @@ describe("telegram inbound media", () => { createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, runtime: { log: runtimeLog, error: runtimeError, @@ -294,7 +300,7 @@ describe("telegram media groups", () => { }); const MEDIA_GROUP_TEST_TIMEOUT_MS = process.platform === "win32" ? 45_000 : 20_000; - const MEDIA_GROUP_FLUSH_MS = MEDIA_GROUP_TIMEOUT_MS + 25; + const MEDIA_GROUP_FLUSH_MS = TELEGRAM_TEST_TIMINGS.mediaGroupFlushMs + 120; it( "buffers messages with same media_group_id and processes them together", @@ -317,6 +323,7 @@ describe("telegram media groups", () => { createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, runtime: { log: vi.fn(), error: runtimeError, @@ -390,7 +397,7 @@ describe("telegram media groups", () => { arrayBuffer: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]).buffer, } as Response); - createTelegramBot({ token: "tok" }); + createTelegramBot({ token: "tok", testTimings: TELEGRAM_TEST_TIMINGS }); const handler = onSpy.mock.calls.find((call) => call[0] === "message")?.[1] as ( ctx: Record, ) => Promise; @@ -459,6 +466,7 @@ describe("telegram stickers", () => { const runtimeError = vi.fn(); createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, runtime: { log: runtimeLog, error: runtimeError, @@ -541,6 +549,7 @@ describe("telegram stickers", () => { const runtimeError = vi.fn(); createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, runtime: { log: vi.fn(), error: runtimeError, @@ -615,6 +624,7 @@ describe("telegram stickers", () => { createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, runtime: { log: vi.fn(), error: runtimeError, @@ -675,6 +685,7 @@ describe("telegram stickers", () => { createTelegramBot({ token: "tok", + testTimings: TELEGRAM_TEST_TIMINGS, runtime: { log: vi.fn(), error: runtimeError, @@ -726,7 +737,7 @@ describe("telegram text fragments", () => { }); const TEXT_FRAGMENT_TEST_TIMEOUT_MS = process.platform === "win32" ? 45_000 : 20_000; - const TEXT_FRAGMENT_FLUSH_MS = 1600; + const TEXT_FRAGMENT_FLUSH_MS = TELEGRAM_TEST_TIMINGS.textFragmentGapMs + 160; it( "buffers near-limit text and processes sequential parts as one message", @@ -738,7 +749,7 @@ describe("telegram text fragments", () => { onSpy.mockReset(); replySpy.mockReset(); - createTelegramBot({ token: "tok" }); + createTelegramBot({ token: "tok", testTimings: TELEGRAM_TEST_TIMINGS }); const handler = onSpy.mock.calls.find((call) => call[0] === "message")?.[1] as ( ctx: Record, ) => Promise; diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index 61e2038b6c..4101ce66fb 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -62,6 +62,10 @@ export type TelegramBotOptions = { lastUpdateId?: number | null; onUpdateId?: (updateId: number) => void | Promise; }; + testTimings?: { + mediaGroupFlushMs?: number; + textFragmentGapMs?: number; + }; }; export function getTelegramSequentialKey(ctx: {