@bililive-tools/manager 1.11.0 → 1.12.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.
|
@@ -47,6 +47,7 @@ class BililiveRecorderCommand extends EventEmitter {
|
|
|
47
47
|
const bililiveExecutable = getBililivePath();
|
|
48
48
|
this.process = spawn(bililiveExecutable, args, {
|
|
49
49
|
stdio: ["pipe", "pipe", "pipe"],
|
|
50
|
+
windowsHide: true,
|
|
50
51
|
});
|
|
51
52
|
if (this.process.stdout) {
|
|
52
53
|
this.process.stdout.on("data", (data) => {
|
|
@@ -46,6 +46,7 @@ class MesioCommand extends EventEmitter {
|
|
|
46
46
|
const mesioExecutable = getMesioPath();
|
|
47
47
|
this.process = spawn(mesioExecutable, args, {
|
|
48
48
|
stdio: ["pipe", "pipe", "pipe"],
|
|
49
|
+
windowsHide: true,
|
|
49
50
|
});
|
|
50
51
|
if (this.process.stdout) {
|
|
51
52
|
this.process.stdout.on("data", (data) => {
|
package/lib/recorder.d.ts
CHANGED
|
@@ -42,7 +42,10 @@ export interface RecorderCreateOpts<E extends AnyObject = UnknownObject> {
|
|
|
42
42
|
codecName?: CodecName;
|
|
43
43
|
/** 选择使用的api,虎牙支持: auto,web,mp,wup,抖音支持:web,webHTML,mobile,userHTML */
|
|
44
44
|
api?: "auto" | "web" | "mp" | "wup" | "webHTML" | "mobile" | "userHTML" | "balance" | "random" | string;
|
|
45
|
-
/**
|
|
45
|
+
/** 标题关键词,如果直播间标题包含这些关键词,则不会自动录制,支持两种格式:
|
|
46
|
+
* 1. 逗号分隔的关键词:'回放,录播,重播'
|
|
47
|
+
* 2. 正则表达式:'/pattern/flags'(如:'/回放|录播/i')
|
|
48
|
+
*/
|
|
46
49
|
titleKeywords?: string;
|
|
47
50
|
/** 用于指定录制文件格式,auto时,分段使用ts,不分段使用mp4 */
|
|
48
51
|
videoFormat?: "auto" | "ts" | "mkv" | "flv";
|
package/lib/utils.d.ts
CHANGED
|
@@ -85,6 +85,11 @@ export declare const sleep: (ms: number) => Promise<unknown>;
|
|
|
85
85
|
export declare function shouldUseStrictQuality(qualityRetryLeft: number, qualityRetry: number, isManualStart?: boolean): boolean;
|
|
86
86
|
/**
|
|
87
87
|
* 检查标题是否包含黑名单关键词
|
|
88
|
+
* @param title 直播间标题
|
|
89
|
+
* @param titleKeywords 关键词配置,支持两种格式:
|
|
90
|
+
* 1. 逗号分隔的关键词:'关键词1,关键词2,关键词3'
|
|
91
|
+
* 2. 正则表达式:'/pattern/flags'(如:'/回放|录播/i')
|
|
92
|
+
* @returns 如果标题包含关键词返回 true,否则返回 false
|
|
88
93
|
*/
|
|
89
94
|
declare function hasBlockedTitleKeywords(title: string, titleKeywords: string | undefined): boolean;
|
|
90
95
|
/**
|
package/lib/utils.js
CHANGED
|
@@ -355,9 +355,33 @@ export function shouldUseStrictQuality(qualityRetryLeft, qualityRetry, isManualS
|
|
|
355
355
|
}
|
|
356
356
|
/**
|
|
357
357
|
* 检查标题是否包含黑名单关键词
|
|
358
|
+
* @param title 直播间标题
|
|
359
|
+
* @param titleKeywords 关键词配置,支持两种格式:
|
|
360
|
+
* 1. 逗号分隔的关键词:'关键词1,关键词2,关键词3'
|
|
361
|
+
* 2. 正则表达式:'/pattern/flags'(如:'/回放|录播/i')
|
|
362
|
+
* @returns 如果标题包含关键词返回 true,否则返回 false
|
|
358
363
|
*/
|
|
359
364
|
function hasBlockedTitleKeywords(title, titleKeywords) {
|
|
360
|
-
|
|
365
|
+
if (!titleKeywords || !titleKeywords.trim()) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
const trimmedKeywords = titleKeywords.trim();
|
|
369
|
+
// 检测是否为正则表达式格式 /pattern/flags
|
|
370
|
+
const regexMatch = trimmedKeywords.match(/^\/(.+?)\/([gimsuvy]*)$/);
|
|
371
|
+
if (regexMatch) {
|
|
372
|
+
try {
|
|
373
|
+
const [, pattern, flags] = regexMatch;
|
|
374
|
+
const regex = new RegExp(pattern, flags);
|
|
375
|
+
return regex.test(title);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
// 正则表达式无效,降级到普通匹配,并记录日志
|
|
379
|
+
console.warn(`Invalid regex pattern: ${trimmedKeywords}, falling back to normal matching`, error);
|
|
380
|
+
// 继续使用普通匹配逻辑
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// 普通关键词匹配(逗号分隔)
|
|
384
|
+
const keywords = trimmedKeywords
|
|
361
385
|
.split(",")
|
|
362
386
|
.map((k) => k.trim())
|
|
363
387
|
.filter((k) => k);
|
|
@@ -23,7 +23,7 @@ export function createRecordExtraDataController(savePath) {
|
|
|
23
23
|
isInitialized = true;
|
|
24
24
|
try {
|
|
25
25
|
// 创建XML文件头,使用占位符预留metadata位置
|
|
26
|
-
const header = `<?xml version="1.0" encoding="utf-8"?>\n<i>\n<!--METADATA_PLACEHOLDER-->\n
|
|
26
|
+
const header = `<?xml version="1.0" encoding="utf-8"?>\n<?xml-stylesheet type="text/xsl" href="#s"?>\n<i>\n<!--METADATA_PLACEHOLDER-->\n<BililiveRecorderXmlStyle><z:stylesheet version="1.0" id="s" xml:id="s" xmlns:z="http://www.w3.org/1999/XSL/Transform"><z:output method="html"/><z:template match="/"><html><meta name="viewport" content="width=device-width"/><title>弹幕文件 <z:value-of select="/i/metadata/user_name/text()"/></title><style>body{margin:0}h1,h2,p,table{margin-left:5px}table{border-spacing:0}td,th{border:1px solid grey;padding:1px 5px}th{position:sticky;top:0;background:#4098de}tr:hover{background:#d9f4ff}div{overflow:auto;max-height:80vh;max-width:100vw;width:fit-content}</style><h1>弹幕XML文件</h1><p>本文件不支持在 IE 浏览器里预览,请使用 Chrome Firefox Edge 等浏览器。</p><p>文件用法参考文档 <a href="https://rec.danmuji.org/user/danmaku/">https://rec.danmuji.org/user/danmaku/</a></p><table><tr><td>房间号</td><td><z:value-of select="/i/metadata/room_id/text()"/></td></tr><tr><td>主播名</td><td><z:value-of select="/i/metadata/user_name/text()"/></td></tr><tr><td><a href="#d">弹幕</a></td><td>共<z:value-of select="count(/i/d)"/>条记录</td></tr><tr><td><a href="#guard">上船</a></td><td>共<z:value-of select="count(/i/guard)"/>条记录</td></tr><tr><td><a href="#sc">SC</a></td><td>共<z:value-of select="count(/i/sc)"/>条记录</td></tr><tr><td><a href="#gift">礼物</a></td><td>共<z:value-of select="count(/i/gift)"/>条记录</td></tr></table><h2 id="d">弹幕</h2><div id="dm"><table><tr><th>用户名</th><th>出现时间</th><th>用户ID</th><th>弹幕</th><th>参数</th></tr><z:for-each select="/i/d"><tr><td><z:value-of select="@user"/></td><td></td><td></td><td><z:value-of select="."/></td><td><z:value-of select="@p"/></td></tr></z:for-each></table></div><script>Array.from(document.querySelectorAll('#dm tr')).slice(1).map(t=>t.querySelectorAll('td')).forEach(t=>{let p=t[4].textContent.split(','),a=p[0];t[1].textContent=\`\u0024{(Math.floor(a/60/60)+'').padStart(2,0)}:\u0024{(Math.floor(a/60%60)+'').padStart(2,0)}:\u0024{(a%60).toFixed(3).padStart(6,0)}\`;t[2].innerHTML=\`<a target=_blank rel="nofollow noreferrer" ">\u0024{p[6]}</a>\`})</script><h2 id="guard">舰长购买</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>舰长等级</th><th>购买数量</th><th>出现时间</th></tr><z:for-each select="/i/guard"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="@level"/></td><td><z:value-of select="@count"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="sc">SuperChat 醒目留言</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>内容</th><th>显示时长</th><th>价格</th><th>出现时间</th></tr><z:for-each select="/i/sc"><tr><td><z:value-of select="@user"/></td><td><a rel="nofollow noreferrer"><z:attribute name="href"><z:text></z:text><z:value-of select="@uid" /></z:attribute><z:value-of select="@uid"/></a></td><td><z:value-of select="."/></td><td><z:value-of select="@time"/></td><td><z:value-of select="@price"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div><h2 id="gift">礼物</h2><div><table><tr><th>用户名</th><th>用户ID</th><th>礼物名</th><th>礼物数量</th><th>出现时间</th></tr><z:for-each select="/i/gift"><tr><td><z:value-of select="@user"/></td><td><span rel="nofollow noreferrer"><z:attribute name="href"></z:attribute><z:value-of select="@uid"/></span></td><td><z:value-of select="@giftname"/></td><td><z:value-of select="@giftcount"/></td><td><z:value-of select="@ts"/></td></tr></z:for-each></table></div></html></z:template></z:stylesheet></BililiveRecorderXmlStyle>`;
|
|
27
27
|
await fs.promises.writeFile(savePath, header);
|
|
28
28
|
}
|
|
29
29
|
catch (error) {
|