Fix xianyu QR login refresh

Exclude tiny QR-login icon candidates, expose captured QR dimensions in the Web UI, and refresh the portable Hermes skill release archives.
This commit is contained in:
chick
2026-05-07 15:29:05 +08:00
parent 6c8fdc861f
commit efd6eb8fb9
10 changed files with 264 additions and 22 deletions
+3 -1
View File
@@ -330,7 +330,7 @@ Session-specific implementation notes are captured in `references/web-login-cook
Cookie JSON import timeout and verification notes are captured in `references/cookie-json-import-timeout-2026-05.md`.
Web dashboard QR-login and account status panel implementation notes are captured in `references/web-qr-login-account-panel-2026-05.md`.
Web dashboard QR-login returning a tiny 24x24 icon instead of the real QR, and the minimum-size selector fix, are captured in `references/qr-login-tiny-icon-fix-2026-05-07.md`.
BOSS later simplified the preferred login UX: the visible Web login modal should expose only **获取/刷新登录二维码** and **刷新账号状态**; hide legacy buttons such as manual browser open, Cookie JSON upload, wait-detect, and close-session from the normal flow. Details are captured in `references/web-login-simplified-buttons-2026-05.md`.
@@ -338,6 +338,8 @@ Login UX, persistent-profile lock recovery (`ProcessSingleton` / `SingletonLock`
Web task editing, manual save-only tasks, scheduling, `maxItems`, AI-optimize fallback, API smoke tests, and stale port restart pitfalls are captured in `references/web-task-editing-scheduling-ai-optimize-2026-05.md`.
Existing task schedule updates through the running API, including the 6-hour monitor pattern and API/disk verification, are captured in `references/existing-task-schedule-api-2026-05.md`.
Web AI optimize fallback and automatic Hermes analysis/notification are captured in `references/web-ai-fallback-and-auto-analysis-2026-05.md`.
Web cache cleanup implementation notes are captured in `references/web-cache-cleanup-2026-05.md`.
@@ -0,0 +1,88 @@
# Existing task schedule update via API (2026-05)
## Trigger
BOSS asks to change an existing xianyu-hunter monitor task to run periodically, e.g. “把闲鱼监控中的显示器任务改为每6个小时运行一次”.
## Proven workflow
Use the running local service API first so in-memory scheduler state and disk persistence stay consistent. Do **not** edit `data/tasks.json` directly unless the service is stopped or API is unavailable.
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
python3 - <<'PY'
import json, time, urllib.request
base='http://127.0.0.1:3000'
needle='显示器'
interval_minutes=360
tasks=json.loads(urllib.request.urlopen(base+'/api/tasks', timeout=5).read().decode())
matches=[t for t in tasks if needle in t.get('name','') or any(needle in str(q) for q in t.get('config',{}).get('queries',[]))]
if not matches:
raise SystemExit(f'未找到任务: {needle}')
matches.sort(key=lambda t: t.get('updatedAt') or t.get('createdAt') or 0, reverse=True)
t=matches[0]
config=dict(t.get('config') or {})
config['name']=t.get('name')
config['scheduleEnabled']=True
config['scheduleIntervalMinutes']=interval_minutes
# Let updateTask/_normalizeSchedule compute nextRunAt from now when missing/expired.
config.pop('scheduleNextRunAt', None)
config.pop('scheduleLastRunAt', None)
req=urllib.request.Request(
base+f"/api/tasks/{t['id']}",
data=json.dumps(config).encode(),
headers={'Content-Type':'application/json'},
method='PUT',
)
updated=json.loads(urllib.request.urlopen(req, timeout=10).read().decode())
s=updated.get('config',{}).get('schedule',{})
print(json.dumps({
'id': updated.get('id'),
'name': updated.get('name'),
'stage': updated.get('stage'),
'running': updated.get('running'),
'schedule': s,
'nextRunLocal': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime((s.get('nextRunAt') or 0)/1000)) if s.get('nextRunAt') else None,
'intervalHours': (s.get('intervalMinutes') or 0)/60,
}, ensure_ascii=False, indent=2))
PY
```
## Verification
Verify both the API/in-memory state and `data/tasks.json` disk state:
```bash
python3 - <<'PY'
import json, time, urllib.request
base='http://127.0.0.1:3000'
task_id='<task_id>'
for source, arr in [
('API', json.loads(urllib.request.urlopen(base+'/api/tasks', timeout=5).read().decode())),
('DISK', json.loads(open('data/tasks.json').read())),
]:
for t in arr:
if t.get('id') == task_id:
s=t.get('config',{}).get('schedule',{})
print(source, json.dumps({
'id': t.get('id'),
'name': t.get('name'),
'enabled': s.get('enabled'),
'intervalMinutes': s.get('intervalMinutes'),
'nextRunAt': s.get('nextRunAt'),
'nextRunLocal': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime((s.get('nextRunAt') or 0)/1000)) if s.get('nextRunAt') else None,
}, ensure_ascii=False))
PY
```
Also poll the service/process or check `lsof -nP -iTCP:3000 -sTCP:LISTEN` so the scheduler is actually running.
## Pitfalls
- `scheduleEnabled` and `scheduleIntervalMinutes` are accepted by `updateTask()` and normalized into nested `config.schedule.{enabled,intervalMinutes,nextRunAt,lastRunAt}`.
- The scheduler checks every 30 seconds and triggers due tasks with `startTask(task.id, { reset: true, scheduled: true })`.
- Direct disk edits can be overwritten by the running server and will not update the in-memory scheduler until restart.
- If multiple tasks match the keyword, pick the most recently updated one only after printing matches, or ask BOSS if ambiguity matters.
@@ -110,6 +110,31 @@ git remote set-url origin "$GITEA_URL/$OWNER/$REPO.git"
git remote -v
```
## Additional verified sync: per-item notifications + clickable links
Commit pushed during the notification-link fix:
```text
ad48715664900040e8b92f3d987357694fea2835
fix: make xianyu notifications per-item with clickable links
```
Representative files to raw-verify for this class of update:
```bash
for f in \
project/xianyu-hunter/lib/links.mjs \
project/xianyu-hunter/lib/notifier.mjs \
skill/xianyu-hunter-monitor/references/per-item-notifications-feishu-links-2026-05.md \
project/xianyu-hunter/data/notify-config.example.json; do
curl -sS -L -o /tmp/gitea_raw_check \
-w "$f HTTP %{http_code} size=%{size_download}\n" \
"$GITEA_URL/$OWNER/$REPO/raw/branch/main/$f"
done
```
Also confirm `project/xianyu-hunter/lib/notifier.mjs` raw content contains `perItem`, `maxNotifyItems`, and `打开闲鱼商品`.
## Post-push verification
```bash
@@ -10,11 +10,11 @@ BOSS 希望在每商品单独推送之后,增加一个“单次运行最多推
- 默认:`10`
- 保存时限制范围:`150`
- 位置:`data/notify-config.json`
- 后端:`lib/notifier.mjs`
- `DEFAULT_CONFIG.maxNotifyItems = 10`
- `normalizeNotifyConfig()` 同时用于读取和保存,确保旧配置文件缺少字段时也能返回默认值
- `sendNotifications()` 调用 `buildNotificationMessages()` 时传入 `cfg.maxNotifyItems`
- `selectNotificationItems()` 对符合条件的候选 `.slice(0, maxNotifyItems)`
- 后端:`lib/analyzer.mjs` + `lib/notifier.mjs`
- `buildAnalysis()` 会先按 `hermesAnalysis.score` 从高到低排序:`sort((a, b) => b.score - a.score)`,再生成 `summary.topItems`
- `sendNotifications()`/`selectNotificationItems()` 从已经排序好的 `summary.topItems` 里筛选 `recommend` 或达到 `minScore` 的商品,再 `.slice(0, maxNotifyItems)`
- 因此单次最大推送数语义是:**按评分高到低,最多推送满足通知条件的前 N 个商品**
- 如果未来改动 `summary.topItems` 来源,必须保留“先按评分降序排序,再截断推送数量”的顺序,避免低分商品占用推送名额
- 前端:`public/index.html` + `public/app.js`
- 通知设置弹窗增加输入框:`单次最多推送商品数`
- `loadNotifyConfig()` 回填 `n-max-items`
@@ -0,0 +1,66 @@
# QR login tiny-icon filtering fix — 2026-05-07
## Trigger
BOSS reported that after the xianyu/goofish cookie expired, the Web dashboard failed to correctly refresh the login QR code.
## Root cause
`POST /api/login/qr` returned `qrScreenshotKind: "二维码区域"`, but the returned PNG was only `24x24`. The QR selector logic accepted the first visible `canvas`/QR-looking image, which on Goofish can be a tiny QR-mode switch icon rather than the actual scannable QR code. The frontend then displayed that tiny icon as if it were the login QR.
Evidence from API before fix:
- `qrImage: true`
- PNG size: `24 x 24`
- source URL: `https://www.goofish.com/`
## Fix pattern
In `lib/login.mjs`:
- Add `isUsableQrCandidate(loc)` using `boundingBox()`.
- Reject QR candidates smaller than `120 x 120`.
- Iterate through multiple candidates instead of taking `.first()`.
- Use the same minimum-size filter for modal/container fallback.
- Return `qrImageWidth` and `qrImageHeight` parsed from PNG bytes so the Web UI/logs can expose screenshot dimensions.
In `public/app.js`:
- Display screenshot dimensions in the QR panel hint when present.
## Verification
After killing stale port 3000 server/Chromium profile users and restarting:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check lib/login.mjs
node --check server.mjs
node --check public/app.js
curl -sS -m 120 -X POST http://127.0.0.1:3000/api/login/qr -H 'Content-Type: application/json' -d '{}' > /tmp/xianyu_login_qr_fixed.json
```
Expected key fields:
```json
{
"ok": true,
"loggedIn": false,
"qrScreenshotKind": "二维码区域",
"qrImageWidth": 148,
"qrImageHeight": 148
}
```
Vision/browser verification confirmed the Web modal displays a real black/white QR code with the yellow Xianyu logo, not a tiny icon.
## Operational note
If the API still returns `24x24` after patching, the old Node server is still serving port 3000. `tools/xianyu_ops.mjs stop` may point to stale `.runtime/server.json`; kill the actual listener:
```bash
for p in $(lsof -tiTCP:3000 -sTCP:LISTEN 2>/dev/null); do kill "$p" 2>/dev/null || true; done
pkill -f 'Google Chrome for Testing.*xianyu-hunter/browser-data' || true
```
Then restart the server and verify `/api/login/qr` again.