fix: make xianyu notifications per-item with clickable links

This commit is contained in:
2026-05-06 23:30:25 +08:00
parent d5294a3f9b
commit ad48715664
13 changed files with 454 additions and 53 deletions
+6
View File
@@ -348,10 +348,16 @@ Home dashboard clickable overview/workflow cards and deep-link behavior are capt
Portable skill/project packaging, safe archive exclusions, install script shape, and self-hosted Gitea publishing information requirements are captured in `references/portable-package-and-gitea-publishing-2026-05.md`.
Gitea portable package sync workflow for updating the published package from the local messy working tree is captured in `references/gitea-portable-package-sync-2026-05.md`.
Public Gitea publish verification, token-scrubbed remote handling, and final reporting checklist are captured in `references/gitea-public-publish-verification-2026-05.md`.
Return navigation after deep-linking from the home dashboard is captured in `references/web-home-return-navigation-2026-05.md`.
Item link normalization for mobile-clickable reports/notifications is captured in `references/item-link-normalization-2026-05.md`.
Per-item notifications and Feishu Markdown clickable-link formatting are captured in `references/per-item-notifications-feishu-links-2026-05.md`.
Portable export packaging for sharing this skill/project with other Hermes instances is captured in `references/portable-skill-package-2026-05.md`.
@@ -0,0 +1,133 @@
# Sync local xianyu-hunter changes into the Gitea portable package (2026-05)
## Trigger
BOSS says “同步更新 gitea” after local xianyu-hunter project changes. In this setup the local working clone may still have upstream `origin=https://github.com/Disrush/xianyu-hunter.git`, while the public Gitea repo is the portable package:
```text
https://gitea.chickliu.fun/Hermes/xianyu-hunter-hermes-skill
```
Do **not** push the messy local working clone directly to GitHub/upstream or to a new guessed Gitea repo. Update the existing portable package repo by cloning it to a temp workdir and rsyncing sanitized contents into `project/xianyu-hunter/` plus the skill into `skill/xianyu-hunter-monitor/`.
## Proven workflow
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
set -a
source ~/.hermes/env/gitea.env
set +a
OWNER="${GITEA_OWNER:-Hermes}"
REPO=xianyu-hunter-hermes-skill
rm -rf /tmp/xianyu-hunter-gitea-sync
git clone "$GITEA_URL/$OWNER/$REPO.git" /tmp/xianyu-hunter-gitea-sync
SRC=/Users/chick/.Hermes/workspace/research/xianyu-hunter
DST=/tmp/xianyu-hunter-gitea-sync/project/xianyu-hunter
SKILL_SRC=/Users/chick/.hermes/skills/social-media/xianyu-hunter-monitor
SKILL_DST=/tmp/xianyu-hunter-gitea-sync/skill/xianyu-hunter-monitor
rsync -a --delete \
--exclude='.git/' \
--exclude='node_modules/' \
--exclude='browser-data/' \
--exclude='.runtime/' \
--exclude='xianyu-server.log' \
--exclude='*.log' \
--exclude='data/config.json' \
--exclude='data/tasks.json' \
--exclude='data/notify-config.json' \
--exclude='reports/*.md' \
"$SRC/" "$DST/"
mkdir -p "$DST/reports"
touch "$DST/reports/.gitkeep"
rsync -a --delete "$SKILL_SRC/" "$SKILL_DST/"
```
Preserve/restore package examples that should remain committed even if absent locally, e.g. `project/xianyu-hunter/data/notify-config.example.json`:
```bash
cat > /tmp/xianyu-hunter-gitea-sync/project/xianyu-hunter/data/notify-config.example.json <<'EOF'
{
"enabled": false,
"notifyOnlyGood": true,
"minScore": 75,
"channels": []
}
EOF
```
Refresh the package `.gitignore` so runtime data cannot be committed:
```bash
cat > /tmp/xianyu-hunter-gitea-sync/.gitignore <<'EOF'
.DS_Store
node_modules/
browser-data/
.runtime/
*.log
project/xianyu-hunter/node_modules/
project/xianyu-hunter/browser-data/
project/xianyu-hunter/.runtime/
project/xianyu-hunter/data/config.json
project/xianyu-hunter/data/tasks.json
project/xianyu-hunter/data/notify-config.json
project/xianyu-hunter/reports/*.md
EOF
```
## Verification before commit
```bash
cd /tmp/xianyu-hunter-gitea-sync
node --check project/xianyu-hunter/server.mjs
node --check project/xianyu-hunter/lib/login.mjs
node --check project/xianyu-hunter/lib/hermes_optimizer.mjs
node --check project/xianyu-hunter/public/app.js
git status --short --ignored
git ls-files | grep -E 'browser-data|data/(config|tasks|notify-config)\.json|node_modules|\.runtime|reports/.*\.md' || true
```
Expected sensitive-file grep output is empty. Review `git diff --stat` and representative diffs before committing.
## Commit and push safely
```bash
cd /tmp/xianyu-hunter-gitea-sync
git config user.name 'Hermes Agent'
git config user.email 'Hermes@Hermes.Hermes'
git add .
git commit -m 'feat: update xianyu web login and Hermes optimizer'
git remote set-url origin "https://${GITEA_USERNAME:-Hermes}:${GITEA_TOKEN}@${GITEA_URL#https://}/$OWNER/$REPO.git"
git push origin main
# scrub token immediately
git remote set-url origin "$GITEA_URL/$OWNER/$REPO.git"
git remote -v
```
## Post-push verification
```bash
curl -sS -H "Authorization: token $GITEA_TOKEN" \
-o /tmp/gitea_repo_verify.json \
-w 'HTTP %{http_code} size=%{size_download}\n' \
"$GITEA_URL/api/v1/repos/$OWNER/$REPO"
git ls-remote "$GITEA_URL/$OWNER/$REPO.git" HEAD refs/heads/main
for f in \
project/xianyu-hunter/lib/hermes_optimizer.mjs \
project/xianyu-hunter/lib/login.mjs \
skill/xianyu-hunter-monitor/references/web-qr-source-and-hermes-optimize-2026-05.md; 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
```
Report the repo URL, commit hash, and verification results back to BOSS in the final visible reply. Do not leave an empty response after tool calls.
@@ -0,0 +1,62 @@
# Xianyu Item Link Normalization — 2026-05
## Context
BOSS reported that generated Xianyu/Goofish candidate links were not reliably clickable from Feishu/mobile messages. Existing data often stored desktop links like:
```text
https://www.goofish.com/item?id=<id>&categoryId=<category>
```
These can return HTTP 200 in curl but may not deep-link well from messaging clients. Mobile item links are more reliable for user-facing reports/notifications:
```text
https://h5.m.goofish.com/item?id=<id>
```
## Implemented pattern
Add/use `lib/links.mjs`:
- `normalizeItemId(value)` extracts item IDs from `id`, `itemId`, full item URLs, IM URLs, and raw text.
- `buildItemLinks(itemOrId)` returns desktop and mobile URLs.
- `enrichItemLinks(item)` preserves original URL in `originalHref`, stores desktop URL in `webHref`, mobile URL in `mobileHref`, and sets user-facing `href` to the mobile URL.
Integration points:
- `lib/search.mjs`: after scraping desktop card href/category, call `enrichItemLinks(item)` before returning results.
- `lib/filter.mjs`: after merging detail fields, call `enrichItemLinks({ ...product, ...detail })` so fine-filtered candidates keep clickable mobile links.
- `lib/task.mjs`: normalize products during load/serialize/persist so existing `data/tasks.json` records become compatible without manual migration.
- `lib/analyzer.mjs`, `lib/notifier.mjs`, `tools/hermes_report.mjs`: use `buildItemLinks(item).primaryUrl` when rendering reports and notifications.
## Verification commands
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check lib/links.mjs && node --check lib/search.mjs && node --check lib/filter.mjs && node --check lib/task.mjs && node --check lib/analyzer.mjs && node --check lib/notifier.mjs && node --check tools/hermes_report.mjs
node --input-type=module - <<'NODE'
import { buildItemLinks, enrichItemLinks } from './lib/links.mjs';
console.log(buildItemLinks({ id: '1017096975429', href: 'https://www.goofish.com/item?id=1017096975429&categoryId=126856267' }));
console.log(enrichItemLinks({ href: '/item?id=1039232668094&categoryId=126856522' }));
NODE
node tools/hermes_report.mjs --task <task_id> --limit 3 --out /tmp/xianyu-link-test.md
sed -n '1,80p' /tmp/xianyu-link-test.md
```
Expected user-facing links in reports/notifications:
```text
https://h5.m.goofish.com/item?id=<id>
```
## Pitfall
Do not replace `chatUrl` with mobile item links. Seller chat still needs desktop IM URLs such as:
```text
https://www.goofish.com/im?itemId=<id>&peerUserId=<sellerId>
```
Only user-facing product links should prefer h5 mobile URLs.
@@ -0,0 +1,85 @@
# Xianyu Per-Item Notifications + Feishu Clickable Links — 2026-05
## Context
BOSS reported two UX issues after initial link normalization:
1. Feishu still did not show reliable clickable product links.
2. Notifications should behave like the original project: each product should be pushed as its own message, not bundled into one long report-style message.
A key pitfall was that the running `server.mjs` process still used the old notifier module until the service was restarted. Always restart the Node service after patching notification rendering.
## Implemented pattern
`lib/notifier.mjs` now defaults to:
```json
{
"perItem": true,
"maxNotifyItems": 10
}
```
Notification rendering now:
- Selects candidates by `verdict === 'recommend' || score >= minScore`.
- Sends up to `maxNotifyItems` items.
- Sends one message per item when `perItem !== false`.
- Uses explicit Feishu-compatible Markdown link syntax:
```md
[🔗 打开闲鱼商品](https://h5.m.goofish.com/item?id=<id>)
备用链接:https://h5.m.goofish.com/item?id=<id>
```
The backup plain h5 URL is kept because Feishu/clients sometimes vary in Markdown rendering.
## Verification
After patching, run:
```bash
cd /Users/chick/.Hermes/workspace/research/xianyu-hunter
node --check lib/notifier.mjs && node --check server.mjs
```
Dry-run without sending:
```bash
node --input-type=module - <<'NODE'
import fs from 'fs';
import { buildAnalysis } from './lib/analyzer.mjs';
import { sendNotifications } from './lib/notifier.mjs';
const data = JSON.parse(fs.readFileSync('data/tasks.json','utf8'));
const task = data.at(-1);
const analysis = buildAnalysis(task, { limit: 3 });
const res = await sendNotifications(task, { ...analysis.summary, maxNotifyItems: 3 }, {
force: true,
config: { enabled: true, notifyOnlyGood: false, minScore: 0, perItem: true, maxNotifyItems: 3, channels: [] }
});
console.log(res.messages.join('\n\n---\n\n'));
NODE
```
Restart exact Node server process, then test through API:
```bash
# kill old node server.mjs PID, then start tracked/background process
PORT=3000 node server.mjs
curl -sS -X POST http://127.0.0.1:3000/api/notify-test \
-H 'Content-Type: application/json' \
-d '{"title":"xianyu-hunter 单商品链接测试"}' | python3 -m json.tool
```
Expected API response includes:
- `perItem: true`
- `messages: [ ... ]`
- message text contains `[🔗 打开闲鱼商品](https://h5.m.goofish.com/item?id=...)`
- `results[].itemIndex` for each individual pushed item
## Notes
- `cfg.perItem === false` can still be used to send a bundled message for webhook channels if needed.
- `chatUrl` should remain a desktop IM URL; only user-facing product links use h5 mobile URLs.