Add archived/atomk-to-woocommerce
This commit is contained in:
@@ -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 <caddy_container> cat /etc/caddy/Caddyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the store:
|
||||||
|
```bash
|
||||||
|
curl -s -L https://<domain>/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 <wpcli_container> wp user list --format=csv --fields=user_login,user_email
|
||||||
|
|
||||||
|
# Generate WooCommerce API key pair
|
||||||
|
sudo docker exec <wpcli_container> 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'
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user