import type { MessageEntity } from "@grammyjs/types"; import { buildTelegramThreadParams, type TelegramThreadSpec } from "./bot/helpers.js"; import { normalizeTelegramReplyToMessageId } from "./outbound-params.js"; type TelegramReplyParameters = { message_id: number; allow_sending_without_reply: true; quote?: string; quote_position?: number; quote_entities?: MessageEntity[]; }; type TelegramThreadReplyParams = { message_thread_id?: number; reply_parameters?: TelegramReplyParameters; reply_to_message_id?: number; allow_sending_without_reply?: true; }; export function resolveTelegramSendThreadSpec(params: { targetMessageThreadId?: number; messageThreadId?: number; chatType?: "direct" | "group" | "unknown"; }): TelegramThreadSpec | undefined { const messageThreadId = params.messageThreadId != null ? params.messageThreadId : params.targetMessageThreadId; if (messageThreadId == null) { return undefined; } // Telegram supports DM topics; keep direct chat thread IDs and rely on // thread-not-found retry fallback when a plain DM rejects them. return { id: messageThreadId, scope: params.chatType === "direct" ? "dm" : "forum", }; } export function buildTelegramThreadReplyParams(opts?: { thread?: TelegramThreadSpec | null; replyToMessageId?: number; replyQuoteMessageId?: number; replyQuoteText?: string; replyQuotePosition?: number; replyQuoteEntities?: unknown[]; useReplyIdAsQuoteSource?: boolean; }): TelegramThreadReplyParams { const params: TelegramThreadReplyParams = {}; const threadParams = buildTelegramThreadParams(opts?.thread); if (threadParams) { params.message_thread_id = threadParams.message_thread_id; } const replyToMessageId = normalizeTelegramReplyToMessageId(opts?.replyToMessageId); if (replyToMessageId == null) { return params; } const defaultQuoteMessageId = opts?.useReplyIdAsQuoteSource === true ? replyToMessageId : undefined; const replyQuoteMessageId = normalizeTelegramReplyToMessageId( opts?.replyQuoteMessageId ?? defaultQuoteMessageId, ); const replyQuoteTextRaw = replyQuoteMessageId === replyToMessageId ? opts?.replyQuoteText : undefined; const replyQuoteText = replyQuoteTextRaw?.trim() ? replyQuoteTextRaw : undefined; if (!replyQuoteText) { params.reply_to_message_id = replyToMessageId; params.allow_sending_without_reply = true; return params; } const replyParameters: TelegramReplyParameters = { message_id: replyToMessageId, quote: replyQuoteText, allow_sending_without_reply: true, }; if (typeof opts?.replyQuotePosition === "number" && Number.isFinite(opts.replyQuotePosition)) { replyParameters.quote_position = Math.trunc(opts.replyQuotePosition); } if (Array.isArray(opts?.replyQuoteEntities) && opts.replyQuoteEntities.length > 0) { replyParameters.quote_entities = opts.replyQuoteEntities as MessageEntity[]; } params.reply_parameters = replyParameters; return params; } export function buildTelegramSendParams(opts?: { replyToMessageId?: number; replyQuoteMessageId?: number; replyQuoteText?: string; replyQuotePosition?: number; replyQuoteEntities?: unknown[]; thread?: TelegramThreadSpec | null; silent?: boolean; useReplyIdAsQuoteSource?: boolean; }): Record { const params: Record = { ...buildTelegramThreadReplyParams(opts) }; if (opts?.silent === true) { params.disable_notification = true; } return params; } export function getTelegramNativeQuoteReplyMessageId( params: Record | undefined, ): number | undefined { const replyParameters = params?.reply_parameters; if (!replyParameters || typeof replyParameters !== "object") { return undefined; } const messageId = (replyParameters as { message_id?: unknown }).message_id; return typeof messageId === "number" && Number.isFinite(messageId) ? messageId : undefined; } export function removeTelegramNativeQuoteParam( params: Record | undefined, ): Record { if (!params) { return {}; } const replyMessageId = getTelegramNativeQuoteReplyMessageId(params); const { reply_parameters: _ignored, ...rest } = params; if (replyMessageId != null) { rest.reply_to_message_id = replyMessageId; rest.allow_sending_without_reply = true; } return rest; }