Xingtu Task Invite Code
/install xingtu-task-invite-code
\r \r
xingtuTaskInviteCode -- 星图邀约二维码批量下载\r
\r
Overview\r
\r
This skill automates batch downloading of QR code invitation images from XingTu (星图) recruitment tasks. The complete workflow involves: cookie authentication -> task list retrieval with pagination -> per-task browser automation to download QR codes.\r
\r
Key tools used: agent-browser (browser automation, cookie injection, file download), PowerShell Invoke-WebRequest (API calls), Python subprocess (batch orchestration).\r
\r
CRITICAL: Never use mock or simulated data. All data must come from real API calls and browser interactions. If any step fails, report the actual error rather than fabricating results.\r
\r
Prerequisites\r
\r
Before starting, ensure the agent-browser skill is loaded via use_skill agent-browser. The skill uses these agent-browser commands:\r
\r
| Command | Purpose |\r
|---------|---------|\r
| agent-browser open \x3Curl> | Navigate to URL |\r
| agent-browser get url | Get current page URL (for login detection) |\r
| agent-browser eval "\x3Cjs>" | Execute JavaScript (for cookie injection & DOM manipulation) |\r
| agent-browser snapshot -i | Get interactive element tree with refs |\r
| agent-browser click \x3Cref> | Click element by ref |\r
| agent-browser screenshot | Take screenshot for debugging |\r
| agent-browser close | Close browser session |\r
\r
Path Resolution (DO NOT Hardcode User Paths)\r
\r
All file paths MUST be resolved dynamically based on the runtime environment. Never hardcode C:\Users\xxx\ in scripts.\r
\r
| Resource | Dynamic Resolution (Python) | Dynamic Resolution (PowerShell) |\r
|----------|----------------------------|-------------------------------|\r
| Cookie file | os.path.expanduser('~/.xingtuCookie.txt') | "$env:USERPROFILE\.xingtuCookie.txt" |\r
| User home | os.path.expanduser('~') | $env:USERPROFILE |\r
| agent-browser | os.path.join(os.environ['APPDATA'], 'npm', 'agent-browser.cmd') | "$env:APPDATA\ pm\agent-browser.cmd" |\r
| Downloads | os.path.expanduser('~/Downloads') | "$env:USERPROFILE\Downloads" |\r
| Task cache | os.path.join(WORKSPACE, 'tasks_cache.json') | "$PWD asks_cache.json" |\r
| Progress file | os.path.join(WORKSPACE, 'qr_progress.json') | "$PWD\qr_progress.json" |\r
| Output root | Configurable, default D:\xingtu ask-invite | Same |\r
\r
WORKSPACE = the directory where the batch script runs (typically the current project workspace). Use os.getcwd() or os.path.dirname(os.path.abspath(__file__)).\r
\r
Workflow\r
\r
The skill follows a sequential multi-phase workflow. Each phase must complete successfully before proceeding to the next.\r
\r
Session rule: Keep the same agent-browser daemon alive across all phases. Only call agent-browser close at the very end (or on fatal error). Do NOT close between steps.\r
\r
---\r
\r
Phase 1: Cookie Authentication\r
\r
Step 1.1: Check Cookie File\r
\r
Read ~/.xingtuCookie.txt (i.e., {USERPROFILE}\.xingtuCookie.txt) using read_file.\r
\r
- If the file does NOT exist: go to Case A.\r
- If the file exists and contains a non-empty string: go to Step 1.2.\r \r
Step 1.2: Validate Cookie\r
\r Use PowerShell to make a test API call:\r \r
$cookieFile = "$env:USERPROFILE\.xingtuCookie.txt"\r
$cookie = (Get-Content $cookieFile -Encoding UTF8 -Raw).Trim()\r
$headers = @{\r
"Accept" = "application/json, text/plain, */*"\r
"Content-Type" = "application/json"\r
"agw-js-conv" = "str"\r
"Cookie" = $cookie\r
"User-Agent" = "Apifox/1.0.0 (https://apifox.com)"\r
"Host" = "www.xingtu.cn"\r
"Accept-Charset" = "UTF-8"\r
}\r
$response = Invoke-WebRequest -Uri "https://www.xingtu.cn/gw/api/task/provider_get_task_order_list?page=1&limit=1" -Headers $headers -Method GET -TimeoutSec 15\r
# Check response: if status 200 and JSON contains valid data (not "用户未登录" or status_code 11001), cookie is valid\r
```\r
\r
**Validation criteria**:\r
- HTTP 200 AND response body does NOT contain `status_code: 11001` (未登录) -> cookie valid, proceed to Phase 2.\r
- HTTP 200 but `status_code: 11001` -> Case B (cookie expired).\r
- Any other non-200 status -> Case B.\r
\r
### Step 1.3: Handle Missing or Invalid Cookie\r
\r
#### Case A: No cookie file exists\r
1. Output: **【星图后台还未登录】**\r
2. Go to Step 1.4.\r
\r
#### Case B: Cookie file exists but validation fails\r
1. Output: **【星图后台登录失效】**\r
2. Check if the user's current message contains a cookie string (look for `sessionid=`, `uid_tt=`, `sid_guard=`, etc.).\r
- If found: validate it with the same API call. If valid, write to `~/.xingtuCookie.txt` and go to Phase 2. If invalid, warn and go to Step 1.4.\r
- If not found: go to Step 1.4.\r
\r
### Step 1.4: Inject Cookie into Agent-Browser (Fast Path)\r
\r
**When to use**: User has provided a cookie string but it contains httpOnly cookies that don't work via `document.cookie` injection. This is the PREFERRED method when cookie is available.\r
\r
**⚠️ Known limitation**: Some essential cookies (e.g., `sessionid`, `sid_guard`) may be httpOnly and CANNOT be injected via `document.cookie`. In this case, `agent-browser eval` injection will still result in a login redirect. Fall through to Step 1.5 (manual login).\r
\r
```python\r
# Python subprocess is PREFERRED over PowerShell for eval calls\r
# PowerShell truncates JS eval parameters containing special characters\r
\r
import subprocess, json, time, os\r
\r
AGENT = os.path.join(os.environ['APPDATA'], 'npm', 'agent-browser.cmd')\r
COOKIE_FILE = os.path.expanduser('~/.xingtuCookie.txt')\r
\r
def ag(*args):\r
cmd = [AGENT] + list(args)\r
r = subprocess.run(cmd, capture_output=True, timeout=30)\r
return r.stdout.decode('utf-8', errors='replace').strip()\r
\r
def ev(js):\r
js1 = ' '.join(js.split()) # collapse whitespace\r
r = subprocess.run([AGENT, 'eval', js1], capture_output=True, timeout=15)\r
return r.stdout.decode('utf-8', errors='replace').strip()\r
\r
# Step 1: Open xingtu.cn first (must be on the domain before setting cookies)\r
ag('open', 'https://www.xingtu.cn')\r
time.sleep(2)\r
\r
# Step 2: Read cookie and inject via document.cookie\r
cookie_str = open(COOKIE_FILE, encoding='utf-8').read().strip()\r
pairs = cookie_str.split('; ')\r
cookie_json = json.dumps(pairs)\r
\r
js_inject = f"""(function(){{\r
var pairs={cookie_json};\r
var c=0;\r
for(var i=0;i\x3Cpairs.length;i++){{\r
var kv=pairs[i].trim().split('=');\r
if(kv.length>=2){{\r
try{{document.cookie=kv[0]+'='+kv.slice(1).join('=')+';path=/;domain=.xingtu.cn';c++;}}catch(e){{}}\r
}}\r
}}\r
// Also set without domain for current path\r
for(var i=0;i\x3Cpairs.length;i++){{\r
var kv=pairs[i].trim().split('=');\r
if(kv.length>=2){{\r
try{{document.cookie=kv[0]+'='+kv.slice(1).join('=')+';path=/';c++;}}catch(e){{}}\r
}}\r
}}\r
return c;\r
}})()"""\r
\r
count = ev(js_inject)\r
print(f"Injected {count} cookies")\r
\r
# Step 3: Verify by navigating to a known task page\r
ag('open', 'https://www.xingtu.cn/provider/pages/recruit/management/7644492294913278002')\r
time.sleep(6)\r
current_url = ev("location.href")\r
\r
if 'login' in current_url.lower() or 'sso' in current_url.lower():\r
print("Cookie injection insufficient (httpOnly cookies). Falling back to manual login.")\r
# Go to Step 1.5\r
else:\r
print("Cookie injection successful. Proceeding to Phase 2.")\r
# Proceed to Phase 2\r
```\r
\r
**Error Handling:**\r
- If `ev("location.href")` still shows login page after injection: httpOnly cookies are blocking. Fall through to Step 1.5.\r
- If `ev(js_inject)` returns 0: no cookies were injectable. Check cookie file format.\r
\r
### Step 1.5: Browser Login (Manual, Fallback)\r
\r
**Execution steps:**\r
\r
1. Load `agent-browser` skill if not already loaded.\r
2. Open the login page:\r
```bash\r
agent-browser open "https://sso.oceanengine.com/xingtu/login?role=7"\r
```\r
3. Tell the user: **请在打开的浏览器中完成星图登录(手机验证码登录)。**\r
4. Poll for login completion using `agent-browser get url`:\r
```powershell\r
# Poll every 5 seconds, max 24 times (2 minutes)\r
for ($i = 1; $i -le 24; $i++) {\r
Start-Sleep -Seconds 5\r
$url = (agent-browser get url 2>&1 | Select-String -Pattern "^https?://" | Out-String).Trim()\r
if ($url -notmatch "sso.oceanengine.com" -and $url -match "xingtu") {\r
Write-Host "LOGIN_DETECTED: $url"\r
break\r
}\r
}\r
```\r
5. Once URL no longer contains `sso.oceanengine.com` (redirected to xingtu.cn):\r
- Extract cookies: `agent-browser eval "document.cookie"`\r
- Parse the output to get the cookie string (strip any CLI noise, keep the raw cookie text).\r
- Write the cookie string to `~/.xingtuCookie.txt`.\r
- Validate the saved cookie with the test API call from Step 1.2.\r
- If validation still fails: report error and ask user to check login.\r
\r
**Error Handling:**\r
- If polling times out (2 minutes, 24 attempts): Tell user login did not complete in time. Ask if they want to retry or provide cookie manually.\r
- If `agent-browser eval "document.cookie"` returns empty: the session may not have cookies on the current domain. Navigate to `https://www.xingtu.cn` first, then retry.\r
\r
---\r
\r
## Phase 2: Fetch Task List\r
\r
### Step 2.1: Read Cookie\r
\r
Read the cookie string from `~/.xingtuCookie.txt` using `read_file`.\r
\r
### Step 2.2: Paginate Through All Tasks\r
\r
Use PowerShell `Invoke-WebRequest` to make POST requests:\r
\r
**URL**: `https://www.xingtu.cn/gw/api/task/provider_get_task_order_list`\r
\r
**Headers**:\r
```\r
Accept: application/json, text/plain, */*\r
Content-Type: application/json\r
agw-js-conv: str\r
Cookie: {{cookie}}\r
User-Agent: Apifox/1.0.0 (https://apifox.com)\r
Host: www.xingtu.cn\r
Accept-Charset: UTF-8\r
Accept-Encoding: gzip, deflate\r
```\r
\r
**Request Body per page**:\r
```json\r
{\r
"page": {{page_number}},\r
"limit": 10,\r
"query": {\r
"order_status": [2],\r
"task_category_list": [133],\r
"pay_type_list": [3, 4, 12]\r
}\r
}\r
```\r
\r
**Pagination implementation**:\r
\r
```powershell\r
$cookieFile = "$env:USERPROFILE\.xingtuCookie.txt"\r
$cookie = (Get-Content $cookieFile -Encoding UTF8 -Raw).Trim()\r
$allTasks = @()\r
$page = 1\r
$hasMore = $true\r
\r
while ($hasMore) {\r
$body = @{ page = $page; limit = 10; query = @{ order_status = @(2); task_category_list = @(133); pay_type_list = @(3, 4, 12) } } | ConvertTo-Json -Depth 5\r
$headers = @{...} # same headers as above with Cookie=$cookie\r
$response = Invoke-RestMethod -Uri "https://www.xingtu.cn/gw/api/task/provider_get_task_order_list" -Method POST -Headers $headers -Body $body -ContentType "application/json"\r
\r
if ($response.data.list.Count -eq 0) { break }\r
$allTasks += $response.data.list\r
$page++\r
Start-Sleep -Milliseconds 500 # rate limit protection\r
}\r
```\r
\r
### Step 2.3: Pre-filter Tasks\r
\r
**CRITICAL**: Before entering Phase 3, filter out tasks that cannot have QR codes:\r
\r
```python\r
# Python reference implementation\r
import json\r
\r
with open('tasks_cache.json', 'w', encoding='utf-8') as f:\r
json.dump(all_tasks, f, ensure_ascii=False)\r
\r
# Pre-filter: only keep tasks with participants\r
tasks = [t for t in all_tasks \r
if int(t['challenge_info'].get('participate_author_count') or 0) > 0]\r
\r
skipped_zero = len(all_tasks) - len(tasks)\r
if skipped_zero > 0:\r
print(f"跳过 {skipped_zero} 个达人报名数为 0 的任务(弹窗无法打开)")\r
```\r
\r
**Why pre-filter**: Tasks with `participate_author_count == 0` have no registered influencers. The "邀约达人" modal cannot open for these tasks because there are no influencers to invite. Attempting to download QR codes for them will always fail.\r
\r
**Logging**: For each filtered task, record task ID + name with status `no_participants`.\r
\r
**Error Handling:**\r
- If any page fails: retry up to 3 times with 2-second delays. If still failing, log page number and continue.\r
- If page 1 returns empty: report **"没有找到符合条件的星图任务"** and stop.\r
- If API returns `status_code: 11001`: cookie expired mid-way. Re-run Phase 1, then resume from failed page.\r
\r
### Step 2.4: Extract Task IDs\r
\r
From the collected and filtered task list, extract each task's ID field (`challenge_info.id`). Log total count: `共找到 N 个可下载任务(已过滤 M 个无达人任务)`.\r
\r
---\r
\r
## Phase 3: Download QR Code Images Per Task\r
\r
### Architecture Decision: Python subprocess over PowerShell\r
\r
**⚠️ Critical**: Use Python `subprocess.run()` with array arguments for all agent-browser interactions in this phase. Do NOT use PowerShell for agent-browser calls because:\r
1. PowerShell truncates/splits JS `eval` arguments containing special characters (parentheses, quotes, arrows)\r
2. PowerShell Job-based background tasks get lost when the session ends\r
3. Python's `subprocess.run()` with array mode preserves arguments intact\r
\r
```python\r
# CORRECT - Python subprocess with array args\r
import subprocess, time, json, os, glob, shutil, sys, io\r
\r
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')\r
sys.stdout.reconfigure(line_buffering=True)\r
\r
AGENT = os.path.join(os.environ['APPDATA'], 'npm', 'agent-browser.cmd')\r
WORKSPACE = os.getcwd() # current project directory\r
DOWNLOADS = os.path.expanduser('~/Downloads')\r
\r
def ab(args, timeout=20):\r
"""Run agent-browser command and return stdout"""\r
cmd = [AGENT] + (args if isinstance(args, list) else args.split())\r
return subprocess.run(cmd, capture_output=True, timeout=timeout).stdout.decode('utf-8', errors='replace')\r
\r
def e(js, timeout=20):\r
"""Evaluate JavaScript in browser, return result string"""\r
js1 = ' '.join(js.split()) # collapse whitespace, critical for arg passing\r
r = subprocess.run([AGENT, 'eval', js1], capture_output=True, timeout=timeout)\r
out = r.stdout.decode('utf-8', errors='replace').strip()\r
if out.startswith('"') and out.endswith('"'):\r
try: out = json.loads(out)\r
except: pass\r
return out\r
```\r
\r
### Step 3.1: Prepare Output Directory\r
\r
```powershell\r
New-Item -ItemType Directory -Force -Path "D:\xingtu ask-invite"\r
```\r
\r
### Step 3.2: Implement Resume (断点续传)\r
\r
```python\r
PROGRESS_FILE = os.path.join(WORKSPACE, 'qr_progress.json')\r
\r
# Load existing progress\r
if os.path.exists(PROGRESS_FILE):\r
with open(PROGRESS_FILE, encoding='utf-8') as f:\r
done = json.load(f)\r
else:\r
done = []\r
\r
done_ids = {r['task_id'] for r in done if r['status'] in ('ok', 'skipped')}\r
pending = [t for t in tasks if t['challenge_info']['id'] not in done_ids]\r
```\r
\r
### Step 3.3: Process Each Task\r
\r
For each pending task, use agent-browser (already authenticated from Phase 1):\r
\r
#### Step 3.3.1: Navigate to Task Detail\r
\r
```python\r
ab(['open', f'https://www.xingtu.cn/provider/pages/recruit/management/{tid}'], timeout=20)\r
time.sleep(5) # ⚠️ Do NOT use wait --load networkidle - it hangs on SPAs\r
```\r
\r
**⚠️ Anti-pattern**: `agent-browser wait --load networkidle` hangs indefinitely on SPA (Single Page Application) pages like xingtu.cn because the page continuously emits network events through XHR polling and WebSocket connections. ALWAYS use fixed `time.sleep()` instead.\r
\r
**Error Handling:**\r
- Page fails to load (agent-browser timeout): log task ID + reason, skip to next task.\r
- Page redirects to login: cookie expired. Re-run Phase 1, then resume.\r
\r
#### Step 3.3.2: Click "邀约达人" Button\r
\r
**⚠️ Anti-pattern**: Do NOT store `snapshot -i` ref IDs and use them later. Refs expire after page repaint / DOM mutation. Use `eval` to find and click buttons directly.\r
\r
```python\r
clicked = False\r
for retry in range(5): # Retry up to 5 times, page may still be loading\r
r = e("""(function(){\r
var b = document.querySelectorAll('button');\r
for (var i=0; i\x3Cb.length; i++) {\r
if (b[i].textContent.includes('邀约达人') && b[i].offsetParent !== null) {\r
b[i].click();\r
return 'clicked';\r
}\r
}\r
return 'not found';\r
})()""")\r
if r == 'clicked':\r
clicked = True\r
break\r
time.sleep(2) # Wait between retries for page to load\r
\r
if not clicked:\r
# Record failure and skip\r
done.append({'task_id': tid, 'name': tname, 'status': 'no_invite_btn'})\r
continue\r
```\r
\r
**Error Handling:**\r
- Button not found after 5 retries: record as `no_invite_btn`, skip task. Some task pages have unusual DOM layouts.\r
- Button found but click has no effect: the popup may be blocked. Record as `no_popup`, skip task.\r
\r
#### Step 3.3.3: Wait for and Verify Popup\r
\r
```python\r
# Wait for popup to become visible (up to 8 seconds)\r
for i in range(16):\r
s = e("""(function(){\r
var p = document.querySelector('.ovui-popup__lock');\r
return p && p.offsetWidth > 0 ? 'visible' : 'hidden';\r
})()""")\r
if s == 'visible':\r
break\r
time.sleep(0.5)\r
\r
if s != 'visible':\r
done.append({'task_id': tid, 'name': tname, 'status': 'no_popup'})\r
continue\r
```\r
\r
**Popup container**: The invite modal uses `ovui-popup__lock` class (NOT `el-dialog__wrapper`). This was discovered through screenshot debugging.\r
\r
#### Step 3.3.4: Click "二维码邀请" Radio\r
\r
```python\r
time.sleep(1) # Let popup animation finish\r
e("""(function(){\r
var p = document.querySelector('.ovui-popup__lock');\r
if (!p) return 'no popup';\r
var items = p.querySelectorAll('.ovui-radio-item');\r
for (var i=0; i\x3Citems.length; i++) {\r
if (items[i].offsetParent === null) continue; // hidden elements\r
if (items[i].textContent.trim() == '二维码邀请') {\r
items[i].click();\r
return 'clicked';\r
}\r
}\r
return 'not found';\r
})()""")\r
time.sleep(2) # Wait for QR code to render\r
```\r
\r
**Radio element**: `.ovui-radio-item` with text exactly "二维码邀请" (NOT a button or span).\r
\r
#### Step 3.3.5: Verify Download Button\r
\r
```python\r
dl = e("""(function(){\r
var bs = document.querySelectorAll('button');\r
for (var i=0; i\x3Cbs.length; i++) {\r
if (bs[i].textContent.trim() == '下载图片' && bs[i].offsetParent !== null) {\r
return 'found';\r
}\r
}\r
return 'not found';\r
})()""")\r
\r
if dl != 'found':\r
done.append({'task_id': tid, 'name': tname, 'status': 'no_dl_btn'})\r
# Close popup before skipping\r
e("""(function(){\r
var p = document.querySelector('.ovui-popup__lock');\r
if (!p) return;\r
var c = p.querySelector('[class*=close]');\r
if (c) c.click();\r
})()""")\r
continue\r
```\r
\r
**Error Handling:**\r
- Download button not found: record as `no_dl_btn`. This happens on some task pages (e.g., task 7641072633181650953) where the DOM renders differently.\r
\r
#### Step 3.3.6: Download QR Code Image\r
\r
```python\r
# Track existing files in Downloads BEFORE clicking\r
before = {f: os.path.getmtime(f) for f in glob.glob(os.path.join(DOWNLOADS, '*.png'))}\r
\r
# Click download button\r
e("""(function(){\r
var bs = document.querySelectorAll('button');\r
for (var i=0; i\x3Cbs.length; i++) {\r
if (bs[i].textContent.trim() == '下载图片' && bs[i].offsetParent !== null) {\r
bs[i].click();\r
return 'clicked';\r
}\r
}\r
return 'not found';\r
})()""")\r
\r
# Wait for new file to appear in Downloads (up to 30 seconds)\r
new_file = None\r
for i in range(60):\r
for f in glob.glob(os.path.join(DOWNLOADS, '*.png')):\r
if f not in before:\r
time.sleep(2) # Let file finish writing\r
if os.path.getsize(f) > 100000: # ⚠️ Validate file size\r
new_file = f\r
break\r
if new_file:\r
break\r
time.sleep(0.5)\r
\r
if new_file:\r
sz = os.path.getsize(new_file)\r
shutil.move(new_file, qr_path) # Move from Downloads to output\r
print(f" OK: {sz:,}b") # Expected: ~234,000 bytes\r
done.append({'task_id': tid, 'name': tname, 'status': 'ok', 'size': sz})\r
else:\r
print(f" FAIL: download timeout")\r
done.append({'task_id': tid, 'name': tname, 'status': 'download_timeout'})\r
```\r
\r
**Download behavior notes**:\r
- Browser saves to `~/Downloads/` with filename = task name (e.g., `WL-1.3.1-万益蓝女性益生菌_好货koc-6月.png`), NOT task_id\r
- Valid QR codes are ~234KB, 420×784 pixels, RGB PNG format\r
- Files smaller than 100KB should be treated as failures (likely DOM screenshot or thumbnail)\r
\r
**Error Handling:**\r
- No new file after 30s: record `download_timeout`.\r
- File appears but \x3C 100KB: the download was partial or wrong. Record as `download_small`.\r
\r
#### Step 3.3.7: Close Popup\r
\r
```python\r
# Always close popup after each task to prevent DOM pollution\r
e("""(function(){\r
var p = document.querySelector('.ovui-popup__lock');\r
if (!p) return;\r
// Try close button first\r
var c = p.querySelector('[class*=close]');\r
if (c) {\r
c.click();\r
return 'closed by button';\r
}\r
// Fallback: press Escape\r
document.dispatchEvent(new KeyboardEvent('keydown', {\r
key: 'Escape', keyCode: 27, bubbles: true\r
}));\r
return 'closed by escape';\r
})()""")\r
time.sleep(0.5)\r
```\r
\r
### Step 3.4: Save Progress After EVERY Task\r
\r
```python\r
# CRITICAL: Write progress file after each task completes (or fails)\r
# This enables safe resume if the process is interrupted\r
with open(PROGRESS_FILE, 'w', encoding='utf-8') as f:\r
json.dump(done, f, ensure_ascii=False)\r
```\r
\r
### Step 3.5: Inter-Task Delay\r
\r
Add a 0.5-1 second delay between tasks to avoid rate limiting. Not strictly necessary since each task takes 10-20 seconds, but adds safety.\r
\r
---\r
\r
## Phase 4: Cleanup and Summary\r
\r
### Step 4.1: Close Browser\r
\r
```bash\r
agent-browser close\r
```\r
\r
### Step 4.2: Output Summary Table\r
\r
| 字段 | 说明 |\r
|------|------|\r
| 总任务数 | Total from API (including filtered) |\r
| 符合条件 | After `participate_author_count > 0` filter |\r
| 成功下载 | Tasks with QR code successfully saved (>100KB) |\r
| 跳过/失败 | Tasks skipped or failed, with reason codes |\r
| 输出目录 | `D:\xingtu ask-invite\` |\r
\r
**Failure reason codes**:\r
- `no_participants`: `participate_author_count == 0`, cannot open modal\r
- `no_invite_btn`: "邀约达人" button not found (DOM layout issue)\r
- `no_popup`: Click on invite button didn't open modal\r
- `no_dl_btn`: "下载图片" button not found in modal\r
- `download_timeout`: Download button clicked but no file appeared in 30s\r
- `download_small`: Downloaded file \x3C 100KB (not a valid QR code)\r
\r
For each failed/skipped task, list: task ID, task name, failure reason code.\r
\r
---\r
\r
## Phase 5: Background Execution (Production Mode)\r
\r
When running batch download for 50+ tasks, use background execution to avoid session timeout:\r
\r
### Windows Background Process\r
\r
```cmd\r
REM Start batch in independent minimized CMD window\r
cmd /c start "xingtu_batch" /min cmd /c "python -u inject_and_batch.py > batch_log.txt 2>&1"\r
```\r
\r
**Why this pattern**:\r
- `cmd /c start` creates a truly independent process (not tied to PowerShell session)\r
- `/min` minimizes the window\r
- Output is redirected to a log file for monitoring\r
- The process survives even if the WorkBuddy session ends\r
\r
**⚠️ Anti-pattern**: Using PowerShell `Start-Job` or background jobs. These are tied to the PowerShell session and will be killed if the session ends.\r
\r
### Monitoring\r
\r
```python\r
# Check log file periodically\r
import os\r
log_file = 'batch_log.txt'\r
if os.path.exists(log_file):\r
lines = open(log_file, encoding='utf-8', errors='replace').readlines()\r
print(f"Progress: {len(lines)} lines, {os.path.getsize(log_file)} bytes")\r
# Show last 5 lines for recent status\r
for l in lines[-5:]:\r
print(l.rstrip())\r
```\r
\r
---\r
\r
## Agent Execution Protocol\r
\r
**IMPORTANT: After loading this skill, the agent MUST follow this cycle:**\r
\r
1. **Execute**: Run through all phases (Phase 1 → Phase 2 → Phase 3 → Phase 4) sequentially.\r
2. **Test**: Verify each phase produces real results (not mock data). Validate API responses, check downloaded files exist and are >100KB.\r
3. **Adjust**: If any step fails, fix the issue and retry. Common adjustments:\r
- Cookie expired → re-authenticate\r
- Element not found → take screenshot, check page state, try eval-based selectors\r
- Download failed → check Downloads folder, verify file size\r
- API error → check response, adjust headers/body\r
4. **Report**: After full execution, output the summary table with real data.\r
\r
Never stop at "showing the plan" -- the skill is designed to be executed end-to-end.\r
\r
---\r
\r
## Important Notes\r
\r
1. **Data integrity**: All task data must come from real API responses. Never fabricate task IDs, counts, or statuses.\r
2. **Rate limiting**: 500ms delay between API page requests, 0.5s delay between browser tasks.\r
3. **Cookie refresh**: If any API or page returns 401/redirect-to-login, immediately run Phase 1, then resume.\r
4. **File naming**: QR code images saved as `qrcode.png` under `D:\xingtu ask-invite\{{task_id}}\`.\r
5. **Session management**: Keep agent-browser daemon alive across all phases. Only close at the very end.\r
6. **Fallback**: If "下载图片" is unavailable after clicking "二维码邀请", record as failure and move on. Do not attempt screenshot fallback as it produces only ~2KB thumbnails.\r
7. **File validation**: Always verify downloaded files are >100KB. Valid QR codes are ~234KB. Anything \x3C100KB is a failure.\r
8. **Encoding**: Always use UTF-8 with `errors='replace'` when reading/writing files. Set `sys.stdout` encoding to avoid print crashes.\r
9. **Popup close**: Always close the popup after each task (success or failure) to prevent DOM pollution affecting subsequent tasks.\r
\r
---\r
\r
## 踩坑经验总结 (Pitfalls & Defensive Programming)\r
\r
以下是实际操作中反复验证得出的经验,每个坑都浪费过大量时间。**严格遵守这些规则可以避免 90% 的故障。**\r
\r
### Pitfall 1: `agent-browser wait --load networkidle` 永远卡死\r
\r
**现象**: SPA 页面(如 xingtu.cn)持续发送 XHR 轮询和 WebSocket 心跳包,networkidle 条件永远不满足。\r
\r
**解决方案**: 永远不要用 `wait --load networkidle`。改用固定 `time.sleep(N)`,N 根据实测调整(导航后 5s,弹窗后 1-2s)。\r
\r
```python\r
# ❌ BAD\r
ab('wait --load networkidle')\r
\r
# ✅ GOOD\r
ab(['open', url])\r
time.sleep(5) # empirically determined for xingtu.cn\r
```\r
\r
---\r
\r
### Pitfall 2: PowerShell 的 `eval` 参数被截断\r
\r
**现象**: PowerShell 传递包含括号、引号、尖括号的 JS 代码给 `agent-browser eval` 时,参数在 shell 层被截断或错误转义。\r
\r
```powershell\r
# ❌ BAD - PowerShell truncates the JS after certain characters\r
agent-browser eval "(function(){var b=document.querySelectorAll('button');...})()"\r
```\r
\r
**解决方案**: 使用 Python `subprocess.run()` + 数组参数模式。数组模式不会经过 shell 解析,参数完整传递。\r
\r
```python\r
# ✅ GOOD\r
subprocess.run([AGENT, 'eval', js_code], capture_output=True)\r
```\r
\r
---\r
\r
### Pitfall 3: Snapshot ref ID 在 DOM 变化后过期\r
\r
**现象**: 使用 `agent-browser snapshot -i` 获取 ref ID(如 `[ref=e22]`),然后在页面发生任何变化(导航、弹窗、AJAX 更新)后使用该 ref,点击失败。\r
\r
**解决方案**: \r
- 尽可能使用 `eval` 直接查找并点击元素,不依赖 ref ID\r
- 如果必须用 ref,每次操作前重新 snapshot\r
- `eval` 模式天然免疫 DOM 更新问题\r
\r
```python\r
# ❌ BAD - ref may be expired\r
ag('snapshot -i') # gets ref=e22\r
# ... page changes ...\r
ag('click', '[ref=e22]') # FAILS\r
\r
# ✅ GOOD - always finds the current element\r
e("""(function(){\r
var b = document.querySelectorAll('button');\r
for (var i=0; i\x3Cb.length; i++) {\r
if (b[i].textContent.includes('邀约达人') && b[i].offsetParent !== null) {\r
b[i].click();\r
return 'clicked';\r
}\r
}\r
return 'not found';\r
})()""")\r
```\r
\r
---\r
\r
### Pitfall 4: `participate_author_count == 0` 的任务弹窗打不开\r
\r
**现象**: 对于没有达人报名的任务,点击"邀约达人"按钮后弹窗无法打开(因为没有可邀约的对象)。\r
\r
**解决方案**: Phase 2 结束后立即预过滤,避免在 Phase 3 中浪费时间。\r
\r
```python\r
tasks = [t for t in all_tasks \r
if int(t['challenge_info'].get('participate_author_count') or 0) > 0]\r
```\r
\r
---\r
\r
### Pitfall 5: 弹窗 DOM 结构特殊(`ovui-popup__lock`)\r
\r
**现象**: 星图使用 `ovui-popup__lock` 作为弹窗容器(不是常见的 `el-dialog__wrapper` 或 `modal`)。如果用错选择器,会一直找不到弹窗。\r
\r
**已验证的选择器**:\r
- 弹窗容器: `.ovui-popup__lock`\r
- 二维码邀请 radio: `.ovui-radio-item`(文本 "二维码邀请")\r
- 下载按钮: `button`(文本 "下载图片")\r
- 关闭按钮: `[class*=close]`(在 popup 内部)\r
\r
---\r
\r
### Pitfall 6: Cookie 包含 httpOnly 属性时 `document.cookie` 注入无效\r
\r
**现象**: 用户提供的 cookie 字符串中,`sessionid`、`sid_guard` 等关键 cookie 带有 `httpOnly` 标志。通过 `document.cookie = ...` 注入时,浏览器拒绝设置这些 cookie。\r
\r
**解决方案**: 先尝试 eval 注入。如果注入后页面仍然跳转到登录页,则必须走手动浏览器登录流程(Step 1.5)。\r
\r
检测方法:注入后导航到任务详情页,用 `ev("location.href")` 检查是否在 login/sso 页面。\r
\r
---\r
\r
### Pitfall 7: 下载文件不直接到目标目录\r
\r
**现象**: `agent-browser download` 命令下载到 `~/Downloads/`,文件名为任务名称(中文),不是 task_id。\r
\r
**解决方案**: \r
1. 在点击下载前,记录 Downloads 目录中已有的 `.png` 文件\r
2. 点击下载后,检测 Downloads 中新增的 `.png` 文件\r
3. 用 `shutil.move()` 将文件移动到 `D:\xingtu ask-invite\{task_id}\qrcode.png`\r
4. 验证文件大小 > 100KB\r
\r
```python\r
before = {f: os.path.getmtime(f) for f in glob.glob(os.path.join(DOWNLOADS, '*.png'))}\r
# ... click download ...\r
# Find new file\r
for f in glob.glob(os.path.join(DOWNLOADS, '*.png')):\r
if f not in before and os.path.getsize(f) > 100000:\r
shutil.move(f, qr_path)\r
```\r
\r
---\r
\r
### Pitfall 8: 断点续传需要逐任务保存\r
\r
**现象**: 批量处理 50+ 任务时,如果中途崩溃(浏览器崩溃、网络断开、Cookie 过期),没有进度记录的话需要全部重来。\r
\r
**解决方案**: 每完成一个任务(无论成功或失败)立即写 `qr_progress.json`。这样任何时候中断都可以从中断点继续。\r
\r
```python\r
# After EACH task, not at the end:\r
with open(PROGRESS_FILE, 'w', encoding='utf-8') as f:\r
json.dump(done, f, ensure_ascii=False)\r
```\r
\r
---\r
\r
### Pitfall 9: 弹窗不关闭导致 DOM 污染\r
\r
**现象**: 如果弹窗不关闭,下一个任务的页面可能残留上一个弹窗的 DOM 元素,导致 `querySelector` 找到过期元素。\r
\r
**解决方案**: 每个任务处理完(无论成功/失败)后,主动关闭弹窗。使用 close 按钮 + Escape 双重兜底。\r
\r
```python\r
e("""(function(){\r
var p = document.querySelector('.ovui-popup__lock');\r
if (!p) return;\r
var c = p.querySelector('[class*=close]');\r
if (c) c.click();\r
else document.dispatchEvent(new KeyboardEvent('keydown',\r
{key:'Escape', keyCode:27, bubbles:true}));\r
})()""")\r
```\r
\r
---\r
\r
### Pitfall 10: Python stdout 编码问题导致中文乱码/崩溃\r
\r
**现象**: Windows 环境下 `print()` 输出中文时抛出 `UnicodeEncodeError`,或者日志文件出现乱码。\r
\r
**解决方案**: 在所有 Python 脚本开头设置 stdout 编码:\r
\r
```python\r
import sys, io\r
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')\r
sys.stdout.reconfigure(line_buffering=True) # 实时输出,不缓冲\r
```\r
\r
同时,所有 `open()` 调用使用 `encoding='utf-8'`。\r
\r
---\r
\r
### Quick Reference: 正确的下载流程\r
\r
```\r
1. 导航到任务页 → time.sleep(5) ← 别用 networkidle!\r
2. eval 点击"邀约达人" → 最多重试 5 次\r
3. 等待 .ovui-popup__lock → 最多等 8 秒\r
4. eval 点击"二维码邀请" → .ovui-radio-item\r
5. 验证"下载图片"按钮存在 → 不存在则记录 no_dl_btn\r
6. 记录 Downloads 已有 PNG\r
7. eval 点击"下载图片"\r
8. 检测 Downloads 新文件 → 等待 30 秒 → 验证 > 100KB\r
9. shutil.move → D:\xingtu ask-invite\{task_id}\qrcode.png\r
10. 关闭弹窗 → 保存进度 → 下一个任务\r
```\r
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install xingtu-task-invite-code - After installation, invoke the skill by name or use
/xingtu-task-invite-code - Provide required inputs per the skill's parameter spec and get structured output
What is Xingtu Task Invite Code?
获取星图招募任务(进行中的任务)-邀约达人-二维码邀请 把每个任务的邀约二维码下载到本地电脑,D:\xingtu\task-invite,以任务ID命名每个文件夹. It is an AI Agent Skill for Claude Code / OpenClaw, with 77 downloads so far.
How do I install Xingtu Task Invite Code?
Run "/install xingtu-task-invite-code" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Xingtu Task Invite Code free?
Yes, Xingtu Task Invite Code is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Xingtu Task Invite Code support?
Xingtu Task Invite Code is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Xingtu Task Invite Code?
It is built and maintained by juanjuan2538 (@juanjuan2538); the current version is v1.0.0.