koishi-plugin-video-parser-all 0.1.3 → 0.1.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.d.ts +22 -6
- package/lib/index.js +199 -301
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -3,21 +3,37 @@ export declare const name = "video-parser-all";
|
|
|
3
3
|
export interface Config {
|
|
4
4
|
enable: boolean;
|
|
5
5
|
showWaitingTip: boolean;
|
|
6
|
+
revokeWaitingTip: boolean;
|
|
6
7
|
waitingTipText: string;
|
|
7
8
|
sameLinkInterval: number;
|
|
8
9
|
imageParseFormat: string;
|
|
9
|
-
|
|
10
|
+
returnContent: {
|
|
11
|
+
showImageText: boolean;
|
|
12
|
+
showVideoUrl: boolean;
|
|
13
|
+
showVideoFile: boolean;
|
|
14
|
+
};
|
|
10
15
|
maxDescLength: number;
|
|
11
|
-
bugpkUniversalApi: string;
|
|
12
|
-
bugpkDouyinMainApi: string;
|
|
13
|
-
bugpkDouyinBackupApi: string;
|
|
14
|
-
bugpkKuaishouApi: string;
|
|
15
|
-
bugpkBilibiliApi: string;
|
|
16
16
|
timeout: number;
|
|
17
17
|
ignoreSendError: boolean;
|
|
18
18
|
enableForward: boolean;
|
|
19
19
|
downloadVideoBeforeSend: boolean;
|
|
20
20
|
messageBufferDelay: number;
|
|
21
|
+
commonApi: string;
|
|
22
|
+
douyin: {
|
|
23
|
+
mode: 'common' | 'own' | 'custom';
|
|
24
|
+
ownApi: string;
|
|
25
|
+
customApi: string;
|
|
26
|
+
};
|
|
27
|
+
kuaishou: {
|
|
28
|
+
mode: 'common' | 'own' | 'custom';
|
|
29
|
+
ownApi: string;
|
|
30
|
+
customApi: string;
|
|
31
|
+
};
|
|
32
|
+
bilibili: {
|
|
33
|
+
mode: 'common' | 'own' | 'custom';
|
|
34
|
+
ownApi: string;
|
|
35
|
+
customApi: string;
|
|
36
|
+
};
|
|
21
37
|
}
|
|
22
38
|
export declare const Config: Schema<Config>;
|
|
23
39
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -15,375 +15,273 @@ exports.name = 'video-parser-all';
|
|
|
15
15
|
exports.Config = koishi_1.Schema.object({
|
|
16
16
|
enable: koishi_1.Schema.boolean().default(true).description('是否启用插件'),
|
|
17
17
|
showWaitingTip: koishi_1.Schema.boolean().default(true).description('是否显示解析等待提示'),
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
revokeWaitingTip: koishi_1.Schema.boolean().default(true).description('是否撤回等待提示文本'),
|
|
19
|
+
waitingTipText: koishi_1.Schema.string().default('正在解析视频…').description('等待提示文本'),
|
|
20
|
+
sameLinkInterval: koishi_1.Schema.number().default(180).description('相同链接重复间隔秒'),
|
|
20
21
|
imageParseFormat: koishi_1.Schema.string()
|
|
21
22
|
.role('textarea')
|
|
22
|
-
.default(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.description('
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
.default('${标题} ${tab} ${UP主}\n${简介}\n${~~~}\n${封面}')
|
|
24
|
+
.description('图文格式:${标题} ${UP主} ${简介} ${点赞} ${投币} ${收藏} ${转发} ${观看} ${弹幕} ${tab} ${~~~} ${封面}'),
|
|
25
|
+
returnContent: koishi_1.Schema.object({
|
|
26
|
+
showImageText: koishi_1.Schema.boolean().default(true).description('返回图文'),
|
|
27
|
+
showVideoUrl: koishi_1.Schema.boolean().default(false).description('返回视频直链'),
|
|
28
|
+
showVideoFile: koishi_1.Schema.boolean().default(true).description('返回视频'),
|
|
29
|
+
}).description('内容组件'),
|
|
30
|
+
maxDescLength: koishi_1.Schema.number().default(200).description('简介最大长度'),
|
|
31
|
+
timeout: koishi_1.Schema.number().default(15000).description('请求超时毫秒'),
|
|
32
|
+
ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略发送错误'),
|
|
33
|
+
enableForward: koishi_1.Schema.boolean().default(false).description('合并转发(仅onebot)'),
|
|
34
|
+
downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('先下载视频再发送(解决onebot问题)'),
|
|
35
|
+
messageBufferDelay: koishi_1.Schema.number().default(1).description('消息缓冲延迟秒'),
|
|
36
|
+
commonApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('通用解析API'),
|
|
37
|
+
douyin: koishi_1.Schema.object({
|
|
38
|
+
mode: koishi_1.Schema.union([
|
|
39
|
+
koishi_1.Schema.const('common').description('使用通用API'),
|
|
40
|
+
koishi_1.Schema.const('own').description('使用平台专属API'),
|
|
41
|
+
koishi_1.Schema.const('custom').description('使用自定义API'),
|
|
42
|
+
]).default('common').description('解析模式'),
|
|
43
|
+
ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/douyin').description('抖音专属API'),
|
|
44
|
+
customApi: koishi_1.Schema.string().description('抖音自定义API'),
|
|
45
|
+
}).description('抖音配置'),
|
|
46
|
+
kuaishou: koishi_1.Schema.object({
|
|
47
|
+
mode: koishi_1.Schema.union([
|
|
48
|
+
koishi_1.Schema.const('common').description('使用通用API'),
|
|
49
|
+
koishi_1.Schema.const('own').description('使用平台专属API'),
|
|
50
|
+
koishi_1.Schema.const('custom').description('使用自定义API'),
|
|
51
|
+
]).default('common').description('解析模式'),
|
|
52
|
+
ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/ksjx').description('快手专属API'),
|
|
53
|
+
customApi: koishi_1.Schema.string().description('快手自定义API'),
|
|
54
|
+
}).description('快手配置'),
|
|
55
|
+
bilibili: koishi_1.Schema.object({
|
|
56
|
+
mode: koishi_1.Schema.union([
|
|
57
|
+
koishi_1.Schema.const('common').description('使用通用API'),
|
|
58
|
+
koishi_1.Schema.const('own').description('使用平台专属API'),
|
|
59
|
+
koishi_1.Schema.const('custom').description('使用自定义API'),
|
|
60
|
+
]).default('common').description('解析模式'),
|
|
61
|
+
ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/bilibili').description('B站专属API'),
|
|
62
|
+
customApi: koishi_1.Schema.string().description('B站自定义API'),
|
|
63
|
+
}).description('B站配置'),
|
|
39
64
|
});
|
|
40
65
|
const processed = new Map();
|
|
41
66
|
const linkBuffer = new Map();
|
|
42
67
|
const PLATFORM_KEYWORDS = {
|
|
43
68
|
bilibili: ['bilibili', 'b23', 'B站'],
|
|
44
69
|
kuaishou: ['kuaishou', '快手'],
|
|
45
|
-
douyin: ['douyin', '抖音']
|
|
70
|
+
douyin: ['douyin', '抖音'],
|
|
46
71
|
};
|
|
47
72
|
function extractUrl(content) {
|
|
48
73
|
const urlMatches = content.match(/https?:\/\/[^\s]+/gi) || [];
|
|
49
74
|
return urlMatches.filter(url => {
|
|
50
|
-
const
|
|
51
|
-
return Object.values(PLATFORM_KEYWORDS).some(
|
|
75
|
+
const lower = url.toLowerCase();
|
|
76
|
+
return Object.values(PLATFORM_KEYWORDS).some(g => g.some(k => lower.includes(k.toLowerCase())));
|
|
52
77
|
});
|
|
53
78
|
}
|
|
54
79
|
function hasPlatformKeyword(content) {
|
|
55
|
-
const
|
|
56
|
-
return Object.values(PLATFORM_KEYWORDS).some(
|
|
57
|
-
const target = /[\u4e00-\u9fa5]/.test(keyword) ? keyword : keyword.toLowerCase();
|
|
58
|
-
return lowerContent.includes(target);
|
|
59
|
-
}));
|
|
80
|
+
const lower = content.toLowerCase();
|
|
81
|
+
return Object.values(PLATFORM_KEYWORDS).some(g => g.some(k => lower.includes(k.toLowerCase())));
|
|
60
82
|
}
|
|
61
83
|
function getPlatformType(url) {
|
|
62
|
-
const
|
|
63
|
-
if (PLATFORM_KEYWORDS.douyin.some(k =>
|
|
84
|
+
const lower = url.toLowerCase();
|
|
85
|
+
if (PLATFORM_KEYWORDS.douyin.some(k => lower.includes(k.toLowerCase())))
|
|
64
86
|
return 'douyin';
|
|
65
|
-
if (PLATFORM_KEYWORDS.kuaishou.some(k =>
|
|
87
|
+
if (PLATFORM_KEYWORDS.kuaishou.some(k => lower.includes(k.toLowerCase())))
|
|
66
88
|
return 'kuaishou';
|
|
67
|
-
if (PLATFORM_KEYWORDS.bilibili.some(k =>
|
|
89
|
+
if (PLATFORM_KEYWORDS.bilibili.some(k => lower.includes(k.toLowerCase())))
|
|
68
90
|
return 'bilibili';
|
|
69
91
|
return null;
|
|
70
92
|
}
|
|
71
93
|
async function downloadVideo(url, filename) {
|
|
72
|
-
const
|
|
73
|
-
if (!fs_1.default.existsSync(
|
|
74
|
-
fs_1.default.mkdirSync(
|
|
75
|
-
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
method: 'GET',
|
|
80
|
-
responseType: 'stream',
|
|
81
|
-
timeout: 30000
|
|
82
|
-
});
|
|
83
|
-
await (0, promises_1.pipeline)(response.data, fs_1.default.createWriteStream(filePath));
|
|
84
|
-
return filePath;
|
|
85
|
-
}
|
|
86
|
-
function parseUniversalApiData(data) {
|
|
87
|
-
return {
|
|
88
|
-
title: data.title || '无标题',
|
|
89
|
-
author: data.author || '未知作者',
|
|
90
|
-
desc: data.title || '无简介',
|
|
91
|
-
digg: data.like || 0,
|
|
92
|
-
coin: 0,
|
|
93
|
-
collect: 0,
|
|
94
|
-
share: 0,
|
|
95
|
-
play: 0,
|
|
96
|
-
danmaku: 0,
|
|
97
|
-
cover: data.cover || '',
|
|
98
|
-
video: data.url || ''
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
function parseDouyin(data) {
|
|
102
|
-
const videoUrl = data.url || (data.live_photo?.[0]?.video || '');
|
|
103
|
-
return {
|
|
104
|
-
title: data.title || '无标题',
|
|
105
|
-
author: data.author?.name || '未知作者',
|
|
106
|
-
desc: data.desc || data.title || '无简介',
|
|
107
|
-
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
108
|
-
cover: data.cover || '',
|
|
109
|
-
video: videoUrl
|
|
110
|
-
};
|
|
94
|
+
const dir = path_1.default.join(process.cwd(), 'temp_videos');
|
|
95
|
+
if (!fs_1.default.existsSync(dir))
|
|
96
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
97
|
+
const file = path_1.default.join(dir, `${filename}.mp4`);
|
|
98
|
+
const res = await (0, axios_1.default)({ url, method: 'GET', responseType: 'stream', timeout: 30000 });
|
|
99
|
+
await (0, promises_1.pipeline)(res.data, fs_1.default.createWriteStream(file));
|
|
100
|
+
return file;
|
|
111
101
|
}
|
|
112
|
-
function
|
|
102
|
+
function parseData(data) {
|
|
113
103
|
return {
|
|
114
104
|
title: data.title || '无标题',
|
|
115
|
-
author: '未知作者',
|
|
116
|
-
desc: data.title || '无简介',
|
|
117
|
-
digg:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const author = data.user?.name || data.author || '未知UP主';
|
|
126
|
-
const desc = data.description || data.desc || title || '无简介';
|
|
127
|
-
let videoUrl = '';
|
|
128
|
-
if (data.url)
|
|
129
|
-
videoUrl = data.url;
|
|
130
|
-
else if (data.video_url)
|
|
131
|
-
videoUrl = data.video_url;
|
|
132
|
-
else if (data.videos && Array.isArray(data.videos) && data.videos.length > 0) {
|
|
133
|
-
videoUrl = data.videos.reduce((prev, curr) => {
|
|
134
|
-
return (prev.size || 0) > (curr.size || 0) ? prev : curr;
|
|
135
|
-
}).url || '';
|
|
136
|
-
}
|
|
137
|
-
else if (data.play)
|
|
138
|
-
videoUrl = data.play;
|
|
139
|
-
else if (data.durl && Array.isArray(data.durl))
|
|
140
|
-
videoUrl = data.durl[0]?.url || '';
|
|
141
|
-
else if (data.hd_url)
|
|
142
|
-
videoUrl = data.hd_url;
|
|
143
|
-
else if (data.sd_url)
|
|
144
|
-
videoUrl = data.sd_url;
|
|
145
|
-
const digg = data.like || data.digg || 0;
|
|
146
|
-
const coin = data.coin || 0;
|
|
147
|
-
const collect = data.favorite || data.collect || 0;
|
|
148
|
-
const share = data.share || 0;
|
|
149
|
-
const play = data.play || data.view || 0;
|
|
150
|
-
const danmaku = data.danmaku || data.comment || 0;
|
|
151
|
-
return {
|
|
152
|
-
title,
|
|
153
|
-
author,
|
|
154
|
-
desc,
|
|
155
|
-
digg, coin, collect, share, play, danmaku,
|
|
156
|
-
cover,
|
|
157
|
-
video: videoUrl
|
|
105
|
+
author: data.author || data.user?.name || '未知作者',
|
|
106
|
+
desc: (data.desc || data.title || '无简介').slice(0, 200),
|
|
107
|
+
digg: data.like || data.digg || 0,
|
|
108
|
+
coin: data.coin || 0,
|
|
109
|
+
collect: data.collect || data.favorite || 0,
|
|
110
|
+
share: data.share || 0,
|
|
111
|
+
play: data.play || data.view || 0,
|
|
112
|
+
danmaku: data.danmaku || data.comment || 0,
|
|
113
|
+
cover: data.cover || data.imgurl || data.pic || '',
|
|
114
|
+
video: data.url || data.video_url || data.play || '',
|
|
158
115
|
};
|
|
159
116
|
}
|
|
160
117
|
function apply(ctx, config) {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
118
|
+
const http = axios_1.default.create({ timeout: config.timeout });
|
|
119
|
+
function getApi(platform) {
|
|
120
|
+
const conf = config[platform];
|
|
121
|
+
if (conf.mode === 'custom' && conf.customApi)
|
|
122
|
+
return conf.customApi;
|
|
123
|
+
if (conf.mode === 'own')
|
|
124
|
+
return conf.ownApi;
|
|
125
|
+
return config.commonApi;
|
|
126
|
+
}
|
|
127
|
+
async function parse(url) {
|
|
128
|
+
const pt = getPlatformType(url);
|
|
129
|
+
if (!pt)
|
|
130
|
+
return null;
|
|
131
|
+
const api = getApi(pt);
|
|
132
|
+
if (!api)
|
|
133
|
+
return null;
|
|
170
134
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
ctx.logger.debug('通用API解析成功');
|
|
175
|
-
return parseUniversalApiData(res.data.data);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
catch (e) {
|
|
179
|
-
ctx.logger.debug(`通用API调用失败: ${e.message},尝试平台专属API`);
|
|
135
|
+
const { data } = await http.get(api, { params: { url } });
|
|
136
|
+
if (data.code === 200 && data.data)
|
|
137
|
+
return parseData(data.data);
|
|
180
138
|
}
|
|
139
|
+
catch { }
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
async function revoke(session, key) {
|
|
143
|
+
if (!config.revokeWaitingTip)
|
|
144
|
+
return;
|
|
145
|
+
const buf = linkBuffer.get(key);
|
|
146
|
+
if (!buf?.tipMsgId)
|
|
147
|
+
return;
|
|
181
148
|
try {
|
|
182
|
-
|
|
183
|
-
if (platform === 'douyin') {
|
|
184
|
-
try {
|
|
185
|
-
const res = await request.get(config.bugpkDouyinMainApi, { params: { url } });
|
|
186
|
-
if (res.data.code === 200 && res.data.data)
|
|
187
|
-
return parseDouyin(res.data.data);
|
|
188
|
-
}
|
|
189
|
-
catch (e) {
|
|
190
|
-
ctx.logger.debug(`抖音主接口失败: ${e.message}`);
|
|
191
|
-
}
|
|
192
|
-
try {
|
|
193
|
-
const res = await request.get(config.bugpkDouyinBackupApi, { params: { url } });
|
|
194
|
-
if (res.data.code === 200 && res.data.data)
|
|
195
|
-
return parseDouyin(res.data.data);
|
|
196
|
-
}
|
|
197
|
-
catch (e) {
|
|
198
|
-
ctx.logger.debug(`抖音备用接口失败: ${e.message}`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (platform === 'kuaishou') {
|
|
202
|
-
try {
|
|
203
|
-
const res = await request.get(config.bugpkKuaishouApi, { params: { url } });
|
|
204
|
-
if (res.data.code === 200 && res.data.data)
|
|
205
|
-
return parseKuaishou(res.data.data);
|
|
206
|
-
}
|
|
207
|
-
catch (e) {
|
|
208
|
-
ctx.logger.debug(`快手接口失败: ${e.message}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (platform === 'bilibili') {
|
|
212
|
-
try {
|
|
213
|
-
const res = await request.get(config.bugpkBilibiliApi, { params: { url } });
|
|
214
|
-
if (res.data.code === 200 && res.data.data)
|
|
215
|
-
return parseBilibili(res.data.data);
|
|
216
|
-
}
|
|
217
|
-
catch (e) {
|
|
218
|
-
ctx.logger.debug(`B站接口失败: ${e.message}`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
catch (e) {
|
|
223
|
-
ctx.logger.error(`平台专属API解析异常: ${e.message}`);
|
|
149
|
+
await session.bot.deleteMessage(session.channelId, buf.tipMsgId);
|
|
224
150
|
}
|
|
225
|
-
|
|
226
|
-
title: '解析失败',
|
|
227
|
-
author: '',
|
|
228
|
-
desc: '通用接口和平台专属接口均解析失败,请稍后重试',
|
|
229
|
-
digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
|
|
230
|
-
cover: '', video: ''
|
|
231
|
-
};
|
|
151
|
+
catch { }
|
|
232
152
|
}
|
|
233
|
-
async function
|
|
153
|
+
async function processOne(session, url) {
|
|
234
154
|
const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
|
|
235
155
|
const now = Date.now();
|
|
236
156
|
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
|
|
237
|
-
return;
|
|
157
|
+
return null;
|
|
238
158
|
processed.set(hash, now);
|
|
239
|
-
const data = await
|
|
159
|
+
const data = await parse(url);
|
|
160
|
+
if (!data)
|
|
161
|
+
return null;
|
|
240
162
|
let text = config.imageParseFormat
|
|
241
163
|
.replace(/\${标题}/g, data.title)
|
|
242
164
|
.replace(/\${UP主}/g, data.author)
|
|
243
|
-
.replace(/\${简介}/g, data.desc
|
|
244
|
-
.replace(/\${点赞}/g, data.digg
|
|
245
|
-
.replace(/\${投币}/g, data.coin
|
|
246
|
-
.replace(/\${收藏}/g, data.collect
|
|
247
|
-
.replace(/\${转发}/g, data.share
|
|
248
|
-
.replace(/\${观看}/g, data.play
|
|
249
|
-
.replace(/\${弹幕}/g, data.danmaku
|
|
165
|
+
.replace(/\${简介}/g, data.desc)
|
|
166
|
+
.replace(/\${点赞}/g, String(data.digg))
|
|
167
|
+
.replace(/\${投币}/g, String(data.coin))
|
|
168
|
+
.replace(/\${收藏}/g, String(data.collect))
|
|
169
|
+
.replace(/\${转发}/g, String(data.share))
|
|
170
|
+
.replace(/\${观看}/g, String(data.play))
|
|
171
|
+
.replace(/\${弹幕}/g, String(data.danmaku))
|
|
250
172
|
.replace(/\${tab}/g, '\t')
|
|
251
|
-
.replace(/\${~~~}/g, '
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
let
|
|
263
|
-
if (data.video && !config.showVideoUrl) {
|
|
264
|
-
if (config.downloadVideoBeforeSend
|
|
173
|
+
.replace(/\${~~~}/g, '\n');
|
|
174
|
+
const parts = [];
|
|
175
|
+
if (config.returnContent.showImageText) {
|
|
176
|
+
const [a, b] = text.split('${封面}');
|
|
177
|
+
if (a?.trim())
|
|
178
|
+
parts.push(a.trim());
|
|
179
|
+
if (data.cover)
|
|
180
|
+
parts.push(koishi_1.h.image(data.cover));
|
|
181
|
+
if (b?.trim())
|
|
182
|
+
parts.push(b.trim());
|
|
183
|
+
}
|
|
184
|
+
let videoPart = '';
|
|
185
|
+
if (config.returnContent.showVideoFile && data.video && !config.returnContent.showVideoUrl) {
|
|
186
|
+
if (config.downloadVideoBeforeSend) {
|
|
265
187
|
try {
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
188
|
+
const fn = crypto_1.default.createHash('md5').update(data.video).digest('hex');
|
|
189
|
+
const fp = await downloadVideo(data.video, fn);
|
|
190
|
+
videoPart = koishi_1.h.video(`file://${fp}`);
|
|
269
191
|
}
|
|
270
|
-
catch
|
|
271
|
-
|
|
272
|
-
videoMsg = `📥 无水印视频:${data.video}`;
|
|
192
|
+
catch {
|
|
193
|
+
videoPart = `视频:${data.video}`;
|
|
273
194
|
}
|
|
274
195
|
}
|
|
275
196
|
else {
|
|
276
|
-
|
|
197
|
+
videoPart = koishi_1.h.video(data.video);
|
|
277
198
|
}
|
|
278
199
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
};
|
|
200
|
+
if (config.returnContent.showVideoUrl && data.video) {
|
|
201
|
+
parts.push(`直链:${data.video}`);
|
|
202
|
+
}
|
|
203
|
+
return { content: parts.join('\n'), video: videoPart };
|
|
284
204
|
}
|
|
285
|
-
async function
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
if (!
|
|
205
|
+
async function flush(session) {
|
|
206
|
+
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
207
|
+
const buf = linkBuffer.get(key);
|
|
208
|
+
if (!buf)
|
|
289
209
|
return;
|
|
290
|
-
clearTimeout(
|
|
291
|
-
linkBuffer.delete(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
210
|
+
clearTimeout(buf.timer);
|
|
211
|
+
linkBuffer.delete(key);
|
|
212
|
+
await revoke(session, key);
|
|
213
|
+
const items = [];
|
|
214
|
+
for (const u of buf.urls) {
|
|
215
|
+
const it = await processOne(session, u);
|
|
216
|
+
if (it)
|
|
217
|
+
items.push(it);
|
|
297
218
|
}
|
|
298
|
-
if (
|
|
219
|
+
if (!items.length)
|
|
299
220
|
return;
|
|
300
|
-
// 修复:通过平台判断是否为onebot,而非直接访问session.adapter
|
|
301
221
|
if (config.enableForward && session.platform === 'onebot') {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (result.video)
|
|
307
|
-
messages.push((0, koishi_1.h)('message', { user_id: session.selfId }, [result.video]));
|
|
308
|
-
return messages;
|
|
309
|
-
}).flat();
|
|
310
|
-
await session.send((0, koishi_1.h)('forward', {
|
|
311
|
-
messages: forwardMessages,
|
|
312
|
-
title: '视频解析结果',
|
|
313
|
-
brief: `共解析${results.length}个视频链接`,
|
|
314
|
-
source: '视频解析插件',
|
|
315
|
-
preview: [results[0].content.substring(0, 10) + '...'],
|
|
316
|
-
summary: `查看${results.length}条解析结果`
|
|
317
|
-
})).catch(e => {
|
|
318
|
-
if (!config.ignoreSendError)
|
|
319
|
-
ctx.logger.warn(`合并转发发送失败: ${e.message}`);
|
|
320
|
-
sendIndividualMessages(session, results);
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
sendIndividualMessages(session, results);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
async function sendIndividualMessages(session, results) {
|
|
328
|
-
for (const result of results) {
|
|
222
|
+
const msgs = items.map(it => [
|
|
223
|
+
it.content ? (0, koishi_1.h)('message', { user_id: session.selfId }, [it.content]) : null,
|
|
224
|
+
it.video ? (0, koishi_1.h)('message', { user_id: session.selfId }, [it.video]) : null,
|
|
225
|
+
].filter(Boolean)).flat();
|
|
329
226
|
try {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (result.video)
|
|
333
|
-
await session.send(result.video);
|
|
334
|
-
}
|
|
335
|
-
catch (e) {
|
|
336
|
-
if (!config.ignoreSendError)
|
|
337
|
-
ctx.logger.warn(`单条消息发送失败: ${e.message}`);
|
|
227
|
+
await session.send((0, koishi_1.h)('forward', { messages: msgs }));
|
|
228
|
+
return;
|
|
338
229
|
}
|
|
230
|
+
catch { }
|
|
231
|
+
}
|
|
232
|
+
for (const it of items) {
|
|
233
|
+
if (it.content)
|
|
234
|
+
await session.send(it.content).catch(() => { });
|
|
235
|
+
if (it.video)
|
|
236
|
+
await session.send(it.video).catch(() => { });
|
|
339
237
|
}
|
|
340
238
|
}
|
|
341
239
|
ctx.on('message', async (session) => {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
240
|
+
if (!config.enable)
|
|
241
|
+
return;
|
|
242
|
+
const content = session.content.trim();
|
|
243
|
+
if (!hasPlatformKeyword(content))
|
|
244
|
+
return;
|
|
245
|
+
const urls = extractUrl(content);
|
|
246
|
+
if (!urls.length)
|
|
247
|
+
return;
|
|
248
|
+
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
249
|
+
if (config.showWaitingTip && !linkBuffer.has(key)) {
|
|
250
|
+
const tip = await session.send(config.waitingTipText).catch(() => null);
|
|
251
|
+
const mid = tip?.messageId || tip?.id;
|
|
252
|
+
linkBuffer.set(key, {
|
|
253
|
+
urls,
|
|
254
|
+
timer: setTimeout(() => flush(session), config.messageBufferDelay * 1000),
|
|
255
|
+
tipMsgId: mid,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
const ex = linkBuffer.get(key);
|
|
260
|
+
if (ex) {
|
|
261
|
+
clearTimeout(ex.timer);
|
|
262
|
+
ex.urls.push(...urls);
|
|
263
|
+
ex.timer = setTimeout(() => flush(session), config.messageBufferDelay * 1000);
|
|
264
|
+
linkBuffer.set(key, ex);
|
|
364
265
|
}
|
|
365
266
|
else {
|
|
366
|
-
|
|
367
|
-
|
|
267
|
+
linkBuffer.set(key, {
|
|
268
|
+
urls,
|
|
269
|
+
timer: setTimeout(() => flush(session), config.messageBufferDelay * 1000),
|
|
270
|
+
});
|
|
368
271
|
}
|
|
369
272
|
}
|
|
370
|
-
catch (e) {
|
|
371
|
-
ctx.logger.error(`消息处理异常: ${e.message}`);
|
|
372
|
-
}
|
|
373
273
|
});
|
|
374
274
|
setInterval(() => {
|
|
375
275
|
const now = Date.now();
|
|
376
276
|
processed.forEach((t, k) => now - t > 86400000 && processed.delete(k));
|
|
377
|
-
const
|
|
378
|
-
if (fs_1.default.existsSync(
|
|
379
|
-
fs_1.default.readdirSync(
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
fs_1.default.unlinkSync(filePath);
|
|
384
|
-
}
|
|
277
|
+
const dir = path_1.default.join(process.cwd(), 'temp_videos');
|
|
278
|
+
if (fs_1.default.existsSync(dir)) {
|
|
279
|
+
fs_1.default.readdirSync(dir).forEach(f => {
|
|
280
|
+
const p = path_1.default.join(dir, f);
|
|
281
|
+
if (now - fs_1.default.statSync(p).ctimeMs > 3600000)
|
|
282
|
+
fs_1.default.unlinkSync(p);
|
|
385
283
|
});
|
|
386
284
|
}
|
|
387
285
|
}, 3600000);
|
|
388
|
-
ctx.logger.info('
|
|
286
|
+
ctx.logger.info('video-parser-all 加载完成');
|
|
389
287
|
}
|