feat(telegram): improve DM topics support (#30579) (thanks @kesor)

This commit is contained in:
Ayaan Zaidi
2026-03-02 09:06:10 +05:30
committed by Ayaan Zaidi
parent aafc4d56e3
commit c13b35b83d
16 changed files with 335 additions and 44 deletions

View File

@@ -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,