mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 09:42:44 +00:00
feat(telegram): improve DM topics support (#30579) (thanks @kesor)
This commit is contained in:
@@ -18,7 +18,11 @@ import { loadConfig } from "../config/config.js";
|
||||
import { writeConfigFile } from "../config/io.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
||||
import type { DmPolicy } from "../config/types.base.js";
|
||||
import type { TelegramGroupConfig, TelegramTopicConfig } from "../config/types.js";
|
||||
import type {
|
||||
TelegramDirectConfig,
|
||||
TelegramGroupConfig,
|
||||
TelegramTopicConfig,
|
||||
} from "../config/types.js";
|
||||
import { danger, logVerbose, warn } from "../globals.js";
|
||||
import { enqueueSystemEvent } from "../infra/system-events.js";
|
||||
import { MediaFetchError } from "../media/fetch.js";
|
||||
@@ -608,22 +612,30 @@ export const registerTelegramHandlers = ({
|
||||
|
||||
const resolveTelegramEventAuthorizationContext = async (params: {
|
||||
chatId: number;
|
||||
isGroup: boolean;
|
||||
isForum: boolean;
|
||||
messageThreadId?: number;
|
||||
groupAllowContext?: TelegramGroupAllowContext;
|
||||
}): Promise<TelegramEventAuthorizationContext> => {
|
||||
const dmPolicy = telegramCfg.dmPolicy ?? "pairing";
|
||||
const groupAllowContext =
|
||||
params.groupAllowContext ??
|
||||
(await resolveTelegramGroupAllowFromContext({
|
||||
chatId: params.chatId,
|
||||
accountId,
|
||||
isGroup: params.isGroup,
|
||||
isForum: params.isForum,
|
||||
messageThreadId: params.messageThreadId,
|
||||
groupAllowFrom,
|
||||
resolveTelegramGroupConfig,
|
||||
}));
|
||||
return { dmPolicy, ...groupAllowContext };
|
||||
// Use direct config dmPolicy override if available for DMs
|
||||
const effectiveDmPolicy =
|
||||
!params.isGroup &&
|
||||
groupAllowContext.groupConfig &&
|
||||
"dmPolicy" in groupAllowContext.groupConfig
|
||||
? (groupAllowContext.groupConfig.dmPolicy ?? telegramCfg.dmPolicy ?? "pairing")
|
||||
: (telegramCfg.dmPolicy ?? "pairing");
|
||||
return { dmPolicy: effectiveDmPolicy, ...groupAllowContext };
|
||||
};
|
||||
|
||||
const authorizeTelegramEventSender = (params: {
|
||||
@@ -642,6 +654,7 @@ export const registerTelegramHandlers = ({
|
||||
storeAllowFrom,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
groupAllowOverride,
|
||||
effectiveGroupAllow,
|
||||
hasGroupAllowOverride,
|
||||
} = context;
|
||||
@@ -677,8 +690,10 @@ export const registerTelegramHandlers = ({
|
||||
return { allowed: false, reason: "direct-disabled" };
|
||||
}
|
||||
if (dmPolicy !== "open") {
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom,
|
||||
allowFrom: dmAllowFrom,
|
||||
storeAllowFrom,
|
||||
dmPolicy,
|
||||
});
|
||||
@@ -729,6 +744,7 @@ export const registerTelegramHandlers = ({
|
||||
}
|
||||
const eventAuthContext = await resolveTelegramEventAuthorizationContext({
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
});
|
||||
const senderAuthorization = authorizeTelegramEventSender({
|
||||
@@ -744,6 +760,20 @@ export const registerTelegramHandlers = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Enforce requireTopic for DM reactions: since Telegram doesn't provide messageThreadId
|
||||
// for reactions, we cannot determine if the reaction came from a topic, so block all
|
||||
// reactions if requireTopic is enabled for this DM.
|
||||
if (!isGroup) {
|
||||
const requireTopic = (eventAuthContext.groupConfig as TelegramDirectConfig | undefined)
|
||||
?.requireTopic;
|
||||
if (requireTopic === true) {
|
||||
logVerbose(
|
||||
`Blocked telegram reaction in DM ${chatId}: requireTopic=true but topic unknown for reactions`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect added reactions.
|
||||
const oldEmojis = new Set(
|
||||
reaction.old_reaction
|
||||
@@ -811,6 +841,7 @@ export const registerTelegramHandlers = ({
|
||||
msg: Message;
|
||||
chatId: number;
|
||||
resolvedThreadId?: number;
|
||||
dmThreadId?: number;
|
||||
storeAllowFrom: string[];
|
||||
sendOversizeWarning: boolean;
|
||||
oversizeLogMessage: string;
|
||||
@@ -820,6 +851,7 @@ export const registerTelegramHandlers = ({
|
||||
msg,
|
||||
chatId,
|
||||
resolvedThreadId,
|
||||
dmThreadId,
|
||||
storeAllowFrom,
|
||||
sendOversizeWarning,
|
||||
oversizeLogMessage,
|
||||
@@ -832,7 +864,9 @@ export const registerTelegramHandlers = ({
|
||||
if (text && !isCommandLike) {
|
||||
const nowMs = Date.now();
|
||||
const senderId = msg.from?.id != null ? String(msg.from.id) : "unknown";
|
||||
const key = `text:${chatId}:${resolvedThreadId ?? "main"}:${senderId}`;
|
||||
// Use resolvedThreadId for forum groups, dmThreadId for DM topics
|
||||
const threadId = resolvedThreadId ?? dmThreadId;
|
||||
const key = `text:${chatId}:${threadId ?? "main"}:${senderId}`;
|
||||
const existing = textFragmentBuffer.get(key);
|
||||
|
||||
if (existing) {
|
||||
@@ -970,8 +1004,9 @@ export const registerTelegramHandlers = ({
|
||||
]
|
||||
: [];
|
||||
const senderId = msg.from?.id ? String(msg.from.id) : "";
|
||||
const conversationThreadId = resolvedThreadId ?? dmThreadId;
|
||||
const conversationKey =
|
||||
resolvedThreadId != null ? `${chatId}:topic:${resolvedThreadId}` : String(chatId);
|
||||
conversationThreadId != null ? `${chatId}:topic:${conversationThreadId}` : String(chatId);
|
||||
const debounceLane = resolveTelegramDebounceLane(msg);
|
||||
const debounceKey = senderId
|
||||
? `telegram:${accountId ?? "default"}:${conversationKey}:${senderId}:${debounceLane}`
|
||||
@@ -1065,10 +1100,18 @@ export const registerTelegramHandlers = ({
|
||||
const isForum = callbackMessage.chat.is_forum === true;
|
||||
const eventAuthContext = await resolveTelegramEventAuthorizationContext({
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
});
|
||||
const { resolvedThreadId, storeAllowFrom } = eventAuthContext;
|
||||
const { resolvedThreadId, dmThreadId, storeAllowFrom, groupConfig } = eventAuthContext;
|
||||
const requireTopic = (groupConfig as { requireTopic?: boolean } | undefined)?.requireTopic;
|
||||
if (!isGroup && requireTopic === true && dmThreadId == null) {
|
||||
logVerbose(
|
||||
`Blocked telegram callback in DM ${chatId}: requireTopic=true but no topic present`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const senderId = callback.from?.id ? String(callback.from.id) : "";
|
||||
const senderUsername = callback.from?.username ?? "";
|
||||
const authorizationMode: TelegramEventAuthorizationMode =
|
||||
@@ -1323,20 +1366,25 @@ export const registerTelegramHandlers = ({
|
||||
}
|
||||
const eventAuthContext = await resolveTelegramEventAuthorizationContext({
|
||||
chatId: event.chatId,
|
||||
isGroup: event.isGroup,
|
||||
isForum: event.isForum,
|
||||
messageThreadId: event.messageThreadId,
|
||||
});
|
||||
const {
|
||||
dmPolicy,
|
||||
resolvedThreadId,
|
||||
dmThreadId,
|
||||
storeAllowFrom,
|
||||
groupConfig,
|
||||
topicConfig,
|
||||
groupAllowOverride,
|
||||
effectiveGroupAllow,
|
||||
hasGroupAllowOverride,
|
||||
} = eventAuthContext;
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom,
|
||||
allowFrom: dmAllowFrom,
|
||||
storeAllowFrom,
|
||||
dmPolicy,
|
||||
});
|
||||
@@ -1384,6 +1432,7 @@ export const registerTelegramHandlers = ({
|
||||
msg: event.msg,
|
||||
chatId: event.chatId,
|
||||
resolvedThreadId,
|
||||
dmThreadId,
|
||||
storeAllowFrom,
|
||||
sendOversizeWarning: event.sendOversizeWarning,
|
||||
oversizeLogMessage: event.oversizeLogMessage,
|
||||
|
||||
Reference in New Issue
Block a user