mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-23 14:45:46 +00:00
refactor(telegram): streamline media runtime options
This commit is contained in:
@@ -933,6 +933,12 @@ channels:
|
||||
dangerouslyAllowPrivateNetwork: true
|
||||
```
|
||||
|
||||
- The same opt-in is available per account at
|
||||
`channels.telegram.accounts.<accountId>.network.dangerouslyAllowPrivateNetwork`.
|
||||
- If your proxy resolves Telegram media hosts into `198.18.x.x`, leave the
|
||||
dangerous flag off first. Telegram media already allows the RFC 2544
|
||||
benchmark range by default.
|
||||
|
||||
<Warning>
|
||||
`channels.telegram.network.dangerouslyAllowPrivateNetwork` weakens Telegram
|
||||
media SSRF protections. Use it only for trusted operator-controlled proxy
|
||||
|
||||
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { withEnv } from "../../../test/helpers/plugins/env.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
resolveTelegramMediaRuntimeOptions,
|
||||
resetMissingDefaultWarnFlag,
|
||||
resolveTelegramPollActionGateState,
|
||||
resolveDefaultTelegramAccountId,
|
||||
@@ -416,3 +417,67 @@ describe("resolveTelegramAccount groups inheritance (#30673)", () => {
|
||||
expect(resolved.config.groups).toEqual({ "-100123": { requireMention: false } });
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveTelegramMediaRuntimeOptions", () => {
|
||||
it("uses per-account network overrides for Telegram media downloads", () => {
|
||||
const resolved = resolveTelegramMediaRuntimeOptions({
|
||||
cfg: {
|
||||
channels: {
|
||||
telegram: {
|
||||
apiRoot: "https://api.telegram.org",
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
botToken: "123:work",
|
||||
apiRoot: "http://tg-proxy.internal:8081",
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountId: "work",
|
||||
token: "123:work",
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
token: "123:work",
|
||||
apiRoot: "http://tg-proxy.internal:8081",
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
transport: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to top-level Telegram media settings when account override is absent", () => {
|
||||
const resolved = resolveTelegramMediaRuntimeOptions({
|
||||
cfg: {
|
||||
channels: {
|
||||
telegram: {
|
||||
apiRoot: "http://tg-proxy.internal:8081",
|
||||
network: {
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
},
|
||||
accounts: {
|
||||
work: {
|
||||
botToken: "123:work",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
accountId: "work",
|
||||
token: "123:work",
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
token: "123:work",
|
||||
apiRoot: "http://tg-proxy.internal:8081",
|
||||
dangerouslyAllowPrivateNetwork: true,
|
||||
transport: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/routing";
|
||||
import { formatSetExplicitDefaultInstruction } from "openclaw/plugin-sdk/routing";
|
||||
import { createSubsystemLogger, isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { TelegramTransport } from "./fetch.js";
|
||||
import { resolveTelegramToken } from "./token.js";
|
||||
|
||||
let log: ReturnType<typeof createSubsystemLogger> | null = null;
|
||||
@@ -57,6 +58,13 @@ export type ResolvedTelegramAccount = {
|
||||
config: TelegramAccountConfig;
|
||||
};
|
||||
|
||||
export type TelegramMediaRuntimeOptions = {
|
||||
token: string;
|
||||
transport?: TelegramTransport;
|
||||
apiRoot?: string;
|
||||
dangerouslyAllowPrivateNetwork?: boolean;
|
||||
};
|
||||
|
||||
function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
|
||||
const ids = new Set<string>();
|
||||
for (const key of Object.keys(cfg.channels?.telegram?.accounts ?? {})) {
|
||||
@@ -155,6 +163,24 @@ export function createTelegramActionGate(params: {
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveTelegramMediaRuntimeOptions(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
token: string;
|
||||
transport?: TelegramTransport;
|
||||
}): TelegramMediaRuntimeOptions {
|
||||
const normalizedAccountId = normalizeOptionalAccountId(params.accountId);
|
||||
const accountCfg = normalizedAccountId
|
||||
? mergeTelegramAccountConfig(params.cfg, normalizedAccountId)
|
||||
: params.cfg.channels?.telegram;
|
||||
return {
|
||||
token: params.token,
|
||||
transport: params.transport,
|
||||
apiRoot: accountCfg?.apiRoot,
|
||||
dangerouslyAllowPrivateNetwork: accountCfg?.network?.dangerouslyAllowPrivateNetwork,
|
||||
};
|
||||
}
|
||||
|
||||
export type TelegramPollActionGateState = {
|
||||
sendMessageEnabled: boolean;
|
||||
pollEnabled: boolean;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-inbound";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { danger, logVerbose, warn } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { mergeTelegramAccountConfig } from "./accounts.js";
|
||||
import { resolveTelegramMediaRuntimeOptions } from "./accounts.js";
|
||||
import {
|
||||
hasInboundMedia,
|
||||
isRecoverableMediaGroupError,
|
||||
@@ -85,9 +85,12 @@ export function createTelegramInboundBufferRuntime(params: {
|
||||
runtime,
|
||||
telegramTransport,
|
||||
} = params;
|
||||
const telegramCfg = accountId
|
||||
? mergeTelegramAccountConfig(cfg, accountId)
|
||||
: cfg.channels?.telegram;
|
||||
const mediaRuntimeOptions = resolveTelegramMediaRuntimeOptions({
|
||||
cfg,
|
||||
accountId,
|
||||
token: opts.token,
|
||||
transport: telegramTransport,
|
||||
});
|
||||
const TELEGRAM_TEXT_FRAGMENT_START_THRESHOLD_CHARS = 4000;
|
||||
const TELEGRAM_TEXT_FRAGMENT_MAX_GAP_MS =
|
||||
typeof opts.testTimings?.textFragmentGapMs === "number" &&
|
||||
@@ -151,18 +154,15 @@ export function createTelegramInboundBufferRuntime(params: {
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const media = await resolveMedia(
|
||||
{
|
||||
const media = await resolveMedia({
|
||||
ctx: {
|
||||
message: replyMessage,
|
||||
me: ctx.me,
|
||||
getFile: async () => await bot.api.getFile(replyFileId),
|
||||
},
|
||||
mediaMaxBytes,
|
||||
opts.token,
|
||||
telegramTransport,
|
||||
telegramCfg?.apiRoot,
|
||||
telegramCfg?.network?.dangerouslyAllowPrivateNetwork,
|
||||
);
|
||||
maxBytes: mediaMaxBytes,
|
||||
...mediaRuntimeOptions,
|
||||
});
|
||||
if (!media) {
|
||||
return [];
|
||||
}
|
||||
@@ -192,14 +192,11 @@ export function createTelegramInboundBufferRuntime(params: {
|
||||
for (const { ctx } of entry.messages) {
|
||||
let media;
|
||||
try {
|
||||
media = await resolveMedia(
|
||||
media = await resolveMedia({
|
||||
ctx,
|
||||
mediaMaxBytes,
|
||||
opts.token,
|
||||
telegramTransport,
|
||||
telegramCfg?.apiRoot,
|
||||
telegramCfg?.network?.dangerouslyAllowPrivateNetwork,
|
||||
);
|
||||
maxBytes: mediaMaxBytes,
|
||||
...mediaRuntimeOptions,
|
||||
});
|
||||
} catch (mediaErr) {
|
||||
if (!isRecoverableMediaGroupError(mediaErr)) {
|
||||
throw mediaErr;
|
||||
|
||||
@@ -36,6 +36,7 @@ import { dispatchPluginInteractiveHandler } from "openclaw/plugin-sdk/plugin-run
|
||||
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
||||
import { danger, logVerbose, warn } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { resolveTelegramMediaRuntimeOptions } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import {
|
||||
isSenderAllowed,
|
||||
@@ -117,6 +118,12 @@ export const registerTelegramHandlers = ({
|
||||
logger,
|
||||
telegramDeps = defaultTelegramBotDeps,
|
||||
}: RegisterTelegramHandlerParams) => {
|
||||
const mediaRuntimeOptions = resolveTelegramMediaRuntimeOptions({
|
||||
cfg,
|
||||
accountId,
|
||||
token: opts.token,
|
||||
transport: telegramTransport,
|
||||
});
|
||||
const DEFAULT_TEXT_FRAGMENT_MAX_GAP_MS = 1500;
|
||||
const TELEGRAM_TEXT_FRAGMENT_START_THRESHOLD_CHARS = 4000;
|
||||
const TELEGRAM_TEXT_FRAGMENT_MAX_GAP_MS =
|
||||
@@ -381,14 +388,11 @@ export const registerTelegramHandlers = ({
|
||||
for (const { ctx } of entry.messages) {
|
||||
let media;
|
||||
try {
|
||||
media = await resolveMedia(
|
||||
media = await resolveMedia({
|
||||
ctx,
|
||||
mediaMaxBytes,
|
||||
opts.token,
|
||||
telegramTransport,
|
||||
telegramCfg.apiRoot,
|
||||
telegramCfg.network?.dangerouslyAllowPrivateNetwork,
|
||||
);
|
||||
maxBytes: mediaMaxBytes,
|
||||
...mediaRuntimeOptions,
|
||||
});
|
||||
} catch (mediaErr) {
|
||||
if (!isRecoverableMediaGroupError(mediaErr)) {
|
||||
throw mediaErr;
|
||||
@@ -486,18 +490,15 @@ export const registerTelegramHandlers = ({
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
const media = await resolveMedia(
|
||||
{
|
||||
const media = await resolveMedia({
|
||||
ctx: {
|
||||
message: replyMessage,
|
||||
me: ctx.me,
|
||||
getFile: async () => await bot.api.getFile(replyFileId),
|
||||
},
|
||||
mediaMaxBytes,
|
||||
opts.token,
|
||||
telegramTransport,
|
||||
telegramCfg.apiRoot,
|
||||
telegramCfg.network?.dangerouslyAllowPrivateNetwork,
|
||||
);
|
||||
maxBytes: mediaMaxBytes,
|
||||
...mediaRuntimeOptions,
|
||||
});
|
||||
if (!media) {
|
||||
return [];
|
||||
}
|
||||
@@ -1015,14 +1016,11 @@ export const registerTelegramHandlers = ({
|
||||
|
||||
let media: Awaited<ReturnType<typeof resolveMedia>> = null;
|
||||
try {
|
||||
media = await resolveMedia(
|
||||
media = await resolveMedia({
|
||||
ctx,
|
||||
mediaMaxBytes,
|
||||
opts.token,
|
||||
telegramTransport,
|
||||
telegramCfg.apiRoot,
|
||||
telegramCfg.network?.dangerouslyAllowPrivateNetwork,
|
||||
);
|
||||
maxBytes: mediaMaxBytes,
|
||||
...mediaRuntimeOptions,
|
||||
});
|
||||
} catch (mediaErr) {
|
||||
if (isMediaSizeLimitError(mediaErr)) {
|
||||
if (sendOversizeWarning) {
|
||||
|
||||
@@ -219,6 +219,15 @@ vi.doMock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.doMock("./bot-message-context.session.runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./bot-message-context.session.runtime.js")>();
|
||||
return {
|
||||
...actual,
|
||||
readSessionUpdatedAt: () => undefined,
|
||||
resolveStorePath: (storePath?: string) => storePath ?? "/tmp/sessions.json",
|
||||
};
|
||||
});
|
||||
|
||||
vi.doMock("openclaw/plugin-sdk/agent-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/agent-runtime")>();
|
||||
return {
|
||||
|
||||
@@ -151,9 +151,21 @@ function createFileTooBigError(): Error {
|
||||
return new Error("GrammyError: Call to 'getFile' failed! (400: Bad Request: file is too big)");
|
||||
}
|
||||
|
||||
function resolveMediaWithDefaults(
|
||||
ctx: TelegramContext,
|
||||
overrides: Partial<Parameters<typeof resolveMedia>[0]> = {},
|
||||
) {
|
||||
return resolveMedia({
|
||||
ctx,
|
||||
maxBytes: MAX_MEDIA_BYTES,
|
||||
token: BOT_TOKEN,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
async function expectTransientGetFileRetrySuccess() {
|
||||
const getFile = setupTransientGetFileRetry();
|
||||
const promise = resolveMedia(makeCtx("voice", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const promise = resolveMediaWithDefaults(makeCtx("voice", getFile));
|
||||
await flushRetryTimers();
|
||||
const result = await promise;
|
||||
expect(getFile).toHaveBeenCalledTimes(2);
|
||||
@@ -196,7 +208,7 @@ describe("resolveMedia getFile retry", () => {
|
||||
async (mediaField) => {
|
||||
const getFile = vi.fn().mockRejectedValue(new Error("Network request for 'getFile' failed!"));
|
||||
|
||||
const promise = resolveMedia(makeCtx(mediaField, getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const promise = resolveMediaWithDefaults(makeCtx(mediaField, getFile));
|
||||
await flushRetryTimers();
|
||||
const result = await promise;
|
||||
|
||||
@@ -209,9 +221,9 @@ describe("resolveMedia getFile retry", () => {
|
||||
const getFile = vi.fn().mockResolvedValue({ file_path: "voice/file_0.oga" });
|
||||
fetchRemoteMedia.mockRejectedValueOnce(new Error("download failed"));
|
||||
|
||||
await expect(
|
||||
resolveMedia(makeCtx("voice", getFile), MAX_MEDIA_BYTES, BOT_TOKEN),
|
||||
).rejects.toThrow("download failed");
|
||||
await expect(resolveMediaWithDefaults(makeCtx("voice", getFile))).rejects.toThrow(
|
||||
"download failed",
|
||||
);
|
||||
|
||||
expect(getFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -221,7 +233,7 @@ describe("resolveMedia getFile retry", () => {
|
||||
const fileTooBigError = createFileTooBigError();
|
||||
const getFile = vi.fn().mockRejectedValue(fileTooBigError);
|
||||
|
||||
const result = await resolveMedia(makeCtx("video", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(makeCtx("video", getFile));
|
||||
|
||||
// Should NOT retry - "file is too big" is a permanent error, not transient.
|
||||
expect(getFile).toHaveBeenCalledTimes(1);
|
||||
@@ -234,7 +246,7 @@ describe("resolveMedia getFile retry", () => {
|
||||
);
|
||||
const getFile = vi.fn().mockRejectedValue(fileTooBigError);
|
||||
|
||||
const result = await resolveMedia(makeCtx("video", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(makeCtx("video", getFile));
|
||||
|
||||
expect(getFile).toHaveBeenCalledTimes(1);
|
||||
expect(result).toBeNull();
|
||||
@@ -245,7 +257,7 @@ describe("resolveMedia getFile retry", () => {
|
||||
async (mediaField) => {
|
||||
const getFile = vi.fn().mockRejectedValue(createFileTooBigError());
|
||||
|
||||
const result = await resolveMedia(makeCtx(mediaField, getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(makeCtx(mediaField, getFile));
|
||||
|
||||
expect(getFile).toHaveBeenCalledTimes(1);
|
||||
expect(result).toBeNull();
|
||||
@@ -254,9 +266,9 @@ describe("resolveMedia getFile retry", () => {
|
||||
|
||||
it("throws when getFile returns no file_path", async () => {
|
||||
const getFile = vi.fn().mockResolvedValue({});
|
||||
await expect(
|
||||
resolveMedia(makeCtx("voice", getFile), MAX_MEDIA_BYTES, BOT_TOKEN),
|
||||
).rejects.toThrow("Telegram getFile returned no file_path");
|
||||
await expect(resolveMediaWithDefaults(makeCtx("voice", getFile))).rejects.toThrow(
|
||||
"Telegram getFile returned no file_path",
|
||||
);
|
||||
expect(getFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -283,7 +295,7 @@ describe("resolveMedia getFile retry", () => {
|
||||
});
|
||||
|
||||
const ctx = makeCtx("sticker", getFile);
|
||||
const promise = resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const promise = resolveMediaWithDefaults(ctx);
|
||||
await flushRetryTimers();
|
||||
const result = await promise;
|
||||
|
||||
@@ -297,7 +309,7 @@ describe("resolveMedia getFile retry", () => {
|
||||
const getFile = vi.fn().mockRejectedValue(new Error("Network request for 'getFile' failed!"));
|
||||
|
||||
const ctx = makeCtx("sticker", getFile);
|
||||
const promise = resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const promise = resolveMediaWithDefaults(ctx);
|
||||
await flushRetryTimers();
|
||||
const result = await promise;
|
||||
|
||||
@@ -319,12 +331,9 @@ describe("resolveMedia getFile retry", () => {
|
||||
contentType: "application/pdf",
|
||||
});
|
||||
|
||||
const result = await resolveMedia(
|
||||
makeCtx("document", getFile),
|
||||
MAX_MEDIA_BYTES,
|
||||
BOT_TOKEN,
|
||||
callerTransport,
|
||||
);
|
||||
const result = await resolveMediaWithDefaults(makeCtx("document", getFile), {
|
||||
transport: callerTransport,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(fetchRemoteMedia).toHaveBeenCalledWith(
|
||||
@@ -348,12 +357,9 @@ describe("resolveMedia getFile retry", () => {
|
||||
contentType: "image/webp",
|
||||
});
|
||||
|
||||
const result = await resolveMedia(
|
||||
makeCtx("sticker", getFile),
|
||||
MAX_MEDIA_BYTES,
|
||||
BOT_TOKEN,
|
||||
callerTransport,
|
||||
);
|
||||
const result = await resolveMediaWithDefaults(makeCtx("sticker", getFile), {
|
||||
transport: callerTransport,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(fetchRemoteMedia).toHaveBeenCalledWith(
|
||||
@@ -366,10 +372,8 @@ 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(
|
||||
const result = await resolveMediaWithDefaults(
|
||||
makeCtx("document", getFile, { mime_type: "application/pdf" }),
|
||||
MAX_MEDIA_BYTES,
|
||||
BOT_TOKEN,
|
||||
);
|
||||
|
||||
expect(fetchRemoteMedia).not.toHaveBeenCalled();
|
||||
@@ -388,7 +392,7 @@ describe("resolveMedia getFile retry", () => {
|
||||
.fn()
|
||||
.mockResolvedValue({ file_path: "/var/lib/telegram-bot-api/sticker.webp" });
|
||||
|
||||
const result = await resolveMedia(makeCtx("sticker", getFile), MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(makeCtx("sticker", getFile));
|
||||
|
||||
expect(fetchRemoteMedia).not.toHaveBeenCalled();
|
||||
expect(saveMediaBuffer).not.toHaveBeenCalled();
|
||||
@@ -425,7 +429,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
});
|
||||
|
||||
const ctx = makeCtx("document", getFile, { file_name: "business-plan.pdf" });
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(ctx);
|
||||
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
@@ -450,7 +454,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
});
|
||||
|
||||
const ctx = makeCtx("audio", getFile, { file_name: "my-song.mp3" });
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(ctx);
|
||||
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
@@ -475,7 +479,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
});
|
||||
|
||||
const ctx = makeCtx("video", getFile, { file_name: "presentation.mp4" });
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(ctx);
|
||||
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
@@ -492,7 +496,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
mockPdfFetchAndSave("file_42.pdf");
|
||||
|
||||
const ctx = makeCtx("document", getFile);
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(ctx);
|
||||
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
@@ -509,7 +513,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
mockPdfFetchAndSave(undefined);
|
||||
|
||||
const ctx = makeCtx("document", getFile);
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN);
|
||||
const result = await resolveMediaWithDefaults(ctx);
|
||||
|
||||
expect(saveMediaBuffer).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
@@ -526,13 +530,9 @@ describe("resolveMedia original filename preservation", () => {
|
||||
mockPdfFetchAndSave("file_42.pdf");
|
||||
|
||||
const ctx = makeCtx("document", getFile);
|
||||
const result = await resolveMedia(
|
||||
ctx,
|
||||
MAX_MEDIA_BYTES,
|
||||
BOT_TOKEN,
|
||||
undefined,
|
||||
"http://192.168.1.50:8081/custom-bot-api/",
|
||||
);
|
||||
const result = await resolveMediaWithDefaults(ctx, {
|
||||
apiRoot: "http://192.168.1.50:8081/custom-bot-api/",
|
||||
});
|
||||
|
||||
expect(fetchRemoteMedia).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -551,7 +551,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
mockPdfFetchAndSave("file_42.pdf");
|
||||
|
||||
const ctx = makeCtx("document", getFile);
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN, undefined, undefined, true);
|
||||
const result = await resolveMediaWithDefaults(ctx, { dangerouslyAllowPrivateNetwork: true });
|
||||
|
||||
expect(fetchRemoteMedia).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -571,7 +571,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
|
||||
const customApiRoot = "http://192.168.1.50:8081/custom-bot-api";
|
||||
const ctx = makeCtx("document", getFile);
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN, undefined, customApiRoot);
|
||||
const result = await resolveMediaWithDefaults(ctx, { apiRoot: customApiRoot });
|
||||
|
||||
// Verify the URL uses the custom apiRoot, not the default Telegram API
|
||||
expect(fetchRemoteMedia).toHaveBeenCalledWith(
|
||||
@@ -596,7 +596,7 @@ describe("resolveMedia original filename preservation", () => {
|
||||
|
||||
const customApiRoot = "http://localhost:8081/bot";
|
||||
const ctx = makeCtx("sticker", getFile);
|
||||
const result = await resolveMedia(ctx, MAX_MEDIA_BYTES, BOT_TOKEN, undefined, customApiRoot);
|
||||
const result = await resolveMediaWithDefaults(ctx, { apiRoot: customApiRoot });
|
||||
|
||||
// Verify the URL uses the custom apiRoot for sticker downloads
|
||||
expect(fetchRemoteMedia).toHaveBeenCalledWith(
|
||||
|
||||
@@ -297,19 +297,20 @@ async function resolveStickerMedia(params: {
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveMedia(
|
||||
ctx: TelegramContext,
|
||||
maxBytes: number,
|
||||
token: string,
|
||||
transport?: TelegramTransport,
|
||||
apiRoot?: string,
|
||||
dangerouslyAllowPrivateNetwork?: boolean,
|
||||
): Promise<{
|
||||
export async function resolveMedia(params: {
|
||||
ctx: TelegramContext;
|
||||
maxBytes: number;
|
||||
token: string;
|
||||
transport?: TelegramTransport;
|
||||
apiRoot?: string;
|
||||
dangerouslyAllowPrivateNetwork?: boolean;
|
||||
}): Promise<{
|
||||
path: string;
|
||||
contentType?: string;
|
||||
placeholder: string;
|
||||
stickerMetadata?: StickerMetadata;
|
||||
} | null> {
|
||||
const { ctx, maxBytes, token, transport, apiRoot, dangerouslyAllowPrivateNetwork } = params;
|
||||
const msg = ctx.message;
|
||||
const stickerResolved = await resolveStickerMedia({
|
||||
msg,
|
||||
|
||||
Reference in New Issue
Block a user