@bilibili-notify/dynamic 0.0.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/lib/index.cjs +556 -0
- package/lib/index.d.cts +316 -0
- package/lib/index.d.mts +316 -0
- package/lib/index.mjs +553 -0
- package/package.json +47 -0
package/lib/index.d.cts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { CommentaryCallOverride, CommentaryGenerator } from "@bilibili-notify/ai";
|
|
2
|
+
import { BilibiliAPI } from "@bilibili-notify/api";
|
|
3
|
+
import { ImageRenderer } from "@bilibili-notify/image";
|
|
4
|
+
import { Logger, MessageBus, ServiceContext } from "@bilibili-notify/internal";
|
|
5
|
+
|
|
6
|
+
//#region src/types.d.ts
|
|
7
|
+
type RichTextNode = {
|
|
8
|
+
orig_text: string;
|
|
9
|
+
text: string;
|
|
10
|
+
type: string;
|
|
11
|
+
emoji?: {
|
|
12
|
+
icon_url: string;
|
|
13
|
+
size: number;
|
|
14
|
+
text: string;
|
|
15
|
+
type: number;
|
|
16
|
+
};
|
|
17
|
+
jump_url?: string;
|
|
18
|
+
rid?: string;
|
|
19
|
+
goods?: any;
|
|
20
|
+
icon_name?: string;
|
|
21
|
+
};
|
|
22
|
+
type Dynamic = {
|
|
23
|
+
basic: Object;
|
|
24
|
+
id_str: string;
|
|
25
|
+
type: string;
|
|
26
|
+
orig?: Dynamic;
|
|
27
|
+
modules: {
|
|
28
|
+
module_author: {
|
|
29
|
+
face: string;
|
|
30
|
+
following: boolean;
|
|
31
|
+
jump_url: string;
|
|
32
|
+
label: string;
|
|
33
|
+
mid: number;
|
|
34
|
+
name: string;
|
|
35
|
+
pub_action: string;
|
|
36
|
+
pub_time: string;
|
|
37
|
+
pub_ts: number;
|
|
38
|
+
type: string;
|
|
39
|
+
[key: string]: any;
|
|
40
|
+
};
|
|
41
|
+
module_dynamic: {
|
|
42
|
+
additional?: any;
|
|
43
|
+
desc?: {
|
|
44
|
+
rich_text_nodes: RichTextNode[];
|
|
45
|
+
text: string;
|
|
46
|
+
};
|
|
47
|
+
major?: {
|
|
48
|
+
opus?: {
|
|
49
|
+
fold_action: string[];
|
|
50
|
+
jump_url: string;
|
|
51
|
+
pics: Array<{
|
|
52
|
+
height: number;
|
|
53
|
+
live_url: string;
|
|
54
|
+
size: number;
|
|
55
|
+
url: string;
|
|
56
|
+
width: number;
|
|
57
|
+
}>;
|
|
58
|
+
summary: {
|
|
59
|
+
rich_text_nodes: RichTextNode[];
|
|
60
|
+
text: string;
|
|
61
|
+
};
|
|
62
|
+
title: string;
|
|
63
|
+
};
|
|
64
|
+
archive?: {
|
|
65
|
+
title: string;
|
|
66
|
+
jump_url: string;
|
|
67
|
+
[key: string]: any;
|
|
68
|
+
};
|
|
69
|
+
[key: string]: any;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
type AllDynamicInfo = {
|
|
75
|
+
code: number;
|
|
76
|
+
message: string;
|
|
77
|
+
data: {
|
|
78
|
+
has_more: boolean;
|
|
79
|
+
items: Dynamic[];
|
|
80
|
+
offset: string;
|
|
81
|
+
update_baseline: string;
|
|
82
|
+
update_num: number;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
type DynamicTimelineManager = Map<string, number>;
|
|
86
|
+
interface DynamicFilterConfig {
|
|
87
|
+
enable?: boolean;
|
|
88
|
+
notify?: boolean;
|
|
89
|
+
regex?: string;
|
|
90
|
+
keywords?: string[];
|
|
91
|
+
forward?: boolean;
|
|
92
|
+
article?: boolean;
|
|
93
|
+
whitelistEnable?: boolean;
|
|
94
|
+
whitelistRegex?: string;
|
|
95
|
+
whitelistKeywords?: string[];
|
|
96
|
+
}
|
|
97
|
+
declare enum DynamicFilterReason {
|
|
98
|
+
BlacklistKeyword = "blacklist-keyword",
|
|
99
|
+
BlacklistForward = "blacklist-forward",
|
|
100
|
+
BlacklistArticle = "blacklist-article",
|
|
101
|
+
WhitelistUnmatched = "whitelist-unmatched"
|
|
102
|
+
}
|
|
103
|
+
interface DynamicFilterResult {
|
|
104
|
+
blocked: boolean;
|
|
105
|
+
reason?: DynamicFilterReason;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/push-like.d.ts
|
|
109
|
+
/** dynamic-engine 渲染好的图片缓冲(无 mime/扩展信息时默认 image/jpeg)。 */
|
|
110
|
+
interface PushImagePart {
|
|
111
|
+
type: "image";
|
|
112
|
+
buffer: Buffer;
|
|
113
|
+
mime: string;
|
|
114
|
+
}
|
|
115
|
+
/** 文本片段。 */
|
|
116
|
+
interface PushTextPart {
|
|
117
|
+
type: "text";
|
|
118
|
+
text: string;
|
|
119
|
+
}
|
|
120
|
+
/** 用于「专题」转发图集等需要折叠成 forward message 的多段图片。 */
|
|
121
|
+
interface PushImageGroup {
|
|
122
|
+
type: "image-group";
|
|
123
|
+
forward: boolean;
|
|
124
|
+
urls: string[];
|
|
125
|
+
}
|
|
126
|
+
type PushSegment = PushImagePart | PushTextPart | PushImageGroup;
|
|
127
|
+
/**
|
|
128
|
+
* dynamic-engine 仅需以下三类语义化推送动作。
|
|
129
|
+
* 业务核心调用前已经决定好「此次推送的目标维度」(通过 uid + PushKind),
|
|
130
|
+
* adapter 负责把它翻译为具体平台的 channel 列表 / atAll / 图片折叠等行为。
|
|
131
|
+
*/
|
|
132
|
+
type PushKind = /** 主体动态卡片:可能携带图片 + 文本 */"dynamic" | /** 动态附图(DYNAMIC_TYPE_DRAW 的多张原图,转发消息形式) */"dynamic-images";
|
|
133
|
+
interface PushLike {
|
|
134
|
+
/**
|
|
135
|
+
* 向某个 UP 主对应的全部订阅频道广播一段消息。
|
|
136
|
+
* - kind="dynamic":主卡片消息,包含 image + text 段。
|
|
137
|
+
* - kind="dynamic-images":DYNAMIC_TYPE_DRAW 的图集,adapter 通常以 forward message 投递。
|
|
138
|
+
*/
|
|
139
|
+
broadcastDynamic(uid: string, segments: PushSegment[], kind: PushKind): Promise<void>;
|
|
140
|
+
/** 私信发送给配置的管理员账号(master)。adapter 端校验启用状态与 bot 在线性。 */
|
|
141
|
+
sendPrivateMsg(content: string): Promise<void>;
|
|
142
|
+
/** 与 sendPrivateMsg 等价,但 adapter 应当在内部把内容追加到 error 日志。 */
|
|
143
|
+
sendErrorMsg(reason: string): Promise<void>;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 平台中立的订阅条目最小视图。dynamic-engine 仅访问 `uid` 与 `customCardStyle`
|
|
147
|
+
* 相关字段;adapter 提供完整 SubItem 实例时会被结构性兼容(额外字段不影响)。
|
|
148
|
+
*
|
|
149
|
+
* `filter` / `aiOverride` 为 per-UP 覆盖(可选):adapter 折叠 `Subscription.overrides`
|
|
150
|
+
* 后填入;缺失时 engine 回退到 `DynamicEngineConfig.filter` / 全局 CommentaryGenerator 配置。
|
|
151
|
+
*/
|
|
152
|
+
interface SubItemView {
|
|
153
|
+
uid: string;
|
|
154
|
+
uname: string;
|
|
155
|
+
dynamic?: boolean;
|
|
156
|
+
customCardStyle?: {
|
|
157
|
+
enable?: boolean;
|
|
158
|
+
cardColorStart?: string;
|
|
159
|
+
cardColorEnd?: string;
|
|
160
|
+
};
|
|
161
|
+
/** Per-UP 动态过滤覆盖;undefined 时使用 engine 的全局 filter。 */
|
|
162
|
+
filter?: DynamicFilterConfig & {
|
|
163
|
+
notify?: boolean;
|
|
164
|
+
};
|
|
165
|
+
/** Per-UP AI 覆盖;undefined 时使用 CommentaryGenerator 的全局 config。 */
|
|
166
|
+
aiOverride?: CommentaryCallOverride;
|
|
167
|
+
}
|
|
168
|
+
type SubscriptionsView = Record<string, SubItemView>;
|
|
169
|
+
type SubManagerView = Map<string, SubItemView>;
|
|
170
|
+
/**
|
|
171
|
+
* Adapter 提供给 engine 的「最新订阅快照」访问器与增量操作描述。
|
|
172
|
+
* Koishi adapter 在收到 `bilibili-notify/subscription-changed` 时调用 engine.applyOps;
|
|
173
|
+
* 独立端在 SubscriptionStore 写入后同样转译为 SubscriptionOpView 列表。
|
|
174
|
+
*/
|
|
175
|
+
type SubscriptionOpView = {
|
|
176
|
+
type: "add";
|
|
177
|
+
sub: SubItemView;
|
|
178
|
+
} | {
|
|
179
|
+
type: "delete";
|
|
180
|
+
uid: string;
|
|
181
|
+
} | {
|
|
182
|
+
type: "update";
|
|
183
|
+
uid: string;
|
|
184
|
+
changes: Array<{
|
|
185
|
+
scope: string;
|
|
186
|
+
dynamic?: boolean;
|
|
187
|
+
}>;
|
|
188
|
+
};
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/dynamic-engine.d.ts
|
|
191
|
+
/**
|
|
192
|
+
* Runtime configuration for {@link DynamicEngine}. Mirrors the platform-neutral
|
|
193
|
+
* subset of `BilibiliNotifyDynamicConfig`; the koishi shell maps its schema
|
|
194
|
+
* fields onto this struct, the standalone runtime fills it from its own config
|
|
195
|
+
* store. The `logLevel` field is intentionally dropped — adapter sets logger
|
|
196
|
+
* level externally via {@link ServiceContext}.
|
|
197
|
+
*/
|
|
198
|
+
interface DynamicEngineConfig {
|
|
199
|
+
/** 推送动态时是否附带 URL(QQ 官方机器人需关闭)。 */
|
|
200
|
+
dynamicUrl: boolean;
|
|
201
|
+
/** 轮询动态的 cron 表达式。 */
|
|
202
|
+
dynamicCron: string;
|
|
203
|
+
/** 视频动态时是否将 URL 替换为 BV 号。 */
|
|
204
|
+
dynamicVideoUrlToBV: boolean;
|
|
205
|
+
/** 是否额外推送 DYNAMIC_TYPE_DRAW 中的图集(forward message)。 */
|
|
206
|
+
pushImgsInDynamic: boolean;
|
|
207
|
+
/** 内容过滤配置(含 notify:被屏蔽时是否通知)。 */
|
|
208
|
+
filter: DynamicFilterConfig & {
|
|
209
|
+
notify?: boolean;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* 是否启用图片卡片渲染。`false` 时跳过 puppeteer 调用,推送降级为纯文字。缺省视为 true,
|
|
213
|
+
* 保留旧 adapter 不传该字段时的既有行为。Adapter 通常用 `globals.defaults.cardStyle.enabled` 填充。
|
|
214
|
+
*/
|
|
215
|
+
imageEnabled?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* 是否启用 AI 动态点评。`false` 时跳过 `CommentaryGenerator.comment()` 调用,推送只用原始动态文本。
|
|
218
|
+
* 缺省视为 true。Adapter 通常用 `globals.defaults.ai.enabled` 填充。
|
|
219
|
+
*/
|
|
220
|
+
aiEnabled?: boolean;
|
|
221
|
+
}
|
|
222
|
+
interface DynamicEngineOptions {
|
|
223
|
+
serviceCtx: ServiceContext;
|
|
224
|
+
bus: MessageBus;
|
|
225
|
+
api: BilibiliAPI;
|
|
226
|
+
push: PushLike;
|
|
227
|
+
/** 可选注入:图片渲染器;缺失时降级为纯文字推送。 */
|
|
228
|
+
image?: ImageRenderer;
|
|
229
|
+
/** 可选注入:AI 点评生成器;缺失时跳过 AI 文案生成。 */
|
|
230
|
+
ai?: CommentaryGenerator;
|
|
231
|
+
config: DynamicEngineConfig;
|
|
232
|
+
/**
|
|
233
|
+
* Adapter 提供的订阅快照访问器。返回 null 表示订阅尚未就绪
|
|
234
|
+
* (engine 会在收到 `subscription-changed` / `auth-restored` 后再次拉取)。
|
|
235
|
+
*/
|
|
236
|
+
getSubs: () => SubscriptionsView | null;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 平台中立的动态轮询/过滤/渲染核心。
|
|
240
|
+
*
|
|
241
|
+
* - 不依赖 koishi runtime;adapter 提供 ServiceContext / MessageBus / PushLike。
|
|
242
|
+
* - image / ai 通过 **构造期注入**(不在 detect 循环内做服务查找),缺失时降级。
|
|
243
|
+
* - 时间线、过滤、API 错误处理逻辑与原 koishi 版 BilibiliNotifyDynamic 一致。
|
|
244
|
+
*/
|
|
245
|
+
declare class DynamicEngine {
|
|
246
|
+
private readonly serviceCtx;
|
|
247
|
+
private readonly bus;
|
|
248
|
+
private readonly api;
|
|
249
|
+
private readonly push;
|
|
250
|
+
private readonly image?;
|
|
251
|
+
private ai?;
|
|
252
|
+
private readonly logger;
|
|
253
|
+
private readonly getSubs;
|
|
254
|
+
private config;
|
|
255
|
+
private dynamicJob?;
|
|
256
|
+
/** 风控/瞬时错误后的一次性退避重启句柄;非 undefined 表示已排程,不叠加。 */
|
|
257
|
+
private detectorRestartTimer?;
|
|
258
|
+
/**
|
|
259
|
+
* -352 风控态边沿标记。进入风控只 error+DM+engine-error 一次;退避重探仍
|
|
260
|
+
* 风控 → debug(不重复告警);成功拉取(code 0)→ info 一次并清除。避免在
|
|
261
|
+
* 退避重试热路径反复刷 error(Q1)。
|
|
262
|
+
*/
|
|
263
|
+
private riskControlled;
|
|
264
|
+
private dynamicSubManager;
|
|
265
|
+
private dynamicTimelineManager;
|
|
266
|
+
/** 连续图片渲染失败计数,达到阈值时仅通知一次但不停 cron */
|
|
267
|
+
private imageFailureStreak;
|
|
268
|
+
private imageFailureNotified;
|
|
269
|
+
private readonly busHandles;
|
|
270
|
+
constructor(opts: DynamicEngineOptions);
|
|
271
|
+
/** 启动钩子。Adapter 在 ServiceContext 就绪、订阅可访问后调用。 */
|
|
272
|
+
start(): void;
|
|
273
|
+
/** 停止钩子。停止 cron、释放事件订阅。 */
|
|
274
|
+
stop(): void;
|
|
275
|
+
/**
|
|
276
|
+
* 替换运行时配置(adapter 在 koishi config / dashboard 编辑后调用)。
|
|
277
|
+
* `dynamicCron` 变化时会自动停掉旧 CronJob 并按新表达式重新 schedule —— 否则
|
|
278
|
+
* 配置已经写进 this.config,但 node-cron 句柄还在跑旧节奏,纯粹的字段更新
|
|
279
|
+
* 是看不见的 bug。
|
|
280
|
+
*/
|
|
281
|
+
updateConfig(config: DynamicEngineConfig): void;
|
|
282
|
+
/**
|
|
283
|
+
* 热替换 CommentaryGenerator 实例。adapter 在用户运行时打开 / 关闭 / 更换 AI
|
|
284
|
+
* 配置后调用,引擎随后的动态点评会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
285
|
+
*/
|
|
286
|
+
setAi(ai: CommentaryGenerator | undefined): void;
|
|
287
|
+
get isActive(): boolean;
|
|
288
|
+
/** 用最新订阅快照重启动态检测;保留已有 UID 的时间戳避免重推旧动态。 */
|
|
289
|
+
startDynamicDetector(subs: SubscriptionsView): void;
|
|
290
|
+
private startDynamicForUid;
|
|
291
|
+
private stopDynamicForUid;
|
|
292
|
+
/**
|
|
293
|
+
* UID 是否仍订阅。detectDynamics 在 image/AI/broadcast 等多个 await 处挂起,
|
|
294
|
+
* `applyOps`(由 adapter 在 subscription-changed 时调,**不**在 withLock 内)
|
|
295
|
+
* 可在挂起期 stopDynamicForUid 删表。每个 dispatch / 时间线回写前用它重校,
|
|
296
|
+
* 否则会给已退订 UID 推送、并把其时间线“复活”进而长期抑制再订阅后的动态。
|
|
297
|
+
*/
|
|
298
|
+
private stillSubscribed;
|
|
299
|
+
/** Incrementally apply subscription ops without restarting the cron job. */
|
|
300
|
+
applyOps(ops: SubscriptionOpView[]): void;
|
|
301
|
+
private startJob;
|
|
302
|
+
private reconcileJob;
|
|
303
|
+
private detectDynamics;
|
|
304
|
+
private handleApiError;
|
|
305
|
+
/**
|
|
306
|
+
* 风控/瞬时错误后排一次性退避重启。已有待执行的不叠加(`detectorRestartTimer`
|
|
307
|
+
* 非空即跳过)。到点取最新订阅快照重启检测;`stop()` / 显式 `startDynamicDetector`
|
|
308
|
+
* 会作废本计时(避免 dispose 后 / 重启后仍触发陈旧重启)。
|
|
309
|
+
*/
|
|
310
|
+
private scheduleDetectorRestart;
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/dynamic-filter.d.ts
|
|
314
|
+
declare function filterDynamic(dynamic: Dynamic, config: DynamicFilterConfig, logger?: Logger): DynamicFilterResult;
|
|
315
|
+
//#endregion
|
|
316
|
+
export { type AllDynamicInfo, type Dynamic, DynamicEngine, type DynamicEngineConfig, type DynamicEngineOptions, type DynamicFilterConfig, DynamicFilterReason, type DynamicFilterResult, type DynamicTimelineManager, type PushImageGroup, type PushImagePart, type PushKind, type PushLike, type PushSegment, type PushTextPart, type RichTextNode, type SubItemView, type SubManagerView, type SubscriptionOpView, type SubscriptionsView, filterDynamic };
|
package/lib/index.d.mts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { Logger, MessageBus, ServiceContext } from "@bilibili-notify/internal";
|
|
2
|
+
import { CommentaryCallOverride, CommentaryGenerator } from "@bilibili-notify/ai";
|
|
3
|
+
import { BilibiliAPI } from "@bilibili-notify/api";
|
|
4
|
+
import { ImageRenderer } from "@bilibili-notify/image";
|
|
5
|
+
|
|
6
|
+
//#region src/types.d.ts
|
|
7
|
+
type RichTextNode = {
|
|
8
|
+
orig_text: string;
|
|
9
|
+
text: string;
|
|
10
|
+
type: string;
|
|
11
|
+
emoji?: {
|
|
12
|
+
icon_url: string;
|
|
13
|
+
size: number;
|
|
14
|
+
text: string;
|
|
15
|
+
type: number;
|
|
16
|
+
};
|
|
17
|
+
jump_url?: string;
|
|
18
|
+
rid?: string;
|
|
19
|
+
goods?: any;
|
|
20
|
+
icon_name?: string;
|
|
21
|
+
};
|
|
22
|
+
type Dynamic = {
|
|
23
|
+
basic: Object;
|
|
24
|
+
id_str: string;
|
|
25
|
+
type: string;
|
|
26
|
+
orig?: Dynamic;
|
|
27
|
+
modules: {
|
|
28
|
+
module_author: {
|
|
29
|
+
face: string;
|
|
30
|
+
following: boolean;
|
|
31
|
+
jump_url: string;
|
|
32
|
+
label: string;
|
|
33
|
+
mid: number;
|
|
34
|
+
name: string;
|
|
35
|
+
pub_action: string;
|
|
36
|
+
pub_time: string;
|
|
37
|
+
pub_ts: number;
|
|
38
|
+
type: string;
|
|
39
|
+
[key: string]: any;
|
|
40
|
+
};
|
|
41
|
+
module_dynamic: {
|
|
42
|
+
additional?: any;
|
|
43
|
+
desc?: {
|
|
44
|
+
rich_text_nodes: RichTextNode[];
|
|
45
|
+
text: string;
|
|
46
|
+
};
|
|
47
|
+
major?: {
|
|
48
|
+
opus?: {
|
|
49
|
+
fold_action: string[];
|
|
50
|
+
jump_url: string;
|
|
51
|
+
pics: Array<{
|
|
52
|
+
height: number;
|
|
53
|
+
live_url: string;
|
|
54
|
+
size: number;
|
|
55
|
+
url: string;
|
|
56
|
+
width: number;
|
|
57
|
+
}>;
|
|
58
|
+
summary: {
|
|
59
|
+
rich_text_nodes: RichTextNode[];
|
|
60
|
+
text: string;
|
|
61
|
+
};
|
|
62
|
+
title: string;
|
|
63
|
+
};
|
|
64
|
+
archive?: {
|
|
65
|
+
title: string;
|
|
66
|
+
jump_url: string;
|
|
67
|
+
[key: string]: any;
|
|
68
|
+
};
|
|
69
|
+
[key: string]: any;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
type AllDynamicInfo = {
|
|
75
|
+
code: number;
|
|
76
|
+
message: string;
|
|
77
|
+
data: {
|
|
78
|
+
has_more: boolean;
|
|
79
|
+
items: Dynamic[];
|
|
80
|
+
offset: string;
|
|
81
|
+
update_baseline: string;
|
|
82
|
+
update_num: number;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
type DynamicTimelineManager = Map<string, number>;
|
|
86
|
+
interface DynamicFilterConfig {
|
|
87
|
+
enable?: boolean;
|
|
88
|
+
notify?: boolean;
|
|
89
|
+
regex?: string;
|
|
90
|
+
keywords?: string[];
|
|
91
|
+
forward?: boolean;
|
|
92
|
+
article?: boolean;
|
|
93
|
+
whitelistEnable?: boolean;
|
|
94
|
+
whitelistRegex?: string;
|
|
95
|
+
whitelistKeywords?: string[];
|
|
96
|
+
}
|
|
97
|
+
declare enum DynamicFilterReason {
|
|
98
|
+
BlacklistKeyword = "blacklist-keyword",
|
|
99
|
+
BlacklistForward = "blacklist-forward",
|
|
100
|
+
BlacklistArticle = "blacklist-article",
|
|
101
|
+
WhitelistUnmatched = "whitelist-unmatched"
|
|
102
|
+
}
|
|
103
|
+
interface DynamicFilterResult {
|
|
104
|
+
blocked: boolean;
|
|
105
|
+
reason?: DynamicFilterReason;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/push-like.d.ts
|
|
109
|
+
/** dynamic-engine 渲染好的图片缓冲(无 mime/扩展信息时默认 image/jpeg)。 */
|
|
110
|
+
interface PushImagePart {
|
|
111
|
+
type: "image";
|
|
112
|
+
buffer: Buffer;
|
|
113
|
+
mime: string;
|
|
114
|
+
}
|
|
115
|
+
/** 文本片段。 */
|
|
116
|
+
interface PushTextPart {
|
|
117
|
+
type: "text";
|
|
118
|
+
text: string;
|
|
119
|
+
}
|
|
120
|
+
/** 用于「专题」转发图集等需要折叠成 forward message 的多段图片。 */
|
|
121
|
+
interface PushImageGroup {
|
|
122
|
+
type: "image-group";
|
|
123
|
+
forward: boolean;
|
|
124
|
+
urls: string[];
|
|
125
|
+
}
|
|
126
|
+
type PushSegment = PushImagePart | PushTextPart | PushImageGroup;
|
|
127
|
+
/**
|
|
128
|
+
* dynamic-engine 仅需以下三类语义化推送动作。
|
|
129
|
+
* 业务核心调用前已经决定好「此次推送的目标维度」(通过 uid + PushKind),
|
|
130
|
+
* adapter 负责把它翻译为具体平台的 channel 列表 / atAll / 图片折叠等行为。
|
|
131
|
+
*/
|
|
132
|
+
type PushKind = /** 主体动态卡片:可能携带图片 + 文本 */"dynamic" | /** 动态附图(DYNAMIC_TYPE_DRAW 的多张原图,转发消息形式) */"dynamic-images";
|
|
133
|
+
interface PushLike {
|
|
134
|
+
/**
|
|
135
|
+
* 向某个 UP 主对应的全部订阅频道广播一段消息。
|
|
136
|
+
* - kind="dynamic":主卡片消息,包含 image + text 段。
|
|
137
|
+
* - kind="dynamic-images":DYNAMIC_TYPE_DRAW 的图集,adapter 通常以 forward message 投递。
|
|
138
|
+
*/
|
|
139
|
+
broadcastDynamic(uid: string, segments: PushSegment[], kind: PushKind): Promise<void>;
|
|
140
|
+
/** 私信发送给配置的管理员账号(master)。adapter 端校验启用状态与 bot 在线性。 */
|
|
141
|
+
sendPrivateMsg(content: string): Promise<void>;
|
|
142
|
+
/** 与 sendPrivateMsg 等价,但 adapter 应当在内部把内容追加到 error 日志。 */
|
|
143
|
+
sendErrorMsg(reason: string): Promise<void>;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 平台中立的订阅条目最小视图。dynamic-engine 仅访问 `uid` 与 `customCardStyle`
|
|
147
|
+
* 相关字段;adapter 提供完整 SubItem 实例时会被结构性兼容(额外字段不影响)。
|
|
148
|
+
*
|
|
149
|
+
* `filter` / `aiOverride` 为 per-UP 覆盖(可选):adapter 折叠 `Subscription.overrides`
|
|
150
|
+
* 后填入;缺失时 engine 回退到 `DynamicEngineConfig.filter` / 全局 CommentaryGenerator 配置。
|
|
151
|
+
*/
|
|
152
|
+
interface SubItemView {
|
|
153
|
+
uid: string;
|
|
154
|
+
uname: string;
|
|
155
|
+
dynamic?: boolean;
|
|
156
|
+
customCardStyle?: {
|
|
157
|
+
enable?: boolean;
|
|
158
|
+
cardColorStart?: string;
|
|
159
|
+
cardColorEnd?: string;
|
|
160
|
+
};
|
|
161
|
+
/** Per-UP 动态过滤覆盖;undefined 时使用 engine 的全局 filter。 */
|
|
162
|
+
filter?: DynamicFilterConfig & {
|
|
163
|
+
notify?: boolean;
|
|
164
|
+
};
|
|
165
|
+
/** Per-UP AI 覆盖;undefined 时使用 CommentaryGenerator 的全局 config。 */
|
|
166
|
+
aiOverride?: CommentaryCallOverride;
|
|
167
|
+
}
|
|
168
|
+
type SubscriptionsView = Record<string, SubItemView>;
|
|
169
|
+
type SubManagerView = Map<string, SubItemView>;
|
|
170
|
+
/**
|
|
171
|
+
* Adapter 提供给 engine 的「最新订阅快照」访问器与增量操作描述。
|
|
172
|
+
* Koishi adapter 在收到 `bilibili-notify/subscription-changed` 时调用 engine.applyOps;
|
|
173
|
+
* 独立端在 SubscriptionStore 写入后同样转译为 SubscriptionOpView 列表。
|
|
174
|
+
*/
|
|
175
|
+
type SubscriptionOpView = {
|
|
176
|
+
type: "add";
|
|
177
|
+
sub: SubItemView;
|
|
178
|
+
} | {
|
|
179
|
+
type: "delete";
|
|
180
|
+
uid: string;
|
|
181
|
+
} | {
|
|
182
|
+
type: "update";
|
|
183
|
+
uid: string;
|
|
184
|
+
changes: Array<{
|
|
185
|
+
scope: string;
|
|
186
|
+
dynamic?: boolean;
|
|
187
|
+
}>;
|
|
188
|
+
};
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/dynamic-engine.d.ts
|
|
191
|
+
/**
|
|
192
|
+
* Runtime configuration for {@link DynamicEngine}. Mirrors the platform-neutral
|
|
193
|
+
* subset of `BilibiliNotifyDynamicConfig`; the koishi shell maps its schema
|
|
194
|
+
* fields onto this struct, the standalone runtime fills it from its own config
|
|
195
|
+
* store. The `logLevel` field is intentionally dropped — adapter sets logger
|
|
196
|
+
* level externally via {@link ServiceContext}.
|
|
197
|
+
*/
|
|
198
|
+
interface DynamicEngineConfig {
|
|
199
|
+
/** 推送动态时是否附带 URL(QQ 官方机器人需关闭)。 */
|
|
200
|
+
dynamicUrl: boolean;
|
|
201
|
+
/** 轮询动态的 cron 表达式。 */
|
|
202
|
+
dynamicCron: string;
|
|
203
|
+
/** 视频动态时是否将 URL 替换为 BV 号。 */
|
|
204
|
+
dynamicVideoUrlToBV: boolean;
|
|
205
|
+
/** 是否额外推送 DYNAMIC_TYPE_DRAW 中的图集(forward message)。 */
|
|
206
|
+
pushImgsInDynamic: boolean;
|
|
207
|
+
/** 内容过滤配置(含 notify:被屏蔽时是否通知)。 */
|
|
208
|
+
filter: DynamicFilterConfig & {
|
|
209
|
+
notify?: boolean;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* 是否启用图片卡片渲染。`false` 时跳过 puppeteer 调用,推送降级为纯文字。缺省视为 true,
|
|
213
|
+
* 保留旧 adapter 不传该字段时的既有行为。Adapter 通常用 `globals.defaults.cardStyle.enabled` 填充。
|
|
214
|
+
*/
|
|
215
|
+
imageEnabled?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* 是否启用 AI 动态点评。`false` 时跳过 `CommentaryGenerator.comment()` 调用,推送只用原始动态文本。
|
|
218
|
+
* 缺省视为 true。Adapter 通常用 `globals.defaults.ai.enabled` 填充。
|
|
219
|
+
*/
|
|
220
|
+
aiEnabled?: boolean;
|
|
221
|
+
}
|
|
222
|
+
interface DynamicEngineOptions {
|
|
223
|
+
serviceCtx: ServiceContext;
|
|
224
|
+
bus: MessageBus;
|
|
225
|
+
api: BilibiliAPI;
|
|
226
|
+
push: PushLike;
|
|
227
|
+
/** 可选注入:图片渲染器;缺失时降级为纯文字推送。 */
|
|
228
|
+
image?: ImageRenderer;
|
|
229
|
+
/** 可选注入:AI 点评生成器;缺失时跳过 AI 文案生成。 */
|
|
230
|
+
ai?: CommentaryGenerator;
|
|
231
|
+
config: DynamicEngineConfig;
|
|
232
|
+
/**
|
|
233
|
+
* Adapter 提供的订阅快照访问器。返回 null 表示订阅尚未就绪
|
|
234
|
+
* (engine 会在收到 `subscription-changed` / `auth-restored` 后再次拉取)。
|
|
235
|
+
*/
|
|
236
|
+
getSubs: () => SubscriptionsView | null;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 平台中立的动态轮询/过滤/渲染核心。
|
|
240
|
+
*
|
|
241
|
+
* - 不依赖 koishi runtime;adapter 提供 ServiceContext / MessageBus / PushLike。
|
|
242
|
+
* - image / ai 通过 **构造期注入**(不在 detect 循环内做服务查找),缺失时降级。
|
|
243
|
+
* - 时间线、过滤、API 错误处理逻辑与原 koishi 版 BilibiliNotifyDynamic 一致。
|
|
244
|
+
*/
|
|
245
|
+
declare class DynamicEngine {
|
|
246
|
+
private readonly serviceCtx;
|
|
247
|
+
private readonly bus;
|
|
248
|
+
private readonly api;
|
|
249
|
+
private readonly push;
|
|
250
|
+
private readonly image?;
|
|
251
|
+
private ai?;
|
|
252
|
+
private readonly logger;
|
|
253
|
+
private readonly getSubs;
|
|
254
|
+
private config;
|
|
255
|
+
private dynamicJob?;
|
|
256
|
+
/** 风控/瞬时错误后的一次性退避重启句柄;非 undefined 表示已排程,不叠加。 */
|
|
257
|
+
private detectorRestartTimer?;
|
|
258
|
+
/**
|
|
259
|
+
* -352 风控态边沿标记。进入风控只 error+DM+engine-error 一次;退避重探仍
|
|
260
|
+
* 风控 → debug(不重复告警);成功拉取(code 0)→ info 一次并清除。避免在
|
|
261
|
+
* 退避重试热路径反复刷 error(Q1)。
|
|
262
|
+
*/
|
|
263
|
+
private riskControlled;
|
|
264
|
+
private dynamicSubManager;
|
|
265
|
+
private dynamicTimelineManager;
|
|
266
|
+
/** 连续图片渲染失败计数,达到阈值时仅通知一次但不停 cron */
|
|
267
|
+
private imageFailureStreak;
|
|
268
|
+
private imageFailureNotified;
|
|
269
|
+
private readonly busHandles;
|
|
270
|
+
constructor(opts: DynamicEngineOptions);
|
|
271
|
+
/** 启动钩子。Adapter 在 ServiceContext 就绪、订阅可访问后调用。 */
|
|
272
|
+
start(): void;
|
|
273
|
+
/** 停止钩子。停止 cron、释放事件订阅。 */
|
|
274
|
+
stop(): void;
|
|
275
|
+
/**
|
|
276
|
+
* 替换运行时配置(adapter 在 koishi config / dashboard 编辑后调用)。
|
|
277
|
+
* `dynamicCron` 变化时会自动停掉旧 CronJob 并按新表达式重新 schedule —— 否则
|
|
278
|
+
* 配置已经写进 this.config,但 node-cron 句柄还在跑旧节奏,纯粹的字段更新
|
|
279
|
+
* 是看不见的 bug。
|
|
280
|
+
*/
|
|
281
|
+
updateConfig(config: DynamicEngineConfig): void;
|
|
282
|
+
/**
|
|
283
|
+
* 热替换 CommentaryGenerator 实例。adapter 在用户运行时打开 / 关闭 / 更换 AI
|
|
284
|
+
* 配置后调用,引擎随后的动态点评会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
285
|
+
*/
|
|
286
|
+
setAi(ai: CommentaryGenerator | undefined): void;
|
|
287
|
+
get isActive(): boolean;
|
|
288
|
+
/** 用最新订阅快照重启动态检测;保留已有 UID 的时间戳避免重推旧动态。 */
|
|
289
|
+
startDynamicDetector(subs: SubscriptionsView): void;
|
|
290
|
+
private startDynamicForUid;
|
|
291
|
+
private stopDynamicForUid;
|
|
292
|
+
/**
|
|
293
|
+
* UID 是否仍订阅。detectDynamics 在 image/AI/broadcast 等多个 await 处挂起,
|
|
294
|
+
* `applyOps`(由 adapter 在 subscription-changed 时调,**不**在 withLock 内)
|
|
295
|
+
* 可在挂起期 stopDynamicForUid 删表。每个 dispatch / 时间线回写前用它重校,
|
|
296
|
+
* 否则会给已退订 UID 推送、并把其时间线“复活”进而长期抑制再订阅后的动态。
|
|
297
|
+
*/
|
|
298
|
+
private stillSubscribed;
|
|
299
|
+
/** Incrementally apply subscription ops without restarting the cron job. */
|
|
300
|
+
applyOps(ops: SubscriptionOpView[]): void;
|
|
301
|
+
private startJob;
|
|
302
|
+
private reconcileJob;
|
|
303
|
+
private detectDynamics;
|
|
304
|
+
private handleApiError;
|
|
305
|
+
/**
|
|
306
|
+
* 风控/瞬时错误后排一次性退避重启。已有待执行的不叠加(`detectorRestartTimer`
|
|
307
|
+
* 非空即跳过)。到点取最新订阅快照重启检测;`stop()` / 显式 `startDynamicDetector`
|
|
308
|
+
* 会作废本计时(避免 dispose 后 / 重启后仍触发陈旧重启)。
|
|
309
|
+
*/
|
|
310
|
+
private scheduleDetectorRestart;
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/dynamic-filter.d.ts
|
|
314
|
+
declare function filterDynamic(dynamic: Dynamic, config: DynamicFilterConfig, logger?: Logger): DynamicFilterResult;
|
|
315
|
+
//#endregion
|
|
316
|
+
export { type AllDynamicInfo, type Dynamic, DynamicEngine, type DynamicEngineConfig, type DynamicEngineOptions, type DynamicFilterConfig, DynamicFilterReason, type DynamicFilterResult, type DynamicTimelineManager, type PushImageGroup, type PushImagePart, type PushKind, type PushLike, type PushSegment, type PushTextPart, type RichTextNode, type SubItemView, type SubManagerView, type SubscriptionOpView, type SubscriptionsView, filterDynamic };
|