fix(telegram): expose media group flush config

This commit is contained in:
Vincent Koc
2026-05-03 12:05:39 -07:00
parent 52257fd05e
commit c5488ea577
10 changed files with 123 additions and 5 deletions

View File

@@ -148,7 +148,10 @@ export const registerTelegramHandlers = ({
typeof opts.testTimings?.mediaGroupFlushMs === "number" &&
Number.isFinite(opts.testTimings.mediaGroupFlushMs)
? Math.max(10, Math.floor(opts.testTimings.mediaGroupFlushMs))
: MEDIA_GROUP_TIMEOUT_MS;
: typeof telegramCfg.mediaGroupFlushMs === "number" &&
Number.isFinite(telegramCfg.mediaGroupFlushMs)
? Math.max(10, Math.floor(telegramCfg.mediaGroupFlushMs))
: MEDIA_GROUP_TIMEOUT_MS;
const mediaGroupBuffer = new Map<string, MediaGroupEntry>();
let mediaGroupProcessing: Promise<void> = Promise.resolve();

View File

@@ -45,9 +45,18 @@ function getChannelPostHandler() {
return getOnHandler("channel_post") as (ctx: Record<string, unknown>) => Promise<void>;
}
function getChannelPostHandlerWithRuntimeTimings() {
createTelegramBot({ token: "tok" });
return getOnHandler("channel_post") as (ctx: Record<string, unknown>) => Promise<void>;
}
function resolveFlushTimer(setTimeoutSpy: ReturnType<typeof vi.spyOn>) {
return resolveFlushTimerForDelay(setTimeoutSpy, TELEGRAM_TEST_TIMINGS.mediaGroupFlushMs);
}
function resolveFlushTimerForDelay(setTimeoutSpy: ReturnType<typeof vi.spyOn>, delayMs: number) {
const flushTimerCallIndex = setTimeoutSpy.mock.calls.findLastIndex(
(call: Parameters<typeof setTimeout>) => call[1] === TELEGRAM_TEST_TIMINGS.mediaGroupFlushMs,
(call: Parameters<typeof setTimeout>) => call[1] === delayMs,
);
const flushTimer =
flushTimerCallIndex >= 0
@@ -104,6 +113,15 @@ async function flushChannelPostMediaGroup(setTimeoutSpy: ReturnType<typeof vi.sp
await flushTimer?.();
}
async function flushChannelPostMediaGroupForDelay(
setTimeoutSpy: ReturnType<typeof vi.spyOn>,
delayMs: number,
) {
const flushTimer = resolveFlushTimerForDelay(setTimeoutSpy, delayMs);
expect(flushTimer).toBeTypeOf("function");
await flushTimer?.();
}
async function queueChannelPostAlbum(
handler: ReturnType<typeof getChannelPostHandler>,
params: {
@@ -181,6 +199,44 @@ describe("createTelegramBot channel_post media", () => {
}
});
it("honors configured mediaGroupFlushMs for channel_post albums", async () => {
loadConfig.mockReturnValue({
channels: {
telegram: {
groupPolicy: "open",
mediaGroupFlushMs: 75,
groups: {
"-100777111222": {
enabled: true,
requireMention: false,
},
},
},
},
});
const fetchSpy = createImageFetchSpy();
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
try {
const handler = getChannelPostHandlerWithRuntimeTimings();
await queueChannelPostAlbum(handler, {
caption: "configured album",
mediaGroupId: "channel-album-configured",
firstMessageId: 211,
secondMessageId: 212,
});
expect(replySpy).not.toHaveBeenCalled();
await flushChannelPostMediaGroupForDelay(setTimeoutSpy, 75);
expect(replySpy).toHaveBeenCalledTimes(1);
const payload = replySpy.mock.calls[0]?.[0] as { Body?: string };
expect(payload.Body).toContain("configured album");
} finally {
setTimeoutSpy.mockRestore();
fetchSpy.mockRestore();
}
});
it("coalesces channel_post near-limit text fragments into one message", async () => {
setOpenChannelPostConfig();

View File

@@ -66,6 +66,24 @@ describe("telegram custom commands schema", () => {
}
});
it("accepts mediaGroupFlushMs overrides per account", () => {
const res = TelegramConfigSchema.safeParse({
mediaGroupFlushMs: 750,
accounts: { ops: { mediaGroupFlushMs: 1500 } },
});
expect(res.success).toBe(true);
if (res.success) {
expect(res.data.mediaGroupFlushMs).toBe(750);
expect(res.data.accounts?.ops?.mediaGroupFlushMs).toBe(1500);
}
});
it("rejects mediaGroupFlushMs outside the supported flush bounds", () => {
expectTelegramConfigIssue({ mediaGroupFlushMs: 9 }, "mediaGroupFlushMs");
expectTelegramConfigIssue({ mediaGroupFlushMs: 60_001 }, "mediaGroupFlushMs");
});
it("accepts DM thread reply policy overrides", () => {
const res = TelegramConfigSchema.safeParse({
dm: { threadReplies: "off" },

View File

@@ -101,6 +101,10 @@ export const telegramChannelConfigUiHints = {
label: "Telegram API Timeout (seconds)",
help: "Max seconds before Telegram API requests are aborted (default: 500 per grammY).",
},
mediaGroupFlushMs: {
label: "Telegram Media Group Flush (ms)",
help: "Milliseconds to buffer Telegram albums/media groups before dispatching them as one inbound message. Default: 500.",
},
pollingStallThresholdMs: {
label: "Telegram Polling Stall Threshold (ms)",
help: "Milliseconds without completed Telegram getUpdates liveness before the polling watchdog restarts the polling runner. Default: 120000.",