Skip to content

Commit c4c0313

Browse files
committed
fix(telegram): resolve DM topic thread ids
1 parent 2d36be8 commit c4c0313

7 files changed

Lines changed: 70 additions & 10 deletions

File tree

extensions/telegram/src/bot-handlers.runtime.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import {
7878
resolveTelegramForumFlag,
7979
resolveTelegramForumThreadId,
8080
resolveTelegramGroupAllowFromContext,
81+
resolveTelegramMessageThreadId,
8182
shouldUseTelegramDmThreadSession,
8283
withResolvedTelegramForumFlag,
8384
} from "./bot/helpers.js";
@@ -433,7 +434,7 @@ export const registerTelegramHandlers = ({
433434
runtime.error?.(danger(`telegram debounce flush failed: ${String(err)}`));
434435
const chatId = items[0]?.msg.chat.id;
435436
if (chatId != null) {
436-
const threadId = items[0]?.msg.message_thread_id;
437+
const threadId = resolveTelegramMessageThreadId(items[0]?.msg);
437438
void bot.api
438439
.sendMessage(
439440
chatId,
@@ -1587,7 +1588,7 @@ export const registerTelegramHandlers = ({
15871588
}
15881589
}
15891590

1590-
const messageThreadId = callbackMessage.message_thread_id;
1591+
const messageThreadId = resolveTelegramMessageThreadId(callbackMessage);
15911592
const isForum = await resolveTelegramForumFlag({
15921593
chatId,
15931594
chatType: callbackMessage.chat.type,
@@ -2242,11 +2243,12 @@ export const registerTelegramHandlers = ({
22422243
getChat,
22432244
});
22442245
const normalizedMsg = withResolvedTelegramForumFlag(msg, isForum);
2246+
const messageThreadId = resolveTelegramMessageThreadId(normalizedMsg);
22452247
const resolvedThreadId = resolveTelegramForumThreadId({
22462248
isForum,
2247-
messageThreadId: normalizedMsg.message_thread_id,
2249+
messageThreadId,
22482250
});
2249-
const dmThreadId = !isGroup ? normalizedMsg.message_thread_id : undefined;
2251+
const dmThreadId = !isGroup ? messageThreadId : undefined;
22502252
recordMessageForReplyChain(normalizedMsg, resolvedThreadId ?? dmThreadId);
22512253
};
22522254

@@ -2368,7 +2370,7 @@ export const registerTelegramHandlers = ({
23682370
chatId: normalizedMsg.chat.id,
23692371
isGroup,
23702372
isForum,
2371-
messageThreadId: normalizedMsg.message_thread_id,
2373+
messageThreadId: resolveTelegramMessageThreadId(normalizedMsg),
23722374
senderId: normalizedMsg.from?.id != null ? String(normalizedMsg.from.id) : "",
23732375
senderUsername: normalizedMsg.from?.username ?? "",
23742376
requireConfiguredGroup: false,

extensions/telegram/src/bot-message-context.dm-topic-threadid.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ describe("buildTelegramMessageContext DM topic threadId in deliveryContext (#889
7777
expectRecordedRoute({ to: "telegram:1234", threadId: "42" });
7878
});
7979

80+
it("uses direct_messages_topic.topic_id as the DM topic thread id", async () => {
81+
const ctx = await buildCtx({
82+
message: {
83+
chat: { id: 1234, type: "private" },
84+
direct_messages_topic: { topic_id: 43 },
85+
},
86+
});
87+
88+
if (!ctx?.ctxPayload) {
89+
throw new Error("expected Telegram DM topic context payload");
90+
}
91+
expect(recordInboundSessionMock).toHaveBeenCalled();
92+
93+
expectRecordedRoute({ to: "telegram:1234", threadId: "43" });
94+
});
95+
8096
it("builds Telegram payloads through the shared channel turn context", async () => {
8197
const { buildChannelTurnContext } = await import("openclaw/plugin-sdk/channel-inbound");
8298
const buildChannelTurnContextMock = vi.fn(buildChannelTurnContext);

extensions/telegram/src/bot-message-context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
buildTypingThreadParams,
3333
extractTelegramForumFlag,
3434
resolveTelegramForumFlag,
35+
resolveTelegramMessageThreadId,
3536
resolveTelegramThreadSpec,
3637
shouldUseTelegramDmThreadSession,
3738
} from "./bot/helpers.js";
@@ -145,7 +146,7 @@ export const buildTelegramMessageContext = async ({
145146
const chatId = msg.chat.id;
146147
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
147148
const senderId = msg.from?.id ? String(msg.from.id) : "";
148-
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
149+
const messageThreadId = resolveTelegramMessageThreadId(msg);
149150
const reactionApi =
150151
typeof bot.api.setMessageReaction === "function"
151152
? bot.api.setMessageReaction.bind(bot.api)

extensions/telegram/src/bot-native-commands.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import {
7575
resolveTelegramCommandAuthorization,
7676
resolveTelegramForumFlag,
7777
resolveTelegramGroupAllowFromContext,
78+
resolveTelegramMessageThreadId,
7879
resolveTelegramThreadSpec,
7980
shouldUseTelegramDmThreadSession,
8081
} from "./bot/helpers.js";
@@ -366,7 +367,7 @@ async function resolveTelegramNativeCommandThreadContext(params: {
366367
const { msg, bot } = params;
367368
const chatId = msg.chat.id;
368369
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
369-
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
370+
const messageThreadId = resolveTelegramMessageThreadId(msg);
370371
const getChat =
371372
typeof bot.api.getChat === "function"
372373
? (bot.api.getChat.bind(bot.api) as TelegramGetChat)
@@ -838,7 +839,7 @@ export const registerTelegramNativeCommands = ({
838839
} | null> => {
839840
const { msg, runtimeCfg, isGroup, isForum, resolvedThreadId, senderId, topicAgentId } = params;
840841
const chatId = msg.chat.id;
841-
const messageThreadId = (msg as { message_thread_id?: number }).message_thread_id;
842+
const messageThreadId = resolveTelegramMessageThreadId(msg);
842843
const threadSpec = resolveTelegramThreadSpec({
843844
isGroup,
844845
isForum,

extensions/telegram/src/bot/helpers.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,31 @@ import {
1212
resolveTelegramDirectPeerId,
1313
resolveTelegramForumFlag,
1414
resolveTelegramForumThreadId,
15+
resolveTelegramMessageThreadId,
1516
resetTelegramForumFlagCacheForTest,
1617
shouldUseTelegramDmThreadSession,
1718
} from "./helpers.js";
1819

20+
describe("resolveTelegramMessageThreadId", () => {
21+
it("prefers message_thread_id when present", () => {
22+
expect(
23+
resolveTelegramMessageThreadId({
24+
message_thread_id: 42,
25+
direct_messages_topic: { topic_id: 43 },
26+
}),
27+
).toBe(42);
28+
});
29+
30+
it("falls back to direct_messages_topic.topic_id for private DM topics", () => {
31+
expect(resolveTelegramMessageThreadId({ direct_messages_topic: { topic_id: 43 } })).toBe(43);
32+
});
33+
34+
it("returns undefined when no Telegram thread id is present", () => {
35+
expect(resolveTelegramMessageThreadId({})).toBeUndefined();
36+
expect(resolveTelegramMessageThreadId(null)).toBeUndefined();
37+
});
38+
});
39+
1940
describe("resolveTelegramForumThreadId", () => {
2041
it.each([
2142
{ isForum: false, messageThreadId: 42 },

extensions/telegram/src/bot/helpers.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,25 @@ export type TelegramThreadSpec = {
8383
scope: "dm" | "forum" | "none";
8484
};
8585

86+
type TelegramDirectMessagesTopicCarrier = {
87+
message_thread_id?: number;
88+
direct_messages_topic?: {
89+
topic_id?: number;
90+
};
91+
};
92+
93+
export function resolveTelegramMessageThreadId(msg: unknown): number | undefined {
94+
if (!msg || typeof msg !== "object") {
95+
return undefined;
96+
}
97+
const candidate = msg as TelegramDirectMessagesTopicCarrier;
98+
if (typeof candidate.message_thread_id === "number") {
99+
return candidate.message_thread_id;
100+
}
101+
const dmTopicId = candidate.direct_messages_topic?.topic_id;
102+
return typeof dmTopicId === "number" ? dmTopicId : undefined;
103+
}
104+
86105
function normalizeTelegramDmThreadReplies(value: unknown): TelegramDmThreadReplies | undefined {
87106
return value === "off" || value === "inbound" || value === "always" ? value : undefined;
88107
}

extensions/telegram/src/sequential-key.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
isAbortRequestText,
1010
isBtwRequestText,
1111
} from "openclaw/plugin-sdk/command-primitives-runtime";
12-
import { resolveTelegramForumThreadId } from "./bot/helpers.js";
12+
import { resolveTelegramForumThreadId, resolveTelegramMessageThreadId } from "./bot/helpers.js";
1313

1414
type TelegramSequentialKeyContext = {
1515
chat?: { id?: number };
@@ -97,7 +97,7 @@ export function getTelegramSequentialKey(ctx: TelegramSequentialKeyContext): str
9797
return "telegram:approval";
9898
}
9999
const isGroup = msg?.chat?.type === "group" || msg?.chat?.type === "supergroup";
100-
const messageThreadId = msg?.message_thread_id;
100+
const messageThreadId = resolveTelegramMessageThreadId(msg);
101101
const isForum =
102102
msg?.chat?.is_forum ?? (msg?.chat?.type === "supergroup" && msg?.is_topic_message === true);
103103
const threadId = isGroup

0 commit comments

Comments
 (0)