feat: add html file upload mode to html-to-image tool

This commit is contained in:
SnapOtter
2026-06-06 21:45:39 +08:00
parent 8512c518b2
commit 6b037e3abc
26 changed files with 248 additions and 31 deletions
+35 -19
View File
@@ -3,7 +3,7 @@ import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z } from "zod";
import { capturePage, isBrowserAvailable } from "../../lib/browser-service.js";
import { captureHtml, capturePage, isBrowserAvailable } from "../../lib/browser-service.js";
import { formatZodErrors, stripInternalPaths } from "../../lib/errors.js";
import { validateFetchUrl } from "../../lib/ssrf.js";
import { createWorkspace } from "../../lib/workspace.js";
@@ -14,15 +14,25 @@ const DEVICE_PRESETS = {
mobile: { width: 375, height: 812, isMobile: true },
} as const;
const settingsSchema = z.object({
url: z.string().url(),
format: z.enum(["jpg", "png", "webp"]).default("png"),
quality: z.number().min(1).max(100).default(90),
fullPage: z.boolean().default(false),
devicePreset: z.enum(["desktop", "tablet", "mobile", "custom"]).default("desktop"),
viewportWidth: z.number().min(320).max(3840).default(1280),
viewportHeight: z.number().min(320).max(2160).default(720),
});
const settingsSchema = z
.object({
url: z.string().url().optional(),
html: z.string().min(1).max(5_000_000).optional(),
format: z.enum(["jpg", "png", "webp"]).default("png"),
quality: z.number().min(1).max(100).default(90),
fullPage: z.boolean().default(false),
devicePreset: z.enum(["desktop", "tablet", "mobile", "custom"]).default("desktop"),
viewportWidth: z.number().min(320).max(3840).default(1280),
viewportHeight: z.number().min(320).max(2160).default(720),
})
.refine((data) => data.url || data.html, {
message: "Either url or html must be provided",
path: ["url"],
})
.refine((data) => !(data.url && data.html), {
message: "Provide either url or html, not both",
path: ["url"],
});
export function registerHtmlToImage(app: FastifyInstance) {
app.post(
@@ -50,13 +60,15 @@ export function registerHtmlToImage(app: FastifyInstance) {
});
}
try {
await validateFetchUrl(settings.url);
} catch (err) {
return reply.status(400).send({
error: "URL is not allowed",
details: err instanceof Error ? err.message : "URL validation failed",
});
if (settings.url) {
try {
await validateFetchUrl(settings.url);
} catch (err) {
return reply.status(400).send({
error: "URL is not allowed",
details: err instanceof Error ? err.message : "URL validation failed",
});
}
}
const preset =
@@ -69,14 +81,18 @@ export function registerHtmlToImage(app: FastifyInstance) {
};
try {
const buffer = await capturePage(settings.url, {
const captureOpts = {
format: settings.format,
quality: settings.quality,
fullPage: settings.fullPage,
viewportWidth: preset.width,
viewportHeight: preset.height,
isMobile: preset.isMobile,
});
};
const buffer = settings.html
? await captureHtml(settings.html, captureOpts)
: await capturePage(settings.url!, captureOpts);
const jobId = randomUUID();
const workspacePath = await createWorkspace(jobId);