@bilibili-notify/dynamic 0.0.1-alpha.3 → 0.1.0-alpha.5
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 +105 -14
- package/lib/index.d.cts +22 -1
- package/lib/index.d.mts +22 -1
- package/lib/index.mjs +106 -15
- package/package.json +3 -3
package/lib/index.cjs
CHANGED
|
@@ -105,6 +105,89 @@ 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
|
+
}
|
|
129
|
+
function parseUid(raw) {
|
|
130
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return String(Math.trunc(raw));
|
|
131
|
+
if (typeof raw !== "string") return void 0;
|
|
132
|
+
const trimmed = raw.trim();
|
|
133
|
+
return /^\d+$/.test(trimmed) ? trimmed : void 0;
|
|
134
|
+
}
|
|
135
|
+
function normalizeUnixSeconds(raw) {
|
|
136
|
+
if (typeof raw !== "number" && typeof raw !== "string") return void 0;
|
|
137
|
+
if (typeof raw === "string" && !/^\d+(?:\.\d+)?$/.test(raw.trim())) return void 0;
|
|
138
|
+
const n = Number(raw);
|
|
139
|
+
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
140
|
+
if (n > 1e10) {
|
|
141
|
+
const seconds = Math.floor(n / 1e3);
|
|
142
|
+
return seconds <= 1e10 ? seconds : void 0;
|
|
143
|
+
}
|
|
144
|
+
return Math.floor(n);
|
|
145
|
+
}
|
|
146
|
+
function parsePubTimeFallback(raw, now = luxon.DateTime.now()) {
|
|
147
|
+
if (typeof raw !== "string") return void 0;
|
|
148
|
+
const text = raw.trim();
|
|
149
|
+
if (!text) return void 0;
|
|
150
|
+
if (text === "刚刚") return Math.floor(now.toSeconds());
|
|
151
|
+
const relative = text.match(/^(\d+)(秒|分钟|小时)前$/);
|
|
152
|
+
if (relative) {
|
|
153
|
+
const amount = Number(relative[1]);
|
|
154
|
+
if (!Number.isFinite(amount)) return void 0;
|
|
155
|
+
const unit = relative[2] === "秒" ? "seconds" : relative[2] === "分钟" ? "minutes" : "hours";
|
|
156
|
+
return Math.floor(now.minus({ [unit]: amount }).toSeconds());
|
|
157
|
+
}
|
|
158
|
+
const normalized = text.replace(/[年月]/g, "-").replace(/日/g, "").replace(/\//g, "-").replace(/\s+/g, " ");
|
|
159
|
+
for (const fmt of [
|
|
160
|
+
"yyyy-M-d H:mm:ss",
|
|
161
|
+
"yyyy-M-d H:mm",
|
|
162
|
+
"yyyy-M-d",
|
|
163
|
+
"M-d H:mm:ss",
|
|
164
|
+
"M-d H:mm"
|
|
165
|
+
]) {
|
|
166
|
+
let dt = luxon.DateTime.fromFormat(normalized, fmt);
|
|
167
|
+
if (!dt.isValid) continue;
|
|
168
|
+
if (!fmt.startsWith("yyyy")) {
|
|
169
|
+
dt = dt.set({ year: now.year });
|
|
170
|
+
if (dt > now.plus({ days: 1 })) dt = dt.minus({ years: 1 });
|
|
171
|
+
}
|
|
172
|
+
return Math.floor(dt.toSeconds());
|
|
173
|
+
}
|
|
174
|
+
const yesterday = normalized.match(/^昨天\s*(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
|
|
175
|
+
if (yesterday) {
|
|
176
|
+
const hour = Number(yesterday[1]);
|
|
177
|
+
const minute = Number(yesterday[2]);
|
|
178
|
+
const second = Number(yesterday[3] ?? 0);
|
|
179
|
+
const dt = now.minus({ days: 1 }).set({
|
|
180
|
+
hour,
|
|
181
|
+
minute,
|
|
182
|
+
second,
|
|
183
|
+
millisecond: 0
|
|
184
|
+
});
|
|
185
|
+
return dt.isValid ? Math.floor(dt.toSeconds()) : void 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function getDynamicPostTime(author) {
|
|
189
|
+
return normalizeUnixSeconds(author.pub_ts) ?? parsePubTimeFallback(author.pub_time);
|
|
190
|
+
}
|
|
108
191
|
/** 从动态数据中提取图片 URL,用于多模态 AI 点评(最多 4 张) */
|
|
109
192
|
function extractDynamicImages(item) {
|
|
110
193
|
const mod = item.modules.module_dynamic;
|
|
@@ -392,15 +475,21 @@ var DynamicEngine = class {
|
|
|
392
475
|
};
|
|
393
476
|
for (const item of content.data.items) {
|
|
394
477
|
if (!item) continue;
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
478
|
+
const author = item.modules?.module_author;
|
|
479
|
+
const uid = parseUid(author?.mid);
|
|
480
|
+
if (!uid) {
|
|
481
|
+
this.logger.debug(`[detector] 跳过无作者 UID 的动态,ID=${item.id_str ?? "unknown"}`);
|
|
398
482
|
continue;
|
|
399
483
|
}
|
|
400
|
-
const uid = item.modules.module_author.mid.toString();
|
|
401
|
-
const name = item.modules.module_author.name;
|
|
402
484
|
const timeline = this.dynamicTimelineManager.get(uid);
|
|
403
485
|
if (timeline === void 0) continue;
|
|
486
|
+
const postTime = getDynamicPostTime(author);
|
|
487
|
+
if (postTime === void 0) {
|
|
488
|
+
const rawPubTs = author.pub_ts;
|
|
489
|
+
this.logger.warn(`[detector] 跳过无效动态:无法解析发布时间,UID=${uid} ID=${item.id_str ?? "unknown"} type=${item.type ?? "unknown"} pub_ts_type=${typeof rawPubTs} pub_ts=${JSON.stringify(rawPubTs)} pub_time=${JSON.stringify(author.pub_time)}`);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
const name = author.name;
|
|
404
493
|
this.logger.debug(`[detector] 检查动态 UP=${name} UID=${uid} 发布时间=${luxon.DateTime.fromSeconds(postTime).toFormat("yyyy-MM-dd HH:mm:ss")}`);
|
|
405
494
|
if (timeline >= postTime) continue;
|
|
406
495
|
const subAtCapture = this.dynamicSubManager.get(uid);
|
|
@@ -456,14 +545,15 @@ var DynamicEngine = class {
|
|
|
456
545
|
this.imageFailureStreak = 0;
|
|
457
546
|
this.imageFailureNotified = false;
|
|
458
547
|
}
|
|
459
|
-
|
|
460
|
-
|
|
548
|
+
const isVideo = item.type === "DYNAMIC_TYPE_AV";
|
|
549
|
+
let url = "";
|
|
550
|
+
if (this.config.dynamicUrl) if (isVideo) {
|
|
461
551
|
const jumpUrl = item.modules.module_dynamic.major?.archive?.jump_url ?? "";
|
|
462
552
|
if (this.config.dynamicVideoUrlToBV) {
|
|
463
553
|
const bvMatch = jumpUrl.match(/BV[0-9A-Za-z]+/);
|
|
464
|
-
|
|
465
|
-
} else
|
|
466
|
-
} else
|
|
554
|
+
url = bvMatch ? bvMatch[0] : "";
|
|
555
|
+
} else url = `https:${jumpUrl}`;
|
|
556
|
+
} else url = `https://t.bilibili.com/${item.id_str}`;
|
|
467
557
|
let aiComment;
|
|
468
558
|
if (this.ai && this.config.aiEnabled !== false) {
|
|
469
559
|
const dynamicText = extractDynamicText(item);
|
|
@@ -483,17 +573,18 @@ var DynamicEngine = class {
|
|
|
483
573
|
this.logger.debug(`[detector] UID=${uid} 在本轮处理中已退订/被替换,跳过推送`);
|
|
484
574
|
continue;
|
|
485
575
|
}
|
|
486
|
-
const
|
|
576
|
+
const tmpl = isVideo ? sub?.customVideoTemplate ?? this.config.videoTemplate ?? DEFAULT_DYNAMIC_TEXT.video : sub?.customDynamicTemplate ?? this.config.dynamicTemplate ?? DEFAULT_DYNAMIC_TEXT.dynamic;
|
|
577
|
+
const text = aiComment ?? renderDynamicText(tmpl, name, url);
|
|
487
578
|
const segments = buffer ? [{
|
|
488
579
|
type: "image",
|
|
489
580
|
buffer,
|
|
490
581
|
mime: "image/jpeg"
|
|
491
|
-
}, ...
|
|
582
|
+
}, ...text ? [{
|
|
492
583
|
type: "text",
|
|
493
|
-
text
|
|
584
|
+
text
|
|
494
585
|
}] : []] : [{
|
|
495
586
|
type: "text",
|
|
496
|
-
text
|
|
587
|
+
text
|
|
497
588
|
}];
|
|
498
589
|
await this.push.broadcastDynamic(uid, segments, "dynamic");
|
|
499
590
|
const subForImgs = this.dynamicSubManager.get(uid);
|
package/lib/index.d.cts
CHANGED
|
@@ -34,7 +34,7 @@ type Dynamic = {
|
|
|
34
34
|
name: string;
|
|
35
35
|
pub_action: string;
|
|
36
36
|
pub_time: string;
|
|
37
|
-
pub_ts
|
|
37
|
+
pub_ts?: number | string;
|
|
38
38
|
type: string;
|
|
39
39
|
[key: string]: any;
|
|
40
40
|
};
|
|
@@ -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
|
package/lib/index.d.mts
CHANGED
|
@@ -34,7 +34,7 @@ type Dynamic = {
|
|
|
34
34
|
name: string;
|
|
35
35
|
pub_action: string;
|
|
36
36
|
pub_time: string;
|
|
37
|
-
pub_ts
|
|
37
|
+
pub_ts?: number | string;
|
|
38
38
|
type: string;
|
|
39
39
|
[key: string]: any;
|
|
40
40
|
};
|
|
@@ -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
|
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,89 @@ 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
|
+
}
|
|
128
|
+
function parseUid(raw) {
|
|
129
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return String(Math.trunc(raw));
|
|
130
|
+
if (typeof raw !== "string") return void 0;
|
|
131
|
+
const trimmed = raw.trim();
|
|
132
|
+
return /^\d+$/.test(trimmed) ? trimmed : void 0;
|
|
133
|
+
}
|
|
134
|
+
function normalizeUnixSeconds(raw) {
|
|
135
|
+
if (typeof raw !== "number" && typeof raw !== "string") return void 0;
|
|
136
|
+
if (typeof raw === "string" && !/^\d+(?:\.\d+)?$/.test(raw.trim())) return void 0;
|
|
137
|
+
const n = Number(raw);
|
|
138
|
+
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
139
|
+
if (n > 1e10) {
|
|
140
|
+
const seconds = Math.floor(n / 1e3);
|
|
141
|
+
return seconds <= 1e10 ? seconds : void 0;
|
|
142
|
+
}
|
|
143
|
+
return Math.floor(n);
|
|
144
|
+
}
|
|
145
|
+
function parsePubTimeFallback(raw, now = DateTime.now()) {
|
|
146
|
+
if (typeof raw !== "string") return void 0;
|
|
147
|
+
const text = raw.trim();
|
|
148
|
+
if (!text) return void 0;
|
|
149
|
+
if (text === "刚刚") return Math.floor(now.toSeconds());
|
|
150
|
+
const relative = text.match(/^(\d+)(秒|分钟|小时)前$/);
|
|
151
|
+
if (relative) {
|
|
152
|
+
const amount = Number(relative[1]);
|
|
153
|
+
if (!Number.isFinite(amount)) return void 0;
|
|
154
|
+
const unit = relative[2] === "秒" ? "seconds" : relative[2] === "分钟" ? "minutes" : "hours";
|
|
155
|
+
return Math.floor(now.minus({ [unit]: amount }).toSeconds());
|
|
156
|
+
}
|
|
157
|
+
const normalized = text.replace(/[年月]/g, "-").replace(/日/g, "").replace(/\//g, "-").replace(/\s+/g, " ");
|
|
158
|
+
for (const fmt of [
|
|
159
|
+
"yyyy-M-d H:mm:ss",
|
|
160
|
+
"yyyy-M-d H:mm",
|
|
161
|
+
"yyyy-M-d",
|
|
162
|
+
"M-d H:mm:ss",
|
|
163
|
+
"M-d H:mm"
|
|
164
|
+
]) {
|
|
165
|
+
let dt = DateTime.fromFormat(normalized, fmt);
|
|
166
|
+
if (!dt.isValid) continue;
|
|
167
|
+
if (!fmt.startsWith("yyyy")) {
|
|
168
|
+
dt = dt.set({ year: now.year });
|
|
169
|
+
if (dt > now.plus({ days: 1 })) dt = dt.minus({ years: 1 });
|
|
170
|
+
}
|
|
171
|
+
return Math.floor(dt.toSeconds());
|
|
172
|
+
}
|
|
173
|
+
const yesterday = normalized.match(/^昨天\s*(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
|
|
174
|
+
if (yesterday) {
|
|
175
|
+
const hour = Number(yesterday[1]);
|
|
176
|
+
const minute = Number(yesterday[2]);
|
|
177
|
+
const second = Number(yesterday[3] ?? 0);
|
|
178
|
+
const dt = now.minus({ days: 1 }).set({
|
|
179
|
+
hour,
|
|
180
|
+
minute,
|
|
181
|
+
second,
|
|
182
|
+
millisecond: 0
|
|
183
|
+
});
|
|
184
|
+
return dt.isValid ? Math.floor(dt.toSeconds()) : void 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function getDynamicPostTime(author) {
|
|
188
|
+
return normalizeUnixSeconds(author.pub_ts) ?? parsePubTimeFallback(author.pub_time);
|
|
189
|
+
}
|
|
107
190
|
/** 从动态数据中提取图片 URL,用于多模态 AI 点评(最多 4 张) */
|
|
108
191
|
function extractDynamicImages(item) {
|
|
109
192
|
const mod = item.modules.module_dynamic;
|
|
@@ -391,15 +474,21 @@ var DynamicEngine = class {
|
|
|
391
474
|
};
|
|
392
475
|
for (const item of content.data.items) {
|
|
393
476
|
if (!item) continue;
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
477
|
+
const author = item.modules?.module_author;
|
|
478
|
+
const uid = parseUid(author?.mid);
|
|
479
|
+
if (!uid) {
|
|
480
|
+
this.logger.debug(`[detector] 跳过无作者 UID 的动态,ID=${item.id_str ?? "unknown"}`);
|
|
397
481
|
continue;
|
|
398
482
|
}
|
|
399
|
-
const uid = item.modules.module_author.mid.toString();
|
|
400
|
-
const name = item.modules.module_author.name;
|
|
401
483
|
const timeline = this.dynamicTimelineManager.get(uid);
|
|
402
484
|
if (timeline === void 0) continue;
|
|
485
|
+
const postTime = getDynamicPostTime(author);
|
|
486
|
+
if (postTime === void 0) {
|
|
487
|
+
const rawPubTs = author.pub_ts;
|
|
488
|
+
this.logger.warn(`[detector] 跳过无效动态:无法解析发布时间,UID=${uid} ID=${item.id_str ?? "unknown"} type=${item.type ?? "unknown"} pub_ts_type=${typeof rawPubTs} pub_ts=${JSON.stringify(rawPubTs)} pub_time=${JSON.stringify(author.pub_time)}`);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const name = author.name;
|
|
403
492
|
this.logger.debug(`[detector] 检查动态 UP=${name} UID=${uid} 发布时间=${DateTime.fromSeconds(postTime).toFormat("yyyy-MM-dd HH:mm:ss")}`);
|
|
404
493
|
if (timeline >= postTime) continue;
|
|
405
494
|
const subAtCapture = this.dynamicSubManager.get(uid);
|
|
@@ -455,14 +544,15 @@ var DynamicEngine = class {
|
|
|
455
544
|
this.imageFailureStreak = 0;
|
|
456
545
|
this.imageFailureNotified = false;
|
|
457
546
|
}
|
|
458
|
-
|
|
459
|
-
|
|
547
|
+
const isVideo = item.type === "DYNAMIC_TYPE_AV";
|
|
548
|
+
let url = "";
|
|
549
|
+
if (this.config.dynamicUrl) if (isVideo) {
|
|
460
550
|
const jumpUrl = item.modules.module_dynamic.major?.archive?.jump_url ?? "";
|
|
461
551
|
if (this.config.dynamicVideoUrlToBV) {
|
|
462
552
|
const bvMatch = jumpUrl.match(/BV[0-9A-Za-z]+/);
|
|
463
|
-
|
|
464
|
-
} else
|
|
465
|
-
} else
|
|
553
|
+
url = bvMatch ? bvMatch[0] : "";
|
|
554
|
+
} else url = `https:${jumpUrl}`;
|
|
555
|
+
} else url = `https://t.bilibili.com/${item.id_str}`;
|
|
466
556
|
let aiComment;
|
|
467
557
|
if (this.ai && this.config.aiEnabled !== false) {
|
|
468
558
|
const dynamicText = extractDynamicText(item);
|
|
@@ -482,17 +572,18 @@ var DynamicEngine = class {
|
|
|
482
572
|
this.logger.debug(`[detector] UID=${uid} 在本轮处理中已退订/被替换,跳过推送`);
|
|
483
573
|
continue;
|
|
484
574
|
}
|
|
485
|
-
const
|
|
575
|
+
const tmpl = isVideo ? sub?.customVideoTemplate ?? this.config.videoTemplate ?? DEFAULT_DYNAMIC_TEXT.video : sub?.customDynamicTemplate ?? this.config.dynamicTemplate ?? DEFAULT_DYNAMIC_TEXT.dynamic;
|
|
576
|
+
const text = aiComment ?? renderDynamicText(tmpl, name, url);
|
|
486
577
|
const segments = buffer ? [{
|
|
487
578
|
type: "image",
|
|
488
579
|
buffer,
|
|
489
580
|
mime: "image/jpeg"
|
|
490
|
-
}, ...
|
|
581
|
+
}, ...text ? [{
|
|
491
582
|
type: "text",
|
|
492
|
-
text
|
|
583
|
+
text
|
|
493
584
|
}] : []] : [{
|
|
494
585
|
type: "text",
|
|
495
|
-
text
|
|
586
|
+
text
|
|
496
587
|
}];
|
|
497
588
|
await this.push.broadcastDynamic(uid, segments, "dynamic");
|
|
498
589
|
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.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/Akokk0/bilibili-notify"
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"luxon": "^3.5.0",
|
|
31
31
|
"@bilibili-notify/ai": "^0.0.1-alpha.1",
|
|
32
32
|
"@bilibili-notify/image": "^0.0.1-alpha.2",
|
|
33
|
-
"@bilibili-notify/
|
|
34
|
-
"@bilibili-notify/
|
|
33
|
+
"@bilibili-notify/internal": "^0.1.0-alpha.4",
|
|
34
|
+
"@bilibili-notify/api": "^0.2.0-alpha.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/luxon": "^3.4.2"
|