// Printables importer. Printables exposes a public GraphQL API at // api.printables.com/graphql — the file list and per-file download links are // available anonymously for free models. A bearer token (optional) is sent when // configured, for anything that needs an account. const ENDPOINT = 'https://api.printables.com/graphql/'; async function gql(query, variables, token) { const headers = { 'Content-Type': 'application/json', Origin: 'https://www.printables.com' }; if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch(ENDPOINT, { method: 'POST', headers, body: JSON.stringify({ query, variables }) }); if (!res.ok) throw new Error(`Printables GraphQL HTTP ${res.status}`); const json = await res.json(); if (json.errors) throw new Error('Printables GraphQL: ' + json.errors.map((e) => e.message).join('; ')); return json.data; } const PRINT_QUERY = `query($id: ID!) { print(id: $id) { id name license { name abbreviation content } stls { id name fileSize } slas { id name fileSize } otherFiles { id name fileSize } } }`; // Returns { name, license, licenseContent, files: [{ id, name, fileType }] }. // (Commercial use is decided by the caller from licenseContent — the on-page // excludeCommercialUsage flag is unreliable, e.g. false for the strictly // non-commercial "Standard Digital File License".) async function fetchModel(printId, token) { const data = await gql(PRINT_QUERY, { id: String(printId) }, token); const p = data.print; if (!p) throw new Error('Print not found'); const files = []; (p.stls || []).forEach((f) => files.push({ id: f.id, name: f.name, fileType: 'stl' })); (p.slas || []).forEach((f) => files.push({ id: f.id, name: f.name, fileType: 'sla' })); (p.otherFiles || []).forEach((f) => files.push({ id: f.id, name: f.name, fileType: 'other' })); return { name: p.name, license: p.license ? p.license.name : null, licenseContent: p.license ? p.license.content : null, files, }; } const DOWNLOAD_MUTATION = `mutation($printId: ID!, $files: [DownloadFileInput], $src: DownloadSourceEnum!) { getDownloadLink(printId: $printId, files: $files, source: $src) { ok output { link } } }`; // Resolve one file to a direct CDN download URL. async function getDownloadLink(printId, fileType, fileId, token) { const data = await gql( DOWNLOAD_MUTATION, { printId: String(printId), files: [{ fileType, ids: [String(fileId)] }], src: 'model_detail' }, token ); const r = data.getDownloadLink; if (!r || !r.ok || !r.output || !r.output.link) throw new Error('No download link returned'); return r.output.link; } module.exports = { fetchModel, getDownloadLink };