import type { Chat, Message } from "@grammyjs/types"; import type { TelegramDirectConfig, TelegramGroupConfig, TelegramTopicConfig } from "openclaw/plugin-sdk/config-runtime"; import { readChannelAllowFromStore } from "openclaw/plugin-sdk/conversation-runtime"; import { type NormalizedAllowFrom } from "../bot-access.js"; import { buildSenderLabel, buildSenderName, expandTextLinks, extractTelegramLocation, getTelegramTextParts, hasBotMention, normalizeForwardedContext, resolveTelegramMediaPlaceholder, type TelegramForwardedContext } from "./body-helpers.js"; import type { TelegramGetChat, TelegramStreamMode } from "./types.js"; export { buildSenderLabel, buildSenderName, expandTextLinks, extractTelegramLocation, getTelegramTextParts, hasBotMention, normalizeForwardedContext, resolveTelegramMediaPlaceholder, }; export type { TelegramForwardedContext, TelegramTextEntity } from "./body-helpers.js"; export type TelegramThreadSpec = { id?: number; scope: "dm" | "forum" | "none"; }; export declare function extractTelegramForumFlag(value: unknown): boolean | undefined; export declare function resolveTelegramForumFlag(params: { chatId: string | number; chatType?: Chat["type"]; isGroup: boolean; isForum?: boolean; getChat?: TelegramGetChat; }): Promise; export declare function withResolvedTelegramForumFlag(message: T, isForum: boolean): T; export declare function resolveTelegramGroupAllowFromContext(params: { chatId: string | number; accountId?: string; isGroup?: boolean; isForum?: boolean; messageThreadId?: number | null; groupAllowFrom?: Array; readChannelAllowFromStore?: typeof readChannelAllowFromStore; resolveTelegramGroupConfig: (chatId: string | number, messageThreadId?: number) => { groupConfig?: TelegramGroupConfig | TelegramDirectConfig; topicConfig?: TelegramTopicConfig; }; }): Promise<{ resolvedThreadId?: number; dmThreadId?: number; storeAllowFrom: string[]; groupConfig?: TelegramGroupConfig | TelegramDirectConfig; topicConfig?: TelegramTopicConfig; groupAllowOverride?: Array; effectiveGroupAllow: NormalizedAllowFrom; hasGroupAllowOverride: boolean; }>; /** * Resolve the thread ID for Telegram forum topics. * For non-forum groups, returns undefined even if messageThreadId is present * (reply threads in regular groups should not create separate sessions). * For forum groups, returns the topic ID (or General topic ID=1 if unspecified). */ export declare function resolveTelegramForumThreadId(params: { isForum?: boolean; messageThreadId?: number | null; }): number | undefined; export declare function resolveTelegramThreadSpec(params: { isGroup: boolean; isForum?: boolean; messageThreadId?: number | null; }): TelegramThreadSpec; /** * Build thread params for Telegram API calls (messages, media). * * IMPORTANT: Thread IDs behave differently based on chat type: * - DMs (private chats): Include message_thread_id when present (DM topics) * - Forum topics: Skip thread_id=1 (General topic), include others * - Regular groups: Thread IDs are ignored by Telegram * * 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"). * * @param thread - Thread specification with ID and scope * @returns API params object or undefined if thread_id should be omitted */ export declare function buildTelegramThreadParams(thread?: TelegramThreadSpec | null): { message_thread_id: number; } | undefined; /** * Build a Telegram routing target that keeps real topic/thread ids in-band. * * This is used by generic reply plumbing that may not always carry a separate * `threadId` field through every hop. General forum topic stays chat-scoped * because Telegram rejects `message_thread_id=1` for message sends. */ export declare function buildTelegramRoutingTarget(chatId: number | string, thread?: TelegramThreadSpec | null): string; /** * Build thread params for typing indicators (sendChatAction). * Empirically, General topic (id=1) needs message_thread_id for typing to appear. */ export declare function buildTypingThreadParams(messageThreadId?: number): { message_thread_id: number; } | undefined; export declare function resolveTelegramStreamMode(telegramCfg?: { streaming?: unknown; streamMode?: unknown; }): TelegramStreamMode; export declare function buildTelegramGroupPeerId(chatId: number | string, messageThreadId?: number): string; /** * Resolve the direct-message peer identifier for Telegram routing/session keys. * * In some Telegram DM deliveries (for example certain business/chat bridge flows), * `chat.id` can differ from the actual sender user id. Prefer sender id when present * so per-peer DM scopes isolate users correctly. */ export declare function resolveTelegramDirectPeerId(params: { chatId: number | string; senderId?: number | string | null; }): string; export declare function buildTelegramGroupFrom(chatId: number | string, messageThreadId?: number): string; /** * Build parentPeer for forum topic binding inheritance. * When a message comes from a forum topic, the peer ID includes the topic suffix * (e.g., `-1001234567890:topic:99`). To allow bindings configured for the base * group ID to match, we provide the parent group as `parentPeer` so the routing * layer can fall back to it when the exact peer doesn't match. */ export declare function buildTelegramParentPeer(params: { isGroup: boolean; resolvedThreadId?: number; chatId: number | string; }): { kind: "group"; id: string; } | undefined; export declare function buildGroupLabel(msg: Message, chatId: number | string, messageThreadId?: number): string; export declare function resolveTelegramReplyId(raw?: string): number | undefined; export type TelegramReplyTarget = { id?: string; sender: string; senderId?: string; senderUsername?: string; body: string; kind: "reply" | "quote"; /** Forward context if the reply target was itself a forwarded message (issue #9619). */ forwardedFrom?: TelegramForwardedContext; }; export declare function describeReplyTarget(msg: Message): TelegramReplyTarget | null;