Files
SnowCatQQ/packages/snowcat-onebot/stable-line.ts
T
天天的鸟蛋蛋 b9655a5ab1 SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37
26 包 monorepo(TS 全源码):
- snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包
- snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线
- snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI)
- snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports)
- snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时
- 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏)
- 工具件: 解包器/启动器/便携制作器/doctor

GitHub 关联已全量指向 DandanLLab/SnowCatQQ
2026-11-12 06:27:36 +08:00

158 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* SnowCat · 稳定性工程线(StableLine)
*
* 三条稳定线(师父令 2026-11-11):
* 1. 连接线:QQ 掉线自愈——登录态监听 + 指数退避重连 + 存活心跳
* 2. 发言线:真人化节奏——发送前抖动延迟 + 突发限流 + 深夜低频
* 3. 账号线:防风控节奏——每小时发送预算 + 连发熔断 + 指数冷却
*
* 全部为可选软层(默认温和值),不干预任何 OneBot 动作语义;
* dsh 侧已有自己的节奏学(去模板化六条/语言学规则),此处只做传输层护栏。
*/
import { SnowCatCore } from 'snowcat-core';
export interface SnowCatStableConfig {
/** 连接线:掉线后重连基础等待(毫秒) */
reconnectBaseMs: number;
/** 连接线:重连指数退避上限(毫秒) */
reconnectCeilingMs: number;
/** 发言线:发送前随机抖动下限(毫秒) */
sendJitterMinMs: number;
/** 发言线:发送前随机抖动上限(毫秒) */
sendJitterMaxMs: number;
/** 发言线:深夜时段(小时,本地时区)发送抖动倍率 */
nightJitterMultiplier: number;
/** 账号线:每小时允许的最大主动发言数(0=不限) */
hourlySendBudget: number;
/** 账号线:连发熔断阈值——连续 N 条无间隔发送后强制冷却 */
burstBreakThreshold: number;
/** 账号线:熔断冷却时长(毫秒) */
burstCooldownMs: number;
}
export const DEFAULT_STABLE_CONFIG: SnowCatStableConfig = {
reconnectBaseMs: 5_000,
reconnectCeilingMs: 300_000,
sendJitterMinMs: 600,
sendJitterMaxMs: 2_400,
nightJitterMultiplier: 1.8,
hourlySendBudget: 120,
burstBreakThreshold: 8,
burstCooldownMs: 90_000,
};
export class SnowCatStableLine {
private cfg: SnowCatStableConfig;
private core: SnowCatCore | null = null;
// 账号线状态
private hourStamp = 0;
private hourCount = 0;
private burstCount = 0;
private burstUntil = 0;
private lastSendAt = 0;
// 连接线状态
private reconnectAttempt = 0;
private online = true;
constructor (cfg: Partial<SnowCatStableConfig> = {}) {
this.cfg = { ...DEFAULT_STABLE_CONFIG, ...cfg };
}
attach (core: SnowCatCore): void {
this.core = core;
}
get config (): SnowCatStableConfig {
return this.cfg;
}
// ─────────────── 连接线 ───────────────
/** 掉线时调用:登记并算下一次重连等待(指数退避)。 */
markOffline (): number {
this.online = false;
this.reconnectAttempt += 1;
const backoff = Math.min(
this.cfg.reconnectBaseMs * 2 ** Math.min(this.reconnectAttempt, 6),
this.cfg.reconnectCeilingMs,
);
return backoff;
}
/** 重连成功时调用:归零退避。 */
markOnline (): void {
this.online = true;
this.reconnectAttempt = 0;
}
get isOnline (): boolean {
return this.online;
}
// ─────────────── 发言线 + 账号线 ───────────────
/**
* 主动发言前调用:返回本次应等待的毫秒数(0=立即)。
* 组合三层:账号预算 → 熔断冷却 → 真人抖动(深夜加倍)。
*/
async beforeSend (): Promise<number> {
const now = Date.now();
// 账号线:小时预算
const hour = Math.floor(now / 3_600_000);
if (this.hourStamp !== hour) {
this.hourStamp = hour;
this.hourCount = 0;
}
if (this.cfg.hourlySendBudget > 0 && this.hourCount >= this.cfg.hourlySendBudget) {
// 本小时预算用尽,睡到下一小时边界(附带随机分钟,别整点齐刷刷)
const nextHour = (hour + 1) * 3_600_000;
return nextHour - now + Math.floor(Math.random() * 60_000);
}
// 账号线:连发熔断
if (now < this.burstUntil) {
return this.burstUntil - now;
}
// 发言线:真人抖动(深夜 0-7 点加倍)
const nightHours = [0, 1, 2, 3, 4, 5, 6, 7];
const h = new Date().getHours();
const mult = nightHours.includes(h) ? this.cfg.nightJitterMultiplier : 1;
let wait = this.cfg.sendJitterMinMs +
Math.random() * (this.cfg.sendJitterMaxMs - this.cfg.sendJitterMinMs);
wait *= mult;
// 账号线:距上条太近计一次连发,超阈值熔断
if (now - this.lastSendAt < 2_000) {
this.burstCount += 1;
if (this.burstCount >= this.cfg.burstBreakThreshold) {
this.burstUntil = now + this.cfg.burstCooldownMs;
this.burstCount = 0;
this.core?.context.logger.logWarn(
`[SnowCat] 连发 ${this.cfg.burstBreakThreshold} 条触发熔断,冷却 ${this.cfg.burstCooldownMs / 1000}s`,
);
return this.cfg.burstCooldownMs;
}
} else {
this.burstCount = 0;
}
this.hourCount += 1;
this.lastSendAt = now;
return Math.floor(wait);
}
/** 便捷等待:beforeSend + sleep。 */
async stableDelay (): Promise<void> {
const ms = await this.beforeSend();
if (ms > 0) await new Promise((r) => setTimeout(r, ms));
}
}
// 全局单例(NapCat 侧 wire 进启动链;dsh 侧动作层经 actions 引用)
export const snowCatStable = new SnowCatStableLine();