@bilibili-notify/dynamic 0.0.1-alpha.2 → 0.1.0-alpha.4
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/lib/index.cjs +43 -9
- package/lib/index.d.cts +31 -1
- package/lib/index.d.mts +31 -1
- package/lib/index.mjs +44 -10
- package/package.json +3 -3
package/lib/index.cjs
CHANGED
|
@@ -105,6 +105,27 @@ const LOG_TAG = "bilibili-notify-dynamic";
|
|
|
105
105
|
* 后即自愈,无需人工重启进程。
|
|
106
106
|
*/
|
|
107
107
|
const DETECTOR_RESTART_BACKOFF_MS = 5 * 6e4;
|
|
108
|
+
/**
|
|
109
|
+
* 动态推送文本模板的内建兜底,仅在 adapter 未填 config.dynamicTemplate /
|
|
110
|
+
* videoTemplate 时使用(真实 adapter 都会从 globals.defaults.templates 填充)。
|
|
111
|
+
* 与 `@bilibili-notify/internal` 的 `DEFAULT_TEMPLATES.dynamic/.dynamicVideo`
|
|
112
|
+
* 保持一致。变量 `{name}`(UP 名) / `{url}`(链接,未启用 URL 时为空)。
|
|
113
|
+
*/
|
|
114
|
+
const DEFAULT_DYNAMIC_TEXT = {
|
|
115
|
+
dynamic: "{name}发布了一条动态:{url}",
|
|
116
|
+
video: "{name}发布了新视频:{url}"
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* 渲染动态推送文本:`{name}` / `{url}` 插值 + `\n` 展开。`url` 为空(未启用 URL)
|
|
120
|
+
* 时连同 `{url}` 的前导分隔符一起去掉,避免行尾残留孤立的「:」。两个推送分支
|
|
121
|
+
* (有图 / 无图)都走此函数 → 文字内容一致。
|
|
122
|
+
*/
|
|
123
|
+
function renderDynamicText(template, name, url) {
|
|
124
|
+
return (0, _bilibili_notify_internal.interpolate)(url ? template : template.replace(/\s*[::]?\s*\{url\}/g, ""), {
|
|
125
|
+
name,
|
|
126
|
+
url
|
|
127
|
+
}).replaceAll("\\n", "\n");
|
|
128
|
+
}
|
|
108
129
|
/** 从动态数据中提取图片 URL,用于多模态 AI 点评(最多 4 张) */
|
|
109
130
|
function extractDynamicImages(item) {
|
|
110
131
|
const mod = item.modules.module_dynamic;
|
|
@@ -233,6 +254,17 @@ var DynamicEngine = class {
|
|
|
233
254
|
setAi(ai) {
|
|
234
255
|
this.ai = ai;
|
|
235
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* 热替换 ImageRenderer 实例。与 setAi 对称:adapter 在 image 服务上下线时
|
|
259
|
+
* 调用,引擎随后的卡片渲染会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
260
|
+
*
|
|
261
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
262
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
263
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
264
|
+
*/
|
|
265
|
+
setImage(image) {
|
|
266
|
+
this.image = image;
|
|
267
|
+
}
|
|
236
268
|
get isActive() {
|
|
237
269
|
return this.dynamicJob?.running ?? false;
|
|
238
270
|
}
|
|
@@ -445,14 +477,15 @@ var DynamicEngine = class {
|
|
|
445
477
|
this.imageFailureStreak = 0;
|
|
446
478
|
this.imageFailureNotified = false;
|
|
447
479
|
}
|
|
448
|
-
|
|
449
|
-
|
|
480
|
+
const isVideo = item.type === "DYNAMIC_TYPE_AV";
|
|
481
|
+
let url = "";
|
|
482
|
+
if (this.config.dynamicUrl) if (isVideo) {
|
|
450
483
|
const jumpUrl = item.modules.module_dynamic.major?.archive?.jump_url ?? "";
|
|
451
484
|
if (this.config.dynamicVideoUrlToBV) {
|
|
452
485
|
const bvMatch = jumpUrl.match(/BV[0-9A-Za-z]+/);
|
|
453
|
-
|
|
454
|
-
} else
|
|
455
|
-
} else
|
|
486
|
+
url = bvMatch ? bvMatch[0] : "";
|
|
487
|
+
} else url = `https:${jumpUrl}`;
|
|
488
|
+
} else url = `https://t.bilibili.com/${item.id_str}`;
|
|
456
489
|
let aiComment;
|
|
457
490
|
if (this.ai && this.config.aiEnabled !== false) {
|
|
458
491
|
const dynamicText = extractDynamicText(item);
|
|
@@ -472,17 +505,18 @@ var DynamicEngine = class {
|
|
|
472
505
|
this.logger.debug(`[detector] UID=${uid} 在本轮处理中已退订/被替换,跳过推送`);
|
|
473
506
|
continue;
|
|
474
507
|
}
|
|
475
|
-
const
|
|
508
|
+
const tmpl = isVideo ? sub?.customVideoTemplate ?? this.config.videoTemplate ?? DEFAULT_DYNAMIC_TEXT.video : sub?.customDynamicTemplate ?? this.config.dynamicTemplate ?? DEFAULT_DYNAMIC_TEXT.dynamic;
|
|
509
|
+
const text = aiComment ?? renderDynamicText(tmpl, name, url);
|
|
476
510
|
const segments = buffer ? [{
|
|
477
511
|
type: "image",
|
|
478
512
|
buffer,
|
|
479
513
|
mime: "image/jpeg"
|
|
480
|
-
}, ...
|
|
514
|
+
}, ...text ? [{
|
|
481
515
|
type: "text",
|
|
482
|
-
text
|
|
516
|
+
text
|
|
483
517
|
}] : []] : [{
|
|
484
518
|
type: "text",
|
|
485
|
-
text
|
|
519
|
+
text
|
|
486
520
|
}];
|
|
487
521
|
await this.push.broadcastDynamic(uid, segments, "dynamic");
|
|
488
522
|
const subForImgs = this.dynamicSubManager.get(uid);
|
package/lib/index.d.cts
CHANGED
|
@@ -180,6 +180,16 @@ interface SubItemView {
|
|
|
180
180
|
* 单图永远不走合并转发(在 engine 内已守卫)。
|
|
181
181
|
*/
|
|
182
182
|
imageGroupForward?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Per-UP 非视频动态文本模板;undefined 继承 engine config `dynamicTemplate`。
|
|
185
|
+
* Adapter 折叠 `Subscription.overrides.templates.dynamic` 后填入。
|
|
186
|
+
*/
|
|
187
|
+
customDynamicTemplate?: string;
|
|
188
|
+
/**
|
|
189
|
+
* Per-UP 视频投稿文本模板;undefined 继承 engine config `videoTemplate`。
|
|
190
|
+
* Adapter 折叠 `Subscription.overrides.templates.dynamicVideo` 后填入。
|
|
191
|
+
*/
|
|
192
|
+
customVideoTemplate?: string;
|
|
183
193
|
}
|
|
184
194
|
type SubscriptionsView = Record<string, SubItemView>;
|
|
185
195
|
type SubManagerView = Map<string, SubItemView>;
|
|
@@ -218,6 +228,17 @@ interface DynamicEngineConfig {
|
|
|
218
228
|
dynamicCron: string;
|
|
219
229
|
/** 视频动态时是否将 URL 替换为 BV 号。 */
|
|
220
230
|
dynamicVideoUrlToBV: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* 非视频动态的推送文本模板。变量 `{name}`(UP 名) / `{url}`(动态链接)。
|
|
233
|
+
* `{url}` 在 `dynamicUrl=false` 时为空,引擎会顺带去掉相邻分隔符。
|
|
234
|
+
* 缺省时回退到内建文案。Adapter 通常用 `globals.defaults.templates.dynamic` 填充。
|
|
235
|
+
*/
|
|
236
|
+
dynamicTemplate?: string;
|
|
237
|
+
/**
|
|
238
|
+
* 视频投稿的推送文本模板。变量 `{name}` / `{url}`(视频链接或 BV)。
|
|
239
|
+
* 缺省时回退到内建文案。Adapter 通常用 `globals.defaults.templates.dynamicVideo` 填充。
|
|
240
|
+
*/
|
|
241
|
+
videoTemplate?: string;
|
|
221
242
|
/**
|
|
222
243
|
* DYNAMIC_TYPE_DRAW 图集图片推送行为。enable=false 时跳过图集广播,
|
|
223
244
|
* 只发文本/卡片。forward=true 时走合并转发(聊天记录卡片,走 OneBot
|
|
@@ -271,7 +292,7 @@ declare class DynamicEngine {
|
|
|
271
292
|
private readonly bus;
|
|
272
293
|
private readonly api;
|
|
273
294
|
private readonly push;
|
|
274
|
-
private
|
|
295
|
+
private image?;
|
|
275
296
|
private ai?;
|
|
276
297
|
private readonly logger;
|
|
277
298
|
private readonly getSubs;
|
|
@@ -308,6 +329,15 @@ declare class DynamicEngine {
|
|
|
308
329
|
* 配置后调用,引擎随后的动态点评会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
309
330
|
*/
|
|
310
331
|
setAi(ai: CommentaryGenerator | undefined): void;
|
|
332
|
+
/**
|
|
333
|
+
* 热替换 ImageRenderer 实例。与 setAi 对称:adapter 在 image 服务上下线时
|
|
334
|
+
* 调用,引擎随后的卡片渲染会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
335
|
+
*
|
|
336
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
337
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
338
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
339
|
+
*/
|
|
340
|
+
setImage(image: ImageRenderer | undefined): void;
|
|
311
341
|
get isActive(): boolean;
|
|
312
342
|
/** 用最新订阅快照重启动态检测;保留已有 UID 的时间戳避免重推旧动态。 */
|
|
313
343
|
startDynamicDetector(subs: SubscriptionsView): void;
|
package/lib/index.d.mts
CHANGED
|
@@ -180,6 +180,16 @@ interface SubItemView {
|
|
|
180
180
|
* 单图永远不走合并转发(在 engine 内已守卫)。
|
|
181
181
|
*/
|
|
182
182
|
imageGroupForward?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Per-UP 非视频动态文本模板;undefined 继承 engine config `dynamicTemplate`。
|
|
185
|
+
* Adapter 折叠 `Subscription.overrides.templates.dynamic` 后填入。
|
|
186
|
+
*/
|
|
187
|
+
customDynamicTemplate?: string;
|
|
188
|
+
/**
|
|
189
|
+
* Per-UP 视频投稿文本模板;undefined 继承 engine config `videoTemplate`。
|
|
190
|
+
* Adapter 折叠 `Subscription.overrides.templates.dynamicVideo` 后填入。
|
|
191
|
+
*/
|
|
192
|
+
customVideoTemplate?: string;
|
|
183
193
|
}
|
|
184
194
|
type SubscriptionsView = Record<string, SubItemView>;
|
|
185
195
|
type SubManagerView = Map<string, SubItemView>;
|
|
@@ -218,6 +228,17 @@ interface DynamicEngineConfig {
|
|
|
218
228
|
dynamicCron: string;
|
|
219
229
|
/** 视频动态时是否将 URL 替换为 BV 号。 */
|
|
220
230
|
dynamicVideoUrlToBV: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* 非视频动态的推送文本模板。变量 `{name}`(UP 名) / `{url}`(动态链接)。
|
|
233
|
+
* `{url}` 在 `dynamicUrl=false` 时为空,引擎会顺带去掉相邻分隔符。
|
|
234
|
+
* 缺省时回退到内建文案。Adapter 通常用 `globals.defaults.templates.dynamic` 填充。
|
|
235
|
+
*/
|
|
236
|
+
dynamicTemplate?: string;
|
|
237
|
+
/**
|
|
238
|
+
* 视频投稿的推送文本模板。变量 `{name}` / `{url}`(视频链接或 BV)。
|
|
239
|
+
* 缺省时回退到内建文案。Adapter 通常用 `globals.defaults.templates.dynamicVideo` 填充。
|
|
240
|
+
*/
|
|
241
|
+
videoTemplate?: string;
|
|
221
242
|
/**
|
|
222
243
|
* DYNAMIC_TYPE_DRAW 图集图片推送行为。enable=false 时跳过图集广播,
|
|
223
244
|
* 只发文本/卡片。forward=true 时走合并转发(聊天记录卡片,走 OneBot
|
|
@@ -271,7 +292,7 @@ declare class DynamicEngine {
|
|
|
271
292
|
private readonly bus;
|
|
272
293
|
private readonly api;
|
|
273
294
|
private readonly push;
|
|
274
|
-
private
|
|
295
|
+
private image?;
|
|
275
296
|
private ai?;
|
|
276
297
|
private readonly logger;
|
|
277
298
|
private readonly getSubs;
|
|
@@ -308,6 +329,15 @@ declare class DynamicEngine {
|
|
|
308
329
|
* 配置后调用,引擎随后的动态点评会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
309
330
|
*/
|
|
310
331
|
setAi(ai: CommentaryGenerator | undefined): void;
|
|
332
|
+
/**
|
|
333
|
+
* 热替换 ImageRenderer 实例。与 setAi 对称:adapter 在 image 服务上下线时
|
|
334
|
+
* 调用,引擎随后的卡片渲染会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
335
|
+
*
|
|
336
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
337
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
338
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
339
|
+
*/
|
|
340
|
+
setImage(image: ImageRenderer | undefined): void;
|
|
311
341
|
get isActive(): boolean;
|
|
312
342
|
/** 用最新订阅快照重启动态检测;保留已有 UID 的时间戳避免重推旧动态。 */
|
|
313
343
|
startDynamicDetector(subs: SubscriptionsView): void;
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkUserRegex, withLock } from "@bilibili-notify/internal";
|
|
1
|
+
import { checkUserRegex, interpolate, withLock } from "@bilibili-notify/internal";
|
|
2
2
|
import { CronJob } from "cron";
|
|
3
3
|
import { DateTime } from "luxon";
|
|
4
4
|
//#region src/types.ts
|
|
@@ -104,6 +104,27 @@ const LOG_TAG = "bilibili-notify-dynamic";
|
|
|
104
104
|
* 后即自愈,无需人工重启进程。
|
|
105
105
|
*/
|
|
106
106
|
const DETECTOR_RESTART_BACKOFF_MS = 5 * 6e4;
|
|
107
|
+
/**
|
|
108
|
+
* 动态推送文本模板的内建兜底,仅在 adapter 未填 config.dynamicTemplate /
|
|
109
|
+
* videoTemplate 时使用(真实 adapter 都会从 globals.defaults.templates 填充)。
|
|
110
|
+
* 与 `@bilibili-notify/internal` 的 `DEFAULT_TEMPLATES.dynamic/.dynamicVideo`
|
|
111
|
+
* 保持一致。变量 `{name}`(UP 名) / `{url}`(链接,未启用 URL 时为空)。
|
|
112
|
+
*/
|
|
113
|
+
const DEFAULT_DYNAMIC_TEXT = {
|
|
114
|
+
dynamic: "{name}发布了一条动态:{url}",
|
|
115
|
+
video: "{name}发布了新视频:{url}"
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* 渲染动态推送文本:`{name}` / `{url}` 插值 + `\n` 展开。`url` 为空(未启用 URL)
|
|
119
|
+
* 时连同 `{url}` 的前导分隔符一起去掉,避免行尾残留孤立的「:」。两个推送分支
|
|
120
|
+
* (有图 / 无图)都走此函数 → 文字内容一致。
|
|
121
|
+
*/
|
|
122
|
+
function renderDynamicText(template, name, url) {
|
|
123
|
+
return interpolate(url ? template : template.replace(/\s*[::]?\s*\{url\}/g, ""), {
|
|
124
|
+
name,
|
|
125
|
+
url
|
|
126
|
+
}).replaceAll("\\n", "\n");
|
|
127
|
+
}
|
|
107
128
|
/** 从动态数据中提取图片 URL,用于多模态 AI 点评(最多 4 张) */
|
|
108
129
|
function extractDynamicImages(item) {
|
|
109
130
|
const mod = item.modules.module_dynamic;
|
|
@@ -232,6 +253,17 @@ var DynamicEngine = class {
|
|
|
232
253
|
setAi(ai) {
|
|
233
254
|
this.ai = ai;
|
|
234
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* 热替换 ImageRenderer 实例。与 setAi 对称:adapter 在 image 服务上下线时
|
|
258
|
+
* 调用,引擎随后的卡片渲染会立即用新实例 (或回退到纯文字) ,无需重启 server。
|
|
259
|
+
*
|
|
260
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
261
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
262
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
263
|
+
*/
|
|
264
|
+
setImage(image) {
|
|
265
|
+
this.image = image;
|
|
266
|
+
}
|
|
235
267
|
get isActive() {
|
|
236
268
|
return this.dynamicJob?.running ?? false;
|
|
237
269
|
}
|
|
@@ -444,14 +476,15 @@ var DynamicEngine = class {
|
|
|
444
476
|
this.imageFailureStreak = 0;
|
|
445
477
|
this.imageFailureNotified = false;
|
|
446
478
|
}
|
|
447
|
-
|
|
448
|
-
|
|
479
|
+
const isVideo = item.type === "DYNAMIC_TYPE_AV";
|
|
480
|
+
let url = "";
|
|
481
|
+
if (this.config.dynamicUrl) if (isVideo) {
|
|
449
482
|
const jumpUrl = item.modules.module_dynamic.major?.archive?.jump_url ?? "";
|
|
450
483
|
if (this.config.dynamicVideoUrlToBV) {
|
|
451
484
|
const bvMatch = jumpUrl.match(/BV[0-9A-Za-z]+/);
|
|
452
|
-
|
|
453
|
-
} else
|
|
454
|
-
} else
|
|
485
|
+
url = bvMatch ? bvMatch[0] : "";
|
|
486
|
+
} else url = `https:${jumpUrl}`;
|
|
487
|
+
} else url = `https://t.bilibili.com/${item.id_str}`;
|
|
455
488
|
let aiComment;
|
|
456
489
|
if (this.ai && this.config.aiEnabled !== false) {
|
|
457
490
|
const dynamicText = extractDynamicText(item);
|
|
@@ -471,17 +504,18 @@ var DynamicEngine = class {
|
|
|
471
504
|
this.logger.debug(`[detector] UID=${uid} 在本轮处理中已退订/被替换,跳过推送`);
|
|
472
505
|
continue;
|
|
473
506
|
}
|
|
474
|
-
const
|
|
507
|
+
const tmpl = isVideo ? sub?.customVideoTemplate ?? this.config.videoTemplate ?? DEFAULT_DYNAMIC_TEXT.video : sub?.customDynamicTemplate ?? this.config.dynamicTemplate ?? DEFAULT_DYNAMIC_TEXT.dynamic;
|
|
508
|
+
const text = aiComment ?? renderDynamicText(tmpl, name, url);
|
|
475
509
|
const segments = buffer ? [{
|
|
476
510
|
type: "image",
|
|
477
511
|
buffer,
|
|
478
512
|
mime: "image/jpeg"
|
|
479
|
-
}, ...
|
|
513
|
+
}, ...text ? [{
|
|
480
514
|
type: "text",
|
|
481
|
-
text
|
|
515
|
+
text
|
|
482
516
|
}] : []] : [{
|
|
483
517
|
type: "text",
|
|
484
|
-
text
|
|
518
|
+
text
|
|
485
519
|
}];
|
|
486
520
|
await this.push.broadcastDynamic(uid, segments, "dynamic");
|
|
487
521
|
const subForImgs = this.dynamicSubManager.get(uid);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bilibili-notify/dynamic",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/Akokk0/bilibili-notify"
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"cron": "^3.1.7",
|
|
30
30
|
"luxon": "^3.5.0",
|
|
31
|
-
"@bilibili-notify/api": "^0.2.0-alpha.2",
|
|
32
31
|
"@bilibili-notify/ai": "^0.0.1-alpha.1",
|
|
32
|
+
"@bilibili-notify/api": "^0.2.0-alpha.2",
|
|
33
33
|
"@bilibili-notify/image": "^0.0.1-alpha.2",
|
|
34
|
-
"@bilibili-notify/internal": "^0.1.0-alpha.
|
|
34
|
+
"@bilibili-notify/internal": "^0.1.0-alpha.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/luxon": "^3.4.2"
|