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
This commit is contained in:
commit
b9655a5ab1
1048 files changed
+130851
No files matched your search
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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();
|
||||
Reference in new issue
Block a user