fix: ensure Thingiverse gets browser agent

This commit is contained in:
Tautvydas Derzinskas committed 2026-10-17 23:22:32 +02:00
1 parent 7432b2125b
commit 40d36a4b20
5 files changed
+13 -14

No files matched your search

-1
View File
@@ -127,7 +127,6 @@ export const COLLECTION_SYNC_ENABLED = (process.env.COLLECTION_SYNC_ENABLED ?? "
export const COLLECTION_SYNC_ITEM_DELAY_MS = envInt("COLLECTION_SYNC_ITEM_DELAY_MS", 60_000);
// Gap between a single import's preview-image fetches (up to ~20 per model).
export const IMPORT_PREVIEW_IMAGE_DELAY_MS = envInt("IMPORT_PREVIEW_IMAGE_DELAY_MS", 250);
export const IMPORT_USER_AGENT = "Thingport/1.0";
export const IMPORT_BROWSER_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36";
@@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
import { DESCRIPTION_IMAGES, IMPORT_TIMEOUT_SECONDS, IMPORT_USER_AGENT } from "../config";
import { DESCRIPTION_IMAGES, IMPORT_BROWSER_USER_AGENT, IMPORT_TIMEOUT_SECONDS } from "../config";
import { prisma } from "../db";
import { mapWithConcurrency } from "../utils/concurrency";
import { validateRemoteUrl } from "../utils/urlUtils";
@@ -48,7 +48,7 @@ async function fetchImage(url: string, deadline: number): Promise<Buffer | null>
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
await validateRemoteUrl(current);
const res = await fetch(current, {
headers: { "User-Agent": IMPORT_USER_AGENT, Accept: "image/*" },
headers: { "User-Agent": IMPORT_BROWSER_USER_AGENT, Accept: "image/*" },
redirect: "manual",
signal: controller.signal,
});
+4 -2
View File
@@ -7,7 +7,6 @@ import {
IMPORT_EXT_PRIORITY,
IMPORT_HTML_MAX_BYTES,
IMPORT_TIMEOUT_SECONDS,
IMPORT_USER_AGENT,
} from "../config";
import { isJsonContentType } from "../utils/fileUtils";
import { htmlToMarkdown } from "./descriptionMarkdown";
@@ -171,7 +170,10 @@ async function fetchJsonFromUrl(
referer?: string | null,
headers?: Record<string, string>,
): Promise<unknown | null> {
const requestHeaders: Record<string, string> = { "User-Agent": IMPORT_USER_AGENT, Accept: "application/json" };
const requestHeaders: Record<string, string> = {
"User-Agent": IMPORT_BROWSER_USER_AGENT,
Accept: "application/json",
};
if (referer) requestHeaders.Referer = referer;
if (headers) Object.assign(requestHeaders, headers);
const result = await fetchCappedBuffer(url, requestHeaders);
+6 -6
View File
@@ -5,12 +5,12 @@ import path from "node:path";
import crypto from "node:crypto";
import {
IMPORT_ALLOWED_EXTS,
IMPORT_BROWSER_USER_AGENT,
IMPORT_DOWNLOAD_RETRY_DELAY_MS,
IMPORT_HTML_MAX_BYTES,
IMPORT_MAX_BYTES,
IMPORT_PREVIEW_IMAGE_DELAY_MS,
IMPORT_TIMEOUT_SECONDS,
IMPORT_USER_AGENT,
} from "../config";
import {
HttpError,
@@ -186,7 +186,7 @@ async function fetchMakerworldPageDesign(
let res: Response;
try {
res = await fetchWithGuard(url, {
"User-Agent": IMPORT_USER_AGENT,
"User-Agent": IMPORT_BROWSER_USER_AGENT,
Accept: "*/*",
...makerworldHtmlHeaders(url, cookie),
});
@@ -223,7 +223,7 @@ export async function fetchMakerworldPageAuthor(
let res: Response;
try {
res = await fetchWithGuard(url, {
"User-Agent": IMPORT_USER_AGENT,
"User-Agent": IMPORT_BROWSER_USER_AGENT,
Accept: "*/*",
...makerworldHtmlHeaders(url, cookie),
});
@@ -331,7 +331,7 @@ export async function openImportResponse(
}
}
const headers: Record<string, string> = { "User-Agent": IMPORT_USER_AGENT, Accept: "*/*" };
const headers: Record<string, string> = { "User-Agent": IMPORT_BROWSER_USER_AGENT, Accept: "*/*" };
let makerworldCookie: string | null = null;
if (host.endsWith("makerworld.com")) {
makerworldCookie = resolveMakerworldCookie(body);
@@ -491,7 +491,7 @@ const PREVIEW_IMAGE_MAX_COUNT = 20;
async function fetchImageBytes(url: string): Promise<Buffer | null> {
try {
const res = await rawFetch(url, { "User-Agent": IMPORT_USER_AGENT, Accept: "image/*" });
const res = await rawFetch(url, { "User-Agent": IMPORT_BROWSER_USER_AGENT, Accept: "image/*" });
if (!res.ok) {
await res.body?.cancel().catch(() => undefined);
return null;
@@ -751,7 +751,7 @@ function isDownloadBlocked(res: Response): boolean {
* which is retried once and then reported so the caller can say why. */
export async function downloadPlainFileToTemp(url: string, suggestedName: string): Promise<PlainDownloadResult> {
try {
const headers = { "User-Agent": IMPORT_USER_AGENT, Accept: "*/*" };
const headers = { "User-Agent": IMPORT_BROWSER_USER_AGENT, Accept: "*/*" };
let res = await rawFetch(url, headers);
if (isDownloadBlocked(res)) {
await res.body?.cancel().catch(() => undefined);
+1 -3
View File
@@ -1,5 +1,3 @@
import { IMPORT_USER_AGENT } from "../config";
// The dashboard's weather, through the server like every other outside service: one place to
// cache, rate-limit and identify ourselves. Open-Meteo gives the forecast and place search,
// OpenStreetMap's Nominatim names a detected location.
@@ -23,7 +21,7 @@ const SEARCH_TTL_MS = 24 * 60 * 60 * 1000;
const CACHE_LIMIT = 500;
// Nominatim's usage policy: at most one request a second, with an identifying User-Agent.
const NOMINATIM_INTERVAL_MS = 1100;
const USER_AGENT = `${IMPORT_USER_AGENT} (self-hosted; +https://github.com/TautvydasDerzinskas/Thingport)`;
const USER_AGENT = "Thingport/1.0 (self-hosted; +https://github.com/TautvydasDerzinskas/Thingport)";
let fetchImpl: typeof fetch = (...args) => fetch(...args);