// Thingiverse importer — uses the official REST API (https://www.thingiverse.com/developers). // Needs a free app token in THINGIVERSE_TOKEN. const API = 'https://api.thingiverse.com'; // Accepts .../thing:12345, .../thing:12345/files, or a bare id. Returns id or null. function parse(url) { if (/^\d+$/.test(url.trim())) return url.trim(); const m = String(url).match(/thing:(\d+)/i); return m ? m[1] : null; } async function api(pathname, token) { const res = await fetch(`${API}${pathname}`, { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) { throw new Error(`Thingiverse API ${res.status} for ${pathname}`); } return res.json(); } // Returns a normalized model description + the list of files to download. async function fetchModel(thingId, token) { const [thing, files, tags] = await Promise.all([ api(`/things/${thingId}`, token), api(`/things/${thingId}/files`, token), api(`/things/${thingId}/tags`, token).catch(() => []), ]); const creator = thing.creator || {}; return { source_site: 'thingiverse', source_id: String(thingId), source_url: thing.public_url || `https://www.thingiverse.com/thing:${thingId}`, name: thing.name || `Thing ${thingId}`, description: stripHtml(thing.description || ''), license: thing.license || null, designer: creator.name ? { name: creator.name, source_site: 'thingiverse', profile_url: creator.public_url } : null, tags: (tags || []).map((t) => t.name).filter(Boolean), downloads: (files || []).map((f) => ({ name: f.name, // download_url is an API endpoint that 302s to the real file; needs the token. url: `${f.download_url}?access_token=${encodeURIComponent(token)}`, })), }; } function stripHtml(html) { return String(html).replace(/<[^>]*>/g, '').replace(/\s+\n/g, '\n').trim(); } module.exports = { parse, fetchModel };