From 6c8fdc861f702e2144c0d6bf35375e004df54d33 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 7 May 2026 00:10:35 +0800 Subject: [PATCH] feat: configure max notification items --- project/xianyu-hunter/lib/notifier.mjs | 9 +++- project/xianyu-hunter/public/app.js | 4 +- project/xianyu-hunter/public/index.html | 5 +++ project/xianyu-hunter/server.mjs | 2 +- skill/xianyu-hunter-monitor/SKILL.md | 2 + .../max-notify-items-config-2026-05.md | 44 +++++++++++++++++++ 6 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 skill/xianyu-hunter-monitor/references/max-notify-items-config-2026-05.md diff --git a/project/xianyu-hunter/lib/notifier.mjs b/project/xianyu-hunter/lib/notifier.mjs index f7b9daf..8f7f5e5 100644 --- a/project/xianyu-hunter/lib/notifier.mjs +++ b/project/xianyu-hunter/lib/notifier.mjs @@ -34,13 +34,13 @@ export function getNotifyConfig() { try { if (!fs.existsSync(NOTIFY_FILE)) return { ...DEFAULT_CONFIG, targetOptions: HERMES_TARGET_OPTIONS }; const cfg = JSON.parse(fs.readFileSync(NOTIFY_FILE, 'utf-8')); - return { ...DEFAULT_CONFIG, ...cfg, targetOptions: HERMES_TARGET_OPTIONS, channels: Array.isArray(cfg.channels) ? cfg.channels : DEFAULT_CONFIG.channels }; + return normalizeNotifyConfig({ ...DEFAULT_CONFIG, ...cfg, channels: Array.isArray(cfg.channels) ? cfg.channels : DEFAULT_CONFIG.channels }, true); } catch { return { ...DEFAULT_CONFIG, targetOptions: HERMES_TARGET_OPTIONS }; } } -export function saveNotifyConfig(config = {}) { +function normalizeNotifyConfig(config = {}, includeTargetOptions = false) { const normalized = { enabled: config.enabled !== false, notifyOnlyGood: config.notifyOnlyGood !== false, @@ -57,6 +57,11 @@ export function saveNotifyConfig(config = {}) { target: c.target || c.platform || 'feishu', })), }; + return includeTargetOptions ? { ...normalized, targetOptions: HERMES_TARGET_OPTIONS } : normalized; +} + +export function saveNotifyConfig(config = {}) { + const normalized = normalizeNotifyConfig(config); fs.mkdirSync(DATA_DIR, { recursive: true }); fs.writeFileSync(NOTIFY_FILE, JSON.stringify(normalized, null, 2), 'utf-8'); return { ...normalized, targetOptions: HERMES_TARGET_OPTIONS }; diff --git a/project/xianyu-hunter/public/app.js b/project/xianyu-hunter/public/app.js index 67e02de..027e8eb 100644 --- a/project/xianyu-hunter/public/app.js +++ b/project/xianyu-hunter/public/app.js @@ -912,6 +912,7 @@ async function loadNotifyConfig() { document.getElementById('n-enabled').checked = notifyConfig.enabled !== false; document.getElementById('n-good-only').checked = notifyConfig.notifyOnlyGood !== false; document.getElementById('n-min-score').value = notifyConfig.minScore ?? 75; + document.getElementById('n-max-items').value = notifyConfig.maxNotifyItems ?? 10; renderNotifyChannels(); } @@ -962,7 +963,7 @@ function renderNotifyChannelsFromDom() { } function addNotifyChannel(type) { - if (!notifyConfig) notifyConfig = { enabled: true, notifyOnlyGood: true, minScore: 75, channels: [] }; + if (!notifyConfig) notifyConfig = { enabled: true, notifyOnlyGood: true, minScore: 75, maxNotifyItems: 10, channels: [] }; const normalizedType = type === 'hermes-webhook' ? 'hermes-send-message' : type; const defaults = { 'hermes-send-message': { name: 'Hermes 平台消息', target: 'feishu:oc_3e963a150d98f254d8f38881bcfa1812', url: '' }, @@ -997,6 +998,7 @@ function collectNotifyConfig() { enabled: document.getElementById('n-enabled').checked, notifyOnlyGood: document.getElementById('n-good-only').checked, minScore: Number(document.getElementById('n-min-score').value || 75), + maxNotifyItems: Number(document.getElementById('n-max-items').value || 10), channels, }; } diff --git a/project/xianyu-hunter/public/index.html b/project/xianyu-hunter/public/index.html index e58e312..1bc0d9d 100644 --- a/project/xianyu-hunter/public/index.html +++ b/project/xianyu-hunter/public/index.html @@ -183,6 +183,11 @@ +
+ + + 每次分析完成后最多单独推送多少个候选,避免刷屏。 +
推荐:Hermes 平台消息会直接调用本机 Hermes send_message,可选择飞书/微信/QQ 等目标;Bark、PushPlus 和自定义 Webhook 仍保留为备用。 diff --git a/project/xianyu-hunter/server.mjs b/project/xianyu-hunter/server.mjs index 36e7154..3c8a4d8 100644 --- a/project/xianyu-hunter/server.mjs +++ b/project/xianyu-hunter/server.mjs @@ -137,7 +137,7 @@ app.post('/api/notify-test', async (req, res) => { const task = { id: 'notify-test', name: '通知渠道测试' }; const summary = { minScore: Number(config.minScore ?? 75), - maxNotifyItems: 1, + maxNotifyItems: config.maxNotifyItems ?? 1, total: 1, recommendedCount: 1, cautionCount: 0, diff --git a/skill/xianyu-hunter-monitor/SKILL.md b/skill/xianyu-hunter-monitor/SKILL.md index 5cc5fc4..275d242 100644 --- a/skill/xianyu-hunter-monitor/SKILL.md +++ b/skill/xianyu-hunter-monitor/SKILL.md @@ -358,6 +358,8 @@ Item link normalization for mobile-clickable reports/notifications is captured i Per-item notifications and Feishu Markdown clickable-link formatting are captured in `references/per-item-notifications-feishu-links-2026-05.md`. +Single-run max notification item count (`maxNotifyItems`) is captured in `references/max-notify-items-config-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`. diff --git a/skill/xianyu-hunter-monitor/references/max-notify-items-config-2026-05.md b/skill/xianyu-hunter-monitor/references/max-notify-items-config-2026-05.md new file mode 100644 index 0000000..e057a6d --- /dev/null +++ b/skill/xianyu-hunter-monitor/references/max-notify-items-config-2026-05.md @@ -0,0 +1,44 @@ +# 单次最大推送商品数配置(2026-05) + +## 背景 + +BOSS 希望在每商品单独推送之后,增加一个“单次运行最多推送多少个商品”的可配置项,避免一次分析结果过多导致飞书/微信刷屏。 + +## 实现要点 + +- 配置字段:`maxNotifyItems` + - 默认:`10` + - 保存时限制范围:`1–50` + - 位置:`data/notify-config.json` +- 后端:`lib/notifier.mjs` + - `DEFAULT_CONFIG.maxNotifyItems = 10` + - `normalizeNotifyConfig()` 同时用于读取和保存,确保旧配置文件缺少字段时也能返回默认值。 + - `sendNotifications()` 调用 `buildNotificationMessages()` 时传入 `cfg.maxNotifyItems`。 + - `selectNotificationItems()` 对符合条件的候选 `.slice(0, maxNotifyItems)`。 +- 前端:`public/index.html` + `public/app.js` + - 通知设置弹窗增加输入框:`单次最多推送商品数`。 + - `loadNotifyConfig()` 回填 `n-max-items`。 + - `collectNotifyConfig()` 提交 `maxNotifyItems`。 +- 测试通知:`server.mjs` 的 `/api/notify-test` 读取 config 中的 `maxNotifyItems`,但测试样例本身仍只有 1 个商品。 + +## 验证命令 + +```bash +cd /Users/chick/.Hermes/workspace/research/xianyu-hunter +node --check lib/notifier.mjs +node --check server.mjs +node --check public/app.js +node --input-type=module <<'JS' +import { getNotifyConfig, saveNotifyConfig, sendNotifications } from './lib/notifier.mjs'; +const before = getNotifyConfig(); +const saved = saveNotifyConfig({ enabled: true, notifyOnlyGood: false, minScore: 50, perItem: true, maxNotifyItems: 3, channels: [] }); +const summary = { topItems: Array.from({length: 8}, (_, i) => ({ id: String(1000+i), title: `候选${i+1}`, score: 90-i, verdict: 'recommend', price: '150', location: '测试', reason: '验证单次最大推送数量', href: `https://www.goofish.com/item?id=${1000+i}` })) }; +const result = await sendNotifications({ id:'t', name:'maxNotifyItems 验证' }, summary, { force: true, config: saved }); +console.log(result.messages.length); // expected: 3 +saveNotifyConfig(before); +JS +``` + +## 运行时注意 + +修改 `lib/notifier.mjs` / `server.mjs` 后必须重启正在监听 3000 的 `node server.mjs`,否则 Web API 和通知逻辑仍可能是旧代码。