From d1750b62eaa15d396069a8fca85265b8c8e05bbb Mon Sep 17 00:00:00 2001 From: admin9webs Date: Fri, 10 Jul 2026 16:10:40 +0800 Subject: [PATCH] Add archived/atomk-to-woocommerce --- skills/archived/atomk-to-woocommerce/SKILL.md | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 skills/archived/atomk-to-woocommerce/SKILL.md diff --git a/skills/archived/atomk-to-woocommerce/SKILL.md b/skills/archived/atomk-to-woocommerce/SKILL.md new file mode 100644 index 0000000..19e9a40 --- /dev/null +++ b/skills/archived/atomk-to-woocommerce/SKILL.md @@ -0,0 +1,171 @@ +--- +name: atomk-to-woocommerce +category: cross-border-ecommerce +description: Claim products from AtomK, generate AI listing data, and publish them to WooCommerce via REST API. +--- + +# AtomK → WooCommerce Product Publishing + +Automates the full pipeline: log into AtomK, claim products from the Product Pool, generate AI listing data, and publish them to an existing WooCommerce store via its REST API. + +## Prerequisites + +- Node.js Playwright at `/home/ubuntu/.hermes/hermes-agent/node_modules/playwright` +- `sharp` npm package installed for image resizing +- Python with `requests` and `Pillow` packages +- AtomK credentials: admincao / Tt123456! +- An existing WooCommerce instance (e.g. `us1.atomk.cn`) + +## Step 1: Claim Products from AtomK & Generate AI Data + +Use the `atomk-bulk-claim` skill approach. Key points for this workflow: + +```bash +export NODE_PATH=/home/ubuntu/.hermes/hermes-agent/node_modules +node /path/to/atomk_claim_script.js +``` + +Script essentials: +- Login at `https://atomlisting.com/login` (press Enter on password field, do NOT click login button) +- Navigate to `https://atomlisting.com/products` +- Select category via `select` element (e.g. `家居百货`) +- Click `认领并编辑` buttons +- For each claimed product (URL contains `/editor/{id}`): + - Click a platform tab (速卖通 / Temu / 亚马逊 / TikTok Shop / etc.) + - Click `AI 生成` and poll until text no longer contains `生成中` + - Extract: title, description, price, keywords, image URLs, weight/dimensions +- Save results to JSON (e.g. `/tmp/atomk_claimed_products.json`) + +## Step 2: Prepare WooCommerce API Access + +### Discover the WooCommerce instance + +Check for existing containers: +```bash +sudo docker ps --filter name=wordpress +``` + +Check Caddyfile for domain: +```bash +sudo docker exec cat /etc/caddy/Caddyfile +``` + +Verify the store: +```bash +curl -s -L https:///wp-json/ | head -5 +``` + +### Generate API Keys programmatically + +**Critical**: Find the actual admin username first — it may NOT be `admin`. + +```bash +# List WP users +sudo docker exec wp user list --format=csv --fields=user_login,user_email + +# Generate WooCommerce API key pair +sudo docker exec wp eval ' +if (function_exists("wc_rand_hash")) { + $u = get_user_by("login","admincao"); # <-- use actual username + if ($u) { + global $wpdb; + $ck = "ck_" . wc_rand_hash(); + $cs = "cs_" . wc_rand_hash(); + $wpdb->insert($wpdb->prefix . "woocommerce_api_keys", [ + "user_id" => $u->ID, + "description" => "Auto API Key", + "permissions" => "read_write", + "consumer_key" => wc_api_hash($ck), + "consumer_secret" => $cs, + "truncated_key" => substr($ck, -7) + ]); + echo "CONSUMER_KEY:" . $ck . "\nCONSUMER_SECRET:" . $cs . "\n"; + } else { echo "user not found\n"; } +} else { echo "wc functions not found\n"; } +' +``` + +## Step 3: Publish to WooCommerce + +### Python publisher script + +```python +import json, os, requests +from io import BytesIO +from PIL import Image + +WC_URL = "https://us1.atomk.cn/wp-json/wc/v3" +CK = "ck_..." +CS = "cs_..." + +def resize_image(data): + img = Image.open(BytesIO(data)).convert("RGB") + img = img.resize((800, 800), Image.LANCZOS) + out = BytesIO() + img.save(out, format="JPEG", quality=90) + out.seek(0) + return out.read() + +def create_product(data, image_url): + url = f"{WC_URL}/products" + payload = { + "name": data.get("title", data.get("productCode")), + "type": "simple", + "regular_price": data.get("price", "10.00"), + "description": data.get("description", ""), + "short_description": data.get("keywords", ""), + "images": [{"src": image_url}], + "manage_stock": False, + "stock_status": "instock", + "status": "publish", + "tags": [{"name": t.strip()} for t in data.get("keywords", "").split(",") if t.strip()] + } + r = requests.post(url, auth=(CK, CS), json=payload) + if r.status_code in (200, 201): + return r.json().get("id"), r.json().get("permalink") + else: + print(f"Failed: {r.status_code} {r.text[:500]}") + return None, None + +def main(): + with open("/tmp/atomk_claimed_products.json") as f: + products = json.load(f) + + for p in products: + code = p.get("productCode") + imgs = p.get("images", []) + if not imgs: + continue + # Download first image + r = requests.get(imgs[0], timeout=30) + r.raise_for_status() + # Resize + resized = resize_image(r.content) + # Save locally + path = f"/tmp/wc_images/{code}_800x800.jpg" + os.makedirs("/tmp/wc_images", exist_ok=True) + with open(path, "wb") as f: + f.write(resized) + # Publish (use external image URL; media upload via WP REST often 401 with WC creds) + pid, url = create_product(p, imgs[0]) + print(f"Created {code} -> ID={pid} {url}") + +if __name__ == "__main__": + main() +``` + +## Pitfalls & Lessons Learned + +- **WP admin username discovery**: The WooCommerce container on this system uses `admincao` as the WP admin, not `admin`. Always run `wp user list` first. +- **WP REST media upload 401**: Uploading images via `/wp-json/wp/v2/media` using WooCommerce consumer key/secret often fails with `invalid_username`. The workaround is to pass the external image URL directly in the product `images` array as `{ "src": "https://..." }`; WooCommerce will download and attach it automatically. +- **Image resizing**: AtomK images may be large or in webp format. Always convert to 800x800 JPEG before referencing, or let WooCommerce handle the external URL and resize server-side. +- **HTTPS cert**: atomlisting.com has a cert mismatch; `ignoreHTTPSErrors: true` is required at both `chromium.launch()` and `browser.newContext()` levels. +- **Login trap**: The login form has `method="get"`. Clicking the button submits GET to `login?` and stays on the page. Press Enter on the password field to trigger the correct JS POST handler. +- **Platform tab order**: In the AtomK editor, click the platform tab FIRST, THEN click `AI 生成`. The button text changes to reflect the selected platform. + +## Verification + +List published products: +```bash +curl -s -u 'CK:CS' 'https://us1.atomk.cn/wp-json/wc/v3/products?per_page=10' +``` \ No newline at end of file