mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
fix(telegram): apply group media policy before inbound downloads (#117537)
* fix(telegram): apply group media policy before inbound downloads * test(telegram): consolidate inbound media owner regression matrix * fix(telegram): format media group ingest * test(telegram): restore media group policy coverage --------- Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
committed by
GitHub
parent
14322647cd
commit
aadbb00f2c
@@ -29,6 +29,7 @@ import type { TelegramSpooledReplayDeferredParticipant } from "./bot-processing-
|
||||
import { MEDIA_GROUP_TIMEOUT_MS, type MediaGroupEntry } from "./bot-updates.js";
|
||||
import { resolveMedia } from "./bot/delivery.resolve-media.js";
|
||||
import {
|
||||
buildTelegramGroupPeerId,
|
||||
buildTelegramThreadParams,
|
||||
getTelegramTextParts,
|
||||
hasBotMention,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
} from "./bot/helpers.js";
|
||||
import type { TelegramContext } from "./bot/types.js";
|
||||
import { isTelegramForumServiceMessage } from "./forum-service-message.js";
|
||||
import { resolveTelegramGroupIngestEnabled } from "./group-config-helpers.js";
|
||||
import { resolveTelegramCommandIngressAuthorization } from "./ingress.js";
|
||||
import type { TelegramMessageDispatchReplayClaim } from "./message-dispatch-dedupe.js";
|
||||
|
||||
@@ -68,6 +70,8 @@ type BufferedMediaGroupEntry = MediaGroupEntry &
|
||||
spooledReplayParticipants: TelegramSpooledReplayDeferredParticipant[];
|
||||
};
|
||||
|
||||
type TelegramGroupMediaDisposition = "process" | "skip" | "silent-ingest";
|
||||
|
||||
export function createTelegramInboundMediaGroupRuntime(
|
||||
params: Pick<
|
||||
RegisterTelegramHandlerParams,
|
||||
@@ -114,9 +118,9 @@ export function createTelegramInboundMediaGroupRuntime(
|
||||
const buffer = new Map<string, BufferedMediaGroupEntry>();
|
||||
const queue = new KeyedAsyncQueue();
|
||||
|
||||
const shouldSkipMediaDownloadForUnaddressedMentionGroup = async (
|
||||
const resolveUnaddressedGroupMediaDisposition = async (
|
||||
authorization: MediaAuthorization & { ctx: TelegramContext; msg: Message },
|
||||
): Promise<boolean> => {
|
||||
): Promise<TelegramGroupMediaDisposition> => {
|
||||
const { ctx, msg, chatId, isGroup, isForum, resolvedThreadId, dmThreadId, senderId } =
|
||||
authorization;
|
||||
const textParts = getTelegramTextParts(msg);
|
||||
@@ -129,7 +133,7 @@ export function createTelegramInboundMediaGroupRuntime(
|
||||
// history, fires ingest hooks, and settles an explicit skipped result;
|
||||
// consuming them here tombstones the ingress row without any trace.
|
||||
if (!isGroup || !hasInboundMedia(msg) || mayNeedDownload) {
|
||||
return false;
|
||||
return "process";
|
||||
}
|
||||
const sessionState = resolveTelegramSessionState({
|
||||
chatId,
|
||||
@@ -154,12 +158,18 @@ export function createTelegramInboundMediaGroupRuntime(
|
||||
resolveGroupRequireMention(chatId, authorization.authorizationCfg),
|
||||
);
|
||||
if (!requireMention) {
|
||||
return false;
|
||||
return "process";
|
||||
}
|
||||
const botUsername = ctx.me?.username?.trim().toLowerCase();
|
||||
const mentionRegexes = buildMentionRegexes(
|
||||
authorization.authorizationCfg,
|
||||
sessionState.agentId,
|
||||
{
|
||||
provider: "telegram",
|
||||
conversationId: buildTelegramGroupPeerId(chatId, resolvedThreadId),
|
||||
providerPolicy:
|
||||
authorization.authorizationCfg.channels?.telegram?.accounts?.[accountId]?.mentionPatterns,
|
||||
},
|
||||
);
|
||||
const hasAnyMention = textParts.entities.some((entity) => entity.type === "mention");
|
||||
const explicitlyMentioned = botUsername ? hasBotMention(msg, botUsername) : false;
|
||||
@@ -215,10 +225,20 @@ export function createTelegramInboundMediaGroupRuntime(
|
||||
},
|
||||
});
|
||||
if (decision.shouldSkip) {
|
||||
if (
|
||||
resolveTelegramGroupIngestEnabled({
|
||||
cfg: authorization.authorizationCfg,
|
||||
chatId,
|
||||
accountId,
|
||||
topicConfig: authorization.topicConfig,
|
||||
})
|
||||
) {
|
||||
return "silent-ingest";
|
||||
}
|
||||
logger.info({ chatId, reason: "no-mention" }, "skipping group media before download");
|
||||
return true;
|
||||
return "skip";
|
||||
}
|
||||
return false;
|
||||
return "process";
|
||||
};
|
||||
|
||||
const processMediaGroup = async (entry: BufferedMediaGroupEntry) => {
|
||||
@@ -275,7 +295,11 @@ export function createTelegramInboundMediaGroupRuntime(
|
||||
});
|
||||
primary = { ctx: combinedContext, msg: combinedMessage };
|
||||
}
|
||||
if (await shouldSkipMediaDownloadForUnaddressedMentionGroup({ ...entry, ...primary })) {
|
||||
const mediaDisposition = await resolveUnaddressedGroupMediaDisposition({
|
||||
...entry,
|
||||
...primary,
|
||||
});
|
||||
if (mediaDisposition === "skip") {
|
||||
releaseDispatchDedupeClaims(entry.dispatchDedupeClaims);
|
||||
settleSpooledReplayParticipants(entry.spooledReplayParticipants, { kind: "skipped" });
|
||||
return;
|
||||
@@ -324,7 +348,7 @@ export function createTelegramInboundMediaGroupRuntime(
|
||||
skippedCount++;
|
||||
}
|
||||
}
|
||||
if (skippedCount > 0) {
|
||||
if (skippedCount > 0 && mediaDisposition !== "silent-ingest") {
|
||||
const verb = skippedCount === 1 ? "was" : "were";
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
@@ -432,5 +456,5 @@ export function createTelegramInboundMediaGroupRuntime(
|
||||
return true;
|
||||
};
|
||||
|
||||
return { handleMediaGroup, shouldSkipMediaDownloadForUnaddressedMentionGroup };
|
||||
return { handleMediaGroup, resolveUnaddressedGroupMediaDisposition };
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export function createTelegramHandlerInboundRuntime(
|
||||
resolveTelegramDebounceLane,
|
||||
} = createTelegramInboundDebounceRuntime({ cfg, bot, runtime }, messageRuntime);
|
||||
|
||||
const { handleMediaGroup, shouldSkipMediaDownloadForUnaddressedMentionGroup } =
|
||||
const { handleMediaGroup, resolveUnaddressedGroupMediaDisposition } =
|
||||
createTelegramInboundMediaGroupRuntime(
|
||||
{
|
||||
accountId,
|
||||
@@ -204,23 +204,22 @@ export function createTelegramHandlerInboundRuntime(
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
await shouldSkipMediaDownloadForUnaddressedMentionGroup({
|
||||
authorizationCfg,
|
||||
ctx,
|
||||
msg,
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
resolvedThreadId,
|
||||
dmThreadId,
|
||||
senderId,
|
||||
effectiveGroupAllow,
|
||||
effectiveDmAllow,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
})
|
||||
) {
|
||||
const mediaDisposition = await resolveUnaddressedGroupMediaDisposition({
|
||||
authorizationCfg,
|
||||
ctx,
|
||||
msg,
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
resolvedThreadId,
|
||||
dmThreadId,
|
||||
senderId,
|
||||
effectiveGroupAllow,
|
||||
effectiveDmAllow,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
});
|
||||
if (mediaDisposition === "skip") {
|
||||
releaseDispatchDedupeClaims(dispatchDedupeClaims);
|
||||
return;
|
||||
}
|
||||
@@ -254,7 +253,7 @@ export function createTelegramHandlerInboundRuntime(
|
||||
return;
|
||||
}
|
||||
if (isMediaSizeLimitError(mediaErr)) {
|
||||
if (sendOversizeWarning) {
|
||||
if (sendOversizeWarning && mediaDisposition !== "silent-ingest") {
|
||||
const limitMb =
|
||||
mediaErr instanceof TelegramBotApiFileTooLargeError
|
||||
? Math.min(mediaErr.limitMb, Math.round(mediaMaxBytes / (1024 * 1024)))
|
||||
@@ -281,18 +280,20 @@ export function createTelegramHandlerInboundRuntime(
|
||||
releaseDispatchDedupeClaims(dispatchDedupeClaims, mediaErr);
|
||||
return;
|
||||
}
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
runtime,
|
||||
fn: () =>
|
||||
bot.api.sendMessage(chatId, "⚠️ Failed to download media. Please try again.", {
|
||||
...warningThreadParams,
|
||||
reply_parameters: {
|
||||
message_id: msg.message_id,
|
||||
allow_sending_without_reply: true,
|
||||
},
|
||||
}),
|
||||
}).catch(() => {});
|
||||
if (mediaDisposition !== "silent-ingest") {
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
runtime,
|
||||
fn: () =>
|
||||
bot.api.sendMessage(chatId, "⚠️ Failed to download media. Please try again.", {
|
||||
...warningThreadParams,
|
||||
reply_parameters: {
|
||||
message_id: msg.message_id,
|
||||
allow_sending_without_reply: true,
|
||||
},
|
||||
}),
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
type InboundEventKind,
|
||||
type NormalizedLocation,
|
||||
} from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { resolveChannelGroupPolicy } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
|
||||
import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
@@ -55,6 +54,7 @@ import {
|
||||
import { buildTelegramGroupPeerId, buildTelegramInboundOriginTarget } from "./bot/helpers.js";
|
||||
import type { TelegramContext } from "./bot/types.js";
|
||||
import { isTelegramForumServiceMessage } from "./forum-service-message.js";
|
||||
import { resolveTelegramGroupIngestEnabled } from "./group-config-helpers.js";
|
||||
import { recordTelegramGroupHistoryEntry } from "./group-history-window.js";
|
||||
import { resolveTelegramCommandIngressAuthorization } from "./ingress.js";
|
||||
type TelegramMentionFacts = NonNullable<
|
||||
@@ -388,17 +388,7 @@ export async function resolveTelegramInboundBody(params: {
|
||||
messageId: typeof msg.message_id === "number" ? String(msg.message_id) : undefined,
|
||||
},
|
||||
});
|
||||
const telegramGroupPolicy = resolveChannelGroupPolicy({
|
||||
cfg,
|
||||
channel: "telegram",
|
||||
groupId: String(chatId),
|
||||
accountId,
|
||||
});
|
||||
const ingestEnabled =
|
||||
topicConfig?.ingest ??
|
||||
telegramGroupPolicy.groupConfig?.ingest ??
|
||||
telegramGroupPolicy.defaultConfig?.ingest;
|
||||
if (ingestEnabled === true && sessionKey) {
|
||||
if (sessionKey && resolveTelegramGroupIngestEnabled({ cfg, chatId, accountId, topicConfig })) {
|
||||
fireAndForgetHook(
|
||||
triggerInternalHook(
|
||||
createInternalHookEvent(
|
||||
@@ -408,7 +398,7 @@ export async function resolveTelegramInboundBody(params: {
|
||||
toInternalMessageReceivedContext({
|
||||
from: `telegram:group:${historyKey ?? chatId}`,
|
||||
to: originatingTo,
|
||||
content: rawBody,
|
||||
content: historyBody,
|
||||
timestamp: msg.date ? msg.date * 1000 : undefined,
|
||||
channelId: "telegram",
|
||||
accountId,
|
||||
@@ -424,6 +414,12 @@ export async function resolveTelegramInboundBody(params: {
|
||||
originatingTo,
|
||||
isGroup: true,
|
||||
groupId: `telegram:${chatId}`,
|
||||
media: materializedMedia.map(({ path, contentType, kind, sourceMessageId }) => ({
|
||||
path,
|
||||
contentType,
|
||||
kind,
|
||||
messageId: sourceMessageId ?? String(msg.message_id),
|
||||
})),
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import type { ScopeTree } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { resolveChannelGroupPolicy, type ScopeTree } from "openclaw/plugin-sdk/channel-policy";
|
||||
// Telegram helper module supports group config helpers behavior.
|
||||
import type {
|
||||
OpenClawConfig,
|
||||
TelegramAccountConfig,
|
||||
TelegramDirectConfig,
|
||||
TelegramGroupConfig,
|
||||
@@ -42,6 +43,21 @@ export function resolveTelegramScopedGroupConfig(
|
||||
return { groupConfig, topicConfig };
|
||||
}
|
||||
|
||||
export function resolveTelegramGroupIngestEnabled(params: {
|
||||
cfg: OpenClawConfig;
|
||||
chatId: string | number;
|
||||
accountId?: string;
|
||||
topicConfig?: TelegramTopicConfig;
|
||||
}): boolean {
|
||||
const { groupConfig, defaultConfig } = resolveChannelGroupPolicy({
|
||||
cfg: params.cfg,
|
||||
channel: "telegram",
|
||||
groupId: String(params.chatId),
|
||||
accountId: params.accountId,
|
||||
});
|
||||
return (params.topicConfig?.ingest ?? groupConfig?.ingest ?? defaultConfig?.ingest) === true;
|
||||
}
|
||||
|
||||
export function resolveTelegramGroupPromptSettings(params: {
|
||||
groupConfig?: TelegramGroupConfig | TelegramDirectConfig;
|
||||
topicConfig?: TelegramTopicConfig;
|
||||
|
||||
Reference in New Issue
Block a user