Merge 2d41a275af into 0761652701
This commit is contained in:
commit
4b360f44ea
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
import { hasControlCommand } from "../auto-reply/command-detection.js";
|
||||
import {
|
||||
createInboundDebouncer,
|
||||
@ -14,14 +13,53 @@ import { danger, logVerbose, warn } from "../globals.js";
|
||||
import { resolveMedia } from "./bot/delivery.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import { resolveTelegramForumThreadId } from "./bot/helpers.js";
|
||||
import type { TelegramMessage } from "./bot/types.js";
|
||||
import type { TelegramContext, TelegramMessage } from "./bot/types.js";
|
||||
import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
|
||||
import { MEDIA_GROUP_TIMEOUT_MS, type MediaGroupEntry } from "./bot-updates.js";
|
||||
import type { TelegramUpdateKeyContext } from "./bot-updates.js";
|
||||
import { migrateTelegramGroupConfig } from "./group-migration.js";
|
||||
import { resolveTelegramInlineButtonsScope } from "./inline-buttons.js";
|
||||
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
||||
import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js";
|
||||
import { buildInlineKeyboard } from "./send.js";
|
||||
import type { Bot } from "grammy";
|
||||
import type { MoltbotConfig } from "../config/types.clawdbot.js";
|
||||
import type {
|
||||
TelegramAccountConfig,
|
||||
TelegramGroupConfig,
|
||||
TelegramTopicConfig,
|
||||
} from "../config/types.telegram.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { ChannelGroupPolicy } from "../config/group-policy.js";
|
||||
import { Logger } from "tslog";
|
||||
|
||||
type TelegramHandlersParams = {
|
||||
cfg: MoltbotConfig;
|
||||
accountId: string | undefined;
|
||||
bot: Bot;
|
||||
opts: { token: string; proxyFetch?: typeof fetch };
|
||||
runtime: RuntimeEnv;
|
||||
mediaMaxBytes: number;
|
||||
telegramCfg: TelegramAccountConfig;
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
resolveGroupPolicy: (chatId: string | number) => ChannelGroupPolicy;
|
||||
resolveTelegramGroupConfig: (
|
||||
chatId: string | number,
|
||||
messageThreadId?: number,
|
||||
) => { groupConfig?: TelegramGroupConfig; topicConfig?: TelegramTopicConfig };
|
||||
shouldSkipUpdate: (ctx: TelegramUpdateKeyContext) => boolean;
|
||||
processMessage: (
|
||||
primaryCtx: TelegramContext,
|
||||
allMedia: Array<{
|
||||
path: string;
|
||||
contentType?: string;
|
||||
stickerMetadata?: { emoji?: string; setName?: string; fileId?: string };
|
||||
}>,
|
||||
storeAllowFrom: string[],
|
||||
options?: { forceWasMentioned?: boolean; messageIdOverride?: string },
|
||||
) => Promise<void>;
|
||||
logger: Logger<Record<string, unknown>>;
|
||||
};
|
||||
|
||||
export const registerTelegramHandlers = ({
|
||||
cfg,
|
||||
@ -37,7 +75,7 @@ export const registerTelegramHandlers = ({
|
||||
shouldSkipUpdate,
|
||||
processMessage,
|
||||
logger,
|
||||
}) => {
|
||||
}: TelegramHandlersParams) => {
|
||||
const TELEGRAM_TEXT_FRAGMENT_START_THRESHOLD_CHARS = 4000;
|
||||
const TELEGRAM_TEXT_FRAGMENT_MAX_GAP_MS = 1500;
|
||||
const TELEGRAM_TEXT_FRAGMENT_MAX_ID_GAP = 1;
|
||||
@ -49,7 +87,7 @@ export const registerTelegramHandlers = ({
|
||||
|
||||
type TextFragmentEntry = {
|
||||
key: string;
|
||||
messages: Array<{ msg: TelegramMessage; ctx: unknown; receivedAtMs: number }>;
|
||||
messages: Array<{ msg: TelegramMessage; ctx: TelegramContext; receivedAtMs: number }>;
|
||||
timer: ReturnType<typeof setTimeout>;
|
||||
};
|
||||
const textFragmentBuffer = new Map<string, TextFragmentEntry>();
|
||||
@ -57,7 +95,7 @@ export const registerTelegramHandlers = ({
|
||||
|
||||
const debounceMs = resolveInboundDebounceMs({ cfg, channel: "telegram" });
|
||||
type TelegramDebounceEntry = {
|
||||
ctx: unknown;
|
||||
ctx: TelegramContext;
|
||||
msg: TelegramMessage;
|
||||
allMedia: Array<{ path: string; contentType?: string }>;
|
||||
storeAllowFrom: string[];
|
||||
@ -86,9 +124,8 @@ export const registerTelegramHandlers = ({
|
||||
.join("\n");
|
||||
if (!combinedText.trim()) return;
|
||||
const first = entries[0];
|
||||
const baseCtx = first.ctx as { me?: unknown; getFile?: unknown } & Record<string, unknown>;
|
||||
const getFile =
|
||||
typeof baseCtx.getFile === "function" ? baseCtx.getFile.bind(baseCtx) : async () => ({});
|
||||
const baseCtx = first.ctx;
|
||||
const getFile = baseCtx.getFile.bind(baseCtx) ?? (async () => ({}));
|
||||
const syntheticMessage: TelegramMessage = {
|
||||
...first.msg,
|
||||
text: combinedText,
|
||||
@ -161,9 +198,8 @@ export const registerTelegramHandlers = ({
|
||||
};
|
||||
|
||||
const storeAllowFrom = await readTelegramAllowFromStore().catch(() => []);
|
||||
const baseCtx = first.ctx as { me?: unknown; getFile?: unknown } & Record<string, unknown>;
|
||||
const getFile =
|
||||
typeof baseCtx.getFile === "function" ? baseCtx.getFile.bind(baseCtx) : async () => ({});
|
||||
const baseCtx = first.ctx;
|
||||
const getFile = baseCtx.getFile.bind(baseCtx) ?? (async () => ({}));
|
||||
|
||||
await processMessage(
|
||||
{ message: syntheticMessage, me: baseCtx.me, getFile },
|
||||
@ -377,7 +413,7 @@ export const registerTelegramHandlers = ({
|
||||
caption_entities: undefined,
|
||||
entities: undefined,
|
||||
};
|
||||
const getFile = typeof ctx.getFile === "function" ? ctx.getFile.bind(ctx) : async () => ({});
|
||||
const getFile = ctx.getFile.bind(ctx) ?? (async () => ({}));
|
||||
await processMessage({ message: syntheticMessage, me: ctx.me, getFile }, [], storeAllowFrom, {
|
||||
forceWasMentioned: true,
|
||||
messageIdOverride: callback.id,
|
||||
|
||||
@ -27,6 +27,7 @@ import { logVerbose, shouldLogVerbose } from "../globals.js";
|
||||
import { recordChannelActivity } from "../infra/channel-activity.js";
|
||||
import { resolveAgentRoute } from "../routing/resolve-route.js";
|
||||
import { resolveThreadSessionKeys } from "../routing/session-key.js";
|
||||
import type { ResolvedAgentRoute } from "../routing/resolve-route.js";
|
||||
import { shouldAckReaction as shouldAckReactionGate } from "../channels/ack-reactions.js";
|
||||
import { resolveMentionGatingWithBypass } from "../channels/mention-gating.js";
|
||||
import { resolveControlCommandGate } from "../channels/command-gating.js";
|
||||
@ -54,6 +55,8 @@ import {
|
||||
} from "./bot-access.js";
|
||||
import { upsertTelegramPairingRequest } from "./pairing-store.js";
|
||||
import type { TelegramContext } from "./bot/types.js";
|
||||
import type { FinalizedMsgContext } from "../auto-reply/templating.js";
|
||||
import type { TelegramMessage } from "./bot/types.js";
|
||||
|
||||
type TelegramMediaRef = {
|
||||
path: string;
|
||||
@ -110,6 +113,33 @@ type BuildTelegramMessageContextParams = {
|
||||
resolveTelegramGroupConfig: ResolveTelegramGroupConfig;
|
||||
};
|
||||
|
||||
export type TelegramMessageContext = {
|
||||
ctxPayload: FinalizedMsgContext;
|
||||
primaryCtx: TelegramContext;
|
||||
msg: TelegramMessage;
|
||||
chatId: number;
|
||||
isGroup: boolean;
|
||||
resolvedThreadId?: number;
|
||||
isForum: boolean;
|
||||
historyKey?: string;
|
||||
historyLimit: number;
|
||||
groupHistories: Map<string, HistoryEntry[]>;
|
||||
route: ResolvedAgentRoute;
|
||||
skillFilter?: string[];
|
||||
sendTyping: () => Promise<void>;
|
||||
sendRecordVoice: () => Promise<void>;
|
||||
ackReactionPromise: Promise<boolean> | null;
|
||||
reactionApi:
|
||||
| ((
|
||||
chatId: number | string,
|
||||
messageId: number,
|
||||
reactions: Array<{ type: "emoji"; emoji: string }>,
|
||||
) => Promise<void>)
|
||||
| null;
|
||||
removeAckAfterReply: boolean;
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
async function resolveStickerVisionSupport(params: {
|
||||
cfg: MoltbotConfig;
|
||||
agentId?: string;
|
||||
@ -231,9 +261,7 @@ export const buildTelegramMessageContext = async ({
|
||||
senderId: candidate,
|
||||
senderUsername,
|
||||
});
|
||||
const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${
|
||||
allowMatch.matchSource ?? "none"
|
||||
}`;
|
||||
const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${allowMatch.matchSource ?? "none"}`;
|
||||
const allowed =
|
||||
effectiveDmAllow.hasWildcard || (effectiveDmAllow.hasEntries && allowMatch.allowed);
|
||||
if (!allowed) {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js";
|
||||
import {
|
||||
findModelInCatalog,
|
||||
@ -20,8 +19,15 @@ import { resolveTelegramDraftStreamingChunking } from "./draft-chunking.js";
|
||||
import { createTelegramDraftStream } from "./draft-stream.js";
|
||||
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
|
||||
import { resolveAgentDir } from "../agents/agent-scope.js";
|
||||
import type { MoltbotConfig } from "../config/types.clawdbot.js";
|
||||
import type { TelegramAccountConfig } from "../config/types.telegram.js";
|
||||
import type { ReplyToMode } from "../config/types.base.js";
|
||||
import type { Bot } from "grammy";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { TelegramContext, TelegramStreamMode } from "./bot/types.js";
|
||||
import type { TelegramMessageContext } from "./bot-message-context.js";
|
||||
|
||||
async function resolveStickerVisionSupport(cfg, agentId) {
|
||||
async function resolveStickerVisionSupport(cfg: MoltbotConfig, agentId: string) {
|
||||
try {
|
||||
const catalog = await loadModelCatalog({ config: cfg });
|
||||
const defaultModel = resolveDefaultModelForAgent({ cfg, agentId });
|
||||
@ -44,6 +50,17 @@ export const dispatchTelegramMessage = async ({
|
||||
telegramCfg,
|
||||
opts,
|
||||
resolveBotTopicsEnabled,
|
||||
}: {
|
||||
context: TelegramMessageContext;
|
||||
bot: Bot;
|
||||
cfg: MoltbotConfig;
|
||||
runtime: RuntimeEnv;
|
||||
replyToMode: ReplyToMode;
|
||||
streamMode: TelegramStreamMode;
|
||||
textLimit: number;
|
||||
telegramCfg: TelegramAccountConfig;
|
||||
opts: { token: string };
|
||||
resolveBotTopicsEnabled: (ctx: TelegramContext) => Promise<boolean>;
|
||||
}) => {
|
||||
const {
|
||||
ctxPayload,
|
||||
@ -185,16 +202,20 @@ export const dispatchTelegramMessage = async ({
|
||||
}
|
||||
|
||||
// Cache the description for future encounters
|
||||
cacheSticker({
|
||||
fileId: sticker.fileId,
|
||||
fileUniqueId: sticker.fileUniqueId,
|
||||
emoji: sticker.emoji,
|
||||
setName: sticker.setName,
|
||||
description,
|
||||
cachedAt: new Date().toISOString(),
|
||||
receivedFrom: ctxPayload.From,
|
||||
});
|
||||
logVerbose(`telegram: cached sticker description for ${sticker.fileUniqueId}`);
|
||||
if (sticker.fileId) {
|
||||
cacheSticker({
|
||||
fileId: sticker.fileId,
|
||||
fileUniqueId: sticker.fileUniqueId,
|
||||
emoji: sticker.emoji,
|
||||
setName: sticker.setName,
|
||||
description,
|
||||
cachedAt: new Date().toISOString(),
|
||||
receivedFrom: ctxPayload.From,
|
||||
});
|
||||
logVerbose(`telegram: cached sticker description for ${sticker.fileUniqueId}`);
|
||||
} else {
|
||||
logVerbose(`telegram: skipped sticker cache (missing fileId) ${sticker.fileUniqueId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,50 @@
|
||||
// @ts-nocheck
|
||||
import type { Bot } from "grammy";
|
||||
import { buildTelegramMessageContext } from "./bot-message-context.js";
|
||||
import { dispatchTelegramMessage } from "./bot-message-dispatch.js";
|
||||
import type { MoltbotConfig } from "../config/types.clawdbot.js";
|
||||
import type {
|
||||
TelegramAccountConfig,
|
||||
TelegramGroupConfig,
|
||||
TelegramTopicConfig,
|
||||
} from "../config/types.telegram.js";
|
||||
import type { DmPolicy, ReplyToMode } from "../config/types.base.js";
|
||||
import type { HistoryEntry } from "../auto-reply/reply/history.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { TelegramContext, TelegramStreamMode } from "./bot/types.js";
|
||||
|
||||
export const createTelegramMessageProcessor = (deps) => {
|
||||
export const createTelegramMessageProcessor = (deps: {
|
||||
bot: Bot;
|
||||
cfg: MoltbotConfig;
|
||||
account: { accountId: string };
|
||||
telegramCfg: TelegramAccountConfig;
|
||||
historyLimit: number;
|
||||
groupHistories: Map<string, HistoryEntry[]>;
|
||||
dmPolicy: DmPolicy;
|
||||
allowFrom?: (string | number)[];
|
||||
groupAllowFrom?: (string | number)[];
|
||||
ackReactionScope: "off" | "group-mentions" | "group-all" | "direct" | "all";
|
||||
logger: { info: (obj: Record<string, unknown>, msg: string) => void };
|
||||
resolveGroupActivation: (params: {
|
||||
chatId: string | number;
|
||||
agentId?: string;
|
||||
messageThreadId?: number;
|
||||
sessionKey?: string;
|
||||
}) => boolean | undefined;
|
||||
resolveGroupRequireMention: (chatId: string | number) => boolean;
|
||||
resolveTelegramGroupConfig: (
|
||||
chatId: string | number,
|
||||
messageThreadId?: number,
|
||||
) => {
|
||||
groupConfig?: TelegramGroupConfig;
|
||||
topicConfig?: TelegramTopicConfig;
|
||||
};
|
||||
runtime: RuntimeEnv;
|
||||
replyToMode: ReplyToMode;
|
||||
streamMode: TelegramStreamMode;
|
||||
textLimit: number;
|
||||
opts: { token: string };
|
||||
resolveBotTopicsEnabled: (ctx?: TelegramContext) => Promise<boolean>;
|
||||
}) => {
|
||||
const {
|
||||
bot,
|
||||
cfg,
|
||||
@ -26,7 +68,16 @@ export const createTelegramMessageProcessor = (deps) => {
|
||||
resolveBotTopicsEnabled,
|
||||
} = deps;
|
||||
|
||||
return async (primaryCtx, allMedia, storeAllowFrom, options) => {
|
||||
return async (
|
||||
primaryCtx: TelegramContext,
|
||||
allMedia: Array<{
|
||||
path: string;
|
||||
contentType?: string;
|
||||
stickerMetadata?: { emoji?: string; setName?: string; fileId?: string };
|
||||
}>,
|
||||
storeAllowFrom: string[],
|
||||
options?: { forceWasMentioned?: boolean; messageIdOverride?: string },
|
||||
) => {
|
||||
const context = await buildTelegramMessageContext({
|
||||
primaryCtx,
|
||||
allMedia,
|
||||
|
||||
@ -49,6 +49,7 @@ import {
|
||||
} from "./bot/helpers.js";
|
||||
import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js";
|
||||
import { readTelegramAllowFromStore } from "./pairing-store.js";
|
||||
import type { TelegramUpdateKeyContext } from "./bot-updates.js";
|
||||
|
||||
type TelegramNativeCommandContext = Context & { match?: string };
|
||||
|
||||
@ -83,7 +84,7 @@ type RegisterTelegramNativeCommandsParams = {
|
||||
chatId: string | number,
|
||||
messageThreadId?: number,
|
||||
) => { groupConfig?: TelegramGroupConfig; topicConfig?: TelegramTopicConfig };
|
||||
shouldSkipUpdate: (ctx: unknown) => boolean;
|
||||
shouldSkipUpdate: (ctx: TelegramUpdateKeyContext) => boolean;
|
||||
opts: { token: string };
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
import { sequentialize } from "@grammyjs/runner";
|
||||
import { apiThrottler } from "@grammyjs/transformer-throttler";
|
||||
import type { ApiClientOptions } from "grammy";
|
||||
@ -46,6 +45,7 @@ import {
|
||||
} from "./bot-updates.js";
|
||||
import { resolveTelegramFetch } from "./fetch.js";
|
||||
import { wasSentByBot } from "./sent-message-cache.js";
|
||||
import type { ReactionTypeEmoji } from "grammy/types";
|
||||
|
||||
export type TelegramBotOptions = {
|
||||
token: string;
|
||||
@ -385,11 +385,15 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
||||
// Detect added reactions
|
||||
const oldEmojis = new Set(
|
||||
reaction.old_reaction
|
||||
.filter((r): r is { type: "emoji"; emoji: string } => r.type === "emoji")
|
||||
.filter(
|
||||
(r): r is { type: "emoji"; emoji: ReactionTypeEmoji["emoji"] } => r.type === "emoji",
|
||||
)
|
||||
.map((r) => r.emoji),
|
||||
);
|
||||
const addedReactions = reaction.new_reaction
|
||||
.filter((r): r is { type: "emoji"; emoji: string } => r.type === "emoji")
|
||||
.filter(
|
||||
(r): r is { type: "emoji"; emoji: ReactionTypeEmoji["emoji"] } => r.type === "emoji",
|
||||
)
|
||||
.filter((r) => !oldEmojis.has(r.emoji));
|
||||
|
||||
if (addedReactions.length === 0) return;
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
// @ts-nocheck
|
||||
import type { Dispatcher } from "undici";
|
||||
import { ProxyAgent } from "undici";
|
||||
import { wrapFetchWithAbortSignal } from "../infra/fetch.js";
|
||||
|
||||
type FetchInit = RequestInit & { dispatcher?: Dispatcher };
|
||||
|
||||
export function makeProxyFetch(proxyUrl: string): typeof fetch {
|
||||
const agent = new ProxyAgent(proxyUrl);
|
||||
return wrapFetchWithAbortSignal((input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const base = init ? { ...init } : {};
|
||||
return fetch(input, { ...base, dispatcher: agent });
|
||||
const nextInit: FetchInit = { ...base, dispatcher: agent };
|
||||
return fetch(input, nextInit);
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user