fix: make xianyu notifications per-item with clickable links
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user