@bilibili-notify/live 0.0.1-alpha.3 → 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 +50 -46
- package/lib/index.d.cts +7 -7
- package/lib/index.d.mts +7 -7
- package/lib/index.mjs +50 -46
- package/package.json +4 -4
package/lib/index.cjs
CHANGED
|
@@ -499,37 +499,41 @@ var RoomContext = class extends RoomContextBase {
|
|
|
499
499
|
* - `customSpecialDanmakuUsers.msgTemplate`
|
|
500
500
|
* - `customSpecialUsersEnterTheRoom.msgTemplate`
|
|
501
501
|
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
502
|
+
* 占位符统一 `{name}` / `{time}` / `{watched}` 语法(与 `@bilibili-notify/internal`
|
|
503
|
+
* 的 `interpolate` 同源)。`applyTemplate` 同时接受 koishi 旧存档里的 legacy
|
|
504
|
+
* `-name` / `-time` 写法 —— 老用户已保存的 `-key` 模板继续生效,新默认与文档
|
|
505
|
+
* 一律走 `{key}`,二者不冲突(单遍正则,longest-first)。
|
|
506
506
|
*/
|
|
507
507
|
/** Defaults applied when neither sub-level nor global config provides a template. */
|
|
508
508
|
const DEFAULT_LIVE_TEMPLATES = {
|
|
509
|
-
liveStart: "
|
|
510
|
-
liveOngoing: "
|
|
511
|
-
liveEnd: "
|
|
509
|
+
liveStart: "{name} 开播啦,当前粉丝数:{follower}\n{link}",
|
|
510
|
+
liveOngoing: "{name} 正在直播,已播 {time},累计观看:{watched}\n{link}",
|
|
511
|
+
liveEnd: "{name} 下播啦,本次直播了 {time},粉丝变化 {follower_change}",
|
|
512
512
|
liveSummaryFallback: "弹幕总结"
|
|
513
513
|
};
|
|
514
514
|
function escapeRegExp(s) {
|
|
515
515
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
516
516
|
}
|
|
517
517
|
/**
|
|
518
|
-
* 单遍替换所有变量 token,再把 `\n`
|
|
518
|
+
* 单遍替换所有变量 token,再把 `\n` 转义展开为真换行。`vars` 以**裸键**(`name`/
|
|
519
|
+
* `follower_change`)给出,每个键同时匹配 `{name}`(主)与 legacy `-name`(兼容)。
|
|
519
520
|
*
|
|
520
521
|
* P2:此前 `for…replaceAll` 顺序替换有两个缺陷 ——
|
|
521
|
-
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含
|
|
522
|
+
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含 `{link}`/`-link` 时
|
|
522
523
|
* 会被后续轮次再次替换;
|
|
523
|
-
* 2.
|
|
524
|
+
* 2. **前缀吞噬**:legacy `-follower` 先于 `-follower_change` 替换,把后者的
|
|
524
525
|
* `-follower` 段吃掉只剩 `_change`。
|
|
525
|
-
*
|
|
526
|
-
*
|
|
526
|
+
* 改为基于原始模板的**单遍正则**:键按长度降序进 alternation(最长优先匹配),
|
|
527
|
+
* 每个 token 恰好替换一次且替换值不再被回扫 → 杜绝注入与吞噬。
|
|
527
528
|
*/
|
|
528
529
|
function applyTemplate(template, vars) {
|
|
529
530
|
const keys = Object.keys(vars).sort((a, b) => b.length - a.length);
|
|
530
531
|
if (keys.length === 0) return template.replaceAll("\\n", "\n");
|
|
531
|
-
const
|
|
532
|
-
|
|
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");
|
|
533
537
|
}
|
|
534
538
|
/**
|
|
535
539
|
* Format follower-change as a signed magnitude string with a 1万 (10K) cutoff,
|
|
@@ -559,27 +563,27 @@ var LiveTemplateRenderer = class {
|
|
|
559
563
|
/** Compose the "开播" notification text for a sub. */
|
|
560
564
|
renderLiveStart(params) {
|
|
561
565
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveStart", DEFAULT_LIVE_TEMPLATES.liveStart), {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
+
name: params.master.username,
|
|
567
|
+
time: params.diffTime,
|
|
568
|
+
follower: params.followerNum,
|
|
569
|
+
link: params.roomLink
|
|
566
570
|
});
|
|
567
571
|
}
|
|
568
572
|
/** Compose the periodic "正在直播" notification text. */
|
|
569
573
|
renderLiveOngoing(params) {
|
|
570
574
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLive", DEFAULT_LIVE_TEMPLATES.liveOngoing), {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
+
name: params.master.username,
|
|
576
|
+
time: params.diffTime,
|
|
577
|
+
watched: params.watched,
|
|
578
|
+
link: params.roomLink
|
|
575
579
|
});
|
|
576
580
|
}
|
|
577
581
|
/** Compose the "下播" notification text. */
|
|
578
582
|
renderLiveEnd(params) {
|
|
579
583
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveEnd", DEFAULT_LIVE_TEMPLATES.liveEnd), {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
584
|
+
name: params.master.username,
|
|
585
|
+
time: params.diffTime,
|
|
586
|
+
follower_change: formatFollowerChange(params.followerChange)
|
|
583
587
|
});
|
|
584
588
|
}
|
|
585
589
|
/**
|
|
@@ -588,24 +592,24 @@ var LiveTemplateRenderer = class {
|
|
|
588
592
|
*/
|
|
589
593
|
renderGuardBuy(params) {
|
|
590
594
|
return applyTemplate(params.guardBuyConfig.guardBuyMsg ?? "", {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
595
|
+
uname: params.uname,
|
|
596
|
+
mname: params.master?.username ?? "",
|
|
597
|
+
guard: params.giftName
|
|
594
598
|
});
|
|
595
599
|
}
|
|
596
600
|
/** Compose the "特别关注弹幕" notification text. */
|
|
597
601
|
renderSpecialDanmaku(params) {
|
|
598
602
|
return applyTemplate(params.template, {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
603
|
+
mastername: params.master?.username ?? "",
|
|
604
|
+
uname: params.uname,
|
|
605
|
+
msg: params.content
|
|
602
606
|
});
|
|
603
607
|
}
|
|
604
608
|
/** Compose the "特别关注进入直播间" notification text. */
|
|
605
609
|
renderSpecialUserEnter(params) {
|
|
606
610
|
return applyTemplate(params.template, {
|
|
607
|
-
|
|
608
|
-
|
|
611
|
+
mastername: params.master?.username ?? "",
|
|
612
|
+
uname: params.uname
|
|
609
613
|
});
|
|
610
614
|
}
|
|
611
615
|
/**
|
|
@@ -618,19 +622,19 @@ var LiveTemplateRenderer = class {
|
|
|
618
622
|
const top = params.topSenders;
|
|
619
623
|
const at = (i) => top[i] ?? ["", 0];
|
|
620
624
|
return applyTemplate(params.template, {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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]}`
|
|
634
638
|
});
|
|
635
639
|
}
|
|
636
640
|
};
|
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
|
/**
|
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
|
/**
|
package/lib/index.mjs
CHANGED
|
@@ -475,37 +475,41 @@ var RoomContext = class extends RoomContextBase {
|
|
|
475
475
|
* - `customSpecialDanmakuUsers.msgTemplate`
|
|
476
476
|
* - `customSpecialUsersEnterTheRoom.msgTemplate`
|
|
477
477
|
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
478
|
+
* 占位符统一 `{name}` / `{time}` / `{watched}` 语法(与 `@bilibili-notify/internal`
|
|
479
|
+
* 的 `interpolate` 同源)。`applyTemplate` 同时接受 koishi 旧存档里的 legacy
|
|
480
|
+
* `-name` / `-time` 写法 —— 老用户已保存的 `-key` 模板继续生效,新默认与文档
|
|
481
|
+
* 一律走 `{key}`,二者不冲突(单遍正则,longest-first)。
|
|
482
482
|
*/
|
|
483
483
|
/** Defaults applied when neither sub-level nor global config provides a template. */
|
|
484
484
|
const DEFAULT_LIVE_TEMPLATES = {
|
|
485
|
-
liveStart: "
|
|
486
|
-
liveOngoing: "
|
|
487
|
-
liveEnd: "
|
|
485
|
+
liveStart: "{name} 开播啦,当前粉丝数:{follower}\n{link}",
|
|
486
|
+
liveOngoing: "{name} 正在直播,已播 {time},累计观看:{watched}\n{link}",
|
|
487
|
+
liveEnd: "{name} 下播啦,本次直播了 {time},粉丝变化 {follower_change}",
|
|
488
488
|
liveSummaryFallback: "弹幕总结"
|
|
489
489
|
};
|
|
490
490
|
function escapeRegExp(s) {
|
|
491
491
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
492
492
|
}
|
|
493
493
|
/**
|
|
494
|
-
* 单遍替换所有变量 token,再把 `\n`
|
|
494
|
+
* 单遍替换所有变量 token,再把 `\n` 转义展开为真换行。`vars` 以**裸键**(`name`/
|
|
495
|
+
* `follower_change`)给出,每个键同时匹配 `{name}`(主)与 legacy `-name`(兼容)。
|
|
495
496
|
*
|
|
496
497
|
* P2:此前 `for…replaceAll` 顺序替换有两个缺陷 ——
|
|
497
|
-
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含
|
|
498
|
+
* 1. **token 注入**:用户可控值(uname / 弹幕内容)含 `{link}`/`-link` 时
|
|
498
499
|
* 会被后续轮次再次替换;
|
|
499
|
-
* 2.
|
|
500
|
+
* 2. **前缀吞噬**:legacy `-follower` 先于 `-follower_change` 替换,把后者的
|
|
500
501
|
* `-follower` 段吃掉只剩 `_change`。
|
|
501
|
-
*
|
|
502
|
-
*
|
|
502
|
+
* 改为基于原始模板的**单遍正则**:键按长度降序进 alternation(最长优先匹配),
|
|
503
|
+
* 每个 token 恰好替换一次且替换值不再被回扫 → 杜绝注入与吞噬。
|
|
503
504
|
*/
|
|
504
505
|
function applyTemplate(template, vars) {
|
|
505
506
|
const keys = Object.keys(vars).sort((a, b) => b.length - a.length);
|
|
506
507
|
if (keys.length === 0) return template.replaceAll("\\n", "\n");
|
|
507
|
-
const
|
|
508
|
-
|
|
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");
|
|
509
513
|
}
|
|
510
514
|
/**
|
|
511
515
|
* Format follower-change as a signed magnitude string with a 1万 (10K) cutoff,
|
|
@@ -535,27 +539,27 @@ var LiveTemplateRenderer = class {
|
|
|
535
539
|
/** Compose the "开播" notification text for a sub. */
|
|
536
540
|
renderLiveStart(params) {
|
|
537
541
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveStart", DEFAULT_LIVE_TEMPLATES.liveStart), {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
+
name: params.master.username,
|
|
543
|
+
time: params.diffTime,
|
|
544
|
+
follower: params.followerNum,
|
|
545
|
+
link: params.roomLink
|
|
542
546
|
});
|
|
543
547
|
}
|
|
544
548
|
/** Compose the periodic "正在直播" notification text. */
|
|
545
549
|
renderLiveOngoing(params) {
|
|
546
550
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLive", DEFAULT_LIVE_TEMPLATES.liveOngoing), {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
+
name: params.master.username,
|
|
552
|
+
time: params.diffTime,
|
|
553
|
+
watched: params.watched,
|
|
554
|
+
link: params.roomLink
|
|
551
555
|
});
|
|
552
556
|
}
|
|
553
557
|
/** Compose the "下播" notification text. */
|
|
554
558
|
renderLiveEnd(params) {
|
|
555
559
|
return applyTemplate(resolveCustomLive(params.sub.customLiveMsg, params.globalCustom, "customLiveEnd", DEFAULT_LIVE_TEMPLATES.liveEnd), {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
560
|
+
name: params.master.username,
|
|
561
|
+
time: params.diffTime,
|
|
562
|
+
follower_change: formatFollowerChange(params.followerChange)
|
|
559
563
|
});
|
|
560
564
|
}
|
|
561
565
|
/**
|
|
@@ -564,24 +568,24 @@ var LiveTemplateRenderer = class {
|
|
|
564
568
|
*/
|
|
565
569
|
renderGuardBuy(params) {
|
|
566
570
|
return applyTemplate(params.guardBuyConfig.guardBuyMsg ?? "", {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
571
|
+
uname: params.uname,
|
|
572
|
+
mname: params.master?.username ?? "",
|
|
573
|
+
guard: params.giftName
|
|
570
574
|
});
|
|
571
575
|
}
|
|
572
576
|
/** Compose the "特别关注弹幕" notification text. */
|
|
573
577
|
renderSpecialDanmaku(params) {
|
|
574
578
|
return applyTemplate(params.template, {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
579
|
+
mastername: params.master?.username ?? "",
|
|
580
|
+
uname: params.uname,
|
|
581
|
+
msg: params.content
|
|
578
582
|
});
|
|
579
583
|
}
|
|
580
584
|
/** Compose the "特别关注进入直播间" notification text. */
|
|
581
585
|
renderSpecialUserEnter(params) {
|
|
582
586
|
return applyTemplate(params.template, {
|
|
583
|
-
|
|
584
|
-
|
|
587
|
+
mastername: params.master?.username ?? "",
|
|
588
|
+
uname: params.uname
|
|
585
589
|
});
|
|
586
590
|
}
|
|
587
591
|
/**
|
|
@@ -594,19 +598,19 @@ var LiveTemplateRenderer = class {
|
|
|
594
598
|
const top = params.topSenders;
|
|
595
599
|
const at = (i) => top[i] ?? ["", 0];
|
|
596
600
|
return applyTemplate(params.template, {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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]}`
|
|
610
614
|
});
|
|
611
615
|
}
|
|
612
616
|
};
|
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
|
-
"@bilibili-notify/
|
|
36
|
-
"@bilibili-notify/
|
|
34
|
+
"@bilibili-notify/image": "^0.0.1-alpha.2",
|
|
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",
|