- name
- threads-auto-post
- description
- Threads affiliate auto-post: v19 — Browserless HTTP primary + Shopee buyer-review 5★+media HD + Pinterest fallback + 1-post=1-link forever. Content CERITA/TIPS Viewbait v5: post_1 first line ALL CAPS + total ≥210 chars. Cron no_agent=true, script_timeout≥900, THREADS_ALLOW_UI_FALLBACK=0. See references/viewbait-v5-and-cron-timeout-fix.md. Triggers: post threads, review image, caption/hook, provider timeout threads post.
- tags
- ["threads","affiliate","shopee","auto-post","browserless","http","image-upload","hook-engine","playwright","sso","dedup","camoufox"]
- related_skills
- ["threads-auto-reply"]
# Threads Affiliate Auto-Post (v18.0)
## CRITICAL RULES
- **Refs**: `references/shopee-review-image-and-dedup-v18.md` (product-only 5★+media HD scrape + permanent link dedup).
- **Pool/batch**: skill `affiliate-pool-isolation-and-batch` + `~/.hermes/scripts/affiliate_batch_manager.py` (auto-advance batch N→N+1 saat unused=0; pool 100 unique terverifikasi 2026-07-13).
- **Refs**: `references/affiliate-pool-batch-and-count.md` — auto-advance batch N→N+1 when UNUSED=0; isolated pools per channel; **always count unique `s.shopee.co.id` URLs** (header "100" can lie — was 97 until 3 missing links injected 2026-07-13).
- **1 Post = 1 Link Affiliate Forever**: Tidak boleh ada daur ulang (recycle) dari `USED` ke `UNUSED`. Permanent: `threads_used_links.json`. Simpan log di `~/.hermes/scripts/threads_used_links.json`.
- **Review Gambar Produk Real (Bukan Katalog)**:
- Wajib ambil ulasan pembeli bintang 5 dengan media (`5 bintang` + `dengan media`).
- Tolak URL non-produk (halaman toko/shop homepage).
- Ekstrak menggunakan `i.src` original (bukan `currentSrc` thumbnail `@resize_w144`).
## CRITICAL DISTINCTIONS
- **"Postingan"** = POST BARU (original content). Skill ini.
- **"Reply"** / **"Komentar"** = Balasan di postingan orang lain. Pakai `threads-auto-reply`.
- **JANGAN CAMPUR ADUK.**
## Shopee Review Image (MUST)
- **Product-link only.** Resolve affiliate shortlink → `/product/{shop_id}/{item_id}`. Reject shop/store URLs.
- **5★ + Dengan Media only.** Never product gallery / catalog / avatar.
- **HD original path:** use `img.getAttribute('src')` (or cleaned original). Do **not** prefer `currentSrc` — Shopee injects `@resize_w144_nl.webp` thumbs (~5KB).
- **Module:** `~/.hermes/scripts/shopee_scraper.py` → `scrape_review_image()`.
- **Cron wire:** `run_threads_post.sh` (`no_agent=true`) → `cron_post.py` → `get_real_review_photo()` **must** call `scrape_review_image` (do not re-inline old camoufox scraper).
- Full procedure + pitfalls: `references/shopee-review-scraper.md`
- **Shopee Review Image**: WAJIB link produk (bukan toko) + filter 5 Bintang + HD. Lihat `references/shopee-review-image-scraper.md`.
## Publish path (2026-07-13)
Engine order in `cron_post.py`:
1. **`threads_post_http.py`** — Browserless HTTP v2 (Image Upload + GraphQL/Rupload, 0 RAM, super-fast)
- Image Upload: `POST /rupload_igphoto/fb_uploader_{upload_id}` (image/jpeg)
- Configure Feed: `POST /api/v1/media/configure_text_post_app_feed/` (image root)
- Chaining/Text replies: `POST /api/v1/media/configure_text_only_post/`
- Speed: 0 RAM, ~1.2s per post, pure `urllib`/`requests`.
2. **`threads_post_applescript.py`** — Chrome Profile 16 live window
- Fill: **System Events paste** (`pbcopy` + cmd+v) — trusted input
- Send: score Kirim **y>300 x>700 first**, Quartz/OS mouse + Meta+Enter fallback
- Hook fetch/XHR for Create/Publish names
- Hard verify profile or mutation
3. **`threads_post_p16_playwright.py`** — clone minimal P16 → `/tmp/chrome_threads_p16_ud`
- Non-default user-data so Chrome allows automation
- Playwright keyboard.type + mouse.click (trusted-ish)
- Clone Cookies/Network/Local Storage only; clear Singleton* locks
4. **`threads_post_v6.py`** — legacy cookie inject (last resort)
Env override: `THREADS_FORCE_PLAYWRIGHT=1` → skip HTTP & AS, use cookie PW path only (debug).
## Browserless HTTP Engine Specification (configure_text_only_post)
- **URL**: `POST https://www.threads.net/api/v1/media/configure_text_only_post/`
- **Headers**:
- `X-IG-App-ID`: `238260118697367` (Native Threads app ID)
- `X-ASBD-ID`: `129477`
- `Cookie`: MUST contain valid `sessionid`, `csrftoken`, `ds_user_id`
- `X-CSRFToken`: value from `csrftoken` cookie
- **Payload Schema (urlencoded)**:
- `publish_mode`: `text_post`
- `timezone_offset`: `25200`
- `caption`: post text content
- `client_context`: `<epoch_ms>-<rand_5_digits>`
- `text_post_app_info`: JSON string containing:
- Root post: `{"reply_control": 0}`
- Child thread reply: `{"reply_control": 0, "reply_id": "<parent_pk>", "is_reply": true, "reply_to_author": "<uid>"}`
- `replied_to_media_id` (only for child replies): parent post `pk` (not code)
- **Success Criteria**: Response JSON status == `ok` AND `media.pk` (e.g. `3939965155132008399`) and `media.code` (e.g. `DatkW8LD2fP`) exist. Use `media.pk` as `replied_to_media_id` and `text_post_app_info.reply_id` for the next reply in the chain.
## Hard gate (same for all engines)
## Browserless / pure HTTP (research 2026-07-13)
Target: GraphQL `POST /api/graphql` tanpa browser. **Belum production.**
- IG cookie alone ≠ Threads session (pure HTTP SSO belum auto).
- Wajib: `lsd` + `fb_dtsg` + Threads session + mutation Create/Publish/Barcelona*Create*.
- Build order: hybrid network capture → `scripts/threads_post_http.py`.
- Full blueprint: `references/browserless-graphql-post.md`, `references/auth-preflight-jagonya.md`.
## 🔥 STORY MODE / JUAL CERITA
> **Publish hard-verify + anti-false-success:** `references/story-mode-publish-hard-verify.md`
> History canonical: `~/.hermes/scripts/threads_post_history.json` only (never skill-dir).
> **Kirim click ≠ success.** Write history / mark USED only after GraphQL Create/Publish mutation **or** strict profile `inner_text` unique snip (hook≥24 / product≥8 / link tail). See `references/kirim-publish-hard-verify.md`.
> **Send order:** bottom-right Kirim first (`y>300`, `x>700`, score≥100) → `has-text("Kirim").last force=True` → never prefer `has-text("Post").last` (false feed matches).
> **False soft-verify:** HTML-only / soft profile match without mutation caused false success 2026-07-12 — independent re-check; revert history if not live.
> Cookie: Chrome Profile 16 → `jagonya_shopee`; multi-domain inject IG + Threads.
> Pre-unpause: dry-run `threads_story_dry_run.py 12` + live post hard-verify; cron stays paused until SYADAGENTIC says unpause. (2026-07-12) — ACTIVE DEFAULT
**SYADAGENTIC redesign: posting = jual cerita, bukan review/value tip polos.**
Engine: `~/.hermes/scripts/threads_story_engine.py` · wired di `cron_post.py` v5.
### 3-beat formula (HARD)
| Post | Isi | Larangan |
|---|---|---|
| **1** | Scene + konflik (curhat) | ❌ produk, brand, CTA, link |
| **2** | Twist / insight cerita | ❌ hard CTA / link; product soft max ~40% |
| **3** | Resolusi + soft CTA + save + `s.shopee.co.id` | ✅ link HANYA di sini |
### 6 story types (rotasi, no repeat 4 last)
`keresahan_malam` · `malu_sosial` · `salah_beli` · `teman_bukti` · `open_loop` · `regret`
Mapped per kategori: skincare / parfum / haircare / makeup.
### Soft CTA bank
- "yang mau coba, link ada di bawah 🫶"
- "yang penasaran, gw taro link-nya 👇"
- "save dulu aja, nanti kalo butuh tinggal klik 📌"
- "buat yang mau coba, cek di bawah ya 🤍"
History fields: `hook_category` = story_type · `content_mode=story_v1` · `story_type`.
### Story ops (2026-07-12)
- **Dry-run:** `~/.hermes/scripts/threads_story_dry_run.py 12` (no browser) — must pass unique link/hook + no adjacent same story before unpause.
- **Dedup stack + USED mark:** `references/story-mode-dedup-no-agent.md`
- **Auth preflight (blocks live post):** `references/auth-preflight-jagonya.md` — verify session **is** `@jagonya_shopee` (`ds_user_id=3310347890`), not just `web_profile_info` 200. P16 can be logged in as another IG account (e.g. olivia.vanesso).
- History path **canonical only:** `~/.hermes/scripts/threads_post_history.json` (not skill dir).
- `cron_post.py` must use `if __name__ == "__main__"` guard (import-safe for dry-run).
- `mark_link_used` + DB sync **only after** publisher success.
## 🔥 SOFT SELLING RULE (2026-06-23) — still applies under story mode
**SYADAGENTIC explicitly wants SOFT SELLING.** Story mode is the implementation.
- **Thread 1–2: Pure story.** No hard product dump, no CTA.
- **Thread terakhir: Minimal CTA.** Soft line + link — NO "cek sekarang", NO "beli di sini".
- **Never brand-dump in hook.** Build curiosity first.
- **Tone: friend sharing, NOT seller.**
- Pattern: scene → twist → soft close + link
### Soft CTA examples (USE these):
- "yang mau coba, link ada di bawah 🫶"
- "yang penasaran, gw taro link-nya 👇"
- "save dulu aja, nanti kalo butuh tinggal klik 📌"
- "buat yang mau coba, cek di bawah ya 🤍"
### Hard CTA examples (DON'T use):
- ❌ "CEK SEKARANG SEBELUM KEHABISAN"
- ❌ "BELI DI SINI 👇👇👇"
- ❌ "Jangan sampai ketinggalan!"
- ❌ "FLASH SALE!!!"
## ⚠️ HARD RULE: WAJIB AFFILIATE LINK
- **POST TERAKHIR (3/5/6/7) HARUS contain `s.shopee.co.id/XXXX`** — no exceptions
- Jangan pernah posting thread tanpa link di post terakhir
- Kalau link database kosong → JANGAN POST, report to user instead
- User explicitly warned: "jangan postingan doang" — always verify POST TERAKHIR has link before Kirim
- **LINK INSERTION (v8)**: Use `navigator.clipboard.writeText()` + `Meta+v` paste. `keyboard.type()` does NOT trigger Threads' URL detection. Clipboard paste is the ONLY method that works (verified 2026-06-11).
- **PRE-SEND VERIFY**: check `editors.nth(N).inner_text()` contains link before clicking Send. If missing → retry with paste, then keyboard.type fallback.
## 📸 IMAGE STRATEGY — MANDATORY REAL USER REVIEW PHOTOS (v15 — 2026-06-27 CORRECTION)
**⚠️ CRITICAL 2026-06-27: Threads posts MUST use Shopee review images ONLY via Camoufox. NO Pinterest fallback. NO AI images. NO product thumbnails. NO random images. If Shopee returns no valid review image → SKIP IMAGE entirely.**
**⚠️ CRITICAL 2026-06-27 v2 — `/product/` URL FIX:** The resolved shortlink format `shopee.co.id/{shop_name}/{shop_id}/{item_id}` loads the STORE PAGE, not the product page. ALWAYS convert to `shopee.co.id/product/{shop_id}/{item_id}` format. Store pages have NO review images. This was SYADAGENTIC's frustration point: "Tolol tolol kenapa masuk ke toko nya sih anjing" and later "INI CONTOH LINK TOKO vs LINK PRODUK".
**⚠️ SCROLL LIMITATION (2026-06-27): JS-only extraction cannot reliably distinguish product carousel images from user review photos.** Shopee's rating-media-list uses randomized class names. Next build needs vision model or deterministic DOM traversal to section correct images.
SYADAGENTIC corrected this mid-session 2026-06-27: Pinterest search returned a valid-looking review image for the WRONG PRODUCT (Vienna Parfum Mist Rose Garden → wrong bottle). Mismatched photos are WORSE than no image. For Threads, image must match the affiliate product EXACTLY.
**2026-06-26: ALWAYS use REAL user review photos. NEVER generate AI images. NEVER use studio mockups.**
SYADAGENTIC explicitly prefers real photos from actual user reviews — people holding the product, swatches on skin, products on messy desks/nightstands. These convert better and look more trustworthy than studio shots.
### Acceptance Criteria for Images:
- ✅ Tangan memegang produk | Swatches di kulit | Produk ditaruh di meja/kasur kasual
- ✅ Pencahayaan natural (bukan studio) | Ada objek sekitar (boneka, gelas, kabel, dsb)
- ❌ AI-generated | Render 3D/mockup | Background putih polos terisolasi | Studio lighting sempurna
### Image Sourcing Priority (v15 — 2026-06-27, CORRECTED BY SYADAGENTIC):
**CRITICAL: Threads posts MUST use Shopee review images ONLY. Pinterest fallback caused mismatched product photos (SYADAGENTIC flagged this mid-session 2026-06-27 — Vienna Parfum Mist Rose Garden got wrong bottle image). For Threads, NO Pinterest, NO AI, NO random images. Only Shopee review via Camoufox — if invalid, skip image.**
#### Threads Posts (AFFILIATE):
1. 🥇 **Shopee Review Image via Camoufox** — PRIMARY
Uses `camoufox` CLI attached to Chrome Profile 16 (`chrome_local_102130715962900495`) in headed mode:
- **Product URL only:** resolve shortlink → `/product/{shop_id}/{item_id}`. Shop/store URL = hard REJECT. (`shopee_scraper.resolve_product_url`)
- Opens product page via `camoufox browser open <profile> "<product_url>" --headed`
- Scroll ~18× to mount rating overview (lazy). Do not stop on product-title "Penilaian" count alone.
- Click filters in order: **`5 Bintang`** → **`Dengan Media`**
- Extract ONLY buyer review imgs: `.rating-media-list img` / `img.rating-media-list__image-wrapper--image`
- **HD trap:** use `i.getAttribute('src')` / `i.src` — NOT `currentSrc` (often `@resize_w144_nl.webp` ~5KB thumb)
- Strip `@resize_w*_nl`, `.webp`, `_tn`, `_sm` → original `susercontent.com/file/{id}`
- Validate: file size ≥20KB AND PIL resolution ≥250×250; pick highest `w*h`
- Download to `/tmp/threads_post_image.jpg`
- **Fallback:** if scraper fails OR images too small/invalid → empty string (skip image) — NEVER fallback to Pinterest for Threads
- Implementation: `~/.hermes/scripts/shopee_scraper.py` + `get_real_review_photo()` in `cron_post.py`
View on GitHub