fix: unify telegram thread handling

This commit is contained in:
Ayaan Zaidi 2026-02-02 08:53:42 +05:30 committed by Ayaan Zaidi
parent 5020bfa2a9
commit 19b8416a81
10 changed files with 151 additions and 46 deletions

View file

@ -51,7 +51,7 @@ import {
describeReplyTarget, describeReplyTarget,
extractTelegramLocation, extractTelegramLocation,
hasBotMention, hasBotMention,
resolveTelegramForumThreadId, resolveTelegramThreadSpec,
} from "./bot/helpers.js"; } from "./bot/helpers.js";
type TelegramMediaRef = { type TelegramMediaRef = {
@ -158,11 +158,13 @@ export const buildTelegramMessageContext = async ({
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup"; const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id; const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
const isForum = (msg.chat as { is_forum?: boolean }).is_forum === true; const isForum = (msg.chat as { is_forum?: boolean }).is_forum === true;
const resolvedThreadId = resolveTelegramForumThreadId({ const threadSpec = resolveTelegramThreadSpec({
isGroup,
isForum, isForum,
messageThreadId, messageThreadId,
}); });
const replyThreadId = isGroup ? resolvedThreadId : messageThreadId; const resolvedThreadId = threadSpec.scope === "forum" ? threadSpec.id : undefined;
const replyThreadId = threadSpec.id;
const { groupConfig, topicConfig } = resolveTelegramGroupConfig(chatId, resolvedThreadId); const { groupConfig, topicConfig } = resolveTelegramGroupConfig(chatId, resolvedThreadId);
const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId); const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId);
const route = resolveAgentRoute({ const route = resolveAgentRoute({
@ -175,8 +177,8 @@ export const buildTelegramMessageContext = async ({
}, },
}); });
const baseSessionKey = route.sessionKey; const baseSessionKey = route.sessionKey;
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums) // DMs: use raw messageThreadId for thread sessions (not forum topic ids)
const dmThreadId = !isGroup ? messageThreadId : undefined; const dmThreadId = threadSpec.scope === "dm" ? threadSpec.id : undefined;
const threadKeys = const threadKeys =
dmThreadId != null dmThreadId != null
? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) }) ? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
@ -621,8 +623,8 @@ export const buildTelegramMessageContext = async ({
Sticker: allMedia[0]?.stickerMetadata, Sticker: allMedia[0]?.stickerMetadata,
...(locationData ? toLocationContext(locationData) : undefined), ...(locationData ? toLocationContext(locationData) : undefined),
CommandAuthorized: commandAuthorized, CommandAuthorized: commandAuthorized,
// For groups: use resolvedThreadId (forum topics only); for DMs: use raw messageThreadId // For groups: use resolved forum topic id; for DMs: use raw messageThreadId
MessageThreadId: isGroup ? resolvedThreadId : messageThreadId, MessageThreadId: threadSpec.id,
IsForum: isForum, IsForum: isForum,
// Originating channel for reply routing. // Originating channel for reply routing.
OriginatingChannel: "telegram" as const, OriginatingChannel: "telegram" as const,
@ -675,6 +677,7 @@ export const buildTelegramMessageContext = async ({
chatId, chatId,
isGroup, isGroup,
resolvedThreadId, resolvedThreadId,
threadSpec,
replyThreadId, replyThreadId,
isForum, isForum,
historyKey, historyKey,

View file

@ -59,6 +59,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
isGroup: false, isGroup: false,
resolvedThreadId: undefined, resolvedThreadId: undefined,
replyThreadId: 777, replyThreadId: 777,
threadSpec: { id: 777, scope: "dm" },
historyKey: undefined, historyKey: undefined,
historyLimit: 0, historyLimit: 0,
groupHistories: new Map(), groupHistories: new Map(),
@ -88,13 +89,13 @@ describe("dispatchTelegramMessage draft streaming", () => {
expect(createTelegramDraftStream).toHaveBeenCalledWith( expect(createTelegramDraftStream).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
chatId: 123, chatId: 123,
messageThreadId: 777, thread: { id: 777, scope: "dm" },
}), }),
); );
expect(draftStream.update).toHaveBeenCalledWith("Hello"); expect(draftStream.update).toHaveBeenCalledWith("Hello");
expect(deliverReplies).toHaveBeenCalledWith( expect(deliverReplies).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
messageThreadId: 777, thread: { id: 777, scope: "dm" },
}), }),
); );
}); });

View file

@ -56,7 +56,7 @@ export const dispatchTelegramMessage = async ({
msg, msg,
chatId, chatId,
isGroup, isGroup,
replyThreadId, threadSpec,
historyKey, historyKey,
historyLimit, historyLimit,
groupHistories, groupHistories,
@ -70,8 +70,7 @@ export const dispatchTelegramMessage = async ({
} = context; } = context;
const isPrivateChat = msg.chat.type === "private"; const isPrivateChat = msg.chat.type === "private";
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id; const draftThreadId = threadSpec.id;
const draftThreadId = replyThreadId ?? messageThreadId;
const draftMaxChars = Math.min(textLimit, 4096); const draftMaxChars = Math.min(textLimit, 4096);
const canStreamDraft = const canStreamDraft =
streamMode !== "off" && streamMode !== "off" &&
@ -84,7 +83,7 @@ export const dispatchTelegramMessage = async ({
chatId, chatId,
draftId: msg.message_id || Date.now(), draftId: msg.message_id || Date.now(),
maxChars: draftMaxChars, maxChars: draftMaxChars,
messageThreadId: draftThreadId, thread: threadSpec,
log: logVerbose, log: logVerbose,
warn: logVerbose, warn: logVerbose,
}) })
@ -243,7 +242,7 @@ export const dispatchTelegramMessage = async ({
bot, bot,
replyToMode, replyToMode,
textLimit, textLimit,
messageThreadId: replyThreadId, thread: threadSpec,
tableMode, tableMode,
chunkMode, chunkMode,
onVoiceRecording: sendRecordVoice, onVoiceRecording: sendRecordVoice,
@ -294,7 +293,7 @@ export const dispatchTelegramMessage = async ({
bot, bot,
replyToMode, replyToMode,
textLimit, textLimit,
messageThreadId: replyThreadId, thread: threadSpec,
tableMode, tableMode,
chunkMode, chunkMode,
linkPreview: telegramCfg.linkPreview, linkPreview: telegramCfg.linkPreview,

View file

@ -45,10 +45,12 @@ import { TelegramUpdateKeyContext } from "./bot-updates.js";
import { TelegramBotOptions } from "./bot.js"; import { TelegramBotOptions } from "./bot.js";
import { deliverReplies } from "./bot/delivery.js"; import { deliverReplies } from "./bot/delivery.js";
import { import {
buildTelegramThreadParams,
buildSenderName, buildSenderName,
buildTelegramGroupFrom, buildTelegramGroupFrom,
buildTelegramGroupPeerId, buildTelegramGroupPeerId,
resolveTelegramForumThreadId, resolveTelegramForumThreadId,
resolveTelegramThreadSpec,
} from "./bot/helpers.js"; } from "./bot/helpers.js";
import { buildInlineKeyboard } from "./send.js"; import { buildInlineKeyboard } from "./send.js";
@ -409,7 +411,12 @@ export const registerTelegramNativeCommands = ({
commandAuthorized, commandAuthorized,
} = auth; } = auth;
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id; const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId; const threadSpec = resolveTelegramThreadSpec({
isGroup,
isForum,
messageThreadId,
});
const threadParams = buildTelegramThreadParams(threadSpec) ?? {};
const commandDefinition = findCommandByNativeName(command.name, "telegram"); const commandDefinition = findCommandByNativeName(command.name, "telegram");
const rawText = ctx.match?.trim() ?? ""; const rawText = ctx.match?.trim() ?? "";
@ -456,7 +463,7 @@ export const registerTelegramNativeCommands = ({
fn: () => fn: () =>
bot.api.sendMessage(chatId, title, { bot.api.sendMessage(chatId, title, {
...(replyMarkup ? { reply_markup: replyMarkup } : {}), ...(replyMarkup ? { reply_markup: replyMarkup } : {}),
...(threadIdForSend != null ? { message_thread_id: threadIdForSend } : {}), ...threadParams,
}), }),
}); });
return; return;
@ -472,7 +479,7 @@ export const registerTelegramNativeCommands = ({
}); });
const baseSessionKey = route.sessionKey; const baseSessionKey = route.sessionKey;
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums) // DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums)
const dmThreadId = !isGroup ? messageThreadId : undefined; const dmThreadId = threadSpec.scope === "dm" ? threadSpec.id : undefined;
const threadKeys = const threadKeys =
dmThreadId != null dmThreadId != null
? resolveThreadSessionKeys({ ? resolveThreadSessionKeys({
@ -521,7 +528,7 @@ export const registerTelegramNativeCommands = ({
SessionKey: `telegram:slash:${senderId || chatId}`, SessionKey: `telegram:slash:${senderId || chatId}`,
AccountId: route.accountId, AccountId: route.accountId,
CommandTargetSessionKey: sessionKey, CommandTargetSessionKey: sessionKey,
MessageThreadId: threadIdForSend, MessageThreadId: threadSpec.id,
IsForum: isForum, IsForum: isForum,
// Originating context for sub-agent announce routing // Originating context for sub-agent announce routing
OriginatingChannel: "telegram" as const, OriginatingChannel: "telegram" as const,
@ -553,7 +560,7 @@ export const registerTelegramNativeCommands = ({
bot, bot,
replyToMode, replyToMode,
textLimit, textLimit,
messageThreadId: threadIdForSend, thread: threadSpec,
tableMode, tableMode,
chunkMode, chunkMode,
linkPreview: telegramCfg.linkPreview, linkPreview: telegramCfg.linkPreview,
@ -585,7 +592,7 @@ export const registerTelegramNativeCommands = ({
bot, bot,
replyToMode, replyToMode,
textLimit, textLimit,
messageThreadId: threadIdForSend, thread: threadSpec,
tableMode, tableMode,
chunkMode, chunkMode,
linkPreview: telegramCfg.linkPreview, linkPreview: telegramCfg.linkPreview,
@ -630,9 +637,13 @@ export const registerTelegramNativeCommands = ({
if (!auth) { if (!auth) {
return; return;
} }
const { resolvedThreadId, senderId, commandAuthorized, isGroup } = auth; const { senderId, commandAuthorized, isGroup, isForum } = auth;
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id; const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
const threadIdForSend = isGroup ? resolvedThreadId : messageThreadId; const threadSpec = resolveTelegramThreadSpec({
isGroup,
isForum,
messageThreadId,
});
const result = await executePluginCommand({ const result = await executePluginCommand({
command: match.command, command: match.command,
@ -658,7 +669,7 @@ export const registerTelegramNativeCommands = ({
bot, bot,
replyToMode, replyToMode,
textLimit, textLimit,
messageThreadId: threadIdForSend, thread: threadSpec,
tableMode, tableMode,
chunkMode, chunkMode,
linkPreview: telegramCfg.linkPreview, linkPreview: telegramCfg.linkPreview,

View file

@ -138,6 +138,34 @@ describe("deliverReplies", () => {
); );
}); });
it("keeps message_thread_id=1 when allowed", async () => {
const runtime = { error: vi.fn(), log: vi.fn() };
const sendMessage = vi.fn().mockResolvedValue({
message_id: 4,
chat: { id: "123" },
});
const bot = { api: { sendMessage } } as unknown as Bot;
await deliverReplies({
replies: [{ text: "Hello" }],
chatId: "123",
token: "tok",
runtime,
bot,
replyToMode: "off",
textLimit: 4000,
thread: { id: 1, scope: "dm" },
});
expect(sendMessage).toHaveBeenCalledWith(
"123",
expect.any(String),
expect.objectContaining({
message_thread_id: 1,
}),
);
});
it("does not include link_preview_options when linkPreview is true", async () => { it("does not include link_preview_options when linkPreview is true", async () => {
const runtime = { error: vi.fn(), log: vi.fn() }; const runtime = { error: vi.fn(), log: vi.fn() };
const sendMessage = vi.fn().mockResolvedValue({ const sendMessage = vi.fn().mockResolvedValue({

View file

@ -22,7 +22,11 @@ import {
import { buildInlineKeyboard } from "../send.js"; import { buildInlineKeyboard } from "../send.js";
import { cacheSticker, getCachedSticker } from "../sticker-cache.js"; import { cacheSticker, getCachedSticker } from "../sticker-cache.js";
import { resolveTelegramVoiceSend } from "../voice.js"; import { resolveTelegramVoiceSend } from "../voice.js";
import { buildTelegramThreadParams, resolveTelegramReplyId } from "./helpers.js"; import {
buildTelegramThreadParams,
resolveTelegramReplyId,
type TelegramThreadSpec,
} from "./helpers.js";
const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i; const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/; const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/;
@ -35,7 +39,7 @@ export async function deliverReplies(params: {
bot: Bot; bot: Bot;
replyToMode: ReplyToMode; replyToMode: ReplyToMode;
textLimit: number; textLimit: number;
messageThreadId?: number; thread?: TelegramThreadSpec | number | null;
tableMode?: MarkdownTableMode; tableMode?: MarkdownTableMode;
chunkMode?: ChunkMode; chunkMode?: ChunkMode;
/** Callback invoked before sending a voice message to switch typing indicator. */ /** Callback invoked before sending a voice message to switch typing indicator. */
@ -52,7 +56,7 @@ export async function deliverReplies(params: {
bot, bot,
replyToMode, replyToMode,
textLimit, textLimit,
messageThreadId, thread,
linkPreview, linkPreview,
replyQuoteText, replyQuoteText,
} = params; } = params;
@ -114,7 +118,7 @@ export async function deliverReplies(params: {
replyToMessageId: replyToMessageId:
replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined, replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined,
replyQuoteText, replyQuoteText,
messageThreadId, thread,
textMode: "html", textMode: "html",
plainText: chunk.text, plainText: chunk.text,
linkPreview, linkPreview,
@ -162,8 +166,8 @@ export async function deliverReplies(params: {
...(shouldAttachButtonsToMedia ? { reply_markup: replyMarkup } : {}), ...(shouldAttachButtonsToMedia ? { reply_markup: replyMarkup } : {}),
...buildTelegramSendParams({ ...buildTelegramSendParams({
replyToMessageId, replyToMessageId,
messageThreadId,
replyQuoteText, replyQuoteText,
thread,
}), }),
}; };
if (isGif) { if (isGif) {
@ -227,7 +231,7 @@ export async function deliverReplies(params: {
replyToId, replyToId,
replyToMode, replyToMode,
hasReplied, hasReplied,
messageThreadId, thread,
linkPreview, linkPreview,
replyMarkup, replyMarkup,
replyQuoteText, replyQuoteText,
@ -268,7 +272,7 @@ export async function deliverReplies(params: {
replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined; replyToId && (replyToMode === "all" || !hasReplied) ? replyToId : undefined;
await sendTelegramText(bot, chatId, chunk.html, runtime, { await sendTelegramText(bot, chatId, chunk.html, runtime, {
replyToMessageId: replyToMessageIdFollowup, replyToMessageId: replyToMessageIdFollowup,
messageThreadId, thread,
textMode: "html", textMode: "html",
plainText: chunk.text, plainText: chunk.text,
linkPreview, linkPreview,
@ -447,7 +451,7 @@ async function sendTelegramVoiceFallbackText(opts: {
replyToId?: number; replyToId?: number;
replyToMode: ReplyToMode; replyToMode: ReplyToMode;
hasReplied: boolean; hasReplied: boolean;
messageThreadId?: number; thread?: TelegramThreadSpec | number | null;
linkPreview?: boolean; linkPreview?: boolean;
replyMarkup?: ReturnType<typeof buildInlineKeyboard>; replyMarkup?: ReturnType<typeof buildInlineKeyboard>;
replyQuoteText?: string; replyQuoteText?: string;
@ -460,7 +464,7 @@ async function sendTelegramVoiceFallbackText(opts: {
replyToMessageId: replyToMessageId:
opts.replyToId && (opts.replyToMode === "all" || !hasReplied) ? opts.replyToId : undefined, opts.replyToId && (opts.replyToMode === "all" || !hasReplied) ? opts.replyToId : undefined,
replyQuoteText: opts.replyQuoteText, replyQuoteText: opts.replyQuoteText,
messageThreadId: opts.messageThreadId, thread: opts.thread,
textMode: "html", textMode: "html",
plainText: chunk.text, plainText: chunk.text,
linkPreview: opts.linkPreview, linkPreview: opts.linkPreview,
@ -475,10 +479,10 @@ async function sendTelegramVoiceFallbackText(opts: {
function buildTelegramSendParams(opts?: { function buildTelegramSendParams(opts?: {
replyToMessageId?: number; replyToMessageId?: number;
messageThreadId?: number; thread?: TelegramThreadSpec | number | null;
replyQuoteText?: string; replyQuoteText?: string;
}): Record<string, unknown> { }): Record<string, unknown> {
const threadParams = buildTelegramThreadParams(opts?.messageThreadId); const threadParams = buildTelegramThreadParams(opts?.thread);
const params: Record<string, unknown> = {}; const params: Record<string, unknown> = {};
const quoteText = opts?.replyQuoteText?.trim(); const quoteText = opts?.replyQuoteText?.trim();
if (opts?.replyToMessageId) { if (opts?.replyToMessageId) {
@ -505,7 +509,7 @@ async function sendTelegramText(
opts?: { opts?: {
replyToMessageId?: number; replyToMessageId?: number;
replyQuoteText?: string; replyQuoteText?: string;
messageThreadId?: number; thread?: TelegramThreadSpec | number | null;
textMode?: "markdown" | "html"; textMode?: "markdown" | "html";
plainText?: string; plainText?: string;
linkPreview?: boolean; linkPreview?: boolean;
@ -515,7 +519,7 @@ async function sendTelegramText(
const baseParams = buildTelegramSendParams({ const baseParams = buildTelegramSendParams({
replyToMessageId: opts?.replyToMessageId, replyToMessageId: opts?.replyToMessageId,
replyQuoteText: opts?.replyQuoteText, replyQuoteText: opts?.replyQuoteText,
messageThreadId: opts?.messageThreadId, thread: opts?.thread,
}); });
// Add link_preview_options when link preview is disabled. // Add link_preview_options when link preview is disabled.
const linkPreviewEnabled = opts?.linkPreview ?? true; const linkPreviewEnabled = opts?.linkPreview ?? true;

View file

@ -41,6 +41,12 @@ describe("buildTelegramThreadParams", () => {
expect(buildTelegramThreadParams(99)).toEqual({ message_thread_id: 99 }); expect(buildTelegramThreadParams(99)).toEqual({ message_thread_id: 99 });
}); });
it("keeps thread id=1 for dm threads", () => {
expect(buildTelegramThreadParams({ id: 1, scope: "dm" })).toEqual({
message_thread_id: 1,
});
});
it("normalizes thread ids to integers", () => { it("normalizes thread ids to integers", () => {
expect(buildTelegramThreadParams(42.9)).toEqual({ message_thread_id: 42 }); expect(buildTelegramThreadParams(42.9)).toEqual({ message_thread_id: 42 });
}); });

View file

@ -12,6 +12,13 @@ import { formatLocationText, type NormalizedLocation } from "../../channels/loca
const TELEGRAM_GENERAL_TOPIC_ID = 1; const TELEGRAM_GENERAL_TOPIC_ID = 1;
export type TelegramThreadScope = "dm" | "forum" | "none";
export type TelegramThreadSpec = {
id?: number;
scope: TelegramThreadScope;
};
/** /**
* Resolve the thread ID for Telegram forum topics. * Resolve the thread ID for Telegram forum topics.
* For non-forum groups, returns undefined even if messageThreadId is present * For non-forum groups, returns undefined even if messageThreadId is present
@ -33,17 +40,47 @@ export function resolveTelegramForumThreadId(params: {
return params.messageThreadId; return params.messageThreadId;
} }
export function resolveTelegramThreadSpec(params: {
isGroup: boolean;
isForum?: boolean;
messageThreadId?: number | null;
}): TelegramThreadSpec {
if (params.isGroup) {
const id = resolveTelegramForumThreadId({
isForum: params.isForum,
messageThreadId: params.messageThreadId,
});
return {
id,
scope: params.isForum ? "forum" : "none",
};
}
if (params.messageThreadId == null) {
return { scope: "dm" };
}
return {
id: params.messageThreadId,
scope: "dm",
};
}
/** /**
* Build thread params for Telegram API calls (messages, media). * Build thread params for Telegram API calls (messages, media).
* General forum topic (id=1) must be treated like a regular supergroup send: * General forum topic (id=1) must be treated like a regular supergroup send:
* Telegram rejects sendMessage/sendMedia with message_thread_id=1 ("thread not found"). * Telegram rejects sendMessage/sendMedia with message_thread_id=1 ("thread not found").
*/ */
export function buildTelegramThreadParams(messageThreadId?: number) { export function buildTelegramThreadParams(thread?: TelegramThreadSpec | number | null) {
if (messageThreadId == null) { let spec: TelegramThreadSpec | undefined;
if (typeof thread === "number") {
spec = { id: thread, scope: "forum" };
} else if (thread && typeof thread === "object") {
spec = thread;
}
if (!spec?.id) {
return undefined; return undefined;
} }
const normalized = Math.trunc(messageThreadId); const normalized = Math.trunc(spec.id);
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) { if (normalized === TELEGRAM_GENERAL_TOPIC_ID && spec.scope === "forum") {
return undefined; return undefined;
} }
return { message_thread_id: normalized }; return { message_thread_id: normalized };

View file

@ -8,7 +8,7 @@ describe("createTelegramDraftStream", () => {
api: api as any, api: api as any,
chatId: 123, chatId: 123,
draftId: 42, draftId: 42,
messageThreadId: 99, thread: { id: 99, scope: "forum" },
}); });
stream.update("Hello"); stream.update("Hello");
@ -24,11 +24,27 @@ describe("createTelegramDraftStream", () => {
api: api as any, api: api as any,
chatId: 123, chatId: 123,
draftId: 42, draftId: 42,
messageThreadId: 1, thread: { id: 1, scope: "forum" },
}); });
stream.update("Hello"); stream.update("Hello");
expect(api.sendMessageDraft).toHaveBeenCalledWith(123, 42, "Hello", undefined); expect(api.sendMessageDraft).toHaveBeenCalledWith(123, 42, "Hello", undefined);
}); });
it("keeps message_thread_id for dm threads", () => {
const api = { sendMessageDraft: vi.fn().mockResolvedValue(true) };
const stream = createTelegramDraftStream({
api: api as any,
chatId: 123,
draftId: 42,
thread: { id: 1, scope: "dm" },
});
stream.update("Hello");
expect(api.sendMessageDraft).toHaveBeenCalledWith(123, 42, "Hello", {
message_thread_id: 1,
});
});
}); });

View file

@ -1,5 +1,5 @@
import type { Bot } from "grammy"; import type { Bot } from "grammy";
import { buildTelegramThreadParams } from "./bot/helpers.js"; import { buildTelegramThreadParams, type TelegramThreadSpec } from "./bot/helpers.js";
const TELEGRAM_DRAFT_MAX_CHARS = 4096; const TELEGRAM_DRAFT_MAX_CHARS = 4096;
const DEFAULT_THROTTLE_MS = 300; const DEFAULT_THROTTLE_MS = 300;
@ -15,7 +15,7 @@ export function createTelegramDraftStream(params: {
chatId: number; chatId: number;
draftId: number; draftId: number;
maxChars?: number; maxChars?: number;
messageThreadId?: number; thread?: TelegramThreadSpec | number | null;
throttleMs?: number; throttleMs?: number;
log?: (message: string) => void; log?: (message: string) => void;
warn?: (message: string) => void; warn?: (message: string) => void;
@ -25,7 +25,7 @@ export function createTelegramDraftStream(params: {
const rawDraftId = Number.isFinite(params.draftId) ? Math.trunc(params.draftId) : 1; const rawDraftId = Number.isFinite(params.draftId) ? Math.trunc(params.draftId) : 1;
const draftId = rawDraftId === 0 ? 1 : Math.abs(rawDraftId); const draftId = rawDraftId === 0 ? 1 : Math.abs(rawDraftId);
const chatId = params.chatId; const chatId = params.chatId;
const threadParams = buildTelegramThreadParams(params.messageThreadId); const threadParams = buildTelegramThreadParams(params.thread);
let lastSentText = ""; let lastSentText = "";
let lastSentAt = 0; let lastSentAt = 0;