Skip to content

Commit

Permalink
feat: add thingiverse support
Browse files Browse the repository at this point in the history
  • Loading branch information
seasick committed Jan 21, 2024
1 parent 36df006 commit b54d8dc
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/lib/fetcha.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import printablesComFetcha from './fetcha/printables.com';
import thingiverseComFetcha from './fetcha/thingiverse.com';

export type FetchaFile = {
name: string;
Expand All @@ -15,6 +16,9 @@ export default async function fetcha(url: string): Promise<FetchaFile[]> {
case 'printables.com':
case 'www.printables.com':
return await printablesComFetcha(url, '.scad');
case 'thingiverse.com':
case 'www.thingiverse.com':
return await thingiverseComFetcha(url, '.scad');
default: {
const urlParts = url.split('/');

Expand Down
54 changes: 54 additions & 0 deletions src/lib/fetcha/thingiverse.com.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { FetchaFile } from '../fetcha';

type Stl = {
id: number;
name: string;
fileSize: number;
// filePreviewPath: string; // Always empty in my tests
};

export default async function thingiverseComFetcha(
url: string,
fileNameFilter?: string
): Promise<FetchaFile[]> {
// Url is expected to look like `https://www.thingiverse.com/thing:2523187`.
// We need to extract the id from the url, which is `2523187` in this case.
const idString = url.match(/\/thing:(\d+)/)?.[1];

// Download the current app bundle which contains a bearer token.
const appBundleResponse = await fetch(
'https://cdn.thingiverse.com/site/js/app.bundle.js'
);
const appBundle = await appBundleResponse.text();

// The app bundle contains minified code, and at the time of writing this comment, the token
// is located in a `u="<token>"` string. The closest identifiable string before that definition
// is `https://tv-zip.thingiverse.com`. So we could search for that string and then search fowards
// for the token. *But* also at the time of writing, it was the only 32char long a-z0-9 string in
// the entire app bundle. So we can just search for that.

/* const tokenStart = appBundle.indexOf('https://tv-zip.thingiverse.com');
const searchPart = appBundle.substring(tokenStart);
const token = searchPart.match(/u="([a-f0-9]{32})"/)?.[1];*/

const token = appBundle.match(/="([a-f0-9]{32})"/)?.[1];

// With the token we can now get the list of files
const thingResponse = await fetch(
`https://api.thingiverse.com/things/${idString}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const thing = await thingResponse.json();

return thing.zip_data.files
.filter((file) => file.name.match(fileNameFilter || ''))
.map((file) => ({
description: '',
name: file.name,
url: file.url,
}));
}

0 comments on commit b54d8dc

Please sign in to comment.