






















@@ -2237,6 +2237,129 @@ describe("createTelegramBot", () => {
22372237expect(mediaFetch).toHaveBeenCalledTimes(1);
22382238});
223922392240+it("hydrates bot reply targets cached from earlier Telegram replies", async () => {
2241+onSpy.mockClear();
2242+replySpy.mockClear();
2243+getFileSpy.mockClear();
2244+2245+const mediaFetch = vi.fn(
2246+async () =>
2247+new Response(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), {
2248+status: 200,
2249+headers: { "content-type": "image/png" },
2250+}),
2251+);
2252+const ssrfMock = mockPinnedHostnameResolution();
2253+2254+try {
2255+createTelegramBot({
2256+token: "tok",
2257+telegramTransport: {
2258+fetch: mediaFetch as typeof fetch,
2259+sourceFetch: mediaFetch as typeof fetch,
2260+close: async () => {},
2261+},
2262+});
2263+const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
2264+const baseCtx = {
2265+me: { id: 999, username: "openclaw_bot" },
2266+getFile: async () => ({ download: async () => new Uint8Array() }),
2267+};
2268+const chat = { id: 7, type: "group", title: "Ops" };
2269+2270+await handler({
2271+ ...baseCtx,
2272+message: {
2273+ chat,
2274+message_id: 102,
2275+text: "Why is there a 4th person?",
2276+date: 1736380750,
2277+from: { id: 2, is_bot: false, first_name: "UserB" },
2278+reply_to_message: {
2279+ chat,
2280+message_id: 101,
2281+text: "Done, here is the image",
2282+date: 1736380700,
2283+from: { id: 999, is_bot: true, first_name: "OpenClaw" },
2284+photo: [{ file_id: "generated-photo-1" }],
2285+},
2286+},
2287+});
2288+2289+replySpy.mockClear();
2290+getFileSpy.mockClear();
2291+mediaFetch.mockClear();
2292+2293+await handler({
2294+ ...baseCtx,
2295+message: {
2296+ chat,
2297+message_id: 103,
2298+text: "@openclaw_bot explain what went wrong",
2299+date: 1736380800,
2300+from: { id: 1, is_bot: false, first_name: "UserA" },
2301+reply_to_message: {
2302+ chat,
2303+message_id: 102,
2304+text: "Why is there a 4th person?",
2305+date: 1736380750,
2306+from: { id: 2, is_bot: false, first_name: "UserB" },
2307+},
2308+},
2309+});
2310+} finally {
2311+ssrfMock.mockRestore();
2312+}
2313+2314+expect(replySpy).toHaveBeenCalledTimes(1);
2315+const payload = mockMsgContextArg(
2316+replySpy as unknown as MockCallSource,
2317+0,
2318+0,
2319+"replySpy call",
2320+) as {
2321+ReplyChain?: Array<{
2322+messageId?: string;
2323+sender?: string;
2324+body?: string;
2325+mediaRef?: string;
2326+mediaPath?: string;
2327+}>;
2328+UntrustedStructuredContext?: unknown[];
2329+};
2330+expect(payload.ReplyChain?.map((entry) => entry.messageId)).toEqual(["102", "101"]);
2331+expect(payload.ReplyChain?.[1]).toMatchObject({
2332+sender: "OpenClaw",
2333+body: "Done, here is the image",
2334+mediaRef: "telegram:file/generated-photo-1",
2335+});
2336+expect(payload.ReplyChain?.[1]?.mediaPath).toBeTypeOf("string");
2337+const [conversationContext] = requireArray(
2338+payload.UntrustedStructuredContext,
2339+"structured context",
2340+);
2341+const contextRecord = requireRecord(conversationContext, "conversation context");
2342+const contextPayload = requireRecord(contextRecord.payload, "conversation context payload");
2343+const messages = requireArray(contextPayload.messages, "conversation context messages").map(
2344+(message, index) => requireRecord(message, `conversation context message ${index + 1}`),
2345+);
2346+const messagesById = new Map(messages.map((message) => [message.message_id, message]));
2347+expect(messagesById.get("101")).toMatchObject({
2348+sender: "OpenClaw",
2349+body: "Done, here is the image",
2350+media_ref: "telegram:file/generated-photo-1",
2351+is_reply_target: true,
2352+});
2353+expect(messagesById.get("102")).toMatchObject({
2354+sender: "UserB",
2355+body: "Why is there a 4th person?",
2356+reply_to_id: "101",
2357+is_reply_target: true,
2358+});
2359+expect(getFileSpy).toHaveBeenCalledWith("generated-photo-1");
2360+expect(mediaFetch).toHaveBeenCalledTimes(1);
2361+});
2362+22402363it("does not fetch reply media for unauthorized DM replies", async () => {
22412364onSpy.mockClear();
22422365replySpy.mockClear();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。