koishi-plugin-video-parser-all 0.1.9 → 0.2.0
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 +3 -16
- package/lib/index.js +135 -239
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -18,22 +18,9 @@ export interface Config {
|
|
|
18
18
|
enableForward: boolean;
|
|
19
19
|
downloadVideoBeforeSend: boolean;
|
|
20
20
|
messageBufferDelay: number;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
retryTimes: number;
|
|
22
|
+
retryInterval: number;
|
|
23
|
+
apiUrl: string;
|
|
37
24
|
}
|
|
38
25
|
export declare const Config: Schema<Config>;
|
|
39
26
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -14,59 +14,36 @@ const promises_1 = require("stream/promises");
|
|
|
14
14
|
const worker_threads_1 = require("worker_threads");
|
|
15
15
|
exports.name = 'video-parser-all';
|
|
16
16
|
exports.Config = koishi_1.Schema.object({
|
|
17
|
-
enable: koishi_1.Schema.boolean().default(true)
|
|
18
|
-
showWaitingTip: koishi_1.Schema.boolean().default(true)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.default('${标题} ${tab} ${UP主}\n${简介}\n${~~~}\n${封面}')
|
|
25
|
-
.description('图文格式:${标题} ${UP主} ${简介} ${点赞} ${投币} ${收藏} ${转发} ${观看} ${弹幕} ${tab} ${~~~} ${封面}'),
|
|
17
|
+
enable: koishi_1.Schema.boolean().default(true),
|
|
18
|
+
showWaitingTip: koishi_1.Schema.boolean().default(true),
|
|
19
|
+
// 修复:boolean.default → Schema.boolean().default
|
|
20
|
+
revokeWaitingTip: koishi_1.Schema.boolean().default(true),
|
|
21
|
+
waitingTipText: koishi_1.Schema.string().default('正在解析视频…'),
|
|
22
|
+
sameLinkInterval: koishi_1.Schema.number().default(180),
|
|
23
|
+
imageParseFormat: koishi_1.Schema.string().role('textarea').default('${标题} ${tab} ${UP主}\n${简介}\n${~~~}\n${封面}'),
|
|
26
24
|
returnContent: koishi_1.Schema.object({
|
|
27
|
-
showImageText: koishi_1.Schema.boolean().default(true)
|
|
28
|
-
showVideoUrl: koishi_1.Schema.boolean().default(false)
|
|
29
|
-
showVideoFile: koishi_1.Schema.boolean().default(true)
|
|
30
|
-
})
|
|
31
|
-
maxDescLength: koishi_1.Schema.number().default(200)
|
|
32
|
-
timeout: koishi_1.Schema.number().default(15000)
|
|
33
|
-
ignoreSendError: koishi_1.Schema.boolean().default(true)
|
|
34
|
-
enableForward: koishi_1.Schema.boolean().default(false)
|
|
35
|
-
downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false)
|
|
36
|
-
messageBufferDelay: koishi_1.Schema.number().default(1).min(0)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
koishi_1.Schema.const('common').description('使用通用API'),
|
|
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站配置'),
|
|
25
|
+
showImageText: koishi_1.Schema.boolean().default(true),
|
|
26
|
+
showVideoUrl: koishi_1.Schema.boolean().default(false),
|
|
27
|
+
showVideoFile: koishi_1.Schema.boolean().default(true),
|
|
28
|
+
}),
|
|
29
|
+
maxDescLength: koishi_1.Schema.number().default(200),
|
|
30
|
+
timeout: koishi_1.Schema.number().default(15000),
|
|
31
|
+
ignoreSendError: koishi_1.Schema.boolean().default(true),
|
|
32
|
+
enableForward: koishi_1.Schema.boolean().default(false),
|
|
33
|
+
downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false),
|
|
34
|
+
messageBufferDelay: koishi_1.Schema.number().default(1).min(0),
|
|
35
|
+
retryTimes: koishi_1.Schema.number().default(3).min(0),
|
|
36
|
+
retryInterval: koishi_1.Schema.number().default(2000).min(500),
|
|
37
|
+
apiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos'),
|
|
65
38
|
});
|
|
66
39
|
if (!worker_threads_1.isMainThread) {
|
|
67
40
|
const { url, filePath } = worker_threads_1.workerData;
|
|
68
41
|
(async () => {
|
|
69
42
|
try {
|
|
43
|
+
if (url.endsWith('.m4a') || url.endsWith('.mp3')) {
|
|
44
|
+
worker_threads_1.parentPort?.postMessage({ success: false, error: '不支持音频' });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
70
47
|
const response = await (0, axios_1.default)({
|
|
71
48
|
url,
|
|
72
49
|
method: 'GET',
|
|
@@ -80,10 +57,7 @@ if (!worker_threads_1.isMainThread) {
|
|
|
80
57
|
worker_threads_1.parentPort?.postMessage({ success: true, filePath });
|
|
81
58
|
}
|
|
82
59
|
catch (error) {
|
|
83
|
-
worker_threads_1.parentPort?.postMessage({
|
|
84
|
-
success: false,
|
|
85
|
-
error: error.message
|
|
86
|
-
});
|
|
60
|
+
worker_threads_1.parentPort?.postMessage({ success: false, error: error.message });
|
|
87
61
|
}
|
|
88
62
|
})();
|
|
89
63
|
}
|
|
@@ -91,8 +65,8 @@ const processed = new Map();
|
|
|
91
65
|
const linkBuffer = new Map();
|
|
92
66
|
const PLATFORM_KEYWORDS = {
|
|
93
67
|
bilibili: ['bilibili', 'b23', 'B站'],
|
|
94
|
-
kuaishou: ['kuaishou', '快手'],
|
|
95
|
-
douyin: ['douyin', '抖音']
|
|
68
|
+
kuaishou: ['kuaishou', '快手', 'v.kuaishou.com'],
|
|
69
|
+
douyin: ['douyin', '抖音', 'v.douyin.com']
|
|
96
70
|
};
|
|
97
71
|
function extractUrl(content) {
|
|
98
72
|
const urlMatches = content.match(/https?:\/\/[^\s]+/gi) || [];
|
|
@@ -121,30 +95,23 @@ async function downloadVideoWithThreads(url, filename) {
|
|
|
121
95
|
if (!fs_1.default.existsSync(dir))
|
|
122
96
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
123
97
|
const filePath = path_1.default.join(dir, `${filename}.mp4`);
|
|
124
|
-
const worker = new worker_threads_1.Worker(__filename, {
|
|
125
|
-
workerData: { url, filePath }
|
|
126
|
-
});
|
|
98
|
+
const worker = new worker_threads_1.Worker(__filename, { workerData: { url, filePath } });
|
|
127
99
|
worker.on('message', (result) => {
|
|
128
|
-
if (result.success)
|
|
100
|
+
if (result.success)
|
|
129
101
|
resolve(result.filePath);
|
|
130
|
-
|
|
131
|
-
else {
|
|
102
|
+
else
|
|
132
103
|
reject(new Error(result.error));
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
worker.on('error', (error) => {
|
|
136
|
-
reject(error);
|
|
137
104
|
});
|
|
105
|
+
worker.on('error', reject);
|
|
138
106
|
worker.on('exit', (code) => {
|
|
139
|
-
if (code !== 0)
|
|
140
|
-
reject(new Error(
|
|
141
|
-
}
|
|
107
|
+
if (code !== 0)
|
|
108
|
+
reject(new Error('worker exit'));
|
|
142
109
|
});
|
|
143
110
|
});
|
|
144
111
|
}
|
|
145
|
-
function parseData(data,
|
|
146
|
-
let title = data.title || '无标题';
|
|
147
|
-
let author = data.
|
|
112
|
+
function parseData(data, maxDescLength) {
|
|
113
|
+
let title = data.title || data.desc || '无标题';
|
|
114
|
+
let author = data.author || data.auther || data.user?.name || '未知作者';
|
|
148
115
|
let desc = data.desc || data.description || title || '无简介';
|
|
149
116
|
desc = desc.slice(0, maxDescLength);
|
|
150
117
|
let digg = data.like || data.digg || 0;
|
|
@@ -155,34 +122,34 @@ function parseData(data, platform, maxDescLength) {
|
|
|
155
122
|
let danmaku = data.danmaku || data.comment || 0;
|
|
156
123
|
let cover = data.cover || data.imgurl || data.pic || '';
|
|
157
124
|
let video = '';
|
|
158
|
-
if (data.url)
|
|
125
|
+
if (data.videos && Array.isArray(data.videos) && data.videos.length > 0 && data.videos[0].url) {
|
|
126
|
+
video = data.videos[0].url;
|
|
127
|
+
}
|
|
128
|
+
else if (data.url) {
|
|
159
129
|
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
130
|
}
|
|
165
|
-
else if (data.
|
|
166
|
-
video = data.
|
|
167
|
-
|
|
168
|
-
|
|
131
|
+
else if (data.video_backup && Array.isArray(data.video_backup) && data.video_backup[0]?.url) {
|
|
132
|
+
video = data.video_backup[0].url;
|
|
133
|
+
}
|
|
134
|
+
if (video.endsWith('.m4a') || video.endsWith('.mp3'))
|
|
135
|
+
video = '';
|
|
169
136
|
return { title, author, desc, digg, coin, collect, share, play, danmaku, cover, video };
|
|
170
137
|
}
|
|
171
138
|
function clearAllCache() {
|
|
172
139
|
processed.clear();
|
|
173
|
-
linkBuffer.forEach(
|
|
140
|
+
linkBuffer.forEach(b => clearTimeout(b.timer));
|
|
174
141
|
linkBuffer.clear();
|
|
175
142
|
const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
|
|
176
143
|
if (fs_1.default.existsSync(tempDir)) {
|
|
177
|
-
fs_1.default.readdirSync(tempDir).forEach(
|
|
144
|
+
fs_1.default.readdirSync(tempDir).forEach(f => {
|
|
178
145
|
try {
|
|
179
|
-
|
|
180
|
-
fs_1.default.unlinkSync(filePath);
|
|
146
|
+
fs_1.default.unlinkSync(path_1.default.join(tempDir, f));
|
|
181
147
|
}
|
|
182
|
-
catch
|
|
148
|
+
catch { }
|
|
183
149
|
});
|
|
184
150
|
}
|
|
185
151
|
}
|
|
152
|
+
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
186
153
|
function apply(ctx, config) {
|
|
187
154
|
if (!worker_threads_1.isMainThread)
|
|
188
155
|
return;
|
|
@@ -193,47 +160,32 @@ function apply(ctx, config) {
|
|
|
193
160
|
'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
161
|
}
|
|
195
162
|
});
|
|
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
163
|
async function parse(url) {
|
|
205
164
|
const platform = getPlatformType(url);
|
|
206
165
|
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)}`);
|
|
166
|
+
return { data: null };
|
|
167
|
+
for (let i = 0; i <= config.retryTimes; i++) {
|
|
168
|
+
try {
|
|
169
|
+
const res = await http.get(config.apiUrl, { params: { url } });
|
|
170
|
+
const isSuccess = (res.data.code === 200 || res.data.code === 0) && res.data.data;
|
|
171
|
+
if (isSuccess) {
|
|
172
|
+
return { data: parseData(res.data.data, config.maxDescLength) };
|
|
173
|
+
}
|
|
221
174
|
}
|
|
175
|
+
catch (e) { }
|
|
176
|
+
if (i < config.retryTimes)
|
|
177
|
+
await delay(config.retryInterval);
|
|
222
178
|
}
|
|
223
|
-
|
|
224
|
-
ctx.logger.error(`解析失败: ${e.message}`);
|
|
225
|
-
}
|
|
226
|
-
return { data: null, platform: null };
|
|
179
|
+
return { data: null };
|
|
227
180
|
}
|
|
228
181
|
async function processSingleUrl(session, url) {
|
|
229
182
|
const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
|
|
230
183
|
const now = Date.now();
|
|
231
|
-
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
|
|
184
|
+
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
|
|
232
185
|
return null;
|
|
233
|
-
}
|
|
234
186
|
processed.set(hash, now);
|
|
235
187
|
const parseResult = await parse(url);
|
|
236
|
-
if (!parseResult.data)
|
|
188
|
+
if (!parseResult.data || !parseResult.data.video)
|
|
237
189
|
return null;
|
|
238
190
|
let text = config.imageParseFormat
|
|
239
191
|
.replace(/\${标题}/g, parseResult.data.title)
|
|
@@ -245,28 +197,26 @@ function apply(ctx, config) {
|
|
|
245
197
|
.replace(/\${转发}/g, String(parseResult.data.share))
|
|
246
198
|
.replace(/\${观看}/g, String(parseResult.data.play))
|
|
247
199
|
.replace(/\${弹幕}/g, String(parseResult.data.danmaku))
|
|
248
|
-
.replace(/\${tab}/g, '\t')
|
|
249
|
-
.replace(/\${~~~}/g, '\n');
|
|
200
|
+
.replace(/\${tab}/g, '\t').replace(/\${~~~}/g, '\n');
|
|
250
201
|
const contentParts = [];
|
|
251
202
|
if (config.returnContent.showImageText) {
|
|
252
|
-
const [
|
|
253
|
-
if (
|
|
254
|
-
contentParts.push(
|
|
203
|
+
const [b, a] = text.split('${封面}');
|
|
204
|
+
if (b?.trim())
|
|
205
|
+
contentParts.push(b.trim());
|
|
255
206
|
if (parseResult.data.cover)
|
|
256
207
|
contentParts.push(koishi_1.h.image(parseResult.data.cover));
|
|
257
|
-
if (
|
|
258
|
-
contentParts.push(
|
|
208
|
+
if (a?.trim())
|
|
209
|
+
contentParts.push(a.trim());
|
|
259
210
|
}
|
|
260
211
|
let videoContent = '';
|
|
261
212
|
if (config.returnContent.showVideoFile && parseResult.data.video) {
|
|
262
213
|
if (config.downloadVideoBeforeSend && session.platform === 'onebot') {
|
|
263
214
|
try {
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
videoContent = koishi_1.h.file(
|
|
215
|
+
const name = crypto_1.default.createHash('md5').update(parseResult.data.video).digest('hex');
|
|
216
|
+
const fp = await downloadVideoWithThreads(parseResult.data.video, name);
|
|
217
|
+
videoContent = koishi_1.h.file(fp);
|
|
267
218
|
}
|
|
268
219
|
catch (e) {
|
|
269
|
-
ctx.logger.error(`下载视频失败: ${e.message}`);
|
|
270
220
|
videoContent = koishi_1.h.video(parseResult.data.video);
|
|
271
221
|
}
|
|
272
222
|
}
|
|
@@ -277,153 +227,99 @@ function apply(ctx, config) {
|
|
|
277
227
|
if (config.returnContent.showVideoUrl && parseResult.data.video) {
|
|
278
228
|
contentParts.push(`🔗 无水印链接:${parseResult.data.video}`);
|
|
279
229
|
}
|
|
280
|
-
return {
|
|
281
|
-
textContent: contentParts.join('\n'),
|
|
282
|
-
videoContent,
|
|
283
|
-
rawData: parseResult.data
|
|
284
|
-
};
|
|
230
|
+
return { textContent: contentParts.join('\n'), videoContent };
|
|
285
231
|
}
|
|
286
|
-
async function
|
|
232
|
+
async function revokeTip(session, key) {
|
|
287
233
|
if (!config.revokeWaitingTip || session.platform !== 'onebot')
|
|
288
234
|
return;
|
|
289
|
-
const
|
|
290
|
-
if (!
|
|
235
|
+
const buf = linkBuffer.get(key);
|
|
236
|
+
if (!buf?.tipMsgId)
|
|
291
237
|
return;
|
|
292
238
|
try {
|
|
293
|
-
await session.bot.deleteMessage(session.channelId,
|
|
294
|
-
}
|
|
295
|
-
catch (e) {
|
|
296
|
-
ctx.logger.debug(`撤回等待提示失败: ${e.message}`);
|
|
239
|
+
await session.bot.deleteMessage(session.channelId, buf.tipMsgId);
|
|
297
240
|
}
|
|
241
|
+
catch { }
|
|
298
242
|
}
|
|
299
|
-
async function
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
if (!
|
|
243
|
+
async function flush(session) {
|
|
244
|
+
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
245
|
+
const buf = linkBuffer.get(key);
|
|
246
|
+
if (!buf)
|
|
303
247
|
return;
|
|
304
|
-
clearTimeout(
|
|
305
|
-
linkBuffer.delete(
|
|
306
|
-
await
|
|
248
|
+
clearTimeout(buf.timer);
|
|
249
|
+
linkBuffer.delete(key);
|
|
250
|
+
await revokeTip(session, key);
|
|
307
251
|
const results = [];
|
|
308
|
-
for (const
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
311
|
-
results.push(
|
|
252
|
+
for (const u of buf.urls) {
|
|
253
|
+
const r = await processSingleUrl(session, u);
|
|
254
|
+
if (r)
|
|
255
|
+
results.push(r);
|
|
312
256
|
}
|
|
313
257
|
if (results.length === 0) {
|
|
314
|
-
await session.send('
|
|
258
|
+
await session.send('视频解析失败');
|
|
315
259
|
return;
|
|
316
260
|
}
|
|
317
261
|
if (config.enableForward && session.platform === 'onebot') {
|
|
318
262
|
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
|
-
}
|
|
263
|
+
const forwardMessages = results.map(result => {
|
|
264
|
+
const content = [];
|
|
265
|
+
if (result.textContent)
|
|
266
|
+
content.push(result.textContent);
|
|
267
|
+
if (result.videoContent)
|
|
268
|
+
content.push(result.videoContent);
|
|
269
|
+
return (0, koishi_1.h)('message', [
|
|
270
|
+
(0, koishi_1.h)('author', { id: session.selfId, name: '解析机器人' }),
|
|
271
|
+
...content
|
|
272
|
+
]);
|
|
338
273
|
});
|
|
339
|
-
await session.send(
|
|
274
|
+
await session.send((0, koishi_1.h)('message', { forward: true }, forwardMessages));
|
|
340
275
|
return;
|
|
341
276
|
}
|
|
342
277
|
catch (e) {
|
|
343
|
-
ctx.logger.error(`合并转发失败: ${e.message}`);
|
|
344
278
|
await session.send('合并转发失败,将分开发送');
|
|
345
279
|
}
|
|
346
280
|
}
|
|
347
|
-
for (const
|
|
281
|
+
for (const r of results) {
|
|
348
282
|
try {
|
|
349
|
-
if (
|
|
350
|
-
await session.send(
|
|
351
|
-
if (
|
|
352
|
-
await session.send(
|
|
353
|
-
}
|
|
354
|
-
catch (e) {
|
|
355
|
-
if (!config.ignoreSendError) {
|
|
356
|
-
ctx.logger.warn(`发送消息失败: ${e.message}`);
|
|
357
|
-
}
|
|
283
|
+
if (r.textContent)
|
|
284
|
+
await session.send(r.textContent);
|
|
285
|
+
if (r.videoContent)
|
|
286
|
+
await session.send(r.videoContent);
|
|
358
287
|
}
|
|
288
|
+
catch { }
|
|
359
289
|
}
|
|
360
290
|
}
|
|
361
291
|
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
|
-
});
|
|
292
|
+
if (!config.enable)
|
|
293
|
+
return;
|
|
294
|
+
const content = session.content.trim();
|
|
295
|
+
if (!hasPlatformKeyword(content))
|
|
296
|
+
return;
|
|
297
|
+
const urls = extractUrl(content);
|
|
298
|
+
if (urls.length === 0)
|
|
299
|
+
return;
|
|
300
|
+
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
301
|
+
if (linkBuffer.has(key)) {
|
|
302
|
+
const b = linkBuffer.get(key);
|
|
303
|
+
b.urls.push(...urls);
|
|
304
|
+
clearTimeout(b.timer);
|
|
305
|
+
b.timer = setTimeout(() => flush(session), Math.max(0, config.messageBufferDelay * 1000));
|
|
306
|
+
linkBuffer.set(key, b);
|
|
307
|
+
return;
|
|
395
308
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
clearTimeout(linkBuffer.get(sessionKey).timer);
|
|
401
|
-
linkBuffer.delete(sessionKey);
|
|
402
|
-
}
|
|
309
|
+
let tipId;
|
|
310
|
+
if (config.showWaitingTip) {
|
|
311
|
+
const m = await session.send(config.waitingTipText).catch(() => null);
|
|
312
|
+
tipId = m?.messageId || m?.id || m;
|
|
403
313
|
}
|
|
314
|
+
linkBuffer.set(key, {
|
|
315
|
+
urls,
|
|
316
|
+
timer: setTimeout(() => flush(session), Math.max(0, config.messageBufferDelay * 1000)),
|
|
317
|
+
tipMsgId: tipId
|
|
318
|
+
});
|
|
404
319
|
});
|
|
405
320
|
setInterval(() => {
|
|
406
321
|
const now = Date.now();
|
|
407
|
-
processed.forEach((
|
|
408
|
-
if (now - timestamp > 86400000) {
|
|
409
|
-
processed.delete(hash);
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
|
|
413
|
-
if (fs_1.default.existsSync(tempDir)) {
|
|
414
|
-
fs_1.default.readdirSync(tempDir).forEach(file => {
|
|
415
|
-
try {
|
|
416
|
-
const filePath = path_1.default.join(tempDir, file);
|
|
417
|
-
const stat = fs_1.default.statSync(filePath);
|
|
418
|
-
if (now - stat.ctimeMs > 3600000) {
|
|
419
|
-
fs_1.default.unlinkSync(filePath);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
catch (e) {
|
|
423
|
-
ctx.logger.error(`清理临时文件失败: ${e.message}`);
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
}
|
|
322
|
+
processed.forEach((t, h) => now - t > 86400000 && processed.delete(h));
|
|
427
323
|
}, 3600000);
|
|
428
|
-
ctx.logger.info('
|
|
324
|
+
ctx.logger.info('视频解析已加载');
|
|
429
325
|
}
|