koishi-plugin-video-parser-all 0.0.6 → 0.0.7
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 +4 -2
- package/lib/index.js +147 -65
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ export interface Config {
|
|
|
8
8
|
imageParseFormat: string;
|
|
9
9
|
showVideoUrl: boolean;
|
|
10
10
|
maxDescLength: number;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
douyinApi: string;
|
|
12
|
+
bilibiliApi: string;
|
|
13
|
+
kuaishouApi: string;
|
|
14
|
+
backupApi: string;
|
|
13
15
|
timeout: number;
|
|
14
16
|
}
|
|
15
17
|
export declare const Config: Schema<Config>;
|
package/lib/index.js
CHANGED
|
@@ -10,10 +10,10 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
10
10
|
const crypto_1 = __importDefault(require("crypto"));
|
|
11
11
|
exports.name = 'video-parser-all';
|
|
12
12
|
exports.Config = koishi_1.Schema.object({
|
|
13
|
-
enable: koishi_1.Schema.boolean().default(true),
|
|
14
|
-
showWaitingTip: koishi_1.Schema.boolean().default(true),
|
|
15
|
-
waitingTipText: koishi_1.Schema.string().default('正在解析视频…'),
|
|
16
|
-
sameLinkInterval: koishi_1.Schema.number().default(180),
|
|
13
|
+
enable: koishi_1.Schema.boolean().default(true).description('开启解析功能'),
|
|
14
|
+
showWaitingTip: koishi_1.Schema.boolean().default(true).description('显示等待提示'),
|
|
15
|
+
waitingTipText: koishi_1.Schema.string().default('正在解析视频…').description('等待提示文字'),
|
|
16
|
+
sameLinkInterval: koishi_1.Schema.number().default(180).description('相同链接去重间隔(秒)'),
|
|
17
17
|
imageParseFormat: koishi_1.Schema.string()
|
|
18
18
|
.role('textarea')
|
|
19
19
|
.default(`\${标题} \${tab} \${UP主}
|
|
@@ -22,96 +22,178 @@ exports.Config = koishi_1.Schema.object({
|
|
|
22
22
|
收藏:\${收藏} \${tab} 转发:\${转发}
|
|
23
23
|
观看:\${观看} \${tab} 弹幕:\${弹幕}
|
|
24
24
|
\${~~~}
|
|
25
|
-
\${封面}`)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
\${封面}`)
|
|
26
|
+
.description('解析输出格式(请勿修改占位符)'),
|
|
27
|
+
showVideoUrl: koishi_1.Schema.boolean().default(false).description('额外显示无水印视频链接'),
|
|
28
|
+
maxDescLength: koishi_1.Schema.number().default(200).description('简介最大长度'),
|
|
29
|
+
douyinApi: koishi_1.Schema.string().default('https://api.douyin.wtf/api/hybrid/video_data').description('抖音主API'),
|
|
30
|
+
bilibiliApi: koishi_1.Schema.string().default('https://api.douyin.wtf/api/bilibili/web/fetch_one_video').description('B站主API'),
|
|
31
|
+
kuaishouApi: koishi_1.Schema.string().default('https://api.douyin.wtf/api/hybrid/video_data').description('快手主API'),
|
|
32
|
+
backupApi: koishi_1.Schema.string().default('https://www.alapi.cn/api/video/jh').description('聚合备用API'),
|
|
33
|
+
timeout: koishi_1.Schema.number().default(15000).description('API请求超时(毫秒)'),
|
|
31
34
|
});
|
|
35
|
+
// 去重缓存
|
|
32
36
|
const processed = new Map();
|
|
33
37
|
function apply(ctx, config) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (d?.video?.play_addr?.url_list?.[0])
|
|
40
|
-
return pack(d);
|
|
38
|
+
// 创建请求实例
|
|
39
|
+
const request = axios_1.default.create({
|
|
40
|
+
timeout: config.timeout,
|
|
41
|
+
headers: {
|
|
42
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
});
|
|
45
|
+
// 核心解析函数(按平台自动匹配API)
|
|
46
|
+
async function parseVideo(url) {
|
|
47
|
+
// 1. B站解析(优先主API)
|
|
48
|
+
if (url.includes('bilibili.com') || url.includes('b23.tv')) {
|
|
49
|
+
try {
|
|
50
|
+
const res = await request.get(config.bilibiliApi, { params: { url } });
|
|
51
|
+
const d = res.data.data;
|
|
47
52
|
return {
|
|
48
|
-
title: d.title || '',
|
|
49
|
-
author: '
|
|
50
|
-
desc: d.
|
|
51
|
-
digg:
|
|
52
|
-
|
|
53
|
+
title: d.title || '无标题',
|
|
54
|
+
author: d.owner?.name || '未知UP主',
|
|
55
|
+
desc: d.desc || '无简介',
|
|
56
|
+
digg: d.stat?.like || 0,
|
|
57
|
+
coin: d.stat?.coin || 0,
|
|
58
|
+
collect: d.stat?.favorite || 0,
|
|
59
|
+
share: d.stat?.share || 0,
|
|
60
|
+
play: d.stat?.view || 0,
|
|
61
|
+
danmaku: d.stat?.danmaku || 0,
|
|
62
|
+
cover: d.pic || '',
|
|
53
63
|
video: d.video_url || ''
|
|
54
64
|
};
|
|
55
65
|
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
ctx.logger.warn(`B站主API解析失败,尝试备用API: ${e.message}`);
|
|
68
|
+
}
|
|
56
69
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
// 2. 快手解析(优先主API)
|
|
71
|
+
if (url.includes('kuaishou.com') || url.includes('ksweb')) {
|
|
72
|
+
try {
|
|
73
|
+
const res = await request.get(config.kuaishouApi, { params: { url } });
|
|
74
|
+
const d = res.data.data?.aweme_detail || res.data.data;
|
|
75
|
+
return {
|
|
76
|
+
title: d.desc || '无标题',
|
|
77
|
+
author: d.author?.nickname || '未知作者',
|
|
78
|
+
desc: d.desc || '无简介',
|
|
79
|
+
digg: d.statistics?.digg_count || 0,
|
|
80
|
+
coin: 0, // 快手无投币
|
|
81
|
+
collect: d.statistics?.collect_count || 0,
|
|
82
|
+
share: d.statistics?.share_count || 0,
|
|
83
|
+
play: d.statistics?.play_count || 0,
|
|
84
|
+
danmaku: d.statistics?.comment_count || 0, // 用评论数替代弹幕
|
|
85
|
+
cover: d.video?.cover || '',
|
|
86
|
+
video: d.video?.play_addr?.url_list?.[0] || ''
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
ctx.logger.warn(`快手主API解析失败,尝试备用API: ${e.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// 3. 抖音解析(优先主API)
|
|
94
|
+
if (url.includes('douyin.com') || url.includes('dy')) {
|
|
95
|
+
try {
|
|
96
|
+
const res = await request.get(config.douyinApi, { params: { url } });
|
|
97
|
+
const d = res.data.data?.aweme_detail || res.data.data;
|
|
98
|
+
return {
|
|
99
|
+
title: d.desc || '无标题',
|
|
100
|
+
author: d.author?.nickname || '未知作者',
|
|
101
|
+
desc: d.desc || '无简介',
|
|
102
|
+
digg: d.statistics?.digg_count || 0,
|
|
103
|
+
coin: 0, // 抖音无投币
|
|
104
|
+
collect: d.statistics?.collect_count || 0,
|
|
105
|
+
share: d.statistics?.share_count || 0,
|
|
106
|
+
play: d.statistics?.play_count || 0,
|
|
107
|
+
danmaku: d.statistics?.comment_count || 0, // 用评论数替代弹幕
|
|
108
|
+
cover: d.video?.cover || '',
|
|
109
|
+
video: d.video?.play_addr?.url_list?.[0] || ''
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
ctx.logger.warn(`抖音主API解析失败,尝试备用API: ${e.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// 4. 备用聚合API(所有主API失败时)
|
|
117
|
+
try {
|
|
118
|
+
const res = await request.get(config.backupApi, { params: { url } });
|
|
119
|
+
const d = res.data.data;
|
|
120
|
+
return {
|
|
121
|
+
title: d.title || '无标题',
|
|
122
|
+
author: '未知作者',
|
|
123
|
+
desc: d.title || '无简介',
|
|
124
|
+
digg: 0,
|
|
125
|
+
coin: 0,
|
|
126
|
+
collect: 0,
|
|
127
|
+
share: 0,
|
|
128
|
+
play: 0,
|
|
129
|
+
danmaku: 0,
|
|
130
|
+
cover: d.cover_url || '',
|
|
131
|
+
video: d.video_url || ''
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
ctx.logger.error(`备用API解析失败: ${e.message}`);
|
|
136
|
+
}
|
|
137
|
+
// 兜底返回
|
|
61
138
|
return {
|
|
62
|
-
title:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
digg: d.statistics?.digg_count || 0,
|
|
66
|
-
comment: d.statistics?.comment_count || 0,
|
|
67
|
-
collect: d.statistics?.collect_count || 0,
|
|
68
|
-
share: d.statistics?.share_count || 0,
|
|
69
|
-
play: d.statistics?.play_count || 0,
|
|
70
|
-
cover: d.video?.cover || d.video?.dynamic_cover || '',
|
|
71
|
-
video: d.video?.play_addr?.url_list?.[0] || ''
|
|
139
|
+
title: '解析失败', author: '', desc: '无法获取视频信息',
|
|
140
|
+
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
141
|
+
cover: '', video: ''
|
|
72
142
|
};
|
|
73
143
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
144
|
+
// 生成并发送解析结果(严格匹配你的格式)
|
|
145
|
+
async function sendResult(session, data) {
|
|
146
|
+
let text = config.imageParseFormat
|
|
147
|
+
.replace(/\${标题}/g, data.title)
|
|
148
|
+
.replace(/\${UP主}/g, data.author)
|
|
149
|
+
.replace(/\${简介}/g, data.desc.slice(0, config.maxDescLength))
|
|
150
|
+
.replace(/\${点赞}/g, data.digg.toString())
|
|
151
|
+
.replace(/\${投币}/g, data.coin.toString())
|
|
152
|
+
.replace(/\${收藏}/g, data.collect.toString())
|
|
153
|
+
.replace(/\${转发}/g, data.share.toString())
|
|
154
|
+
.replace(/\${观看}/g, data.play.toString())
|
|
155
|
+
.replace(/\${弹幕}/g, data.danmaku.toString())
|
|
156
|
+
.replace(/\${tab}/g, '\t')
|
|
157
|
+
.replace(/\${~~~}/g, '——————————————');
|
|
158
|
+
// 拆分封面占位符,避免类型错误
|
|
159
|
+
const [beforeCover, afterCover] = text.split('\${封面}');
|
|
160
|
+
if (beforeCover)
|
|
161
|
+
await session.send(beforeCover.trim());
|
|
90
162
|
if (data.cover)
|
|
91
163
|
await session.send(koishi_1.h.image(data.cover));
|
|
92
|
-
if (
|
|
93
|
-
await session.send(
|
|
164
|
+
if (afterCover)
|
|
165
|
+
await session.send(afterCover.trim());
|
|
166
|
+
// 发送无水印视频
|
|
94
167
|
if (data.video) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
168
|
+
try {
|
|
169
|
+
await session.send(koishi_1.h.video(data.video));
|
|
170
|
+
if (config.showVideoUrl)
|
|
171
|
+
await session.send(`🔗 无水印链接:${data.video}`);
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
await session.send(`📥 无水印视频:${data.video}`);
|
|
175
|
+
}
|
|
98
176
|
}
|
|
99
177
|
}
|
|
178
|
+
// 消息监听与处理
|
|
100
179
|
ctx.on('message', async (session) => {
|
|
101
180
|
if (!config.enable)
|
|
102
181
|
return;
|
|
103
182
|
const url = session.content.trim();
|
|
104
183
|
if (!url.startsWith('http'))
|
|
105
184
|
return;
|
|
185
|
+
// 去重逻辑
|
|
106
186
|
const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
|
|
107
187
|
const now = Date.now();
|
|
108
188
|
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
|
|
109
189
|
return;
|
|
110
190
|
processed.set(hash, now);
|
|
191
|
+
// 发送等待提示
|
|
111
192
|
if (config.showWaitingTip)
|
|
112
193
|
await session.send(config.waitingTipText);
|
|
113
|
-
|
|
114
|
-
await
|
|
194
|
+
// 解析并发送结果
|
|
195
|
+
const data = await parseVideo(url);
|
|
196
|
+
await sendResult(session, data);
|
|
115
197
|
});
|
|
116
|
-
ctx.logger.info('✅
|
|
198
|
+
ctx.logger.info('✅ 抖音+B站+快手 三平台解析插件加载完成');
|
|
117
199
|
}
|