koishi-plugin-bilibili-videolink-analysis 1.2.0 → 1.3.1
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.d.ts +6 -40
- package/lib/index.js +411 -377
- package/lib/utils.d.ts +63 -0
- package/package.json +1 -1
- package/src/index.ts +102 -629
- package/src/utils.ts +536 -0
package/lib/index.js
CHANGED
|
@@ -27,299 +27,50 @@ __export(src_exports, {
|
|
|
27
27
|
usage: () => usage
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(src_exports);
|
|
30
|
-
var
|
|
31
|
-
var logger = new import_koishi.Logger("bilibili-videolink-analysis");
|
|
32
|
-
var name = "bilibili-videolink-analysis";
|
|
33
|
-
var inject = {
|
|
34
|
-
optional: ["puppeteer"]
|
|
35
|
-
// required: ['BiliBiliVideo']
|
|
36
|
-
};
|
|
37
|
-
var usage = `
|
|
38
|
-
|
|
39
|
-
<h2>→ <a href="https://www.npmjs.com/package/koishi-plugin-bilibili-videolink-analysis" target="_blank">可以点击这里查看详细的文档说明✨</a></h2>
|
|
40
|
-
|
|
41
|
-
✨ 只需开启插件,就可以解析B站视频的链接啦~ ✨
|
|
42
|
-
|
|
43
|
-
向bot发送B站视频链接吧~
|
|
44
|
-
|
|
45
|
-
会返回视频信息与视频哦
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
#### ⚠️ **如果你使用不了本项目,请优先检查:** ⚠️
|
|
50
|
-
#### 若无注册的指令,请关开一下[command插件](/market?keyword=commands+email:shigma10826@gmail.com)(没有指令也不影响解析别人的链接)
|
|
51
|
-
#### 视频内容是否为B站的大会员专属视频/付费视频/充电专属视频
|
|
52
|
-
#### 接入方法是否支持获取网址链接/小程序卡片消息
|
|
53
|
-
#### 接入方法是否支持视频元素的发送
|
|
54
|
-
#### 发送视频超时/其他网络问题
|
|
55
|
-
#### 视频内容被平台屏蔽/其他平台因素
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
### 注意,点播功能需要使用 puppeteer 服务
|
|
60
|
-
|
|
61
|
-
点播功能是为了方便群友一起刷B站哦~
|
|
62
|
-
|
|
63
|
-
比如:搜索 “遠い空へ” 的第二页,并且结果以语音格式返回
|
|
64
|
-
|
|
65
|
-
示例:\`点播 遠い空へ -a -p 2\`
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
### 特别鸣谢 💖
|
|
71
|
-
|
|
72
|
-
特别鸣谢以下项目的支持:
|
|
30
|
+
var import_koishi2 = require("koishi");
|
|
73
31
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
demand: import_koishi.Schema.boolean().default(true).description("开启点播指令功能<br>`其实点播登录不登录 都搜不准,登录只是写着玩的`")
|
|
82
|
-
}).description("点播设置(需要puppeteer服务)"),
|
|
83
|
-
import_koishi.Schema.union([
|
|
84
|
-
import_koishi.Schema.object({
|
|
85
|
-
demand: import_koishi.Schema.const(false).required()
|
|
86
|
-
}),
|
|
87
|
-
import_koishi.Schema.object({
|
|
88
|
-
demand: import_koishi.Schema.const(true),
|
|
89
|
-
timeout: import_koishi.Schema.number().role("slider").min(1).max(300).step(1).default(60).description("指定播放视频的输入时限。`单位 秒`"),
|
|
90
|
-
point: import_koishi.Schema.tuple([Number, Number]).description("序号标注位置。分别表示`距离顶部 距离左侧`的百分比").default([50, 50]),
|
|
91
|
-
enable: import_koishi.Schema.boolean().description("是否开启自动解析`选择对应视频 会自动解析视频内容`").default(true)
|
|
92
|
-
})
|
|
93
|
-
]),
|
|
94
|
-
import_koishi.Schema.object({
|
|
95
|
-
enablebilianalysis: import_koishi.Schema.boolean().default(true).description("开启解析功能<br>`关闭后,解析功能将关闭`")
|
|
96
|
-
}).description("视频解析 - 功能开关"),
|
|
97
|
-
import_koishi.Schema.union([
|
|
98
|
-
import_koishi.Schema.object({
|
|
99
|
-
enablebilianalysis: import_koishi.Schema.const(false).required()
|
|
100
|
-
}),
|
|
101
|
-
import_koishi.Schema.intersect([
|
|
102
|
-
import_koishi.Schema.object({
|
|
103
|
-
enablebilianalysis: import_koishi.Schema.const(true),
|
|
104
|
-
// @ts-ignore // 摸了摸了
|
|
105
|
-
waitTip_Switch: import_koishi.Schema.union([
|
|
106
|
-
import_koishi.Schema.const(null).description("不返回文字提示"),
|
|
107
|
-
import_koishi.Schema.string().description("返回文字提示(请在右侧填写文字内容)").default("正在解析B站链接...")
|
|
108
|
-
]).description("是否返回等待提示。开启后,会发送`等待提示语`"),
|
|
109
|
-
linktextParsing: import_koishi.Schema.boolean().default(true).description("是否返回 视频图文数据 `开启后,才发送视频数据的图文解析。`"),
|
|
110
|
-
VideoParsing_ToLink: import_koishi.Schema.union([
|
|
111
|
-
import_koishi.Schema.const("1").description("不返回视频/视频直链"),
|
|
112
|
-
import_koishi.Schema.const("2").description("仅返回视频"),
|
|
113
|
-
import_koishi.Schema.const("3").description("仅返回视频直链"),
|
|
114
|
-
import_koishi.Schema.const("4").description("返回视频和视频直链"),
|
|
115
|
-
import_koishi.Schema.const("5").description("返回视频,仅在日志记录视频直链")
|
|
116
|
-
]).role("radio").default("2").description("是否返回` 视频/视频直链 `"),
|
|
117
|
-
BVnumberParsing: import_koishi.Schema.boolean().default(true).description("是否允许根据`独立的BV、AV号`解析视频 `开启后,可以通过视频的BV、AV号解析视频。` <br> [触发说明见README](https://www.npmjs.com/package/koishi-plugin-bilibili-videolink-analysis)"),
|
|
118
|
-
MinimumTimeInterval: import_koishi.Schema.number().default(180).description("若干`秒`内 不再处理相同链接 `防止多bot互相触发 导致的刷屏/性能浪费`").min(1)
|
|
119
|
-
}),
|
|
120
|
-
import_koishi.Schema.object({
|
|
121
|
-
enablebilianalysis: import_koishi.Schema.const(true),
|
|
122
|
-
Minimumduration: import_koishi.Schema.number().default(0).description("允许解析的视频最小时长(分钟)`低于这个时长 就不会发视频内容`").min(0),
|
|
123
|
-
Minimumduration_tip: import_koishi.Schema.union([
|
|
124
|
-
import_koishi.Schema.const("return").description("不返回文字提示"),
|
|
125
|
-
import_koishi.Schema.object({
|
|
126
|
-
tipcontent: import_koishi.Schema.string().default("视频太短啦!不看不看~").description("文字提示内容"),
|
|
127
|
-
tipanalysis: import_koishi.Schema.boolean().default(true).description("是否进行图文解析(不会返回视频链接)")
|
|
128
|
-
}).description("返回文字提示"),
|
|
129
|
-
import_koishi.Schema.const(null)
|
|
130
|
-
]).description("对`过短视频`的文字提示内容").default(null),
|
|
131
|
-
Maximumduration: import_koishi.Schema.number().default(25).description("允许解析的视频最大时长(分钟)`超过这个时长 就不会发视频内容`").min(1),
|
|
132
|
-
Maximumduration_tip: import_koishi.Schema.union([
|
|
133
|
-
import_koishi.Schema.const("return").description("不返回文字提示"),
|
|
134
|
-
import_koishi.Schema.object({
|
|
135
|
-
tipcontent: import_koishi.Schema.string().default("视频太长啦!内容还是去B站看吧~").description("文字提示内容"),
|
|
136
|
-
tipanalysis: import_koishi.Schema.boolean().default(true).description("是否进行图文解析(不会返回视频链接)")
|
|
137
|
-
}).description("返回文字提示"),
|
|
138
|
-
import_koishi.Schema.const(null)
|
|
139
|
-
]).description("对`过长视频`的文字提示内容").default(null)
|
|
140
|
-
}).description("视频解析 - 内容限制"),
|
|
141
|
-
import_koishi.Schema.object({
|
|
142
|
-
parseLimit: import_koishi.Schema.number().default(3).description("单对话多链接解析上限").hidden(),
|
|
143
|
-
useNumeral: import_koishi.Schema.boolean().default(true).description("使用格式化数字").hidden(),
|
|
144
|
-
showError: import_koishi.Schema.boolean().default(false).description("当链接不正确时提醒发送者").hidden(),
|
|
145
|
-
bVideoIDPreference: import_koishi.Schema.union([
|
|
146
|
-
import_koishi.Schema.const("bv").description("BV 号"),
|
|
147
|
-
import_koishi.Schema.const("av").description("AV 号")
|
|
148
|
-
]).default("bv").description("ID 偏好").hidden(),
|
|
149
|
-
bVideo_area: import_koishi.Schema.string().role("textarea", { rows: [8, 16] }).default("${标题} --- ${UP主}\n${简介}\n点赞:${点赞} --- 投币:${投币}\n收藏:${收藏} --- 转发:${转发}\n观看:${观看} --- 弹幕:${弹幕}\n${~~~}\n${封面}").description(`图文解析的返回格式<br>
|
|
150
|
-
注意变量格式,以及变量名称。<br>比如 \`\${标题}\` 不可以变成\`\${标题123}\`,你可以直接删掉但是不能修改变量名称哦<br>
|
|
151
|
-
当然变量也不能无中生有,下面的默认值内容 就是所有变量了,你仅可以删去变量 或者修改变量之外的格式。<br>
|
|
152
|
-
· 特殊变量\`\${~~~}\`表示分割线,会把上下内容分为两个信息单独发送。\`\${tab}\`表示制表符。`),
|
|
153
|
-
bVideoShowLink: import_koishi.Schema.boolean().default(false).description("在末尾显示视频的链接地址 `开启可能会导致其他bot循环解析`"),
|
|
154
|
-
bVideoShowIntroductionTofixed: import_koishi.Schema.number().default(50).description("视频的`简介`最大的字符长度<br>超出部分会使用 `...` 代替")
|
|
155
|
-
}).description("链接的图文解析设置"),
|
|
156
|
-
import_koishi.Schema.object({
|
|
157
|
-
isfigure: import_koishi.Schema.boolean().default(false).description("是否开启合并转发 `仅支持 onebot 适配器` 其他平台开启 无效").experimental(),
|
|
158
|
-
filebuffer: import_koishi.Schema.boolean().default(true).description("是否将视频链接下载后再发送 (以解决部分onebot协议端的问题)<br>否则使用视频直链发送").experimental(),
|
|
159
|
-
middleware: import_koishi.Schema.boolean().default(false).description("前置中间件模式"),
|
|
160
|
-
userAgent: import_koishi.Schema.string().description("所有 API 请求所用的 User-Agent").default("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
|
|
161
|
-
}).description("调试设置")
|
|
162
|
-
])
|
|
163
|
-
]),
|
|
164
|
-
import_koishi.Schema.object({
|
|
165
|
-
pageclose: import_koishi.Schema.boolean().default(true).description("自动`page.close()`<br>非开发者请勿改动").experimental(),
|
|
166
|
-
loggerinfo: import_koishi.Schema.boolean().default(false).description("日志调试输出 `日常使用无需开启`<br>非开发者请勿改动").experimental(),
|
|
167
|
-
loggerinfofulljson: import_koishi.Schema.boolean().default(false).description("打印完整的机器人发送的json输出").experimental()
|
|
168
|
-
}).description("开发者选项")
|
|
169
|
-
]);
|
|
170
|
-
function apply(ctx, config) {
|
|
171
|
-
const lastProcessedUrls = {};
|
|
172
|
-
if (config.enablebilianalysis) {
|
|
173
|
-
ctx.middleware(async (session, next) => {
|
|
174
|
-
let sessioncontent = session.stripped.content;
|
|
175
|
-
if (config.BVnumberParsing) {
|
|
176
|
-
const bvUrls = convertBVToUrl(sessioncontent);
|
|
177
|
-
if (bvUrls.length > 0) {
|
|
178
|
-
sessioncontent += "\n" + bvUrls.join("\n");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const links = await isProcessLinks(sessioncontent);
|
|
182
|
-
if (links) {
|
|
183
|
-
const ret = await extractLinks(session, links);
|
|
184
|
-
if (ret && !isLinkProcessedRecently(ret, session.channelId)) {
|
|
185
|
-
await processVideoFromLink(session, ret);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return next();
|
|
189
|
-
}, config.middleware);
|
|
32
|
+
// src/utils.ts
|
|
33
|
+
var import_koishi = require("koishi");
|
|
34
|
+
var BilibiliParser = class {
|
|
35
|
+
constructor(ctx, config, logger2) {
|
|
36
|
+
this.ctx = ctx;
|
|
37
|
+
this.config = config;
|
|
38
|
+
this.logger = logger2;
|
|
190
39
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (!keyword) {
|
|
194
|
-
await session.send(import_koishi.h.text("告诉我 你想要点播的关键词吧~"));
|
|
195
|
-
keyword = await session.prompt(30 * 1e3);
|
|
196
|
-
}
|
|
197
|
-
const url = `https://search.bilibili.com/video?keyword=${encodeURIComponent(keyword)}&page=${options.page}&o=30`;
|
|
198
|
-
const page = await ctx.puppeteer.page();
|
|
199
|
-
await page.goto(url, {
|
|
200
|
-
waitUntil: "networkidle2"
|
|
201
|
-
});
|
|
202
|
-
await page.addStyleTag({
|
|
203
|
-
content: `
|
|
204
|
-
div.bili-header,
|
|
205
|
-
div.login-tip,
|
|
206
|
-
div.v-popover,
|
|
207
|
-
div.right-entry__outside {
|
|
208
|
-
display: none !important;
|
|
209
|
-
}
|
|
210
|
-
`
|
|
211
|
-
});
|
|
212
|
-
const videos = await page.evaluate((point) => {
|
|
213
|
-
const items = Array.from(document.querySelectorAll('.video-list-item:not([style*="display: none"])'));
|
|
214
|
-
return items.map((item, index) => {
|
|
215
|
-
const link = item.querySelector("a");
|
|
216
|
-
const href = link?.getAttribute("href") || "";
|
|
217
|
-
const idMatch = href.match(/\/video\/(BV\w+)\//);
|
|
218
|
-
const id = idMatch ? idMatch[1] : "";
|
|
219
|
-
if (!id) {
|
|
220
|
-
const htmlElement = item;
|
|
221
|
-
htmlElement.style.display = "none";
|
|
222
|
-
} else {
|
|
223
|
-
const overlay = document.createElement("div");
|
|
224
|
-
overlay.style.position = "absolute";
|
|
225
|
-
overlay.style.top = `${point[0]}%`;
|
|
226
|
-
overlay.style.left = `${point[1]}%`;
|
|
227
|
-
overlay.style.transform = "translate(-50%, -50%)";
|
|
228
|
-
overlay.style.fontSize = "48px";
|
|
229
|
-
overlay.style.fontWeight = "bold";
|
|
230
|
-
overlay.style.color = "black";
|
|
231
|
-
overlay.style.zIndex = "10";
|
|
232
|
-
overlay.style.backgroundColor = "rgba(255, 255, 255, 0.7)";
|
|
233
|
-
overlay.style.padding = "10px";
|
|
234
|
-
overlay.style.borderRadius = "8px";
|
|
235
|
-
overlay.textContent = `${index + 1}`;
|
|
236
|
-
const videoElement = item;
|
|
237
|
-
videoElement.style.position = "relative";
|
|
238
|
-
videoElement.appendChild(overlay);
|
|
239
|
-
}
|
|
240
|
-
return { id };
|
|
241
|
-
}).filter((video) => video.id);
|
|
242
|
-
}, config.point);
|
|
243
|
-
logInfo(options);
|
|
244
|
-
logInfo(`共找到 ${videos.length} 个视频:`);
|
|
245
|
-
videos.forEach((video, index) => {
|
|
246
|
-
logInfo(`序号 ${index + 1}: ID - ${video.id}`);
|
|
247
|
-
});
|
|
248
|
-
if (videos.length === 0) {
|
|
249
|
-
await page.close();
|
|
250
|
-
await session.send(import_koishi.h.text("未找到相关视频。"));
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
const viewportHeight = 200 + videos.length * 100;
|
|
254
|
-
await page.setViewport({
|
|
255
|
-
width: 1440,
|
|
256
|
-
height: viewportHeight
|
|
257
|
-
});
|
|
258
|
-
logInfo("窗口:宽度:");
|
|
259
|
-
logInfo(1440);
|
|
260
|
-
logInfo("窗口:高度:");
|
|
261
|
-
logInfo(viewportHeight);
|
|
262
|
-
let msg;
|
|
263
|
-
const videoListElement = await page.$(".video-list.row");
|
|
264
|
-
if (videoListElement) {
|
|
265
|
-
const imgBuf = await videoListElement.screenshot({
|
|
266
|
-
captureBeyondViewport: false
|
|
267
|
-
});
|
|
268
|
-
msg = import_koishi.h.image(imgBuf, "image/png");
|
|
269
|
-
}
|
|
270
|
-
if (page && config.pageclose) {
|
|
271
|
-
await page.close();
|
|
272
|
-
}
|
|
273
|
-
await session.send(msg + import_koishi.h.text(`请选择视频的序号:`));
|
|
274
|
-
const userChoice = await session.prompt(config.timeout * 1e3);
|
|
275
|
-
const choiceIndex = parseInt(userChoice) - 1;
|
|
276
|
-
if (isNaN(choiceIndex) || choiceIndex < 0 || choiceIndex >= videos.length) {
|
|
277
|
-
await session.send(import_koishi.h.text("输入无效,请输入正确的序号。"));
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const chosenVideo = videos[choiceIndex];
|
|
281
|
-
logInfo(`渲染序号设置
|
|
282
|
-
overlay.style.top = ${config.point[0]}%
|
|
283
|
-
overlay.style.left = ${config.point[1]}%`);
|
|
284
|
-
logInfo(`用户选择了序号 ${choiceIndex + 1}: ID - ${chosenVideo.id}`);
|
|
285
|
-
if (config.enable) {
|
|
286
|
-
const ret = await extractLinks(session, [{ type: "Video", id: chosenVideo.id }]);
|
|
287
|
-
if (ret && !isLinkProcessedRecently(ret, session.channelId)) {
|
|
288
|
-
await processVideoFromLink(session, ret, options);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
});
|
|
40
|
+
static {
|
|
41
|
+
__name(this, "BilibiliParser");
|
|
292
42
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
43
|
+
lastProcessedUrls = {};
|
|
44
|
+
logInfo(...args) {
|
|
45
|
+
if (this.config.loggerinfo) {
|
|
46
|
+
this.logger.info(...args);
|
|
296
47
|
}
|
|
297
48
|
}
|
|
298
|
-
|
|
299
|
-
async
|
|
300
|
-
const links = link_type_parser(sessioncontent);
|
|
49
|
+
// 判断是否需要解析
|
|
50
|
+
async isProcessLinks(sessioncontent) {
|
|
51
|
+
const links = this.link_type_parser(sessioncontent);
|
|
301
52
|
if (links.length === 0) {
|
|
302
53
|
return false;
|
|
303
54
|
}
|
|
304
55
|
return links;
|
|
305
56
|
}
|
|
306
|
-
|
|
307
|
-
async
|
|
57
|
+
//提取链接
|
|
58
|
+
async extractLinks(session, links) {
|
|
308
59
|
let ret = "";
|
|
309
|
-
if (!config.isfigure) {
|
|
60
|
+
if (!this.config.isfigure) {
|
|
310
61
|
ret += (0, import_koishi.h)("quote", { id: session.messageId });
|
|
311
62
|
}
|
|
312
63
|
let countLink = 0;
|
|
313
64
|
let tp_ret;
|
|
314
65
|
for (const element of links) {
|
|
315
66
|
if (countLink >= 1) ret += "\n";
|
|
316
|
-
if (countLink >= config.parseLimit) {
|
|
67
|
+
if (countLink >= this.config.parseLimit) {
|
|
317
68
|
ret += "已达到解析上限…";
|
|
318
69
|
break;
|
|
319
70
|
}
|
|
320
|
-
tp_ret = await type_processer(element);
|
|
71
|
+
tp_ret = await this.type_processer(element);
|
|
321
72
|
if (tp_ret == "") {
|
|
322
|
-
if (config.showError)
|
|
73
|
+
if (this.config.showError)
|
|
323
74
|
ret = "无法解析链接信息。可能是 ID 不存在,或该类型可能暂不支持。";
|
|
324
75
|
else
|
|
325
76
|
ret = null;
|
|
@@ -330,35 +81,34 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
330
81
|
}
|
|
331
82
|
return ret;
|
|
332
83
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const lastretUrl = extractLastUrl(ret);
|
|
84
|
+
//判断链接是否已经处理过
|
|
85
|
+
isLinkProcessedRecently(ret, channelId) {
|
|
86
|
+
const lastretUrl = this.extractLastUrl(ret);
|
|
336
87
|
const currentTime = Date.now();
|
|
337
88
|
const channelKey = `${channelId}:${lastretUrl}`;
|
|
338
|
-
if (lastretUrl && lastProcessedUrls[channelKey] && currentTime - lastProcessedUrls[channelKey] < config.MinimumTimeInterval * 1e3) {
|
|
339
|
-
ctx.logger.info(`重复出现,略过处理:
|
|
89
|
+
if (lastretUrl && this.lastProcessedUrls[channelKey] && currentTime - this.lastProcessedUrls[channelKey] < this.config.MinimumTimeInterval * 1e3) {
|
|
90
|
+
this.ctx.logger.info(`重复出现,略过处理:
|
|
340
91
|
${lastretUrl} (频道 ${channelId})`);
|
|
341
92
|
return true;
|
|
342
93
|
}
|
|
343
94
|
if (lastretUrl) {
|
|
344
|
-
lastProcessedUrls[channelKey] = currentTime;
|
|
95
|
+
this.lastProcessedUrls[channelKey] = currentTime;
|
|
345
96
|
}
|
|
346
97
|
return false;
|
|
347
98
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const lastretUrl = extractLastUrl(ret);
|
|
99
|
+
async processVideoFromLink(session, ret, options = { video: true }) {
|
|
100
|
+
const lastretUrl = this.extractLastUrl(ret);
|
|
351
101
|
let waitTipMsgId = null;
|
|
352
|
-
if (config.waitTip_Switch) {
|
|
353
|
-
const result = await session.send(`${import_koishi.h.quote(session.messageId)}${config.waitTip_Switch}`);
|
|
102
|
+
if (this.config.waitTip_Switch) {
|
|
103
|
+
const result = await session.send(`${import_koishi.h.quote(session.messageId)}${this.config.waitTip_Switch}`);
|
|
354
104
|
waitTipMsgId = Array.isArray(result) ? result[0] : result;
|
|
355
105
|
}
|
|
356
106
|
let videoElements = [];
|
|
357
107
|
let textElements = [];
|
|
358
|
-
let shouldPerformTextParsing = config.
|
|
108
|
+
let shouldPerformTextParsing = this.config.videoParseComponents.includes("text");
|
|
359
109
|
if (shouldPerformTextParsing) {
|
|
360
110
|
let fullText;
|
|
361
|
-
if (config.bVideoShowLink) {
|
|
111
|
+
if (this.config.bVideoShowLink) {
|
|
362
112
|
fullText = ret;
|
|
363
113
|
} else {
|
|
364
114
|
fullText = ret.replace(lastretUrl, "");
|
|
@@ -376,68 +126,68 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
376
126
|
}
|
|
377
127
|
}
|
|
378
128
|
}
|
|
379
|
-
if (config.
|
|
129
|
+
if (this.config.videoParseComponents.length > 0) {
|
|
380
130
|
const fullAPIurl = `http://api.xingzhige.cn/API/b_parse/?url=${encodeURIComponent(lastretUrl)}`;
|
|
381
131
|
try {
|
|
382
|
-
const responseData = await ctx.http.get(fullAPIurl);
|
|
132
|
+
const responseData = await this.ctx.http.get(fullAPIurl);
|
|
383
133
|
if (responseData.code === 0 && responseData.msg === "video" && responseData.data) {
|
|
384
134
|
const { bvid, cid, video } = responseData.data;
|
|
385
135
|
const bilibiliUrl = `https://api.bilibili.com/x/player/playurl?fnval=80&cid=${cid}&bvid=${bvid}`;
|
|
386
|
-
const playData = await ctx.http.get(bilibiliUrl);
|
|
387
|
-
logInfo(bilibiliUrl);
|
|
136
|
+
const playData = await this.ctx.http.get(bilibiliUrl);
|
|
137
|
+
this.logInfo(bilibiliUrl);
|
|
388
138
|
if (playData.code === 0 && playData.data && playData.data.dash && playData.data.dash.duration) {
|
|
389
139
|
const videoDurationSeconds = playData.data.dash.duration;
|
|
390
140
|
const videoDurationMinutes = videoDurationSeconds / 60;
|
|
391
|
-
if (videoDurationMinutes < config.Minimumduration) {
|
|
392
|
-
if (config.Minimumduration_tip === "return") {
|
|
141
|
+
if (videoDurationMinutes < this.config.Minimumduration) {
|
|
142
|
+
if (this.config.Minimumduration_tip === "return") {
|
|
393
143
|
return;
|
|
394
|
-
} else if (typeof config.Minimumduration_tip === "object" && config.Minimumduration_tip !== null) {
|
|
395
|
-
if (config.Minimumduration_tip.tipcontent) {
|
|
396
|
-
if (config.Minimumduration_tip.tipanalysis) {
|
|
397
|
-
videoElements.push(import_koishi.h.text(config.Minimumduration_tip.tipcontent));
|
|
144
|
+
} else if (typeof this.config.Minimumduration_tip === "object" && this.config.Minimumduration_tip !== null) {
|
|
145
|
+
if (this.config.Minimumduration_tip.tipcontent) {
|
|
146
|
+
if (this.config.Minimumduration_tip.tipanalysis) {
|
|
147
|
+
videoElements.push(import_koishi.h.text(this.config.Minimumduration_tip.tipcontent));
|
|
398
148
|
} else {
|
|
399
|
-
await session.send(config.Minimumduration_tip.tipcontent);
|
|
149
|
+
await session.send(this.config.Minimumduration_tip.tipcontent);
|
|
400
150
|
}
|
|
401
151
|
}
|
|
402
|
-
shouldPerformTextParsing = config.Minimumduration_tip.tipanalysis === true;
|
|
152
|
+
shouldPerformTextParsing = this.config.Minimumduration_tip.tipanalysis === true;
|
|
403
153
|
if (!shouldPerformTextParsing) {
|
|
404
154
|
textElements = [];
|
|
405
155
|
}
|
|
406
156
|
}
|
|
407
|
-
} else if (videoDurationMinutes > config.Maximumduration) {
|
|
408
|
-
if (config.Maximumduration_tip === "return") {
|
|
157
|
+
} else if (videoDurationMinutes > this.config.Maximumduration) {
|
|
158
|
+
if (this.config.Maximumduration_tip === "return") {
|
|
409
159
|
return;
|
|
410
|
-
} else if (typeof config.Maximumduration_tip === "object" && config.Maximumduration_tip !== null) {
|
|
411
|
-
if (config.Maximumduration_tip.tipcontent) {
|
|
412
|
-
if (config.Maximumduration_tip.tipanalysis) {
|
|
413
|
-
videoElements.push(import_koishi.h.text(config.Maximumduration_tip.tipcontent));
|
|
160
|
+
} else if (typeof this.config.Maximumduration_tip === "object" && this.config.Maximumduration_tip !== null) {
|
|
161
|
+
if (this.config.Maximumduration_tip.tipcontent) {
|
|
162
|
+
if (this.config.Maximumduration_tip.tipanalysis) {
|
|
163
|
+
videoElements.push(import_koishi.h.text(this.config.Maximumduration_tip.tipcontent));
|
|
414
164
|
} else {
|
|
415
|
-
await session.send(config.Maximumduration_tip.tipcontent);
|
|
165
|
+
await session.send(this.config.Maximumduration_tip.tipcontent);
|
|
416
166
|
}
|
|
417
167
|
}
|
|
418
|
-
shouldPerformTextParsing = config.Maximumduration_tip.tipanalysis === true;
|
|
168
|
+
shouldPerformTextParsing = this.config.Maximumduration_tip.tipanalysis === true;
|
|
419
169
|
if (!shouldPerformTextParsing) {
|
|
420
170
|
textElements = [];
|
|
421
171
|
}
|
|
422
172
|
}
|
|
423
173
|
} else {
|
|
424
174
|
let videoData = video.url;
|
|
425
|
-
logInfo(videoData);
|
|
426
|
-
if (config.filebuffer) {
|
|
175
|
+
this.logInfo(videoData);
|
|
176
|
+
if (this.config.filebuffer) {
|
|
427
177
|
try {
|
|
428
|
-
const videoFileBuffer = await ctx.http.file(video.url);
|
|
429
|
-
logInfo(videoFileBuffer);
|
|
178
|
+
const videoFileBuffer = await this.ctx.http.file(video.url);
|
|
179
|
+
this.logInfo(videoFileBuffer);
|
|
430
180
|
if (videoFileBuffer && videoFileBuffer.data) {
|
|
431
181
|
const buffer = Buffer.from(videoFileBuffer.data);
|
|
432
182
|
const mimeType = videoFileBuffer.type || videoFileBuffer.mime || "video/mp4";
|
|
433
183
|
const base64Data = buffer.toString("base64");
|
|
434
184
|
videoData = `data:${mimeType};base64,${base64Data}`;
|
|
435
|
-
logInfo("成功使用 ctx.http.file 将视频URL 转换为data URI格式");
|
|
185
|
+
this.logInfo("成功使用 ctx.http.file 将视频URL 转换为data URI格式");
|
|
436
186
|
} else {
|
|
437
|
-
logInfo("文件数据无效,使用原始URL");
|
|
187
|
+
this.logInfo("文件数据无效,使用原始URL");
|
|
438
188
|
}
|
|
439
189
|
} catch (error) {
|
|
440
|
-
logger.error("获取视频文件失败:", error);
|
|
190
|
+
this.logger.error("获取视频文件失败:", error);
|
|
441
191
|
}
|
|
442
192
|
}
|
|
443
193
|
if (videoData) {
|
|
@@ -446,25 +196,14 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
446
196
|
} else if (options.audio) {
|
|
447
197
|
videoElements.push(import_koishi.h.audio(videoData));
|
|
448
198
|
} else {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
break;
|
|
458
|
-
case "4":
|
|
459
|
-
videoElements.push(import_koishi.h.text(video.url));
|
|
460
|
-
videoElements.push(import_koishi.h.video(videoData));
|
|
461
|
-
break;
|
|
462
|
-
case "5":
|
|
463
|
-
logger.info(video.url);
|
|
464
|
-
videoElements.push(import_koishi.h.video(videoData));
|
|
465
|
-
break;
|
|
466
|
-
default:
|
|
467
|
-
break;
|
|
199
|
+
if (this.config.videoParseComponents.includes("log")) {
|
|
200
|
+
this.logger.info(video.url);
|
|
201
|
+
}
|
|
202
|
+
if (this.config.videoParseComponents.includes("link")) {
|
|
203
|
+
videoElements.push(import_koishi.h.text(video.url));
|
|
204
|
+
}
|
|
205
|
+
if (this.config.videoParseComponents.includes("video")) {
|
|
206
|
+
videoElements.push(import_koishi.h.video(videoData));
|
|
468
207
|
}
|
|
469
208
|
}
|
|
470
209
|
} else {
|
|
@@ -478,20 +217,20 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
478
217
|
throw new Error("解析视频信息失败或非视频类型内容");
|
|
479
218
|
}
|
|
480
219
|
} catch (error) {
|
|
481
|
-
logger.error("请求解析 API 失败或处理出错:", error);
|
|
220
|
+
this.logger.error("请求解析 API 失败或处理出错:", error);
|
|
482
221
|
}
|
|
483
222
|
}
|
|
484
223
|
let allElements = [...textElements, ...videoElements];
|
|
485
224
|
if (allElements.length === 0) {
|
|
486
225
|
return;
|
|
487
226
|
}
|
|
488
|
-
if (config.isfigure && (session.platform === "onebot" || session.platform === "red")) {
|
|
489
|
-
logInfo(`使用合并转发,正在合并消息。`);
|
|
227
|
+
if (this.config.isfigure && (session.platform === "onebot" || session.platform === "red")) {
|
|
228
|
+
this.logInfo(`使用合并转发,正在合并消息。`);
|
|
490
229
|
const figureContent = (0, import_koishi.h)("figure", {
|
|
491
230
|
children: allElements
|
|
492
231
|
});
|
|
493
|
-
if (config.loggerinfofulljson) {
|
|
494
|
-
logInfo(JSON.stringify(figureContent, null, 2));
|
|
232
|
+
if (this.config.loggerinfofulljson) {
|
|
233
|
+
this.logInfo(JSON.stringify(figureContent, null, 2));
|
|
495
234
|
}
|
|
496
235
|
await session.send(figureContent);
|
|
497
236
|
} else {
|
|
@@ -499,20 +238,20 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
499
238
|
await session.send(element);
|
|
500
239
|
}
|
|
501
240
|
}
|
|
502
|
-
logInfo(`机器人已发送完整消息。`);
|
|
241
|
+
this.logInfo(`机器人已发送完整消息。`);
|
|
503
242
|
if (waitTipMsgId) {
|
|
504
243
|
await session.bot.deleteMessage(session.channelId, waitTipMsgId);
|
|
505
244
|
}
|
|
506
245
|
return;
|
|
507
246
|
}
|
|
508
|
-
|
|
509
|
-
|
|
247
|
+
// 提取最后一个URL
|
|
248
|
+
extractLastUrl(text) {
|
|
510
249
|
const urlPattern = /https?:\/\/[^\s]+/g;
|
|
511
250
|
const urls = text.match(urlPattern);
|
|
512
251
|
return urls ? urls.pop() : null;
|
|
513
252
|
}
|
|
514
|
-
|
|
515
|
-
|
|
253
|
+
// 检测BV / AV 号并转换为URL
|
|
254
|
+
convertBVToUrl(text) {
|
|
516
255
|
const bvPattern = /(?:^|\s)(BV\w{10})(?:\s|$)/g;
|
|
517
256
|
const avPattern = /(?:^|\s)(av\d+)(?:\s|$)/g;
|
|
518
257
|
const matches = [];
|
|
@@ -525,9 +264,8 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
525
264
|
}
|
|
526
265
|
return matches;
|
|
527
266
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (config.useNumeral) {
|
|
267
|
+
numeral(number) {
|
|
268
|
+
if (this.config.useNumeral) {
|
|
531
269
|
if (number >= 1e4 && number < 1e8) {
|
|
532
270
|
return (number / 1e4).toFixed(1) + "万";
|
|
533
271
|
} else if (number >= 1e8) {
|
|
@@ -539,8 +277,12 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
539
277
|
return number;
|
|
540
278
|
}
|
|
541
279
|
}
|
|
542
|
-
|
|
543
|
-
|
|
280
|
+
/**
|
|
281
|
+
* 解析 ID 类型
|
|
282
|
+
* @param id 视频 ID
|
|
283
|
+
* @returns type: ID 类型, id: 视频 ID
|
|
284
|
+
*/
|
|
285
|
+
vid_type_parse(id) {
|
|
544
286
|
var idRegex = [
|
|
545
287
|
{
|
|
546
288
|
pattern: /av([0-9]+)/i,
|
|
@@ -565,22 +307,26 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
565
307
|
id: null
|
|
566
308
|
};
|
|
567
309
|
}
|
|
568
|
-
|
|
569
|
-
|
|
310
|
+
/**
|
|
311
|
+
* 根据视频 ID 查找视频信息
|
|
312
|
+
* @param id 视频 ID
|
|
313
|
+
* @returns 视频信息 Json
|
|
314
|
+
*/
|
|
315
|
+
async fetch_video_info(id) {
|
|
570
316
|
var ret;
|
|
571
|
-
const vid = vid_type_parse(id);
|
|
317
|
+
const vid = this.vid_type_parse(id);
|
|
572
318
|
switch (vid["type"]) {
|
|
573
319
|
case "av":
|
|
574
|
-
ret = await ctx.http.get("https://api.bilibili.com/x/web-interface/view?aid=" + vid["id"], {
|
|
320
|
+
ret = await this.ctx.http.get("https://api.bilibili.com/x/web-interface/view?aid=" + vid["id"], {
|
|
575
321
|
headers: {
|
|
576
|
-
"User-Agent": config.userAgent
|
|
322
|
+
"User-Agent": this.config.userAgent
|
|
577
323
|
}
|
|
578
324
|
});
|
|
579
325
|
break;
|
|
580
326
|
case "bv":
|
|
581
|
-
ret = await ctx.http.get("https://api.bilibili.com/x/web-interface/view?bvid=" + vid["id"], {
|
|
327
|
+
ret = await this.ctx.http.get("https://api.bilibili.com/x/web-interface/view?bvid=" + vid["id"], {
|
|
582
328
|
headers: {
|
|
583
|
-
"User-Agent": config.userAgent
|
|
329
|
+
"User-Agent": this.config.userAgent
|
|
584
330
|
}
|
|
585
331
|
});
|
|
586
332
|
break;
|
|
@@ -590,13 +336,17 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
590
336
|
}
|
|
591
337
|
return ret;
|
|
592
338
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
339
|
+
/**
|
|
340
|
+
* 生成视频信息
|
|
341
|
+
* @param id 视频 ID
|
|
342
|
+
* @returns 文字视频信息
|
|
343
|
+
*/
|
|
344
|
+
async gen_context(id) {
|
|
345
|
+
const info = await this.fetch_video_info(id);
|
|
596
346
|
if (!info || !info["data"])
|
|
597
347
|
return null;
|
|
598
348
|
let description = info["data"]["desc"];
|
|
599
|
-
const maxLength = config.bVideoShowIntroductionTofixed;
|
|
349
|
+
const maxLength = this.config.bVideoShowIntroductionTofixed;
|
|
600
350
|
if (description.length > maxLength) {
|
|
601
351
|
description = description.substring(0, maxLength) + "...";
|
|
602
352
|
}
|
|
@@ -606,19 +356,19 @@ overlay.style.left = ${config.point[1]}%`);
|
|
|
606
356
|
"${封面}": `<img src="${info["data"]["pic"]}"/>`,
|
|
607
357
|
"${简介}": description,
|
|
608
358
|
// 使用处理后的简介
|
|
609
|
-
"${点赞}": `${numeral(info["data"]["stat"]["like"])}`,
|
|
610
|
-
"${投币}": `${numeral(info["data"]["stat"]["coin"])}`,
|
|
611
|
-
"${收藏}": `${numeral(info["data"]["stat"]["favorite"])}`,
|
|
612
|
-
"${转发}": `${numeral(info["data"]["stat"]["share"])}`,
|
|
613
|
-
"${观看}": `${numeral(info["data"]["stat"]["view"])}`,
|
|
614
|
-
"${弹幕}": `${numeral(info["data"]["stat"]["danmaku"])}`,
|
|
359
|
+
"${点赞}": `${this.numeral(info["data"]["stat"]["like"])}`,
|
|
360
|
+
"${投币}": `${this.numeral(info["data"]["stat"]["coin"])}`,
|
|
361
|
+
"${收藏}": `${this.numeral(info["data"]["stat"]["favorite"])}`,
|
|
362
|
+
"${转发}": `${this.numeral(info["data"]["stat"]["share"])}`,
|
|
363
|
+
"${观看}": `${this.numeral(info["data"]["stat"]["view"])}`,
|
|
364
|
+
"${弹幕}": `${this.numeral(info["data"]["stat"]["danmaku"])}`,
|
|
615
365
|
"${tab}": `<pre> </pre>`
|
|
616
366
|
};
|
|
617
|
-
let ret = config.bVideo_area;
|
|
367
|
+
let ret = this.config.bVideo_area;
|
|
618
368
|
for (const [placeholder, value] of Object.entries(placeholders)) {
|
|
619
369
|
ret = ret.replace(new RegExp(placeholder.replace(/\$/g, "\\$"), "g"), value);
|
|
620
370
|
}
|
|
621
|
-
switch (config.bVideoIDPreference) {
|
|
371
|
+
switch (this.config.bVideoIDPreference) {
|
|
622
372
|
case "bv":
|
|
623
373
|
ret += `
|
|
624
374
|
https://www.bilibili.com/video/${info["data"]["bvid"]}`;
|
|
@@ -632,8 +382,12 @@ https://www.bilibili.com/video/av${info["data"]["aid"]}`;
|
|
|
632
382
|
}
|
|
633
383
|
return ret;
|
|
634
384
|
}
|
|
635
|
-
|
|
636
|
-
|
|
385
|
+
/**
|
|
386
|
+
* 链接类型解析
|
|
387
|
+
* @param content 传入消息
|
|
388
|
+
* @returns type: "链接类型", id :"内容ID"
|
|
389
|
+
*/
|
|
390
|
+
link_type_parser(content) {
|
|
637
391
|
content = content.replace(/\\\//g, "/");
|
|
638
392
|
var linkRegex = [
|
|
639
393
|
{
|
|
@@ -669,19 +423,23 @@ https://www.bilibili.com/video/av${info["data"]["aid"]}`;
|
|
|
669
423
|
}
|
|
670
424
|
return ret;
|
|
671
425
|
}
|
|
672
|
-
|
|
673
|
-
|
|
426
|
+
/**
|
|
427
|
+
* 类型执行器
|
|
428
|
+
* @param element 链接列表
|
|
429
|
+
* @returns 解析来的文本
|
|
430
|
+
*/
|
|
431
|
+
async type_processer(element) {
|
|
674
432
|
var ret = "";
|
|
675
433
|
switch (element["type"]) {
|
|
676
434
|
case "Video":
|
|
677
|
-
const video_info = await gen_context(element["id"]);
|
|
435
|
+
const video_info = await this.gen_context(element["id"]);
|
|
678
436
|
if (video_info != null)
|
|
679
437
|
ret += video_info;
|
|
680
438
|
break;
|
|
681
439
|
case "Short":
|
|
682
|
-
const typed_link = link_type_parser(await get_redir_url(element["id"]));
|
|
440
|
+
const typed_link = this.link_type_parser(await this.get_redir_url(element["id"]));
|
|
683
441
|
for (const element2 of typed_link) {
|
|
684
|
-
const final_info = await type_processer(element2);
|
|
442
|
+
const final_info = await this.type_processer(element2);
|
|
685
443
|
if (final_info != null)
|
|
686
444
|
ret += final_info;
|
|
687
445
|
break;
|
|
@@ -690,12 +448,16 @@ https://www.bilibili.com/video/av${info["data"]["aid"]}`;
|
|
|
690
448
|
}
|
|
691
449
|
return ret;
|
|
692
450
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
451
|
+
/**
|
|
452
|
+
* 根据短链接重定向获取正常链接
|
|
453
|
+
* @param id 短链接 ID
|
|
454
|
+
* @returns 正常链接
|
|
455
|
+
*/
|
|
456
|
+
async get_redir_url(id) {
|
|
457
|
+
var data = await this.ctx.http.get("https://b23.tv/" + id, {
|
|
696
458
|
redirect: "manual",
|
|
697
459
|
headers: {
|
|
698
|
-
"User-Agent": config.userAgent
|
|
460
|
+
"User-Agent": this.config.userAgent
|
|
699
461
|
}
|
|
700
462
|
});
|
|
701
463
|
const match = data.match(/<a\s+(?:[^>]*?\s+)?href="([^"]*)"/i);
|
|
@@ -704,7 +466,279 @@ https://www.bilibili.com/video/av${info["data"]["aid"]}`;
|
|
|
704
466
|
else
|
|
705
467
|
return null;
|
|
706
468
|
}
|
|
707
|
-
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// src/index.ts
|
|
472
|
+
var logger = new import_koishi2.Logger("bilibili-videolink-analysis");
|
|
473
|
+
var name = "bilibili-videolink-analysis";
|
|
474
|
+
var inject = {
|
|
475
|
+
optional: ["puppeteer"]
|
|
476
|
+
// required: ['BiliBiliVideo']
|
|
477
|
+
};
|
|
478
|
+
var usage = `
|
|
479
|
+
|
|
480
|
+
<h2>→ <a href="https://www.npmjs.com/package/koishi-plugin-bilibili-videolink-analysis" target="_blank">可以点击这里查看详细的文档说明✨</a></h2>
|
|
481
|
+
|
|
482
|
+
✨ 只需开启插件,就可以解析B站视频的链接啦~ ✨
|
|
483
|
+
|
|
484
|
+
向bot发送B站视频链接吧~
|
|
485
|
+
|
|
486
|
+
会返回视频信息与视频哦
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
#### ⚠️ **如果你使用不了本项目,请优先检查:** ⚠️
|
|
491
|
+
#### 若无注册的指令,请关开一下[command插件](/market?keyword=commands+email:shigma10826@gmail.com)(没有指令也不影响解析别人的链接)
|
|
492
|
+
#### 视频内容是否为B站的大会员专属视频/付费视频/充电专属视频
|
|
493
|
+
#### 接入方法是否支持获取网址链接/小程序卡片消息
|
|
494
|
+
#### 接入方法是否支持视频元素的发送
|
|
495
|
+
#### 发送视频超时/其他网络问题
|
|
496
|
+
#### 视频内容被平台屏蔽/其他平台因素
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
### 注意,点播功能需要使用 puppeteer 服务
|
|
501
|
+
|
|
502
|
+
点播功能是为了方便群友一起刷B站哦~
|
|
503
|
+
|
|
504
|
+
比如:搜索 “遠い空へ” 的第二页,并且结果以语音格式返回
|
|
505
|
+
|
|
506
|
+
示例:\`点播 遠い空へ -a -p 2\`
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
### 特别鸣谢 💖
|
|
512
|
+
|
|
513
|
+
特别鸣谢以下项目的支持:
|
|
514
|
+
|
|
515
|
+
- [@summonhim/koishi-plugin-bili-parser](/market?keyword=bili-parser)
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
`;
|
|
520
|
+
var Config = import_koishi2.Schema.intersect([
|
|
521
|
+
import_koishi2.Schema.object({
|
|
522
|
+
demand: import_koishi2.Schema.boolean().default(true).description("开启点播指令功能<br>`其实点播登录不登录 都搜不准,登录只是写着玩的`")
|
|
523
|
+
}).description("点播设置(需要puppeteer服务)"),
|
|
524
|
+
import_koishi2.Schema.union([
|
|
525
|
+
import_koishi2.Schema.object({
|
|
526
|
+
demand: import_koishi2.Schema.const(false).required()
|
|
527
|
+
}),
|
|
528
|
+
import_koishi2.Schema.object({
|
|
529
|
+
demand: import_koishi2.Schema.const(true),
|
|
530
|
+
timeout: import_koishi2.Schema.number().role("slider").min(1).max(300).step(1).default(60).description("指定播放视频的输入时限。`单位 秒`"),
|
|
531
|
+
point: import_koishi2.Schema.tuple([Number, Number]).description("序号标注位置。分别表示`距离顶部 距离左侧`的百分比").default([50, 50]),
|
|
532
|
+
enable: import_koishi2.Schema.boolean().description("是否开启自动解析`选择对应视频 会自动解析视频内容`").default(true)
|
|
533
|
+
})
|
|
534
|
+
]),
|
|
535
|
+
import_koishi2.Schema.object({
|
|
536
|
+
enablebilianalysis: import_koishi2.Schema.boolean().default(true).description("开启解析功能<br>`关闭后,解析功能将关闭`")
|
|
537
|
+
}).description("视频解析 - 功能开关"),
|
|
538
|
+
import_koishi2.Schema.union([
|
|
539
|
+
import_koishi2.Schema.object({
|
|
540
|
+
enablebilianalysis: import_koishi2.Schema.const(true),
|
|
541
|
+
waitTip_Switch: import_koishi2.Schema.union([
|
|
542
|
+
import_koishi2.Schema.const(null).description("不返回文字提示"),
|
|
543
|
+
import_koishi2.Schema.string().description("返回文字提示(请在右侧填写文字内容)").default("正在解析B站链接...")
|
|
544
|
+
]).description("是否返回等待提示。开启后,会发送`等待提示语`"),
|
|
545
|
+
videoParseMode: import_koishi2.Schema.array(import_koishi2.Schema.union([
|
|
546
|
+
import_koishi2.Schema.const("link").description("解析链接"),
|
|
547
|
+
import_koishi2.Schema.const("card").description("解析哔哩哔哩分享卡片")
|
|
548
|
+
])).default(["link", "card"]).role("checkbox").description("选择解析来源"),
|
|
549
|
+
videoParseComponents: import_koishi2.Schema.array(import_koishi2.Schema.union([
|
|
550
|
+
import_koishi2.Schema.const("log").description("记录日志"),
|
|
551
|
+
import_koishi2.Schema.const("text").description("返回图文"),
|
|
552
|
+
import_koishi2.Schema.const("link").description("返回视频直链"),
|
|
553
|
+
import_koishi2.Schema.const("video").description("返回视频")
|
|
554
|
+
])).default(["text", "video"]).role("checkbox").description("选择要返回的内容组件"),
|
|
555
|
+
BVnumberParsing: import_koishi2.Schema.boolean().default(true).description("是否允许根据`独立的BV、AV号`解析视频 `开启后,可以通过视频的BV、AV号解析视频。` <br> [触发说明见README](https://www.npmjs.com/package/koishi-plugin-bilibili-videolink-analysis)"),
|
|
556
|
+
MinimumTimeInterval: import_koishi2.Schema.number().default(180).description("若干`秒`内 不再处理相同链接 `防止多bot互相触发 导致的刷屏/性能浪费`").min(1),
|
|
557
|
+
Minimumduration: import_koishi2.Schema.number().default(0).description("允许解析的视频最小时长(分钟)`低于这个时长 就不会发视频内容`").min(0),
|
|
558
|
+
Minimumduration_tip: import_koishi2.Schema.union([
|
|
559
|
+
import_koishi2.Schema.const("return").description("不返回文字提示"),
|
|
560
|
+
import_koishi2.Schema.object({
|
|
561
|
+
tipcontent: import_koishi2.Schema.string().default("视频太短啦!不看不看~").description("文字提示内容"),
|
|
562
|
+
tipanalysis: import_koishi2.Schema.boolean().default(true).description("是否进行图文解析(不会返回视频链接)")
|
|
563
|
+
}).description("返回文字提示"),
|
|
564
|
+
import_koishi2.Schema.const(null)
|
|
565
|
+
]).description("对`过短视频`的文字提示内容").default(null),
|
|
566
|
+
Maximumduration: import_koishi2.Schema.number().default(25).description("允许解析的视频最大时长(分钟)`超过这个时长 就不会发视频内容`").min(1),
|
|
567
|
+
Maximumduration_tip: import_koishi2.Schema.union([
|
|
568
|
+
import_koishi2.Schema.const("return").description("不返回文字提示"),
|
|
569
|
+
import_koishi2.Schema.object({
|
|
570
|
+
tipcontent: import_koishi2.Schema.string().default("视频太长啦!内容还是去B站看吧~").description("文字提示内容"),
|
|
571
|
+
tipanalysis: import_koishi2.Schema.boolean().default(true).description("是否进行图文解析(不会返回视频链接)")
|
|
572
|
+
}).description("返回文字提示"),
|
|
573
|
+
import_koishi2.Schema.const(null)
|
|
574
|
+
]).description("对`过长视频`的文字提示内容").default(null),
|
|
575
|
+
parseLimit: import_koishi2.Schema.number().default(3).description("单对话多链接解析上限").hidden(),
|
|
576
|
+
useNumeral: import_koishi2.Schema.boolean().default(true).description("使用格式化数字").hidden(),
|
|
577
|
+
showError: import_koishi2.Schema.boolean().default(false).description("当链接不正确时提醒发送者").hidden(),
|
|
578
|
+
bVideoIDPreference: import_koishi2.Schema.union([
|
|
579
|
+
import_koishi2.Schema.const("bv").description("BV 号"),
|
|
580
|
+
import_koishi2.Schema.const("av").description("AV 号")
|
|
581
|
+
]).default("bv").description("ID 偏好").hidden(),
|
|
582
|
+
bVideo_area: import_koishi2.Schema.string().role("textarea", { rows: [8, 16] }).default("${标题} ${tab} ${UP主}\n${简介}\n点赞:${点赞} ${tab} 投币:${投币}\n收藏:${收藏} ${tab} 转发:${转发}\n观看:${观看} ${tab} 弹幕:${弹幕}\n${~~~}\n${封面}").description(`图文解析的返回格式<br>
|
|
583
|
+
注意变量格式,以及变量名称。<br>比如 \`\${标题}\` 不可以变成\`\${标题123}\`,你可以直接删掉但是不能修改变量名称哦<br>
|
|
584
|
+
当然变量也不能无中生有,下面的默认值内容 就是所有变量了,你仅可以删去变量 或者修改变量之外的格式。<br>
|
|
585
|
+
· 特殊变量\`\${~~~}\`表示分割线,会把上下内容分为两个信息单独发送。\`\${tab}\`表示制表符。`),
|
|
586
|
+
bVideoShowLink: import_koishi2.Schema.boolean().default(false).description("在末尾显示视频的链接地址 `开启可能会导致其他bot循环解析`"),
|
|
587
|
+
bVideoShowIntroductionTofixed: import_koishi2.Schema.number().default(50).description("视频的`简介`最大的字符长度<br>超出部分会使用 `...` 代替"),
|
|
588
|
+
isfigure: import_koishi2.Schema.boolean().default(true).description("是否开启合并转发 `仅支持 onebot 适配器` 其他平台开启 无效").experimental(),
|
|
589
|
+
filebuffer: import_koishi2.Schema.boolean().default(true).description("是否将视频链接下载后再发送 (以解决部分onebot协议端的问题)<br>否则使用视频直链发送").experimental(),
|
|
590
|
+
middleware: import_koishi2.Schema.boolean().default(false).description("前置中间件模式"),
|
|
591
|
+
userAgent: import_koishi2.Schema.string().description("所有 API 请求所用的 User-Agent").default("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36")
|
|
592
|
+
}),
|
|
593
|
+
import_koishi2.Schema.object({
|
|
594
|
+
enablebilianalysis: import_koishi2.Schema.const(false).required()
|
|
595
|
+
})
|
|
596
|
+
]),
|
|
597
|
+
import_koishi2.Schema.object({
|
|
598
|
+
pageclose: import_koishi2.Schema.boolean().default(true).description("自动`page.close()`<br>非开发者请勿改动").experimental(),
|
|
599
|
+
loggerinfo: import_koishi2.Schema.boolean().default(false).description("日志调试输出 `日常使用无需开启`<br>非开发者请勿改动").experimental(),
|
|
600
|
+
loggerinfofulljson: import_koishi2.Schema.boolean().default(false).description("打印完整的机器人发送的json输出").experimental()
|
|
601
|
+
}).description("开发者选项")
|
|
602
|
+
]);
|
|
603
|
+
function apply(ctx, config) {
|
|
604
|
+
const bilibiliParser = new BilibiliParser(ctx, config, logger);
|
|
605
|
+
if (config.enablebilianalysis) {
|
|
606
|
+
ctx.middleware(async (session, next) => {
|
|
607
|
+
let isCard = false;
|
|
608
|
+
try {
|
|
609
|
+
if (session.stripped.content.startsWith("<json data=")) {
|
|
610
|
+
isCard = true;
|
|
611
|
+
}
|
|
612
|
+
} catch (e) {
|
|
613
|
+
}
|
|
614
|
+
if (isCard) {
|
|
615
|
+
if (!config.videoParseMode.includes("card")) {
|
|
616
|
+
return next();
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
if (!config.videoParseMode.includes("link")) {
|
|
620
|
+
return next();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
let sessioncontent = session.stripped.content;
|
|
624
|
+
if (config.BVnumberParsing) {
|
|
625
|
+
const bvUrls = bilibiliParser.convertBVToUrl(sessioncontent);
|
|
626
|
+
if (bvUrls.length > 0) {
|
|
627
|
+
sessioncontent += "\n" + bvUrls.join("\n");
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const links = await bilibiliParser.isProcessLinks(sessioncontent);
|
|
631
|
+
if (links) {
|
|
632
|
+
const ret = await bilibiliParser.extractLinks(session, links);
|
|
633
|
+
if (ret && !bilibiliParser.isLinkProcessedRecently(ret, session.channelId)) {
|
|
634
|
+
await bilibiliParser.processVideoFromLink(session, ret);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return next();
|
|
638
|
+
}, config.middleware);
|
|
639
|
+
}
|
|
640
|
+
if (config.demand) {
|
|
641
|
+
ctx.command("B站点播 [keyword]", "点播B站视频").option("video", "-v 解析返回视频").option("audio", "-a 解析返回语音").option("link", "-l 解析返回链接").option("page", "-p <page:number> 指定页数", { fallback: "1" }).example("B站点播 遠い空へ -v").action(async ({ options, session }, keyword) => {
|
|
642
|
+
if (!keyword) {
|
|
643
|
+
await session.send(import_koishi2.h.text("告诉我 你想要点播的关键词吧~"));
|
|
644
|
+
keyword = await session.prompt(30 * 1e3);
|
|
645
|
+
}
|
|
646
|
+
const url = `https://search.bilibili.com/video?keyword=${encodeURIComponent(keyword)}&page=${options.page}&o=30`;
|
|
647
|
+
const page = await ctx.puppeteer.page();
|
|
648
|
+
await page.goto(url, {
|
|
649
|
+
waitUntil: "networkidle2"
|
|
650
|
+
});
|
|
651
|
+
await page.addStyleTag({
|
|
652
|
+
content: `
|
|
653
|
+
div.bili-header,
|
|
654
|
+
div.login-tip,
|
|
655
|
+
div.v-popover,
|
|
656
|
+
div.right-entry__outside {
|
|
657
|
+
display: none !important;
|
|
658
|
+
}
|
|
659
|
+
`
|
|
660
|
+
});
|
|
661
|
+
const videos = await page.evaluate((point) => {
|
|
662
|
+
const items = Array.from(document.querySelectorAll('.video-list-item:not([style*="display: none"])'));
|
|
663
|
+
return items.map((item, index) => {
|
|
664
|
+
const link = item.querySelector("a");
|
|
665
|
+
const href = link?.getAttribute("href") || "";
|
|
666
|
+
const idMatch = href.match(/\/video\/(BV\w+)\//);
|
|
667
|
+
const id = idMatch ? idMatch[1] : "";
|
|
668
|
+
if (!id) {
|
|
669
|
+
const htmlElement = item;
|
|
670
|
+
htmlElement.style.display = "none";
|
|
671
|
+
} else {
|
|
672
|
+
const overlay = document.createElement("div");
|
|
673
|
+
overlay.style.position = "absolute";
|
|
674
|
+
overlay.style.top = `${point[0]}%`;
|
|
675
|
+
overlay.style.left = `${point[1]}%`;
|
|
676
|
+
overlay.style.transform = "translate(-50%, -50%)";
|
|
677
|
+
overlay.style.fontSize = "48px";
|
|
678
|
+
overlay.style.fontWeight = "bold";
|
|
679
|
+
overlay.style.color = "black";
|
|
680
|
+
overlay.style.zIndex = "10";
|
|
681
|
+
overlay.style.backgroundColor = "rgba(255, 255, 255, 0.7)";
|
|
682
|
+
overlay.style.padding = "10px";
|
|
683
|
+
overlay.style.borderRadius = "8px";
|
|
684
|
+
overlay.textContent = `${index + 1}`;
|
|
685
|
+
const videoElement = item;
|
|
686
|
+
videoElement.style.position = "relative";
|
|
687
|
+
videoElement.appendChild(overlay);
|
|
688
|
+
}
|
|
689
|
+
return { id };
|
|
690
|
+
}).filter((video) => video.id);
|
|
691
|
+
}, config.point);
|
|
692
|
+
bilibiliParser.logInfo(options);
|
|
693
|
+
bilibiliParser.logInfo(`共找到 ${videos.length} 个视频:`);
|
|
694
|
+
videos.forEach((video, index) => {
|
|
695
|
+
bilibiliParser.logInfo(`序号 ${index + 1}: ID - ${video.id}`);
|
|
696
|
+
});
|
|
697
|
+
if (videos.length === 0) {
|
|
698
|
+
await page.close();
|
|
699
|
+
await session.send(import_koishi2.h.text("未找到相关视频。"));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const viewportHeight = 200 + videos.length * 100;
|
|
703
|
+
await page.setViewport({
|
|
704
|
+
width: 1440,
|
|
705
|
+
height: viewportHeight
|
|
706
|
+
});
|
|
707
|
+
bilibiliParser.logInfo("窗口:宽度:");
|
|
708
|
+
bilibiliParser.logInfo(1440);
|
|
709
|
+
bilibiliParser.logInfo("窗口:高度:");
|
|
710
|
+
bilibiliParser.logInfo(viewportHeight);
|
|
711
|
+
let msg;
|
|
712
|
+
const videoListElement = await page.$(".video-list.row");
|
|
713
|
+
if (videoListElement) {
|
|
714
|
+
const imgBuf = await videoListElement.screenshot({
|
|
715
|
+
captureBeyondViewport: false
|
|
716
|
+
});
|
|
717
|
+
msg = import_koishi2.h.image(imgBuf, "image/png");
|
|
718
|
+
}
|
|
719
|
+
if (page && config.pageclose) {
|
|
720
|
+
await page.close();
|
|
721
|
+
}
|
|
722
|
+
await session.send(msg + import_koishi2.h.text(`请选择视频的序号:`));
|
|
723
|
+
const userChoice = await session.prompt(config.timeout * 1e3);
|
|
724
|
+
const choiceIndex = parseInt(userChoice) - 1;
|
|
725
|
+
if (isNaN(choiceIndex) || choiceIndex < 0 || choiceIndex >= videos.length) {
|
|
726
|
+
await session.send(import_koishi2.h.text("输入无效,请输入正确的序号。"));
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const chosenVideo = videos[choiceIndex];
|
|
730
|
+
bilibiliParser.logInfo(`渲染序号设置
|
|
731
|
+
overlay.style.top = ${config.point[0]}%
|
|
732
|
+
overlay.style.left = ${config.point[1]}%`);
|
|
733
|
+
bilibiliParser.logInfo(`用户选择了序号 ${choiceIndex + 1}: ID - ${chosenVideo.id}`);
|
|
734
|
+
if (config.enable) {
|
|
735
|
+
const ret = await bilibiliParser.extractLinks(session, [{ type: "Video", id: chosenVideo.id }]);
|
|
736
|
+
if (ret && !bilibiliParser.isLinkProcessedRecently(ret, session.channelId)) {
|
|
737
|
+
await bilibiliParser.processVideoFromLink(session, ret, options);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
}
|
|
708
742
|
}
|
|
709
743
|
__name(apply, "apply");
|
|
710
744
|
// Annotate the CommonJS export names for ESM import in node:
|