When a download fails because MakerWorld refused (or is missing) the auth token, the error carries reason makerworld_token. The import and "Download files from source" then open an "Update your MakerWorld token" prompt with steps to copy the token cookie; saving it retries the download straight away and says if MakerWorld still refuses it. A missing FlareSolverr URL is reported separately, since a token alone won't help. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
55 lines
2.5 KiB
JavaScript
55 lines
2.5 KiB
JavaScript
// 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: { message, tokenRejected },
|
||
// or 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 { message: 'MakerWorld didn’t accept your auth token (it has probably expired).', tokenRejected: true };
|
||
}
|
||
return { message: `MakerWorld didn’t give a download link${detail ? `: ${detail}` : ''}`, tokenRejected: false };
|
||
}
|
||
|
||
// 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 };
|