fix(telegram): honor lookup transport and local file paths
This commit is contained in:
parent
59b9ed5eb4
commit
24473b7dd0
@ -54,4 +54,28 @@ describe("fetchTelegramChatId", () => {
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("uses caller-provided fetch impl when present", async () => {
|
||||
const customFetch = vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => ({ ok: true, result: { id: 12345 } }),
|
||||
}));
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => {
|
||||
throw new Error("global fetch should not be called");
|
||||
}),
|
||||
);
|
||||
|
||||
await fetchTelegramChatId({
|
||||
token: "abc",
|
||||
chatId: "@user",
|
||||
fetchImpl: customFetch as unknown as typeof fetch,
|
||||
});
|
||||
|
||||
expect(customFetch).toHaveBeenCalledWith(
|
||||
"https://api.telegram.org/botabc/getChat?chat_id=%40user",
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,15 +1,28 @@
|
||||
import { resolveTelegramApiBase } from "./fetch.js";
|
||||
import type { TelegramNetworkConfig } from "../runtime-api.js";
|
||||
import { resolveTelegramApiBase, resolveTelegramFetch } from "./fetch.js";
|
||||
import { makeProxyFetch } from "./proxy.js";
|
||||
|
||||
export function resolveTelegramChatLookupFetch(params?: {
|
||||
proxyUrl?: string;
|
||||
network?: TelegramNetworkConfig;
|
||||
}): typeof fetch {
|
||||
const proxyUrl = params?.proxyUrl?.trim();
|
||||
const proxyFetch = proxyUrl ? makeProxyFetch(proxyUrl) : undefined;
|
||||
return resolveTelegramFetch(proxyFetch, { network: params?.network });
|
||||
}
|
||||
|
||||
export async function fetchTelegramChatId(params: {
|
||||
token: string;
|
||||
chatId: string;
|
||||
signal?: AbortSignal;
|
||||
apiRoot?: string;
|
||||
fetchImpl?: typeof fetch;
|
||||
}): Promise<string | null> {
|
||||
const apiBase = resolveTelegramApiBase(params.apiRoot);
|
||||
const url = `${apiBase}/bot${params.token}/getChat?chat_id=${encodeURIComponent(params.chatId)}`;
|
||||
const fetchImpl = params.fetchImpl ?? fetch;
|
||||
try {
|
||||
const res = await fetch(url, params.signal ? { signal: params.signal } : undefined);
|
||||
const res = await fetchImpl(url, params.signal ? { signal: params.signal } : undefined);
|
||||
if (!res.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -360,6 +360,38 @@ describe("resolveMedia getFile retry", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses local absolute file paths directly for media downloads", async () => {
|
||||
const getFile = vi.fn().mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/file.pdf" });
|
||||
|
||||
const result = await resolveMedia(makeCtx("document", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
|
||||
expect(fetchRemoteMedia).not.toHaveBeenCalled();
|
||||
expect(saveMediaBuffer).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
path: "/var/lib/telegram-bot-api/file.pdf",
|
||||
placeholder: "<media:document>",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses local absolute file paths directly for sticker downloads", async () => {
|
||||
const getFile = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/sticker.webp" });
|
||||
|
||||
const result = await resolveMedia(makeCtx("sticker", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
|
||||
expect(fetchRemoteMedia).not.toHaveBeenCalled();
|
||||
expect(saveMediaBuffer).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
path: "/var/lib/telegram-bot-api/sticker.webp",
|
||||
placeholder: "<media:sticker>",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveMedia original filename preservation", () => {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import path from "node:path";
|
||||
import { GrammyError } from "grammy";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { retryAsync } from "openclaw/plugin-sdk/infra-runtime";
|
||||
@ -143,6 +144,9 @@ async function downloadAndSaveTelegramFile(params: {
|
||||
telegramFileName?: string;
|
||||
apiRoot?: string;
|
||||
}) {
|
||||
if (path.isAbsolute(params.filePath)) {
|
||||
return { path: params.filePath, contentType: undefined };
|
||||
}
|
||||
const apiBase = resolveTelegramApiBase(params.apiRoot);
|
||||
const url = `${apiBase}/file/bot${params.token}/${params.filePath}`;
|
||||
const fetched = await fetchRemoteMedia({
|
||||
|
||||
@ -3,25 +3,43 @@ import { resolveTelegramAllowFromEntries } from "./setup-core.js";
|
||||
|
||||
describe("resolveTelegramAllowFromEntries", () => {
|
||||
it("passes apiRoot through username lookups", async () => {
|
||||
const globalFetch = vi.fn(async () => {
|
||||
throw new Error("global fetch should not be called");
|
||||
});
|
||||
const fetchMock = vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => ({ ok: true, result: { id: 12345 } }),
|
||||
}));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
vi.stubGlobal("fetch", globalFetch);
|
||||
const proxyFetch = vi.fn();
|
||||
const fetchModule = await import("./fetch.js");
|
||||
const proxyModule = await import("./proxy.js");
|
||||
const resolveTelegramFetch = vi.spyOn(fetchModule, "resolveTelegramFetch");
|
||||
const makeProxyFetch = vi.spyOn(proxyModule, "makeProxyFetch");
|
||||
makeProxyFetch.mockReturnValue(proxyFetch as unknown as typeof fetch);
|
||||
resolveTelegramFetch.mockReturnValue(fetchMock as unknown as typeof fetch);
|
||||
|
||||
try {
|
||||
const resolved = await resolveTelegramAllowFromEntries({
|
||||
entries: ["@user"],
|
||||
credentialValue: "tok",
|
||||
apiRoot: "https://custom.telegram.test/root/",
|
||||
proxyUrl: "http://127.0.0.1:8080",
|
||||
network: { autoSelectFamily: false, dnsResultOrder: "ipv4first" },
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([{ input: "@user", resolved: true, id: "12345" }]);
|
||||
expect(makeProxyFetch).toHaveBeenCalledWith("http://127.0.0.1:8080");
|
||||
expect(resolveTelegramFetch).toHaveBeenCalledWith(proxyFetch, {
|
||||
network: { autoSelectFamily: false, dnsResultOrder: "ipv4first" },
|
||||
});
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"https://custom.telegram.test/root/bottok/getChat?chat_id=%40user",
|
||||
undefined,
|
||||
);
|
||||
} finally {
|
||||
makeProxyFetch.mockRestore();
|
||||
resolveTelegramFetch.mockRestore();
|
||||
vi.unstubAllGlobals();
|
||||
}
|
||||
});
|
||||
|
||||
@ -9,8 +9,9 @@ import {
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import type { ChannelSetupAdapter, ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
|
||||
import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import type { TelegramNetworkConfig } from "../runtime-api.js";
|
||||
import { resolveDefaultTelegramAccountId, resolveTelegramAccount } from "./accounts.js";
|
||||
import { fetchTelegramChatId } from "./api-fetch.js";
|
||||
import { fetchTelegramChatId, resolveTelegramChatLookupFetch } from "./api-fetch.js";
|
||||
|
||||
const channel = "telegram" as const;
|
||||
|
||||
@ -47,7 +48,13 @@ export async function resolveTelegramAllowFromEntries(params: {
|
||||
entries: string[];
|
||||
credentialValue?: string;
|
||||
apiRoot?: string;
|
||||
proxyUrl?: string;
|
||||
network?: TelegramNetworkConfig;
|
||||
}) {
|
||||
const fetchImpl = resolveTelegramChatLookupFetch({
|
||||
proxyUrl: params.proxyUrl,
|
||||
network: params.network,
|
||||
});
|
||||
return await Promise.all(
|
||||
params.entries.map(async (entry) => {
|
||||
const numericId = parseTelegramAllowFromId(entry);
|
||||
@ -63,6 +70,7 @@ export async function resolveTelegramAllowFromEntries(params: {
|
||||
token: params.credentialValue,
|
||||
chatId: username,
|
||||
apiRoot: params.apiRoot,
|
||||
fetchImpl,
|
||||
});
|
||||
return { input: entry, resolved: Boolean(id), id };
|
||||
}),
|
||||
@ -99,6 +107,8 @@ export async function promptTelegramAllowFromForAccount(params: {
|
||||
credentialValue: token,
|
||||
entries,
|
||||
apiRoot: resolved.config.apiRoot,
|
||||
proxyUrl: resolved.config.proxy,
|
||||
network: resolved.config.network,
|
||||
}),
|
||||
});
|
||||
return patchChannelConfigForAccount({
|
||||
|
||||
@ -517,8 +517,11 @@ describe("doctor config flow", () => {
|
||||
});
|
||||
|
||||
it("resolves Telegram @username allowFrom entries to numeric IDs on repair", async () => {
|
||||
const fetchSpy = vi.fn(async (url: string) => {
|
||||
const u = String(url);
|
||||
const globalFetch = vi.fn(async () => {
|
||||
throw new Error("global fetch should not be called");
|
||||
});
|
||||
const fetchSpy = vi.fn(async (input: RequestInfo | URL) => {
|
||||
const u = input instanceof URL ? input.href : typeof input === "string" ? input : input.url;
|
||||
const chatId = new URL(u).searchParams.get("chat_id") ?? "";
|
||||
const id =
|
||||
chatId.toLowerCase() === "@testuser"
|
||||
@ -535,7 +538,14 @@ describe("doctor config flow", () => {
|
||||
json: async () => (id != null ? { ok: true, result: { id } } : { ok: false }),
|
||||
} as unknown as Response;
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchSpy);
|
||||
vi.stubGlobal("fetch", globalFetch);
|
||||
const proxyFetch = vi.fn();
|
||||
const telegramFetchModule = await import("../../extensions/telegram/src/fetch.js");
|
||||
const telegramProxyModule = await import("../../extensions/telegram/src/proxy.js");
|
||||
const resolveTelegramFetch = vi.spyOn(telegramFetchModule, "resolveTelegramFetch");
|
||||
const makeProxyFetch = vi.spyOn(telegramProxyModule, "makeProxyFetch");
|
||||
makeProxyFetch.mockReturnValue(proxyFetch as unknown as typeof fetch);
|
||||
resolveTelegramFetch.mockReturnValue(fetchSpy as unknown as typeof fetch);
|
||||
try {
|
||||
const result = await runDoctorConfigWithInput({
|
||||
repair: true,
|
||||
@ -581,6 +591,8 @@ describe("doctor config flow", () => {
|
||||
expect(cfg.channels.telegram.accounts.default.allowFrom).toEqual(["111"]);
|
||||
expect(cfg.channels.telegram.accounts.default.groupAllowFrom).toEqual(["222"]);
|
||||
} finally {
|
||||
makeProxyFetch.mockRestore();
|
||||
resolveTelegramFetch.mockRestore();
|
||||
vi.unstubAllGlobals();
|
||||
}
|
||||
});
|
||||
@ -634,6 +646,9 @@ describe("doctor config flow", () => {
|
||||
});
|
||||
|
||||
it("uses account apiRoot when repairing Telegram allowFrom usernames", async () => {
|
||||
const globalFetch = vi.fn(async () => {
|
||||
throw new Error("global fetch should not be called");
|
||||
});
|
||||
const fetchSpy = vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = input instanceof URL ? input.href : typeof input === "string" ? input : input.url;
|
||||
expect(url).toBe("https://custom.telegram.test/root/bottok/getChat?chat_id=%40testuser");
|
||||
@ -642,7 +657,14 @@ describe("doctor config flow", () => {
|
||||
json: async () => ({ ok: true, result: { id: 12345 } }),
|
||||
};
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchSpy);
|
||||
vi.stubGlobal("fetch", globalFetch);
|
||||
const proxyFetch = vi.fn();
|
||||
const telegramFetchModule = await import("../../extensions/telegram/src/fetch.js");
|
||||
const telegramProxyModule = await import("../../extensions/telegram/src/proxy.js");
|
||||
const resolveTelegramFetch = vi.spyOn(telegramFetchModule, "resolveTelegramFetch");
|
||||
const makeProxyFetch = vi.spyOn(telegramProxyModule, "makeProxyFetch");
|
||||
makeProxyFetch.mockReturnValue(proxyFetch as unknown as typeof fetch);
|
||||
resolveTelegramFetch.mockReturnValue(fetchSpy as unknown as typeof fetch);
|
||||
const resolveSecretsSpy = vi
|
||||
.spyOn(commandSecretGatewayModule, "resolveCommandSecretRefsViaGateway")
|
||||
.mockResolvedValue({
|
||||
@ -656,6 +678,8 @@ describe("doctor config flow", () => {
|
||||
work: {
|
||||
botToken: "tok",
|
||||
apiRoot: "https://custom.telegram.test/root/",
|
||||
proxy: "http://127.0.0.1:8888",
|
||||
network: { autoSelectFamily: false, dnsResultOrder: "ipv4first" },
|
||||
allowFrom: ["@testuser"],
|
||||
},
|
||||
},
|
||||
@ -690,8 +714,14 @@ describe("doctor config flow", () => {
|
||||
};
|
||||
};
|
||||
expect(cfg.channels?.telegram?.accounts?.work?.allowFrom).toEqual(["12345"]);
|
||||
expect(makeProxyFetch).toHaveBeenCalledWith("http://127.0.0.1:8888");
|
||||
expect(resolveTelegramFetch).toHaveBeenCalledWith(proxyFetch, {
|
||||
network: { autoSelectFamily: false, dnsResultOrder: "ipv4first" },
|
||||
});
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
makeProxyFetch.mockRestore();
|
||||
resolveTelegramFetch.mockRestore();
|
||||
resolveSecretsSpy.mockRestore();
|
||||
vi.unstubAllGlobals();
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import {
|
||||
fetchTelegramChatId,
|
||||
inspectTelegramAccount,
|
||||
isNumericTelegramUserId,
|
||||
listTelegramAccountIds,
|
||||
lookupTelegramChatId,
|
||||
normalizeTelegramAllowFromEntry,
|
||||
} from "../../extensions/telegram/api.js";
|
||||
import type { TelegramNetworkConfig } from "../config/types.telegram.js";
|
||||
import { normalizeChatChannelId } from "../channels/registry.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js";
|
||||
@ -87,6 +88,8 @@ type TelegramAllowFromListRef = {
|
||||
type ResolvedTelegramLookupAccount = {
|
||||
token: string;
|
||||
apiRoot?: string;
|
||||
proxyUrl?: string;
|
||||
network?: TelegramNetworkConfig;
|
||||
};
|
||||
|
||||
function asObjectRecord(value: unknown): Record<string, unknown> | null {
|
||||
@ -421,12 +424,14 @@ async function maybeRepairTelegramAllowFromUsernames(cfg: OpenClawConfig): Promi
|
||||
continue;
|
||||
}
|
||||
const apiRoot = account.config.apiRoot?.trim() || undefined;
|
||||
const cacheKey = `${token}::${apiRoot ?? ""}`;
|
||||
const proxyUrl = account.config.proxy?.trim() || undefined;
|
||||
const network = account.config.network;
|
||||
const cacheKey = `${token}::${apiRoot ?? ""}::${proxyUrl ?? ""}::${JSON.stringify(network ?? {})}`;
|
||||
if (seenLookupAccounts.has(cacheKey)) {
|
||||
continue;
|
||||
}
|
||||
seenLookupAccounts.add(cacheKey);
|
||||
lookupAccounts.push({ token, apiRoot });
|
||||
lookupAccounts.push({ token, apiRoot, proxyUrl, network });
|
||||
}
|
||||
|
||||
if (lookupAccounts.length === 0) {
|
||||
@ -461,11 +466,13 @@ async function maybeRepairTelegramAllowFromUsernames(cfg: OpenClawConfig): Promi
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 4000);
|
||||
try {
|
||||
const id = await fetchTelegramChatId({
|
||||
const id = await lookupTelegramChatId({
|
||||
token: account.token,
|
||||
chatId: username,
|
||||
signal: controller.signal,
|
||||
apiRoot: account.apiRoot,
|
||||
proxyUrl: account.proxyUrl,
|
||||
network: account.network,
|
||||
});
|
||||
if (id) {
|
||||
return id;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user