@bilibili-notify/live 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 +85 -56
- package/lib/index.d.cts +41 -12
- package/lib/index.d.mts +41 -12
- package/lib/index.mjs +85 -56
- package/package.json +3 -3
package/lib/index.cjs
CHANGED
|
@@ -168,11 +168,16 @@ var RoomContextBase = class {
|
|
|
168
168
|
liveSummaryRequester;
|
|
169
169
|
danmakuCollector;
|
|
170
170
|
/**
|
|
171
|
-
*
|
|
171
|
+
* 渲染器 provider —— private 是因为外部应通过 `imageRenderer` getter 访问 ——
|
|
172
172
|
* 后者会在 `config.imageEnabled === false` 时返回 null,让所有
|
|
173
173
|
* `if (this.imageRenderer?.generateXxx)` 自然落入文字回退分支。
|
|
174
|
+
* provider 形式让 LiveEngine 的 setImageRenderer 无需逐 RoomContext 推。
|
|
175
|
+
*
|
|
176
|
+
* **不要直接调 `this._getImageRenderer()` 绕过 imageEnabled 门控**,业务路径
|
|
177
|
+
* 必须通过 `this.imageRenderer` getter,否则用户在 dashboard 关掉卡片渲染时
|
|
178
|
+
* 这条路径仍会渲图。
|
|
174
179
|
*/
|
|
175
|
-
|
|
180
|
+
_getImageRenderer;
|
|
176
181
|
emitEngineError;
|
|
177
182
|
_emitLiveState;
|
|
178
183
|
_emitViewers;
|
|
@@ -199,7 +204,7 @@ var RoomContextBase = class {
|
|
|
199
204
|
this.wordcloudGenerator = opts.wordcloudGenerator;
|
|
200
205
|
this.liveSummaryRequester = opts.liveSummaryRequester;
|
|
201
206
|
this.danmakuCollector = opts.danmakuCollector;
|
|
202
|
-
this.
|
|
207
|
+
this._getImageRenderer = opts.getImageRenderer;
|
|
203
208
|
this.config = opts.config;
|
|
204
209
|
this.emitEngineError = opts.emitEngineError;
|
|
205
210
|
this._emitLiveState = opts.emitLiveState;
|
|
@@ -219,7 +224,7 @@ var RoomContextBase = class {
|
|
|
219
224
|
}
|
|
220
225
|
/** 受 `config.imageEnabled` 门控的渲染器视图;关闭时返回 null。 */
|
|
221
226
|
get imageRenderer() {
|
|
222
|
-
return this.config.imageEnabled === false ? null : this.
|
|
227
|
+
return this.config.imageEnabled === false ? null : this._getImageRenderer();
|
|
223
228
|
}
|
|
224
229
|
updateConfig(config) {
|
|
225
230
|
this.config = config;
|
|
@@ -494,37 +499,41 @@ var RoomContext = class extends RoomContextBase {
|
|
|
494
499
|
* - `customSpecialDanmakuUsers.msgTemplate`
|
|
495
500
|
* - `customSpecialUsersEnterTheRoom.msgTemplate`
|
|
496
501
|
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
502
|
+
* 占位符统一 `{name}` / `{time}` / `{watched}` 语法(与 `@bilibili-notify/internal`
|
|
503
|
+
* 的 `interpolate` 同源)。`applyTemplate` 同时接受 koishi 旧存档里的 legacy
|
|
504
|
+
* `-name` / `-time` 写法 —— 老用户已保存的 `-key` 模板继续生效,新默认与文档
|
|
505
|
+
* 一律走 `{key}`,二者不冲突(单遍正则,longest-first)。
|
|
501
506
|
*/
|
|
502
507
|
/** Defaults applied when neither sub-level nor global config provides a template. */
|
|
503
508
|
const DEFAULT_LIVE_TEMPLATES = {
|
|
504
|
-
liveStart: "
|
|
505
|
-
liveOngoing: "
|
|
506
|
-
liveEnd: "
|
|
509
|
+
liveStart: "{name} 开播啦,当前粉丝数:{follower}\n{link}",
|
|
510
|
+
liveOngoing: "{name} 正在直播,已播 {time},累计观看:{watched}\n{link}",
|
|
511
|
+
liveEnd: "{name} 下播啦,本次直播了 {time},粉丝变化 {follower_change}",
|
|
507
512
|
liveSummaryFallback: "弹幕总结"
|
|
508
513
|
};
|
|
509
514
|
function escapeRegExp(s) {
|
|
510
515
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
511
516
|
}
|
|
512
517
|
/**
|
|
513
|
-
* 单遍替换所有变量 token,再把 `\n`
|
|
518
|
+
* 单遍替换所有变量 token,再把 `\n` 转义展开为真换行。`vars` 以**裸键**(`name`/
|
|
519
|
+
* `follower_change`)给出,每个键同时匹配 `{name}`(主)与 legacy `-name`(兼容)。
|
|
514
520
|
*
|
|
515
521
|
* P2:此前 `for…replaceAll` 顺序替换有两个缺陷 ——
|
|
516
|
-
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含
|
|
522
|
+
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含 `{link}`/`-link` 时
|
|
517
523
|
* 会被后续轮次再次替换;
|
|
518
|
-
* 2.
|
|
524
|
+
* 2. **前缀吞噬**:legacy `-follower` 先于 `-follower_change` 替换,把后者的
|
|
519
525
|
* `-follower` 段吃掉只剩 `_change`。
|
|
520
|
-
*
|
|
521
|
-
*
|
|
526
|
+
* 改为基于原始模板的**单遍正则**:键按长度降序进 alternation(最长优先匹配),
|
|
527
|
+
* 每个 token 恰好替换一次且替换值不再被回扫 → 杜绝注入与吞噬。
|
|
522
528
|
*/
|
|
523
529
|
function applyTemplate(template, vars) {
|
|
524
530
|
const keys = Object.keys(vars).sort((a, b) => b.length - a.length);
|
|
525
531
|
if (keys.length === 0) return template.replaceAll("\\n", "\n");
|
|
526
|
-
const
|
|
527
|
-
|
|
532
|
+
const alts = keys.flatMap((k) => [`\\{${escapeRegExp(k)}\\}`, `-${escapeRegExp(k)}`]);
|
|
533
|
+
const re = new RegExp(alts.join("|"), "g");
|
|
534
|
+
return template.replace(re, (m) => {
|
|
535
|
+
return vars[m.charCodeAt(0) === 123 ? m.slice(1, -1) : m.slice(1)] ?? m;
|
|
536
|
+
}).replaceAll("\\n", "\n");
|
|
528
537
|
}
|
|
529
538
|
/**
|
|
530
539
|
* Format follower-change as a signed magnitude string with a 1万 (10K) cutoff,
|
|
@@ -554,27 +563,27 @@ var LiveTemplateRenderer = class {
|
|
|
554
563
|
/** Compose the "开播" notification text for a sub. */
|
|
555
564
|
renderLiveStart(params) {
|
|
556
565
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveStart", DEFAULT_LIVE_TEMPLATES.liveStart), {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
566
|
+
name: params.master.username,
|
|
567
|
+
time: params.diffTime,
|
|
568
|
+
follower: params.followerNum,
|
|
569
|
+
link: params.roomLink
|
|
561
570
|
});
|
|
562
571
|
}
|
|
563
572
|
/** Compose the periodic "正在直播" notification text. */
|
|
564
573
|
renderLiveOngoing(params) {
|
|
565
574
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLive", DEFAULT_LIVE_TEMPLATES.liveOngoing), {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
575
|
+
name: params.master.username,
|
|
576
|
+
time: params.diffTime,
|
|
577
|
+
watched: params.watched,
|
|
578
|
+
link: params.roomLink
|
|
570
579
|
});
|
|
571
580
|
}
|
|
572
581
|
/** Compose the "下播" notification text. */
|
|
573
582
|
renderLiveEnd(params) {
|
|
574
583
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveEnd", DEFAULT_LIVE_TEMPLATES.liveEnd), {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
584
|
+
name: params.master.username,
|
|
585
|
+
time: params.diffTime,
|
|
586
|
+
follower_change: formatFollowerChange(params.followerChange)
|
|
578
587
|
});
|
|
579
588
|
}
|
|
580
589
|
/**
|
|
@@ -583,24 +592,24 @@ var LiveTemplateRenderer = class {
|
|
|
583
592
|
*/
|
|
584
593
|
renderGuardBuy(params) {
|
|
585
594
|
return applyTemplate(params.guardBuyConfig.guardBuyMsg ?? "", {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
595
|
+
uname: params.uname,
|
|
596
|
+
mname: params.master?.username ?? "",
|
|
597
|
+
guard: params.giftName
|
|
589
598
|
});
|
|
590
599
|
}
|
|
591
600
|
/** Compose the "特别关注弹幕" notification text. */
|
|
592
601
|
renderSpecialDanmaku(params) {
|
|
593
602
|
return applyTemplate(params.template, {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
603
|
+
mastername: params.master?.username ?? "",
|
|
604
|
+
uname: params.uname,
|
|
605
|
+
msg: params.content
|
|
597
606
|
});
|
|
598
607
|
}
|
|
599
608
|
/** Compose the "特别关注进入直播间" notification text. */
|
|
600
609
|
renderSpecialUserEnter(params) {
|
|
601
610
|
return applyTemplate(params.template, {
|
|
602
|
-
|
|
603
|
-
|
|
611
|
+
mastername: params.master?.username ?? "",
|
|
612
|
+
uname: params.uname
|
|
604
613
|
});
|
|
605
614
|
}
|
|
606
615
|
/**
|
|
@@ -613,19 +622,19 @@ var LiveTemplateRenderer = class {
|
|
|
613
622
|
const top = params.topSenders;
|
|
614
623
|
const at = (i) => top[i] ?? ["", 0];
|
|
615
624
|
return applyTemplate(params.template, {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
625
|
+
dmc: `${params.senderCount}`,
|
|
626
|
+
mdn: params.master?.medalName ?? "",
|
|
627
|
+
dca: `${params.danmakuCount}`,
|
|
628
|
+
un1: at(0)[0],
|
|
629
|
+
dc1: `${at(0)[1]}`,
|
|
630
|
+
un2: at(1)[0],
|
|
631
|
+
dc2: `${at(1)[1]}`,
|
|
632
|
+
un3: at(2)[0],
|
|
633
|
+
dc3: `${at(2)[1]}`,
|
|
634
|
+
un4: at(3)[0],
|
|
635
|
+
dc4: `${at(3)[1]}`,
|
|
636
|
+
un5: at(4)[0],
|
|
637
|
+
dc5: `${at(4)[1]}`
|
|
629
638
|
});
|
|
630
639
|
}
|
|
631
640
|
};
|
|
@@ -1576,11 +1585,11 @@ const WORDCLOUD_TOP_WORDS = 90;
|
|
|
1576
1585
|
* platform (e.g. via `LiveContentBuilder.image`).
|
|
1577
1586
|
*/
|
|
1578
1587
|
var WordcloudGenerator = class {
|
|
1579
|
-
|
|
1588
|
+
getImageRenderer;
|
|
1580
1589
|
isImageEnabled;
|
|
1581
1590
|
logger;
|
|
1582
1591
|
constructor(opts) {
|
|
1583
|
-
this.
|
|
1592
|
+
this.getImageRenderer = opts.getImageRenderer;
|
|
1584
1593
|
this.isImageEnabled = opts.isImageEnabled ?? (() => true);
|
|
1585
1594
|
this.logger = opts.logger;
|
|
1586
1595
|
}
|
|
@@ -1602,9 +1611,10 @@ var WordcloudGenerator = class {
|
|
|
1602
1611
|
this.logger.debug("[wordcloud] cardStyle.enabled=false,跳过词云图片生成");
|
|
1603
1612
|
return;
|
|
1604
1613
|
}
|
|
1605
|
-
|
|
1614
|
+
const renderer = this.getImageRenderer();
|
|
1615
|
+
if (!renderer?.generateWordCloudImg) return void 0;
|
|
1606
1616
|
try {
|
|
1607
|
-
return await
|
|
1617
|
+
return await renderer.generateWordCloudImg(sortedWords.slice(0, 90), masterName, masterAvatarUrl);
|
|
1608
1618
|
} catch (e) {
|
|
1609
1619
|
this.logger.error(`[wordcloud] 生成词云失败:${e.message}`);
|
|
1610
1620
|
return;
|
|
@@ -1634,14 +1644,22 @@ var LiveEngine = class {
|
|
|
1634
1644
|
danmakuCollector;
|
|
1635
1645
|
liveSummaryRequester;
|
|
1636
1646
|
config;
|
|
1647
|
+
/**
|
|
1648
|
+
* Image 渲染器的当前引用 —— 单一可变 state,setImageRenderer 在此更新;
|
|
1649
|
+
* 所有子组件(wordcloud / listener / room-context)通过 provider 函数现取,
|
|
1650
|
+
* 无需逐组件 setter 推送。
|
|
1651
|
+
*/
|
|
1652
|
+
currentImageRenderer;
|
|
1637
1653
|
constructor(opts) {
|
|
1638
1654
|
this.logger = opts.serviceCtx.logger;
|
|
1639
1655
|
this.config = opts.config;
|
|
1656
|
+
this.currentImageRenderer = opts.imageRenderer ?? null;
|
|
1657
|
+
const getImageRenderer = () => this.currentImageRenderer;
|
|
1640
1658
|
const stopwords = mergeStopWords(opts.config.wordcloudStopWords);
|
|
1641
1659
|
this.danmakuCollector = new DanmakuCollector(stopwords);
|
|
1642
1660
|
const templateRenderer = new LiveTemplateRenderer();
|
|
1643
1661
|
const wordcloudGenerator = new WordcloudGenerator({
|
|
1644
|
-
|
|
1662
|
+
getImageRenderer,
|
|
1645
1663
|
isImageEnabled: () => this.config.imageEnabled !== false,
|
|
1646
1664
|
logger: this.logger
|
|
1647
1665
|
});
|
|
@@ -1661,7 +1679,7 @@ var LiveEngine = class {
|
|
|
1661
1679
|
wordcloudGenerator,
|
|
1662
1680
|
liveSummaryRequester,
|
|
1663
1681
|
danmakuCollector: this.danmakuCollector,
|
|
1664
|
-
|
|
1682
|
+
getImageRenderer,
|
|
1665
1683
|
config: toListenerConfig(opts.config),
|
|
1666
1684
|
emitEngineError: opts.emitEngineError,
|
|
1667
1685
|
emitLiveState: opts.emitLiveState,
|
|
@@ -1744,6 +1762,17 @@ var LiveEngine = class {
|
|
|
1744
1762
|
setCommentary(commentary) {
|
|
1745
1763
|
this.liveSummaryRequester.setCommentary(commentary);
|
|
1746
1764
|
}
|
|
1765
|
+
/**
|
|
1766
|
+
* 热替换 ImageRenderer 实例。adapter 在 image 服务上下线时调用。子组件 (词云 /
|
|
1767
|
+
* room-context / 卡片渲染) 都通过共享 provider 现取,这里只需更新单一 state。
|
|
1768
|
+
*
|
|
1769
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
1770
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
1771
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
1772
|
+
*/
|
|
1773
|
+
setImageRenderer(imageRenderer) {
|
|
1774
|
+
this.currentImageRenderer = imageRenderer;
|
|
1775
|
+
}
|
|
1747
1776
|
/** Final dispose; the engine instance must not be reused after this. */
|
|
1748
1777
|
stop() {
|
|
1749
1778
|
this.listener.disposeAll();
|
package/lib/index.d.cts
CHANGED
|
@@ -274,16 +274,16 @@ type LivePushTimerManager = Map<string, () => void>;
|
|
|
274
274
|
* - `customSpecialDanmakuUsers.msgTemplate`
|
|
275
275
|
* - `customSpecialUsersEnterTheRoom.msgTemplate`
|
|
276
276
|
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
277
|
+
* 占位符统一 `{name}` / `{time}` / `{watched}` 语法(与 `@bilibili-notify/internal`
|
|
278
|
+
* 的 `interpolate` 同源)。`applyTemplate` 同时接受 koishi 旧存档里的 legacy
|
|
279
|
+
* `-name` / `-time` 写法 —— 老用户已保存的 `-key` 模板继续生效,新默认与文档
|
|
280
|
+
* 一律走 `{key}`,二者不冲突(单遍正则,longest-first)。
|
|
281
281
|
*/
|
|
282
282
|
/** Defaults applied when neither sub-level nor global config provides a template. */
|
|
283
283
|
declare const DEFAULT_LIVE_TEMPLATES: {
|
|
284
|
-
readonly liveStart: "
|
|
285
|
-
readonly liveOngoing: "
|
|
286
|
-
readonly liveEnd: "
|
|
284
|
+
readonly liveStart: "{name} 开播啦,当前粉丝数:{follower}\n{link}";
|
|
285
|
+
readonly liveOngoing: "{name} 正在直播,已播 {time},累计观看:{watched}\n{link}";
|
|
286
|
+
readonly liveEnd: "{name} 下播啦,本次直播了 {time},粉丝变化 {follower_change}";
|
|
287
287
|
readonly liveSummaryFallback: "弹幕总结";
|
|
288
288
|
};
|
|
289
289
|
/**
|
|
@@ -433,11 +433,16 @@ declare const WORDCLOUD_TOP_WORDS = 90;
|
|
|
433
433
|
* platform (e.g. via `LiveContentBuilder.image`).
|
|
434
434
|
*/
|
|
435
435
|
declare class WordcloudGenerator {
|
|
436
|
-
private readonly
|
|
436
|
+
private readonly getImageRenderer;
|
|
437
437
|
private readonly isImageEnabled;
|
|
438
438
|
private readonly logger;
|
|
439
439
|
constructor(opts: {
|
|
440
|
-
|
|
440
|
+
/**
|
|
441
|
+
* 渲染器 provider —— 每次 generate() 现取最新引用,使 LiveEngine 在 image
|
|
442
|
+
* 服务上下线时通过 setImageRenderer 替换内部状态,词云生成自动同步,无需子组件
|
|
443
|
+
* 接 setter。
|
|
444
|
+
*/
|
|
445
|
+
getImageRenderer: () => ImageRenderer | null;
|
|
441
446
|
/**
|
|
442
447
|
* 卡片渲染总开关查询。返回 false 时直接跳过 puppeteer 调用,与缺失 imageRenderer
|
|
443
448
|
* 等价。Adapter 通常用 `() => globals.defaults.cardStyle.enabled` 填充;缺省 () => true。
|
|
@@ -502,7 +507,11 @@ interface RoomContextOptions {
|
|
|
502
507
|
wordcloudGenerator: WordcloudGenerator;
|
|
503
508
|
liveSummaryRequester: LiveSummaryRequester;
|
|
504
509
|
danmakuCollector: DanmakuCollector;
|
|
505
|
-
|
|
510
|
+
/**
|
|
511
|
+
* 渲染器 provider —— LiveEngine 在 image 服务上下线时通过 setImageRenderer
|
|
512
|
+
* 替换内部状态;getter `imageRenderer` 每次现取,所有 RoomContext 自动同步。
|
|
513
|
+
*/
|
|
514
|
+
getImageRenderer: () => ImageRenderer | null;
|
|
506
515
|
config: ListenerManagerConfig;
|
|
507
516
|
emitEngineError: (message: string) => void;
|
|
508
517
|
/**
|
|
@@ -546,11 +555,16 @@ declare class RoomContextBase {
|
|
|
546
555
|
readonly liveSummaryRequester: LiveSummaryRequester;
|
|
547
556
|
readonly danmakuCollector: DanmakuCollector;
|
|
548
557
|
/**
|
|
549
|
-
*
|
|
558
|
+
* 渲染器 provider —— private 是因为外部应通过 `imageRenderer` getter 访问 ——
|
|
550
559
|
* 后者会在 `config.imageEnabled === false` 时返回 null,让所有
|
|
551
560
|
* `if (this.imageRenderer?.generateXxx)` 自然落入文字回退分支。
|
|
561
|
+
* provider 形式让 LiveEngine 的 setImageRenderer 无需逐 RoomContext 推。
|
|
562
|
+
*
|
|
563
|
+
* **不要直接调 `this._getImageRenderer()` 绕过 imageEnabled 门控**,业务路径
|
|
564
|
+
* 必须通过 `this.imageRenderer` getter,否则用户在 dashboard 关掉卡片渲染时
|
|
565
|
+
* 这条路径仍会渲图。
|
|
552
566
|
*/
|
|
553
|
-
private readonly
|
|
567
|
+
private readonly _getImageRenderer;
|
|
554
568
|
readonly emitEngineError: (message: string) => void;
|
|
555
569
|
private readonly _emitLiveState;
|
|
556
570
|
private readonly _emitViewers;
|
|
@@ -950,6 +964,12 @@ declare class LiveEngine {
|
|
|
950
964
|
private readonly danmakuCollector;
|
|
951
965
|
private readonly liveSummaryRequester;
|
|
952
966
|
private config;
|
|
967
|
+
/**
|
|
968
|
+
* Image 渲染器的当前引用 —— 单一可变 state,setImageRenderer 在此更新;
|
|
969
|
+
* 所有子组件(wordcloud / listener / room-context)通过 provider 函数现取,
|
|
970
|
+
* 无需逐组件 setter 推送。
|
|
971
|
+
*/
|
|
972
|
+
private currentImageRenderer;
|
|
953
973
|
constructor(opts: LiveEngineOptions);
|
|
954
974
|
/**
|
|
955
975
|
* Bootstrap the engine with the initial subscription set. Idempotent —
|
|
@@ -973,6 +993,15 @@ declare class LiveEngine {
|
|
|
973
993
|
* 配置后调用,引擎随后的直播总结会立即用新实例 (或回退到模板) ,无需重启 server。
|
|
974
994
|
*/
|
|
975
995
|
setCommentary(commentary: CommentaryGenerator | null): void;
|
|
996
|
+
/**
|
|
997
|
+
* 热替换 ImageRenderer 实例。adapter 在 image 服务上下线时调用。子组件 (词云 /
|
|
998
|
+
* room-context / 卡片渲染) 都通过共享 provider 现取,这里只需更新单一 state。
|
|
999
|
+
*
|
|
1000
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
1001
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
1002
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
1003
|
+
*/
|
|
1004
|
+
setImageRenderer(imageRenderer: ImageRenderer | null): void;
|
|
976
1005
|
/** Final dispose; the engine instance must not be reused after this. */
|
|
977
1006
|
stop(): void;
|
|
978
1007
|
/** Diagnostic accessor, used by the koishi shell for `[conn] state` logging. */
|
package/lib/index.d.mts
CHANGED
|
@@ -274,16 +274,16 @@ type LivePushTimerManager = Map<string, () => void>;
|
|
|
274
274
|
* - `customSpecialDanmakuUsers.msgTemplate`
|
|
275
275
|
* - `customSpecialUsersEnterTheRoom.msgTemplate`
|
|
276
276
|
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
277
|
+
* 占位符统一 `{name}` / `{time}` / `{watched}` 语法(与 `@bilibili-notify/internal`
|
|
278
|
+
* 的 `interpolate` 同源)。`applyTemplate` 同时接受 koishi 旧存档里的 legacy
|
|
279
|
+
* `-name` / `-time` 写法 —— 老用户已保存的 `-key` 模板继续生效,新默认与文档
|
|
280
|
+
* 一律走 `{key}`,二者不冲突(单遍正则,longest-first)。
|
|
281
281
|
*/
|
|
282
282
|
/** Defaults applied when neither sub-level nor global config provides a template. */
|
|
283
283
|
declare const DEFAULT_LIVE_TEMPLATES: {
|
|
284
|
-
readonly liveStart: "
|
|
285
|
-
readonly liveOngoing: "
|
|
286
|
-
readonly liveEnd: "
|
|
284
|
+
readonly liveStart: "{name} 开播啦,当前粉丝数:{follower}\n{link}";
|
|
285
|
+
readonly liveOngoing: "{name} 正在直播,已播 {time},累计观看:{watched}\n{link}";
|
|
286
|
+
readonly liveEnd: "{name} 下播啦,本次直播了 {time},粉丝变化 {follower_change}";
|
|
287
287
|
readonly liveSummaryFallback: "弹幕总结";
|
|
288
288
|
};
|
|
289
289
|
/**
|
|
@@ -433,11 +433,16 @@ declare const WORDCLOUD_TOP_WORDS = 90;
|
|
|
433
433
|
* platform (e.g. via `LiveContentBuilder.image`).
|
|
434
434
|
*/
|
|
435
435
|
declare class WordcloudGenerator {
|
|
436
|
-
private readonly
|
|
436
|
+
private readonly getImageRenderer;
|
|
437
437
|
private readonly isImageEnabled;
|
|
438
438
|
private readonly logger;
|
|
439
439
|
constructor(opts: {
|
|
440
|
-
|
|
440
|
+
/**
|
|
441
|
+
* 渲染器 provider —— 每次 generate() 现取最新引用,使 LiveEngine 在 image
|
|
442
|
+
* 服务上下线时通过 setImageRenderer 替换内部状态,词云生成自动同步,无需子组件
|
|
443
|
+
* 接 setter。
|
|
444
|
+
*/
|
|
445
|
+
getImageRenderer: () => ImageRenderer | null;
|
|
441
446
|
/**
|
|
442
447
|
* 卡片渲染总开关查询。返回 false 时直接跳过 puppeteer 调用,与缺失 imageRenderer
|
|
443
448
|
* 等价。Adapter 通常用 `() => globals.defaults.cardStyle.enabled` 填充;缺省 () => true。
|
|
@@ -502,7 +507,11 @@ interface RoomContextOptions {
|
|
|
502
507
|
wordcloudGenerator: WordcloudGenerator;
|
|
503
508
|
liveSummaryRequester: LiveSummaryRequester;
|
|
504
509
|
danmakuCollector: DanmakuCollector;
|
|
505
|
-
|
|
510
|
+
/**
|
|
511
|
+
* 渲染器 provider —— LiveEngine 在 image 服务上下线时通过 setImageRenderer
|
|
512
|
+
* 替换内部状态;getter `imageRenderer` 每次现取,所有 RoomContext 自动同步。
|
|
513
|
+
*/
|
|
514
|
+
getImageRenderer: () => ImageRenderer | null;
|
|
506
515
|
config: ListenerManagerConfig;
|
|
507
516
|
emitEngineError: (message: string) => void;
|
|
508
517
|
/**
|
|
@@ -546,11 +555,16 @@ declare class RoomContextBase {
|
|
|
546
555
|
readonly liveSummaryRequester: LiveSummaryRequester;
|
|
547
556
|
readonly danmakuCollector: DanmakuCollector;
|
|
548
557
|
/**
|
|
549
|
-
*
|
|
558
|
+
* 渲染器 provider —— private 是因为外部应通过 `imageRenderer` getter 访问 ——
|
|
550
559
|
* 后者会在 `config.imageEnabled === false` 时返回 null,让所有
|
|
551
560
|
* `if (this.imageRenderer?.generateXxx)` 自然落入文字回退分支。
|
|
561
|
+
* provider 形式让 LiveEngine 的 setImageRenderer 无需逐 RoomContext 推。
|
|
562
|
+
*
|
|
563
|
+
* **不要直接调 `this._getImageRenderer()` 绕过 imageEnabled 门控**,业务路径
|
|
564
|
+
* 必须通过 `this.imageRenderer` getter,否则用户在 dashboard 关掉卡片渲染时
|
|
565
|
+
* 这条路径仍会渲图。
|
|
552
566
|
*/
|
|
553
|
-
private readonly
|
|
567
|
+
private readonly _getImageRenderer;
|
|
554
568
|
readonly emitEngineError: (message: string) => void;
|
|
555
569
|
private readonly _emitLiveState;
|
|
556
570
|
private readonly _emitViewers;
|
|
@@ -950,6 +964,12 @@ declare class LiveEngine {
|
|
|
950
964
|
private readonly danmakuCollector;
|
|
951
965
|
private readonly liveSummaryRequester;
|
|
952
966
|
private config;
|
|
967
|
+
/**
|
|
968
|
+
* Image 渲染器的当前引用 —— 单一可变 state,setImageRenderer 在此更新;
|
|
969
|
+
* 所有子组件(wordcloud / listener / room-context)通过 provider 函数现取,
|
|
970
|
+
* 无需逐组件 setter 推送。
|
|
971
|
+
*/
|
|
972
|
+
private currentImageRenderer;
|
|
953
973
|
constructor(opts: LiveEngineOptions);
|
|
954
974
|
/**
|
|
955
975
|
* Bootstrap the engine with the initial subscription set. Idempotent —
|
|
@@ -973,6 +993,15 @@ declare class LiveEngine {
|
|
|
973
993
|
* 配置后调用,引擎随后的直播总结会立即用新实例 (或回退到模板) ,无需重启 server。
|
|
974
994
|
*/
|
|
975
995
|
setCommentary(commentary: CommentaryGenerator | null): void;
|
|
996
|
+
/**
|
|
997
|
+
* 热替换 ImageRenderer 实例。adapter 在 image 服务上下线时调用。子组件 (词云 /
|
|
998
|
+
* room-context / 卡片渲染) 都通过共享 provider 现取,这里只需更新单一 state。
|
|
999
|
+
*
|
|
1000
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
1001
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
1002
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
1003
|
+
*/
|
|
1004
|
+
setImageRenderer(imageRenderer: ImageRenderer | null): void;
|
|
976
1005
|
/** Final dispose; the engine instance must not be reused after this. */
|
|
977
1006
|
stop(): void;
|
|
978
1007
|
/** Diagnostic accessor, used by the koishi shell for `[conn] state` logging. */
|
package/lib/index.mjs
CHANGED
|
@@ -144,11 +144,16 @@ var RoomContextBase = class {
|
|
|
144
144
|
liveSummaryRequester;
|
|
145
145
|
danmakuCollector;
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
147
|
+
* 渲染器 provider —— private 是因为外部应通过 `imageRenderer` getter 访问 ——
|
|
148
148
|
* 后者会在 `config.imageEnabled === false` 时返回 null,让所有
|
|
149
149
|
* `if (this.imageRenderer?.generateXxx)` 自然落入文字回退分支。
|
|
150
|
+
* provider 形式让 LiveEngine 的 setImageRenderer 无需逐 RoomContext 推。
|
|
151
|
+
*
|
|
152
|
+
* **不要直接调 `this._getImageRenderer()` 绕过 imageEnabled 门控**,业务路径
|
|
153
|
+
* 必须通过 `this.imageRenderer` getter,否则用户在 dashboard 关掉卡片渲染时
|
|
154
|
+
* 这条路径仍会渲图。
|
|
150
155
|
*/
|
|
151
|
-
|
|
156
|
+
_getImageRenderer;
|
|
152
157
|
emitEngineError;
|
|
153
158
|
_emitLiveState;
|
|
154
159
|
_emitViewers;
|
|
@@ -175,7 +180,7 @@ var RoomContextBase = class {
|
|
|
175
180
|
this.wordcloudGenerator = opts.wordcloudGenerator;
|
|
176
181
|
this.liveSummaryRequester = opts.liveSummaryRequester;
|
|
177
182
|
this.danmakuCollector = opts.danmakuCollector;
|
|
178
|
-
this.
|
|
183
|
+
this._getImageRenderer = opts.getImageRenderer;
|
|
179
184
|
this.config = opts.config;
|
|
180
185
|
this.emitEngineError = opts.emitEngineError;
|
|
181
186
|
this._emitLiveState = opts.emitLiveState;
|
|
@@ -195,7 +200,7 @@ var RoomContextBase = class {
|
|
|
195
200
|
}
|
|
196
201
|
/** 受 `config.imageEnabled` 门控的渲染器视图;关闭时返回 null。 */
|
|
197
202
|
get imageRenderer() {
|
|
198
|
-
return this.config.imageEnabled === false ? null : this.
|
|
203
|
+
return this.config.imageEnabled === false ? null : this._getImageRenderer();
|
|
199
204
|
}
|
|
200
205
|
updateConfig(config) {
|
|
201
206
|
this.config = config;
|
|
@@ -470,37 +475,41 @@ var RoomContext = class extends RoomContextBase {
|
|
|
470
475
|
* - `customSpecialDanmakuUsers.msgTemplate`
|
|
471
476
|
* - `customSpecialUsersEnterTheRoom.msgTemplate`
|
|
472
477
|
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
478
|
+
* 占位符统一 `{name}` / `{time}` / `{watched}` 语法(与 `@bilibili-notify/internal`
|
|
479
|
+
* 的 `interpolate` 同源)。`applyTemplate` 同时接受 koishi 旧存档里的 legacy
|
|
480
|
+
* `-name` / `-time` 写法 —— 老用户已保存的 `-key` 模板继续生效,新默认与文档
|
|
481
|
+
* 一律走 `{key}`,二者不冲突(单遍正则,longest-first)。
|
|
477
482
|
*/
|
|
478
483
|
/** Defaults applied when neither sub-level nor global config provides a template. */
|
|
479
484
|
const DEFAULT_LIVE_TEMPLATES = {
|
|
480
|
-
liveStart: "
|
|
481
|
-
liveOngoing: "
|
|
482
|
-
liveEnd: "
|
|
485
|
+
liveStart: "{name} 开播啦,当前粉丝数:{follower}\n{link}",
|
|
486
|
+
liveOngoing: "{name} 正在直播,已播 {time},累计观看:{watched}\n{link}",
|
|
487
|
+
liveEnd: "{name} 下播啦,本次直播了 {time},粉丝变化 {follower_change}",
|
|
483
488
|
liveSummaryFallback: "弹幕总结"
|
|
484
489
|
};
|
|
485
490
|
function escapeRegExp(s) {
|
|
486
491
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
487
492
|
}
|
|
488
493
|
/**
|
|
489
|
-
* 单遍替换所有变量 token,再把 `\n`
|
|
494
|
+
* 单遍替换所有变量 token,再把 `\n` 转义展开为真换行。`vars` 以**裸键**(`name`/
|
|
495
|
+
* `follower_change`)给出,每个键同时匹配 `{name}`(主)与 legacy `-name`(兼容)。
|
|
490
496
|
*
|
|
491
497
|
* P2:此前 `for…replaceAll` 顺序替换有两个缺陷 ——
|
|
492
|
-
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含
|
|
498
|
+
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含 `{link}`/`-link` 时
|
|
493
499
|
* 会被后续轮次再次替换;
|
|
494
|
-
* 2.
|
|
500
|
+
* 2. **前缀吞噬**:legacy `-follower` 先于 `-follower_change` 替换,把后者的
|
|
495
501
|
* `-follower` 段吃掉只剩 `_change`。
|
|
496
|
-
*
|
|
497
|
-
*
|
|
502
|
+
* 改为基于原始模板的**单遍正则**:键按长度降序进 alternation(最长优先匹配),
|
|
503
|
+
* 每个 token 恰好替换一次且替换值不再被回扫 → 杜绝注入与吞噬。
|
|
498
504
|
*/
|
|
499
505
|
function applyTemplate(template, vars) {
|
|
500
506
|
const keys = Object.keys(vars).sort((a, b) => b.length - a.length);
|
|
501
507
|
if (keys.length === 0) return template.replaceAll("\\n", "\n");
|
|
502
|
-
const
|
|
503
|
-
|
|
508
|
+
const alts = keys.flatMap((k) => [`\\{${escapeRegExp(k)}\\}`, `-${escapeRegExp(k)}`]);
|
|
509
|
+
const re = new RegExp(alts.join("|"), "g");
|
|
510
|
+
return template.replace(re, (m) => {
|
|
511
|
+
return vars[m.charCodeAt(0) === 123 ? m.slice(1, -1) : m.slice(1)] ?? m;
|
|
512
|
+
}).replaceAll("\\n", "\n");
|
|
504
513
|
}
|
|
505
514
|
/**
|
|
506
515
|
* Format follower-change as a signed magnitude string with a 1万 (10K) cutoff,
|
|
@@ -530,27 +539,27 @@ var LiveTemplateRenderer = class {
|
|
|
530
539
|
/** Compose the "开播" notification text for a sub. */
|
|
531
540
|
renderLiveStart(params) {
|
|
532
541
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveStart", DEFAULT_LIVE_TEMPLATES.liveStart), {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
542
|
+
name: params.master.username,
|
|
543
|
+
time: params.diffTime,
|
|
544
|
+
follower: params.followerNum,
|
|
545
|
+
link: params.roomLink
|
|
537
546
|
});
|
|
538
547
|
}
|
|
539
548
|
/** Compose the periodic "正在直播" notification text. */
|
|
540
549
|
renderLiveOngoing(params) {
|
|
541
550
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLive", DEFAULT_LIVE_TEMPLATES.liveOngoing), {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
551
|
+
name: params.master.username,
|
|
552
|
+
time: params.diffTime,
|
|
553
|
+
watched: params.watched,
|
|
554
|
+
link: params.roomLink
|
|
546
555
|
});
|
|
547
556
|
}
|
|
548
557
|
/** Compose the "下播" notification text. */
|
|
549
558
|
renderLiveEnd(params) {
|
|
550
559
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveEnd", DEFAULT_LIVE_TEMPLATES.liveEnd), {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
560
|
+
name: params.master.username,
|
|
561
|
+
time: params.diffTime,
|
|
562
|
+
follower_change: formatFollowerChange(params.followerChange)
|
|
554
563
|
});
|
|
555
564
|
}
|
|
556
565
|
/**
|
|
@@ -559,24 +568,24 @@ var LiveTemplateRenderer = class {
|
|
|
559
568
|
*/
|
|
560
569
|
renderGuardBuy(params) {
|
|
561
570
|
return applyTemplate(params.guardBuyConfig.guardBuyMsg ?? "", {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
571
|
+
uname: params.uname,
|
|
572
|
+
mname: params.master?.username ?? "",
|
|
573
|
+
guard: params.giftName
|
|
565
574
|
});
|
|
566
575
|
}
|
|
567
576
|
/** Compose the "特别关注弹幕" notification text. */
|
|
568
577
|
renderSpecialDanmaku(params) {
|
|
569
578
|
return applyTemplate(params.template, {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
579
|
+
mastername: params.master?.username ?? "",
|
|
580
|
+
uname: params.uname,
|
|
581
|
+
msg: params.content
|
|
573
582
|
});
|
|
574
583
|
}
|
|
575
584
|
/** Compose the "特别关注进入直播间" notification text. */
|
|
576
585
|
renderSpecialUserEnter(params) {
|
|
577
586
|
return applyTemplate(params.template, {
|
|
578
|
-
|
|
579
|
-
|
|
587
|
+
mastername: params.master?.username ?? "",
|
|
588
|
+
uname: params.uname
|
|
580
589
|
});
|
|
581
590
|
}
|
|
582
591
|
/**
|
|
@@ -589,19 +598,19 @@ var LiveTemplateRenderer = class {
|
|
|
589
598
|
const top = params.topSenders;
|
|
590
599
|
const at = (i) => top[i] ?? ["", 0];
|
|
591
600
|
return applyTemplate(params.template, {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
601
|
+
dmc: `${params.senderCount}`,
|
|
602
|
+
mdn: params.master?.medalName ?? "",
|
|
603
|
+
dca: `${params.danmakuCount}`,
|
|
604
|
+
un1: at(0)[0],
|
|
605
|
+
dc1: `${at(0)[1]}`,
|
|
606
|
+
un2: at(1)[0],
|
|
607
|
+
dc2: `${at(1)[1]}`,
|
|
608
|
+
un3: at(2)[0],
|
|
609
|
+
dc3: `${at(2)[1]}`,
|
|
610
|
+
un4: at(3)[0],
|
|
611
|
+
dc4: `${at(3)[1]}`,
|
|
612
|
+
un5: at(4)[0],
|
|
613
|
+
dc5: `${at(4)[1]}`
|
|
605
614
|
});
|
|
606
615
|
}
|
|
607
616
|
};
|
|
@@ -1552,11 +1561,11 @@ const WORDCLOUD_TOP_WORDS = 90;
|
|
|
1552
1561
|
* platform (e.g. via `LiveContentBuilder.image`).
|
|
1553
1562
|
*/
|
|
1554
1563
|
var WordcloudGenerator = class {
|
|
1555
|
-
|
|
1564
|
+
getImageRenderer;
|
|
1556
1565
|
isImageEnabled;
|
|
1557
1566
|
logger;
|
|
1558
1567
|
constructor(opts) {
|
|
1559
|
-
this.
|
|
1568
|
+
this.getImageRenderer = opts.getImageRenderer;
|
|
1560
1569
|
this.isImageEnabled = opts.isImageEnabled ?? (() => true);
|
|
1561
1570
|
this.logger = opts.logger;
|
|
1562
1571
|
}
|
|
@@ -1578,9 +1587,10 @@ var WordcloudGenerator = class {
|
|
|
1578
1587
|
this.logger.debug("[wordcloud] cardStyle.enabled=false,跳过词云图片生成");
|
|
1579
1588
|
return;
|
|
1580
1589
|
}
|
|
1581
|
-
|
|
1590
|
+
const renderer = this.getImageRenderer();
|
|
1591
|
+
if (!renderer?.generateWordCloudImg) return void 0;
|
|
1582
1592
|
try {
|
|
1583
|
-
return await
|
|
1593
|
+
return await renderer.generateWordCloudImg(sortedWords.slice(0, 90), masterName, masterAvatarUrl);
|
|
1584
1594
|
} catch (e) {
|
|
1585
1595
|
this.logger.error(`[wordcloud] 生成词云失败:${e.message}`);
|
|
1586
1596
|
return;
|
|
@@ -1610,14 +1620,22 @@ var LiveEngine = class {
|
|
|
1610
1620
|
danmakuCollector;
|
|
1611
1621
|
liveSummaryRequester;
|
|
1612
1622
|
config;
|
|
1623
|
+
/**
|
|
1624
|
+
* Image 渲染器的当前引用 —— 单一可变 state,setImageRenderer 在此更新;
|
|
1625
|
+
* 所有子组件(wordcloud / listener / room-context)通过 provider 函数现取,
|
|
1626
|
+
* 无需逐组件 setter 推送。
|
|
1627
|
+
*/
|
|
1628
|
+
currentImageRenderer;
|
|
1613
1629
|
constructor(opts) {
|
|
1614
1630
|
this.logger = opts.serviceCtx.logger;
|
|
1615
1631
|
this.config = opts.config;
|
|
1632
|
+
this.currentImageRenderer = opts.imageRenderer ?? null;
|
|
1633
|
+
const getImageRenderer = () => this.currentImageRenderer;
|
|
1616
1634
|
const stopwords = mergeStopWords(opts.config.wordcloudStopWords);
|
|
1617
1635
|
this.danmakuCollector = new DanmakuCollector(stopwords);
|
|
1618
1636
|
const templateRenderer = new LiveTemplateRenderer();
|
|
1619
1637
|
const wordcloudGenerator = new WordcloudGenerator({
|
|
1620
|
-
|
|
1638
|
+
getImageRenderer,
|
|
1621
1639
|
isImageEnabled: () => this.config.imageEnabled !== false,
|
|
1622
1640
|
logger: this.logger
|
|
1623
1641
|
});
|
|
@@ -1637,7 +1655,7 @@ var LiveEngine = class {
|
|
|
1637
1655
|
wordcloudGenerator,
|
|
1638
1656
|
liveSummaryRequester,
|
|
1639
1657
|
danmakuCollector: this.danmakuCollector,
|
|
1640
|
-
|
|
1658
|
+
getImageRenderer,
|
|
1641
1659
|
config: toListenerConfig(opts.config),
|
|
1642
1660
|
emitEngineError: opts.emitEngineError,
|
|
1643
1661
|
emitLiveState: opts.emitLiveState,
|
|
@@ -1720,6 +1738,17 @@ var LiveEngine = class {
|
|
|
1720
1738
|
setCommentary(commentary) {
|
|
1721
1739
|
this.liveSummaryRequester.setCommentary(commentary);
|
|
1722
1740
|
}
|
|
1741
|
+
/**
|
|
1742
|
+
* 热替换 ImageRenderer 实例。adapter 在 image 服务上下线时调用。子组件 (词云 /
|
|
1743
|
+
* room-context / 卡片渲染) 都通过共享 provider 现取,这里只需更新单一 state。
|
|
1744
|
+
*
|
|
1745
|
+
* 主要给 koishi adapter 用 —— sibling service (-image) 启停时通过 ctx.inject
|
|
1746
|
+
* 后置注入。独立端 imageRenderer 是 engine 同进程一次性 wire,不会动态消失,
|
|
1747
|
+
* 不需要调用本方法 (cardStyle 热更走 imageRenderer.updateConfig)。
|
|
1748
|
+
*/
|
|
1749
|
+
setImageRenderer(imageRenderer) {
|
|
1750
|
+
this.currentImageRenderer = imageRenderer;
|
|
1751
|
+
}
|
|
1723
1752
|
/** Final dispose; the engine instance must not be reused after this. */
|
|
1724
1753
|
stop() {
|
|
1725
1754
|
this.listener.disposeAll();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bilibili-notify/live",
|
|
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"
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"jieba-wasm": "^2.4.0",
|
|
31
31
|
"luxon": "^3.5.0",
|
|
32
32
|
"protobufjs": "^7.4.0",
|
|
33
|
-
"@bilibili-notify/api": "^0.2.0-alpha.2",
|
|
34
33
|
"@bilibili-notify/ai": "^0.0.1-alpha.1",
|
|
35
34
|
"@bilibili-notify/image": "^0.0.1-alpha.2",
|
|
36
|
-
"@bilibili-notify/
|
|
35
|
+
"@bilibili-notify/api": "^0.2.0-alpha.2",
|
|
36
|
+
"@bilibili-notify/internal": "^0.1.0-alpha.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/luxon": "^3.4.2",
|