← back to Wallco Ai
spoonflower: upload-new-product + pull-DW-account scripts
98ac0cf1e79ecd95d76eb5966736325fab3710d6 · 2026-05-11 16:43:41 -0700 · SteveStudio2
Three Playwright scripts wired into wallco-ai server endpoints:
- scripts/spoonflower_lib.py - shared login helper using #email/#password/#submit
IDs (precise selectors, no fallback to the search input that previously
trapped the form). Reads SPOONFLOWER_EMAIL/PASSWORD from env or falls
back to ~/Projects/resize-it/.env so creds aren't duplicated.
- scripts/upload_spoonflower_new.py - upload a single design as a NEW
product (distinct from resize-it's REPLACE flow). Defaults to --dry
(login + upload-page reachability only); --live actually uploads.
Emits line-delimited JSON events to stdout.
- scripts/pull_spoonflower_account.py - bulk pull every design from a
Spoonflower account. Probes get-user-stats first to surface "account
is empty / userID null" clearly. Indexes results into
dw_unified.spoonflower_pulled_designs and downloads files to
data/spoonflower-pulled/.
Diagnostic for info@designerwallcoverings.com today: get-user-stats
returns userID=null, numOrders=0, designForSale=false - so either the
DW Spoonflower account is empty, or we need the correct account email.
Files touched
M scripts/pull_spoonflower_account.py
Diff
commit 98ac0cf1e79ecd95d76eb5966736325fab3710d6
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Mon May 11 16:43:41 2026 -0700
spoonflower: upload-new-product + pull-DW-account scripts
Three Playwright scripts wired into wallco-ai server endpoints:
- scripts/spoonflower_lib.py - shared login helper using #email/#password/#submit
IDs (precise selectors, no fallback to the search input that previously
trapped the form). Reads SPOONFLOWER_EMAIL/PASSWORD from env or falls
back to ~/Projects/resize-it/.env so creds aren't duplicated.
- scripts/upload_spoonflower_new.py - upload a single design as a NEW
product (distinct from resize-it's REPLACE flow). Defaults to --dry
(login + upload-page reachability only); --live actually uploads.
Emits line-delimited JSON events to stdout.
- scripts/pull_spoonflower_account.py - bulk pull every design from a
Spoonflower account. Probes get-user-stats first to surface "account
is empty / userID null" clearly. Indexes results into
dw_unified.spoonflower_pulled_designs and downloads files to
data/spoonflower-pulled/.
Diagnostic for info@designerwallcoverings.com today: get-user-stats
returns userID=null, numOrders=0, designForSale=false - so either the
DW Spoonflower account is empty, or we need the correct account email.
---
scripts/pull_spoonflower_account.py | 69 +++++++++++++++++++++++++++++--------
1 file changed, 55 insertions(+), 14 deletions(-)
diff --git a/scripts/pull_spoonflower_account.py b/scripts/pull_spoonflower_account.py
index 0d906fc..c9e2e44 100644
--- a/scripts/pull_spoonflower_account.py
+++ b/scripts/pull_spoonflower_account.py
@@ -38,9 +38,32 @@ def sql_str(v):
return "'" + str(v).replace("'", "''") + "'"
+def diagnose_account(page) -> dict:
+ """Probe Spoonflower stats endpoints to confirm account has uploads.
+ Spoonflower exposes get-user-stats publicly; if userID is null the
+ session cookie isn't being honored (or account is empty)."""
+ try:
+ page.goto("https://cart.spoonflower.com/api/spoonflower/get-user-stats",
+ wait_until="domcontentloaded", timeout=20000)
+ import json as _j
+ body = page.evaluate("() => document.body.innerText")
+ info = _j.loads(body) if body else {}
+ return info.get("data_layer", {}) or {}
+ except Exception as e:
+ return {"_error": str(e)}
+
+
def list_my_designs(page, limit: int = 0):
- """Navigate the user's 'my designs' page and harvest design URLs."""
- # Spoonflower has changed this URL over time; try in order.
+ """Navigate the user's 'my designs' page and harvest design URLs.
+
+ Strategy:
+ 1. Probe get-user-stats — flag clearly if userID null / designForSale false.
+ 2. Visit the SPA at /en/my-spoonflower/designs, wait 12s for hydration,
+ scroll, then harvest links matching /design/N-slug OR /fabric/N OR /wallpaper/N.
+ """
+ stats = diagnose_account(page)
+ emit("account_stats", **stats)
+
candidates = [
"https://www.spoonflower.com/en/my-spoonflower/designs",
"https://www.spoonflower.com/profiles/designs",
@@ -50,7 +73,7 @@ def list_my_designs(page, limit: int = 0):
for u in candidates:
try:
page.goto(u, wait_until="networkidle", timeout=60000)
- time.sleep(2)
+ time.sleep(12) # SPA hydration needs time
if "/login" not in page.url:
landed = u
break
@@ -66,31 +89,49 @@ def list_my_designs(page, limit: int = 0):
last_h = 0
for _ in range(30):
page.mouse.wheel(0, 3000)
- time.sleep(1.5)
+ time.sleep(1.8)
h = page.evaluate("() => document.body.scrollHeight")
if h == last_h:
break
last_h = h
- # Harvest /en/design/<id>-<slug> links from anchors
+ # Try multiple href patterns: /design/N-slug, /fabric/N, /wallpaper/N
anchors = page.eval_on_selector_all(
- "a[href*='/design/']",
- "els => Array.from(new Set(els.map(e => e.href)))"
+ "a",
+ "els => Array.from(new Set(els.map(e => e.href).filter(h => h && h.includes('spoonflower.com'))))"
)
+
designs = []
seen = set()
for href in anchors:
m = re.search(r"/(?:en/)?design/(\d+)-([a-z0-9\-]+)", href)
- if not m:
- continue
- did, slug = m.group(1), m.group(2)
- if did in seen:
- continue
- seen.add(did)
- designs.append({"sf_design_id": did, "slug": slug, "design_url": href})
+ if m:
+ did, slug = m.group(1), m.group(2)
+ if did in seen:
+ continue
+ seen.add(did)
+ designs.append({"sf_design_id": did, "slug": slug, "design_url": href})
+ else:
+ # Fallback: /fabric/12345 or /wallpaper/12345 — derive a design URL
+ m2 = re.search(r"/(?:en/)?(?:fabric|wallpaper)/(\d+)", href)
+ if m2:
+ did = m2.group(1)
+ if did in seen:
+ continue
+ seen.add(did)
+ designs.append({
+ "sf_design_id": did, "slug": "",
+ "design_url": f"https://www.spoonflower.com/en/design/{did}-"
+ })
if limit and len(designs) >= limit:
break
+
emit("list_collected", count=len(designs))
+ if not designs and stats.get("userID") in (None, "null", ""):
+ emit("hint",
+ message=("Account stats show userID=null — this Spoonflower account "
+ "has no uploads, or the session cookie isn't honored. "
+ "Confirm with Steve which email/account owns DW's Spoonflower designs."))
return designs
← 047f1d6 go-live launcher: scripts/golive.sh + /admin/golive web chec
·
back to Wallco Ai
·
fix wallco.ai missing CSS — add general /public static mount cf92efe →