printhive/lib/importers/makerworld.js
dlawler489 28a95ee330 Say why MakerWorld files didn't download instead of importing silently without them
MakerWorld answers an expired or missing auth token with {"error": "Please log
in to download models."} and no download link, which was skipped without a
word, so imports and "Download files from source" just ended with no files.
Now the reason comes back: missing settings, a refused token (probably expired),
no printable files on the page, or a failed download. The import screen shows
it, and the Settings hint explains where the token comes from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 18:17:46 +10:00

55 lines
2.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// MakerWorld file downloader. MakerWorld is behind Cloudflare and its downloads
// are auth-gated, so we go through FlareSolverr (solves Cloudflare) carrying the
// user's `token` cookie (authenticates). The download API returns a short-lived
// signed CDN URL, which we then fetch directly.
const flaresolverr = require('../flaresolverr');
// A model page embeds its printable "instances" in __NEXT_DATA__. Each instance
// id is what the download API takes.
function parseInstances(html) {
const m = html.match(/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);
if (!m) return [];
let data;
try { data = JSON.parse(m[1]); } catch { return []; }
let result = [];
(function walk(node) {
if (result.length || !node || typeof node !== 'object') return;
if (Array.isArray(node.instances) && node.instances.length && typeof node.instances[0].id === 'number') {
result = node.instances
.filter((x) => x && typeof x.id === 'number')
.map((x) => ({ id: x.id, title: x.title || '' }));
return;
}
for (const key of Object.keys(node)) { walk(node[key]); if (result.length) return; }
})(data);
return result;
}
// Resolve an instance to { name, url } (a signed, short-lived CDN download URL).
async function resolveDownload(flaresolverrUrl, instanceId, token) {
const api = `https://makerworld.com/api/v1/design-service/instance/${instanceId}/f3mf?type=download&fileType=&devModelName=N1`;
return flaresolverr.solveJson(flaresolverrUrl, api, {
cookies: [{ name: 'token', value: token, domain: '.makerworld.com' }],
});
}
// Why a download-link answer came back without a link, in words you can act on;
// null when it has one. Not logged in: {"code":1,"error":"Please log in to download models."}
function downloadProblem(answer) {
if (answer && answer.url) return null;
const detail = String((answer && (answer.error || answer.message || answer.msg)) || '').trim();
if (/log ?in|sign ?in|unauthori[sz]ed|token|expired/i.test(detail)) {
return 'MakerWorld didn’t accept your auth token (it has probably expired). Paste a fresh one in Settings → MakerWorld auth token, then use “Download files from source”.';
}
return `MakerWorld didn’t give a download link${detail ? `: ${detail}` : ''}`;
}
// Pull the license string out of the page data (e.g. "MakerWorld Exclusive License").
function parseLicense(html) {
const m = html.match(/"license"\s*:\s*"([^"]+)"/);
return m ? m[1] : null;
}
module.exports = { parseInstances, resolveDownload, downloadProblem, parseLicense };