koishi-plugin-video-parser-all 0.1.9 → 0.2.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 +4 -16
- package/lib/index.js +166 -223
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -18,22 +18,10 @@ export interface Config {
|
|
|
18
18
|
enableForward: boolean;
|
|
19
19
|
downloadVideoBeforeSend: boolean;
|
|
20
20
|
messageBufferDelay: number;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
+
retryTimes: number;
|
|
22
|
+
retryInterval: number;
|
|
23
|
+
apiUrl: string;
|
|
24
|
+
videoSendTimeout: number;
|
|
37
25
|
}
|
|
38
26
|
export declare const Config: Schema<Config>;
|
|
39
27
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -16,57 +16,46 @@ exports.name = 'video-parser-all';
|
|
|
16
16
|
exports.Config = koishi_1.Schema.object({
|
|
17
17
|
enable: koishi_1.Schema.boolean().default(true).description('是否启用插件'),
|
|
18
18
|
showWaitingTip: koishi_1.Schema.boolean().default(true).description('是否显示解析等待提示'),
|
|
19
|
-
revokeWaitingTip: koishi_1.Schema.boolean().default(true).description('
|
|
20
|
-
waitingTipText: koishi_1.Schema.string().default('正在解析视频…').description('
|
|
21
|
-
sameLinkInterval: koishi_1.Schema.number().default(180).description('
|
|
22
|
-
imageParseFormat: koishi_1.Schema.string()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
revokeWaitingTip: koishi_1.Schema.boolean().default(true).description('解析完成后是否撤回等待提示'),
|
|
20
|
+
waitingTipText: koishi_1.Schema.string().default('正在解析视频…').description('等待提示的文本内容'),
|
|
21
|
+
sameLinkInterval: koishi_1.Schema.number().default(180).description('相同链接解析间隔(秒),防止重复解析'),
|
|
22
|
+
imageParseFormat: koishi_1.Schema.string().role('textarea').default('${标题} ${tab} ${UP主}\n${简介}\n${~~~}\n${封面}').description(`解析结果文本格式,支持的变量:
|
|
23
|
+
- \${标题}:视频标题
|
|
24
|
+
- \${UP主}:作者名称
|
|
25
|
+
- \${简介}:视频简介
|
|
26
|
+
- \${点赞}:点赞数
|
|
27
|
+
- \${投币}:投币数
|
|
28
|
+
- \${收藏}:收藏数
|
|
29
|
+
- \${转发}:转发数
|
|
30
|
+
- \${观看}:播放量
|
|
31
|
+
- \${弹幕}:弹幕数
|
|
32
|
+
- \${tab}:制表符
|
|
33
|
+
- \${~~~}:换行符
|
|
34
|
+
- \${封面}:封面图片位置`),
|
|
26
35
|
returnContent: koishi_1.Schema.object({
|
|
27
|
-
showImageText: koishi_1.Schema.boolean().default(true).description('
|
|
28
|
-
showVideoUrl: koishi_1.Schema.boolean().default(false).description('
|
|
29
|
-
showVideoFile: koishi_1.Schema.boolean().default(true).description('
|
|
30
|
-
}).description('
|
|
31
|
-
maxDescLength: koishi_1.Schema.number().default(200).description('
|
|
32
|
-
timeout: koishi_1.Schema.number().default(15000).description('
|
|
33
|
-
ignoreSendError: koishi_1.Schema.boolean().default(true).description('
|
|
34
|
-
enableForward: koishi_1.Schema.boolean().default(false).description('
|
|
35
|
-
downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('
|
|
36
|
-
messageBufferDelay: koishi_1.Schema.number().default(1).min(0).description('
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
koishi_1.Schema.const('own').description('使用平台专属API'),
|
|
42
|
-
koishi_1.Schema.const('custom').description('使用自定义API'),
|
|
43
|
-
]).default('common').description('抖音解析模式'),
|
|
44
|
-
ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/douyin').description('抖音专属API'),
|
|
45
|
-
customApi: koishi_1.Schema.string().description('抖音自定义API'),
|
|
46
|
-
}).description('抖音配置'),
|
|
47
|
-
kuaishou: koishi_1.Schema.object({
|
|
48
|
-
mode: koishi_1.Schema.union([
|
|
49
|
-
koishi_1.Schema.const('common').description('使用通用API'),
|
|
50
|
-
koishi_1.Schema.const('own').description('使用平台专属API'),
|
|
51
|
-
koishi_1.Schema.const('custom').description('使用自定义API'),
|
|
52
|
-
]).default('common').description('快手解析模式'),
|
|
53
|
-
ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/ksjx').description('快手专属API'),
|
|
54
|
-
customApi: koishi_1.Schema.string().description('快手自定义API'),
|
|
55
|
-
}).description('快手配置'),
|
|
56
|
-
bilibili: koishi_1.Schema.object({
|
|
57
|
-
mode: koishi_1.Schema.union([
|
|
58
|
-
koishi_1.Schema.const('common').description('使用通用API'),
|
|
59
|
-
koishi_1.Schema.const('own').description('使用平台专属API'),
|
|
60
|
-
koishi_1.Schema.const('custom').description('使用自定义API'),
|
|
61
|
-
]).default('common').description('B站解析模式'),
|
|
62
|
-
ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/bilibili').description('B站专属API'),
|
|
63
|
-
customApi: koishi_1.Schema.string().description('B站自定义API'),
|
|
64
|
-
}).description('B站配置'),
|
|
36
|
+
showImageText: koishi_1.Schema.boolean().default(true).description('是否显示解析后的文本和封面'),
|
|
37
|
+
showVideoUrl: koishi_1.Schema.boolean().default(false).description('是否显示无水印视频链接'),
|
|
38
|
+
showVideoFile: koishi_1.Schema.boolean().default(true).description('是否发送视频文件/视频链接'),
|
|
39
|
+
}).description('返回内容配置'),
|
|
40
|
+
maxDescLength: koishi_1.Schema.number().default(200).description('简介最大长度限制'),
|
|
41
|
+
timeout: koishi_1.Schema.number().default(15000).description('API 请求超时时间(毫秒)'),
|
|
42
|
+
ignoreSendError: koishi_1.Schema.boolean().default(true).description('是否忽略发送消息时的错误'),
|
|
43
|
+
enableForward: koishi_1.Schema.boolean().default(false).description('是否启用合并转发模式(仅 OneBot 平台)'),
|
|
44
|
+
downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('发送前是否先下载视频(仅 OneBot 平台,解决部分视频无法播放问题)'),
|
|
45
|
+
messageBufferDelay: koishi_1.Schema.number().default(1).min(0).description('消息缓冲延迟(秒),用于合并短时间内的多个解析请求'),
|
|
46
|
+
retryTimes: koishi_1.Schema.number().default(3).min(0).description('API 请求失败重试次数'),
|
|
47
|
+
retryInterval: koishi_1.Schema.number().default(2000).min(500).description('重试间隔(毫秒)'),
|
|
48
|
+
apiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('视频解析接口地址'),
|
|
49
|
+
videoSendTimeout: koishi_1.Schema.number().default(30000).description('视频消息发送超时时间(毫秒),解决发送视频超时问题')
|
|
65
50
|
});
|
|
66
51
|
if (!worker_threads_1.isMainThread) {
|
|
67
52
|
const { url, filePath } = worker_threads_1.workerData;
|
|
68
53
|
(async () => {
|
|
69
54
|
try {
|
|
55
|
+
if (url.endsWith('.m4a') || url.endsWith('.mp3')) {
|
|
56
|
+
worker_threads_1.parentPort?.postMessage({ success: false, error: '不支持音频' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
70
59
|
const response = await (0, axios_1.default)({
|
|
71
60
|
url,
|
|
72
61
|
method: 'GET',
|
|
@@ -80,10 +69,7 @@ if (!worker_threads_1.isMainThread) {
|
|
|
80
69
|
worker_threads_1.parentPort?.postMessage({ success: true, filePath });
|
|
81
70
|
}
|
|
82
71
|
catch (error) {
|
|
83
|
-
worker_threads_1.parentPort?.postMessage({
|
|
84
|
-
success: false,
|
|
85
|
-
error: error.message
|
|
86
|
-
});
|
|
72
|
+
worker_threads_1.parentPort?.postMessage({ success: false, error: error.message });
|
|
87
73
|
}
|
|
88
74
|
})();
|
|
89
75
|
}
|
|
@@ -91,8 +77,8 @@ const processed = new Map();
|
|
|
91
77
|
const linkBuffer = new Map();
|
|
92
78
|
const PLATFORM_KEYWORDS = {
|
|
93
79
|
bilibili: ['bilibili', 'b23', 'B站'],
|
|
94
|
-
kuaishou: ['kuaishou', '快手'],
|
|
95
|
-
douyin: ['douyin', '抖音']
|
|
80
|
+
kuaishou: ['kuaishou', '快手', 'v.kuaishou.com'],
|
|
81
|
+
douyin: ['douyin', '抖音', 'v.douyin.com']
|
|
96
82
|
};
|
|
97
83
|
function extractUrl(content) {
|
|
98
84
|
const urlMatches = content.match(/https?:\/\/[^\s]+/gi) || [];
|
|
@@ -121,30 +107,23 @@ async function downloadVideoWithThreads(url, filename) {
|
|
|
121
107
|
if (!fs_1.default.existsSync(dir))
|
|
122
108
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
123
109
|
const filePath = path_1.default.join(dir, `${filename}.mp4`);
|
|
124
|
-
const worker = new worker_threads_1.Worker(__filename, {
|
|
125
|
-
workerData: { url, filePath }
|
|
126
|
-
});
|
|
110
|
+
const worker = new worker_threads_1.Worker(__filename, { workerData: { url, filePath } });
|
|
127
111
|
worker.on('message', (result) => {
|
|
128
|
-
if (result.success)
|
|
112
|
+
if (result.success)
|
|
129
113
|
resolve(result.filePath);
|
|
130
|
-
|
|
131
|
-
else {
|
|
114
|
+
else
|
|
132
115
|
reject(new Error(result.error));
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
worker.on('error', (error) => {
|
|
136
|
-
reject(error);
|
|
137
116
|
});
|
|
117
|
+
worker.on('error', reject);
|
|
138
118
|
worker.on('exit', (code) => {
|
|
139
|
-
if (code !== 0)
|
|
140
|
-
reject(new Error(
|
|
141
|
-
}
|
|
119
|
+
if (code !== 0)
|
|
120
|
+
reject(new Error('worker exit'));
|
|
142
121
|
});
|
|
143
122
|
});
|
|
144
123
|
}
|
|
145
|
-
function parseData(data,
|
|
146
|
-
let title = data.title || '无标题';
|
|
147
|
-
let author = data.
|
|
124
|
+
function parseData(data, maxDescLength) {
|
|
125
|
+
let title = data.title || data.desc || '无标题';
|
|
126
|
+
let author = data.author || data.auther || data.user?.name || '未知作者';
|
|
148
127
|
let desc = data.desc || data.description || title || '无简介';
|
|
149
128
|
desc = desc.slice(0, maxDescLength);
|
|
150
129
|
let digg = data.like || data.digg || 0;
|
|
@@ -155,34 +134,34 @@ function parseData(data, platform, maxDescLength) {
|
|
|
155
134
|
let danmaku = data.danmaku || data.comment || 0;
|
|
156
135
|
let cover = data.cover || data.imgurl || data.pic || '';
|
|
157
136
|
let video = '';
|
|
158
|
-
if (data.url)
|
|
137
|
+
if (data.videos && Array.isArray(data.videos) && data.videos.length > 0 && data.videos[0].url) {
|
|
138
|
+
video = data.videos[0].url;
|
|
139
|
+
}
|
|
140
|
+
else if (data.url) {
|
|
159
141
|
video = data.url;
|
|
160
|
-
else if (data.video_url)
|
|
161
|
-
video = data.video_url;
|
|
162
|
-
else if (data.videos && Array.isArray(data.videos) && data.videos.length > 0) {
|
|
163
|
-
video = data.videos[0].url || '';
|
|
164
142
|
}
|
|
165
|
-
else if (data.
|
|
166
|
-
video = data.
|
|
167
|
-
|
|
168
|
-
|
|
143
|
+
else if (data.video_backup && Array.isArray(data.video_backup) && data.video_backup[0]?.url) {
|
|
144
|
+
video = data.video_backup[0].url;
|
|
145
|
+
}
|
|
146
|
+
if (video.endsWith('.m4a') || video.endsWith('.mp3'))
|
|
147
|
+
video = '';
|
|
169
148
|
return { title, author, desc, digg, coin, collect, share, play, danmaku, cover, video };
|
|
170
149
|
}
|
|
171
150
|
function clearAllCache() {
|
|
172
151
|
processed.clear();
|
|
173
|
-
linkBuffer.forEach(
|
|
152
|
+
linkBuffer.forEach(b => clearTimeout(b.timer));
|
|
174
153
|
linkBuffer.clear();
|
|
175
154
|
const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
|
|
176
155
|
if (fs_1.default.existsSync(tempDir)) {
|
|
177
|
-
fs_1.default.readdirSync(tempDir).forEach(
|
|
156
|
+
fs_1.default.readdirSync(tempDir).forEach(f => {
|
|
178
157
|
try {
|
|
179
|
-
|
|
180
|
-
fs_1.default.unlinkSync(filePath);
|
|
158
|
+
fs_1.default.unlinkSync(path_1.default.join(tempDir, f));
|
|
181
159
|
}
|
|
182
|
-
catch
|
|
160
|
+
catch { }
|
|
183
161
|
});
|
|
184
162
|
}
|
|
185
163
|
}
|
|
164
|
+
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
186
165
|
function apply(ctx, config) {
|
|
187
166
|
if (!worker_threads_1.isMainThread)
|
|
188
167
|
return;
|
|
@@ -193,47 +172,32 @@ function apply(ctx, config) {
|
|
|
193
172
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
194
173
|
}
|
|
195
174
|
});
|
|
196
|
-
function getApi(platform) {
|
|
197
|
-
const conf = config[platform];
|
|
198
|
-
if (conf.mode === 'custom' && conf.customApi)
|
|
199
|
-
return conf.customApi;
|
|
200
|
-
if (conf.mode === 'own')
|
|
201
|
-
return conf.ownApi;
|
|
202
|
-
return config.commonApi;
|
|
203
|
-
}
|
|
204
175
|
async function parse(url) {
|
|
205
176
|
const platform = getPlatformType(url);
|
|
206
177
|
if (!platform)
|
|
207
|
-
return { data: null
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
data: parseData(res.data.data, platform, config.maxDescLength),
|
|
216
|
-
platform
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
ctx.logger.error(`API返回非成功状态: ${JSON.stringify(res.data)}`);
|
|
178
|
+
return { data: null };
|
|
179
|
+
for (let i = 0; i <= config.retryTimes; i++) {
|
|
180
|
+
try {
|
|
181
|
+
const res = await http.get(config.apiUrl, { params: { url } });
|
|
182
|
+
const isSuccess = (res.data.code === 200 || res.data.code === 0) && res.data.data;
|
|
183
|
+
if (isSuccess) {
|
|
184
|
+
return { data: parseData(res.data.data, config.maxDescLength) };
|
|
185
|
+
}
|
|
221
186
|
}
|
|
187
|
+
catch (e) { }
|
|
188
|
+
if (i < config.retryTimes)
|
|
189
|
+
await delay(config.retryInterval);
|
|
222
190
|
}
|
|
223
|
-
|
|
224
|
-
ctx.logger.error(`解析失败: ${e.message}`);
|
|
225
|
-
}
|
|
226
|
-
return { data: null, platform: null };
|
|
191
|
+
return { data: null };
|
|
227
192
|
}
|
|
228
193
|
async function processSingleUrl(session, url) {
|
|
229
194
|
const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
|
|
230
195
|
const now = Date.now();
|
|
231
|
-
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
|
|
196
|
+
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
|
|
232
197
|
return null;
|
|
233
|
-
}
|
|
234
198
|
processed.set(hash, now);
|
|
235
199
|
const parseResult = await parse(url);
|
|
236
|
-
if (!parseResult.data)
|
|
200
|
+
if (!parseResult.data || !parseResult.data.video)
|
|
237
201
|
return null;
|
|
238
202
|
let text = config.imageParseFormat
|
|
239
203
|
.replace(/\${标题}/g, parseResult.data.title)
|
|
@@ -245,28 +209,26 @@ function apply(ctx, config) {
|
|
|
245
209
|
.replace(/\${转发}/g, String(parseResult.data.share))
|
|
246
210
|
.replace(/\${观看}/g, String(parseResult.data.play))
|
|
247
211
|
.replace(/\${弹幕}/g, String(parseResult.data.danmaku))
|
|
248
|
-
.replace(/\${tab}/g, '\t')
|
|
249
|
-
.replace(/\${~~~}/g, '\n');
|
|
212
|
+
.replace(/\${tab}/g, '\t').replace(/\${~~~}/g, '\n');
|
|
250
213
|
const contentParts = [];
|
|
251
214
|
if (config.returnContent.showImageText) {
|
|
252
|
-
const [
|
|
253
|
-
if (
|
|
254
|
-
contentParts.push(
|
|
215
|
+
const [b, a] = text.split('${封面}');
|
|
216
|
+
if (b?.trim())
|
|
217
|
+
contentParts.push(b.trim());
|
|
255
218
|
if (parseResult.data.cover)
|
|
256
219
|
contentParts.push(koishi_1.h.image(parseResult.data.cover));
|
|
257
|
-
if (
|
|
258
|
-
contentParts.push(
|
|
220
|
+
if (a?.trim())
|
|
221
|
+
contentParts.push(a.trim());
|
|
259
222
|
}
|
|
260
223
|
let videoContent = '';
|
|
261
224
|
if (config.returnContent.showVideoFile && parseResult.data.video) {
|
|
262
225
|
if (config.downloadVideoBeforeSend && session.platform === 'onebot') {
|
|
263
226
|
try {
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
videoContent = koishi_1.h.file(
|
|
227
|
+
const name = crypto_1.default.createHash('md5').update(parseResult.data.video).digest('hex');
|
|
228
|
+
const fp = await downloadVideoWithThreads(parseResult.data.video, name);
|
|
229
|
+
videoContent = koishi_1.h.file(fp);
|
|
267
230
|
}
|
|
268
231
|
catch (e) {
|
|
269
|
-
ctx.logger.error(`下载视频失败: ${e.message}`);
|
|
270
232
|
videoContent = koishi_1.h.video(parseResult.data.video);
|
|
271
233
|
}
|
|
272
234
|
}
|
|
@@ -277,145 +239,126 @@ function apply(ctx, config) {
|
|
|
277
239
|
if (config.returnContent.showVideoUrl && parseResult.data.video) {
|
|
278
240
|
contentParts.push(`🔗 无水印链接:${parseResult.data.video}`);
|
|
279
241
|
}
|
|
280
|
-
return {
|
|
281
|
-
textContent: contentParts.join('\n'),
|
|
282
|
-
videoContent,
|
|
283
|
-
rawData: parseResult.data
|
|
284
|
-
};
|
|
242
|
+
return { textContent: contentParts.join('\n'), videoContent };
|
|
285
243
|
}
|
|
286
|
-
async function
|
|
244
|
+
async function revokeTip(session, key) {
|
|
287
245
|
if (!config.revokeWaitingTip || session.platform !== 'onebot')
|
|
288
246
|
return;
|
|
289
|
-
const
|
|
290
|
-
if (!
|
|
247
|
+
const buf = linkBuffer.get(key);
|
|
248
|
+
if (!buf?.tipMsgId)
|
|
291
249
|
return;
|
|
292
250
|
try {
|
|
293
|
-
await session.bot.deleteMessage(session.channelId,
|
|
251
|
+
await session.bot.deleteMessage(session.channelId, buf.tipMsgId.toString());
|
|
294
252
|
}
|
|
295
253
|
catch (e) {
|
|
296
|
-
ctx.logger.
|
|
254
|
+
ctx.logger.warn(`撤回提示消息失败: ${e.message}`);
|
|
297
255
|
}
|
|
298
256
|
}
|
|
299
|
-
async function
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
257
|
+
async function sendWithTimeout(session, content, timeout) {
|
|
258
|
+
return Promise.race([
|
|
259
|
+
session.send(content),
|
|
260
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('消息发送超时')), timeout))
|
|
261
|
+
]).catch((e) => {
|
|
262
|
+
ctx.logger.warn(`消息发送超时: ${e.message}`);
|
|
263
|
+
return null;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async function flush(session) {
|
|
267
|
+
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
268
|
+
const buf = linkBuffer.get(key);
|
|
269
|
+
if (!buf)
|
|
303
270
|
return;
|
|
304
|
-
clearTimeout(
|
|
305
|
-
linkBuffer.delete(
|
|
306
|
-
await
|
|
271
|
+
clearTimeout(buf.timer);
|
|
272
|
+
linkBuffer.delete(key);
|
|
273
|
+
await revokeTip(session, key);
|
|
307
274
|
const results = [];
|
|
308
|
-
for (const
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
311
|
-
results.push(
|
|
275
|
+
for (const u of buf.urls) {
|
|
276
|
+
const r = await processSingleUrl(session, u);
|
|
277
|
+
if (r)
|
|
278
|
+
results.push(r);
|
|
312
279
|
}
|
|
313
280
|
if (results.length === 0) {
|
|
314
|
-
await session
|
|
281
|
+
await sendWithTimeout(session, '视频解析失败', config.videoSendTimeout);
|
|
315
282
|
return;
|
|
316
283
|
}
|
|
317
284
|
if (config.enableForward && session.platform === 'onebot') {
|
|
318
285
|
try {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
type: 'node',
|
|
330
|
-
data: {
|
|
331
|
-
name: '视频解析机器人',
|
|
332
|
-
uin: session.selfId,
|
|
333
|
-
content: content
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
})
|
|
337
|
-
}
|
|
286
|
+
const forwardMessages = results.map(result => {
|
|
287
|
+
const content = [];
|
|
288
|
+
if (result.textContent)
|
|
289
|
+
content.push(result.textContent);
|
|
290
|
+
if (result.videoContent)
|
|
291
|
+
content.push(result.videoContent);
|
|
292
|
+
return (0, koishi_1.h)('message', [
|
|
293
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: '解析机器人' }),
|
|
294
|
+
...content
|
|
295
|
+
]);
|
|
338
296
|
});
|
|
339
|
-
await session.
|
|
297
|
+
await sendWithTimeout(session, (0, koishi_1.h)('message', { forward: true }, forwardMessages), config.videoSendTimeout);
|
|
340
298
|
return;
|
|
341
299
|
}
|
|
342
300
|
catch (e) {
|
|
343
|
-
|
|
344
|
-
await session.send('合并转发失败,将分开发送');
|
|
301
|
+
await sendWithTimeout(session, '合并转发失败,将分开发送', config.videoSendTimeout);
|
|
345
302
|
}
|
|
346
303
|
}
|
|
347
|
-
for (const
|
|
304
|
+
for (const r of results) {
|
|
348
305
|
try {
|
|
349
|
-
if (
|
|
350
|
-
await session.
|
|
351
|
-
if (
|
|
352
|
-
await session.
|
|
306
|
+
if (r.textContent)
|
|
307
|
+
await sendWithTimeout(session, r.textContent, config.videoSendTimeout);
|
|
308
|
+
if (r.videoContent)
|
|
309
|
+
await sendWithTimeout(session, r.videoContent, config.videoSendTimeout);
|
|
353
310
|
}
|
|
354
311
|
catch (e) {
|
|
355
|
-
if (!config.ignoreSendError)
|
|
356
|
-
ctx.logger.
|
|
357
|
-
}
|
|
312
|
+
if (!config.ignoreSendError)
|
|
313
|
+
ctx.logger.error(`发送消息失败: ${e.message}`);
|
|
358
314
|
}
|
|
359
315
|
}
|
|
360
316
|
}
|
|
361
317
|
ctx.on('message', async (session) => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
let tipMsgId;
|
|
381
|
-
if (config.showWaitingTip) {
|
|
382
|
-
const tipText = config.waitingTipText;
|
|
383
|
-
const tipMsg = await session.send(tipText).catch(e => {
|
|
384
|
-
if (!config.ignoreSendError)
|
|
385
|
-
ctx.logger.warn(`发送等待提示失败: ${e.message}`);
|
|
386
|
-
return null;
|
|
387
|
-
});
|
|
388
|
-
tipMsgId = tipMsg?.messageId || tipMsg?.id || tipMsg;
|
|
389
|
-
}
|
|
390
|
-
linkBuffer.set(sessionKey, {
|
|
391
|
-
urls,
|
|
392
|
-
timer: setTimeout(() => flushBuffer(session), Math.max(0, config.messageBufferDelay * 1000)),
|
|
393
|
-
tipMsgId
|
|
394
|
-
});
|
|
318
|
+
if (!config.enable)
|
|
319
|
+
return;
|
|
320
|
+
const content = session.content.trim();
|
|
321
|
+
if (!hasPlatformKeyword(content))
|
|
322
|
+
return;
|
|
323
|
+
const urls = extractUrl(content);
|
|
324
|
+
if (urls.length === 0)
|
|
325
|
+
return;
|
|
326
|
+
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
327
|
+
if (linkBuffer.has(key)) {
|
|
328
|
+
const b = linkBuffer.get(key);
|
|
329
|
+
b.urls.push(...urls);
|
|
330
|
+
clearTimeout(b.timer);
|
|
331
|
+
b.timer = setTimeout(() => flush(session), Math.max(0, config.messageBufferDelay * 1000));
|
|
332
|
+
linkBuffer.set(key, b);
|
|
333
|
+
return;
|
|
395
334
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
linkBuffer.delete(sessionKey);
|
|
335
|
+
let tipId;
|
|
336
|
+
if (config.showWaitingTip) {
|
|
337
|
+
const m = await sendWithTimeout(session, config.waitingTipText, config.videoSendTimeout);
|
|
338
|
+
if (m) {
|
|
339
|
+
tipId = m?.messageId || m?.id || m;
|
|
402
340
|
}
|
|
403
341
|
}
|
|
342
|
+
linkBuffer.set(key, {
|
|
343
|
+
urls,
|
|
344
|
+
timer: setTimeout(() => flush(session), Math.max(0, config.messageBufferDelay * 1000)),
|
|
345
|
+
tipMsgId: tipId
|
|
346
|
+
});
|
|
404
347
|
});
|
|
405
348
|
setInterval(() => {
|
|
406
349
|
const now = Date.now();
|
|
407
|
-
processed.forEach((
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
});
|
|
350
|
+
processed.forEach((t, h) => now - t > 86400000 && processed.delete(h));
|
|
351
|
+
}, 3600000);
|
|
352
|
+
setInterval(() => {
|
|
412
353
|
const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
|
|
413
354
|
if (fs_1.default.existsSync(tempDir)) {
|
|
414
|
-
fs_1.default.readdirSync(tempDir)
|
|
355
|
+
const files = fs_1.default.readdirSync(tempDir);
|
|
356
|
+
const now = Date.now();
|
|
357
|
+
files.forEach(file => {
|
|
358
|
+
const filePath = path_1.default.join(tempDir, file);
|
|
415
359
|
try {
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
if (now - stat.ctimeMs > 3600000) {
|
|
360
|
+
const stats = fs_1.default.statSync(filePath);
|
|
361
|
+
if (now - stats.mtimeMs > 3600000) {
|
|
419
362
|
fs_1.default.unlinkSync(filePath);
|
|
420
363
|
}
|
|
421
364
|
}
|
|
@@ -424,6 +367,6 @@ function apply(ctx, config) {
|
|
|
424
367
|
}
|
|
425
368
|
});
|
|
426
369
|
}
|
|
427
|
-
},
|
|
428
|
-
ctx.logger.info('
|
|
370
|
+
}, 1800000);
|
|
371
|
+
ctx.logger.info('视频解析插件已加载完成');
|
|
429
372
|
}
|