[PR #1099] [MERGED] Feature/makerworld #1158

Closed
opened 2026-05-06 12:35:24 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/maziggy/bambuddy/pull/1099
Author: @maziggy
Created: 4/23/2026
Status: Merged
Merged: 4/23/2026
Merged by: @maziggy

Base: devHead: feature/makerworld


📝 Commits (4)

  • b7cb328 feat(makerworld): URL-paste import and print for MakerWorld models
  • 42e9dc5 feat(makerworld): ship working URL-paste import via api.bambulab.com iot-service
  • 8a8216c Merge branch 'dev' into feature/makerworld
  • 6ea6aa1 - frontend/src/App.tsx — removed the 3 stale lines (kept the 3 equivalents). TSC + Vite both clean.

📊 Changes

29 files changed (+12776 additions, -3 deletions)

View changed files

📝 CHANGELOG.md (+7 -0)
📝 README.md (+10 -0)
📝 backend/app/api/routes/library.py (+103 -0)
backend/app/api/routes/makerworld.py (+394 -0)
📝 backend/app/core/database.py (+36 -0)
📝 backend/app/core/permissions.py (+13 -0)
📝 backend/app/main.py (+9 -0)
📝 backend/app/models/library.py (+8 -0)
backend/app/schemas/makerworld.py (+111 -0)
backend/app/services/makerworld.py (+555 -0)
📝 backend/tests/integration/test_auth_api.py (+2 -1)
backend/tests/unit/services/test_makerworld.py (+625 -0)
backend/tests/unit/test_makerworld_routes.py (+446 -0)
📝 frontend/src/App.tsx (+2 -0)
frontend/src/__tests__/pages/MakerworldPage.test.tsx (+321 -0)
📝 frontend/src/api/client.ts (+60 -0)
📝 frontend/src/components/Layout.tsx (+2 -1)
📝 frontend/src/i18n/locales/de.ts (+51 -0)
📝 frontend/src/i18n/locales/en.ts (+51 -0)
📝 frontend/src/i18n/locales/fr.ts (+50 -0)

...and 9 more files

📄 Description

feat(makerworld): ship working URL-paste import via api.bambulab.com iot-service

The MakerWorld integration shipped in 0.2.4b1 dev was broken for most
public models: the makerworld.com/design-service path returns "Please
log in to download models" even with a valid Bambu Cloud bearer,
because it's cookie-gated behind Cloudflare. Published reverse-
engineering projects work around this by pasting browser cookies; we
route around it entirely by using the api.bambulab.com/iot-service
endpoint (documented by Pr0zak/YASTL#51), which accepts the same
bearer Bambuddy already has and returns a presigned S3 URL.

Working flow:
GET api.bambulab.com/v1/design-service/design/{id} → metadata
GET api.bambulab.com/v1/iot-service/api/user/profile/{pid}?model_id=
Authorization: Bearer {cloud_token} → signed S3 URL
urllib.request (no redirects, no query re-encoding) → bytes

Notes on each step:
- The model_id query param is the alphanumeric string from the
design response (e.g. US2bb73b106683e5), NOT the integer designId
from the /models/{N} URL. The import route fetches design metadata
first to get it.
- S3 presigned URLs MUST be fetched with urllib (not httpx/curl_cffi)
because the signature is computed over exact query-string bytes;
any normalising encoder breaks it with SignatureDoesNotMatch 400s
(YASTL#52 hit the same issue). Wrapped in a no-redirect opener so
the .amazonaws.com host allowlist guarantee isn't bypassed by a
302 elsewhere.
- The canonical source_url now includes profile_id so different
plates of the same model get distinct library entries. Older rows
from dev builds keep the model-level URL; the resolve endpoint's
"already imported" check LIKEs both shapes.

UI rebuild:
- Per-plate Save + Save & Slice in Bambu Studio / OrcaSlicer (the
plate is unsliced source, so "Print Now" was misleading and is
replaced by an explicit slicer hand-off).
- Import all plates with sequential progress.
- Folder picker (default: auto-created top-level "MakerWorld"
folder, created on first import, folder tree invalidated so
File Manager shows it immediately).
- Image gallery per plate with keyboard-navigable lightbox.
- Recent imports sidebar (sticky on lg+, vertical list with
jump-to-library / slicer / open-on-makerworld icons).
- Inline follow-up actions on imported plate rows so the user
doesn't scroll back to a top-of-page card.
- Per-plate delete via the standard ConfirmModal (no window.confirm).
- Elapsed-time + phase label during import so the 10-30s synchronous
POST doesn't feel frozen.
- URL-change detection drops the preview when the pasted URL
diverges from the resolved one.

Security hardening (found in review):
- DOMPurify.sanitize on the MakerWorld HTML summary before
dangerouslySetInnerHTML (user-authored content).
- tags in that HTML routed through the thumbnail proxy so
the SPA's img-src 'self' data: blob: CSP isn't widened.
- /makerworld/thumbnail uses follow_redirects=False (the host
allowlist only covers the initial URL).
- 3MF CDN fetch strips the bearer (signed URL is the credential).
- S3 fetch uses a no-op HTTPRedirectHandler for the same reason.
- Upstream filename is os.path.basename'd before persisting.

Tests: 46 backend service unit tests, 19 route tests, 12 frontend
tests — all passing. All user-facing strings localised across the
8 UI languages.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/maziggy/bambuddy/pull/1099 **Author:** [@maziggy](https://github.com/maziggy) **Created:** 4/23/2026 **Status:** ✅ Merged **Merged:** 4/23/2026 **Merged by:** [@maziggy](https://github.com/maziggy) **Base:** `dev` ← **Head:** `feature/makerworld` --- ### 📝 Commits (4) - [`b7cb328`](https://github.com/maziggy/bambuddy/commit/b7cb328577431b37240c622589f6c2a967320570) feat(makerworld): URL-paste import and print for MakerWorld models - [`42e9dc5`](https://github.com/maziggy/bambuddy/commit/42e9dc5f3e26e672cf3fd0596b153e58fab0a300) feat(makerworld): ship working URL-paste import via api.bambulab.com iot-service - [`8a8216c`](https://github.com/maziggy/bambuddy/commit/8a8216c0dd13cc71d726f1b1ee2c275889fbaf19) Merge branch 'dev' into feature/makerworld - [`6ea6aa1`](https://github.com/maziggy/bambuddy/commit/6ea6aa155993e8ac7c1e7d891a1fff2fc1ffbf15) - frontend/src/App.tsx — removed the 3 stale <AdminRoute> lines (kept the 3 <PermissionRoute> equivalents). TSC + Vite both clean. ### 📊 Changes **29 files changed** (+12776 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `CHANGELOG.md` (+7 -0) 📝 `README.md` (+10 -0) 📝 `backend/app/api/routes/library.py` (+103 -0) ➕ `backend/app/api/routes/makerworld.py` (+394 -0) 📝 `backend/app/core/database.py` (+36 -0) 📝 `backend/app/core/permissions.py` (+13 -0) 📝 `backend/app/main.py` (+9 -0) 📝 `backend/app/models/library.py` (+8 -0) ➕ `backend/app/schemas/makerworld.py` (+111 -0) ➕ `backend/app/services/makerworld.py` (+555 -0) 📝 `backend/tests/integration/test_auth_api.py` (+2 -1) ➕ `backend/tests/unit/services/test_makerworld.py` (+625 -0) ➕ `backend/tests/unit/test_makerworld_routes.py` (+446 -0) 📝 `frontend/src/App.tsx` (+2 -0) ➕ `frontend/src/__tests__/pages/MakerworldPage.test.tsx` (+321 -0) 📝 `frontend/src/api/client.ts` (+60 -0) 📝 `frontend/src/components/Layout.tsx` (+2 -1) 📝 `frontend/src/i18n/locales/de.ts` (+51 -0) 📝 `frontend/src/i18n/locales/en.ts` (+51 -0) 📝 `frontend/src/i18n/locales/fr.ts` (+50 -0) _...and 9 more files_ </details> ### 📄 Description feat(makerworld): ship working URL-paste import via api.bambulab.com iot-service The MakerWorld integration shipped in 0.2.4b1 dev was broken for most public models: the makerworld.com/design-service path returns "Please log in to download models" even with a valid Bambu Cloud bearer, because it's cookie-gated behind Cloudflare. Published reverse- engineering projects work around this by pasting browser cookies; we route around it entirely by using the api.bambulab.com/iot-service endpoint (documented by Pr0zak/YASTL#51), which accepts the same bearer Bambuddy already has and returns a presigned S3 URL. Working flow: GET api.bambulab.com/v1/design-service/design/{id} → metadata GET api.bambulab.com/v1/iot-service/api/user/profile/{pid}?model_id=<str> Authorization: Bearer {cloud_token} → signed S3 URL urllib.request (no redirects, no query re-encoding) → bytes Notes on each step: - The model_id query param is the alphanumeric string from the design response (e.g. US2bb73b106683e5), NOT the integer designId from the /models/{N} URL. The import route fetches design metadata first to get it. - S3 presigned URLs MUST be fetched with urllib (not httpx/curl_cffi) because the signature is computed over exact query-string bytes; any normalising encoder breaks it with SignatureDoesNotMatch 400s (YASTL#52 hit the same issue). Wrapped in a no-redirect opener so the .amazonaws.com host allowlist guarantee isn't bypassed by a 302 elsewhere. - The canonical source_url now includes profile_id so different plates of the same model get distinct library entries. Older rows from dev builds keep the model-level URL; the resolve endpoint's "already imported" check LIKEs both shapes. UI rebuild: - Per-plate Save + Save & Slice in Bambu Studio / OrcaSlicer (the plate is unsliced source, so "Print Now" was misleading and is replaced by an explicit slicer hand-off). - Import all plates with sequential progress. - Folder picker (default: auto-created top-level "MakerWorld" folder, created on first import, folder tree invalidated so File Manager shows it immediately). - Image gallery per plate with keyboard-navigable lightbox. - Recent imports sidebar (sticky on lg+, vertical list with jump-to-library / slicer / open-on-makerworld icons). - Inline follow-up actions on imported plate rows so the user doesn't scroll back to a top-of-page card. - Per-plate delete via the standard ConfirmModal (no window.confirm). - Elapsed-time + phase label during import so the 10-30s synchronous POST doesn't feel frozen. - URL-change detection drops the preview when the pasted URL diverges from the resolved one. Security hardening (found in review): - DOMPurify.sanitize on the MakerWorld HTML summary before dangerouslySetInnerHTML (user-authored content). - <img> tags in that HTML routed through the thumbnail proxy so the SPA's img-src 'self' data: blob: CSP isn't widened. - /makerworld/thumbnail uses follow_redirects=False (the host allowlist only covers the initial URL). - 3MF CDN fetch strips the bearer (signed URL is the credential). - S3 fetch uses a no-op HTTPRedirectHandler for the same reason. - Upstream filename is os.path.basename'd before persisting. Tests: 46 backend service unit tests, 19 route tests, 12 frontend tests — all passing. All user-facing strings localised across the 8 UI languages. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 12:35:24 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/bambuddy#1158
No description provided.