vault backup: 2026-11-09 09:42:19

This commit is contained in:
admin committed 2026-11-09 09:42:19 +08:00
1 parent 511f907980
commit 5022dc27b2
8 files changed
+103 -9

No files matched your search

+63 -2
View File
@@ -6,7 +6,7 @@
const fs = require('fs');
const path = require('path');
const { CDP, listPages, EXPR } = require('./cdp');
const { notify } = require('./notify');
const { notify, sendWebhook } = require('./notify');
const DIR = __dirname;
const CONFIG_PATH = path.join(DIR, 'config.json');
@@ -25,6 +25,7 @@ function log(...parts) {
if (fs.existsSync(LOG_PATH) && fs.statSync(LOG_PATH).size > 2 * 1024 * 1024) {
fs.renameSync(LOG_PATH, LOG_PATH + '.old');
}
if (!fs.existsSync(LOG_PATH)) fs.writeFileSync(LOG_PATH, '\uFEFF', 'utf8'); // BOM: 记事本/编辑器不乱码
fs.appendFileSync(LOG_PATH, line + '\r\n', 'utf8');
} catch { }
}
@@ -117,6 +118,58 @@ function savePrev(map) {
} catch { }
}
// ---------- webhook 节流 + 合并(防刷屏、防企微 20条/分钟限流) ----------
const pushLog = []; // 已推送时间戳
let pendingPush = []; // 被节流时暂存的提醒,攒批后一次发出
function rateState() {
const rl = cfg.rateLimit || {};
const minI = Math.max(0, Number(rl.minIntervalSeconds ?? 60)) * 1000;
const maxH = Number(rl.maxPerHour ?? 30);
const now = Date.now();
while (pushLog.length && now - pushLog[0] > 3600000) pushLog.shift();
if (maxH > 0 && pushLog.length >= maxH) {
return { ok: false, reason: `已达每小时上限 ${maxH} 条` };
}
const last = pushLog[pushLog.length - 1];
if (last && now - last < minI) {
return { ok: false, reason: `距上条推送 ${Math.round((now - last) / 1000)}s(下限 ${minI / 1000}s)` };
}
return { ok: true };
}
async function queuePush(lines, pageUrl) {
const hooks = cfg.webhooks || [];
if (!hooks.length) return;
pendingPush.push({ lines, pageUrl, at: new Date().toLocaleTimeString('zh-CN', { hour12: false }) });
const st = rateState();
if (!st.ok) {
log(`webhook 节流:${st.reason},已并入待发队列(${pendingPush.length} 条)`);
return;
}
await flushPush();
}
async function flushPush() {
if (!pendingPush.length) return;
const hooks = cfg.webhooks || [];
if (!hooks.length) { pendingPush = []; return; }
const batch = pendingPush.slice();
pendingPush = [];
const lastUrl = batch[batch.length - 1].pageUrl;
const body = batch.map(b => `[${b.at}] ${b.lines.join(' / ')}`).join('\n') + `\n${lastUrl}`;
const title = batch.length > 1 ? `闲鱼新消息(合并 ${batch.length} 条提醒)` : '闲鱼有新消息';
pushLog.push(Date.now());
await Promise.all(hooks.map(async (h) => {
try {
const r = await sendWebhook(h, title, body, { url: lastUrl });
log(`[webhook] ${h.type} -> ${r.status}`);
} catch (e) {
log(`[webhook] ${h.type} 失败: ${e.message}`);
}
}));
}
function inQuietHours() {
const q = cfg.quietHours;
if (!q || !q.from || !q.to) return false;
@@ -185,10 +238,18 @@ async function tick(prev) {
log('静默时段,跳过通知:', JSON.stringify(events));
} else {
log('检测到新消息:', JSON.stringify(events));
await notify(cfg, title, text, { url: head.page }, log);
// 本地弹窗不节流(这才是工具的意义);webhook 走节流 + 合并
await notify({ ...cfg, webhooks: [] }, title, text, { url: head.page }, log);
await queuePush(lines, head.page);
}
}
// 有积压且节流窗口已过,补发
if (pendingPush.length) {
const st = rateState();
if (st.ok) await flushPush();
}
savePrev(map);
return map;
}