koishi-plugin-video-parser-all 0.9.7 → 0.9.9
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 -0
- package/lib/index.js +209 -146
- package/package.json +2 -2
package/lib/index.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export declare const Config: Schema<{
|
|
|
11
11
|
showImageText?: boolean | null | undefined;
|
|
12
12
|
showVideoFile?: boolean | null | undefined;
|
|
13
13
|
maxDescLength?: number | null | undefined;
|
|
14
|
+
videoDownloadTimeout?: number | null | undefined;
|
|
15
|
+
tempDir?: string | null | undefined;
|
|
16
|
+
maxVideoSize?: number | null | undefined;
|
|
14
17
|
} & {
|
|
15
18
|
timeout?: number | null | undefined;
|
|
16
19
|
videoSendTimeout?: number | null | undefined;
|
|
@@ -38,6 +41,9 @@ export declare const Config: Schema<{
|
|
|
38
41
|
showImageText: boolean;
|
|
39
42
|
showVideoFile: boolean;
|
|
40
43
|
maxDescLength: number;
|
|
44
|
+
videoDownloadTimeout: number;
|
|
45
|
+
tempDir: string;
|
|
46
|
+
maxVideoSize: number;
|
|
41
47
|
} & {
|
|
42
48
|
timeout: number;
|
|
43
49
|
videoSendTimeout: number;
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,10 @@ exports.Config = exports.name = void 0;
|
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
const koishi_1 = require("koishi");
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
|
-
const
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const promises_2 = require("stream/promises");
|
|
11
14
|
exports.name = 'video-parser-all';
|
|
12
15
|
exports.Config = koishi_1.Schema.intersect([
|
|
13
16
|
koishi_1.Schema.object({
|
|
@@ -23,6 +26,9 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
23
26
|
showImageText: koishi_1.Schema.boolean().default(true).description('是否发送解析后的文字内容'),
|
|
24
27
|
showVideoFile: koishi_1.Schema.boolean().default(true).description('是否发送视频文件(关闭则只发送视频链接)'),
|
|
25
28
|
maxDescLength: koishi_1.Schema.number().default(200).description('简介内容最大长度(字符),超出自动截断'),
|
|
29
|
+
videoDownloadTimeout: koishi_1.Schema.number().default(120000).description('视频下载超时(毫秒)'),
|
|
30
|
+
tempDir: koishi_1.Schema.string().default('./temp_videos').description('临时视频存储目录'),
|
|
31
|
+
maxVideoSize: koishi_1.Schema.number().default(100 * 1024 * 1024).description('最大下载视频大小(字节),默认100MB'),
|
|
26
32
|
}).description('内容显示设置'),
|
|
27
33
|
koishi_1.Schema.object({
|
|
28
34
|
timeout: koishi_1.Schema.number().min(0).default(180000).description('API 请求超时(毫秒)'),
|
|
@@ -54,85 +60,76 @@ function debugLog(level, ...args) {
|
|
|
54
60
|
const message = `[${timestamp}] [${level}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')}`;
|
|
55
61
|
logger.info(message);
|
|
56
62
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
63
|
+
function linkTypeParser(content) {
|
|
64
|
+
content = content.replace(/\\\//g, '/');
|
|
65
|
+
const rules = [
|
|
66
|
+
{ pattern: /bilibili\.com\/video\/([ab]v[0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://www.bilibili.com/video/${id}` },
|
|
67
|
+
{ pattern: /b23\.tv(?:\\)?\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://b23.tv/${id}` },
|
|
68
|
+
{ pattern: /bili(?:22|23|33)\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili23.cn/${id}` },
|
|
69
|
+
{ pattern: /bili2233\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili2233.cn/${id}` },
|
|
70
|
+
{ pattern: /douyin\.com\/video\/(\d+)/gi, type: 'douyin', buildUrl: (id) => `https://www.douyin.com/video/${id}` },
|
|
71
|
+
{ pattern: /v\.douyin\.com\/([0-9a-zA-Z]+)/gi, type: 'douyin', buildUrl: (id) => `https://v.douyin.com/${id}` },
|
|
72
|
+
{ pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://www.kuaishou.com/short-video/${id}` },
|
|
73
|
+
{ pattern: /v\.kuaishou\.com\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://v.kuaishou.com/${id}` },
|
|
74
|
+
{ pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://www.xiaohongshu.com/discovery/item/${id}` },
|
|
75
|
+
{ pattern: /xhslink\.com\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://xhslink.com/${id}` },
|
|
76
|
+
{ pattern: /weibo\.com\/\d+\/([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://weibo.com/${id}` },
|
|
77
|
+
{ pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://video.weibo.com/show?fid=${id}` },
|
|
78
|
+
{ pattern: /ixigua\.com\/(\d+)/gi, type: 'xigua', buildUrl: (id) => `https://www.ixigua.com/${id}` },
|
|
79
|
+
{ pattern: /youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://www.youtube.com/watch?v=${id}` },
|
|
80
|
+
{ pattern: /youtu\.be\/([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://youtu.be/${id}` },
|
|
81
|
+
{ pattern: /tiktok\.com\/@[\w.]+\/video\/(\d+)/gi, type: 'tiktok', buildUrl: (id) => `https://www.tiktok.com/@user/video/${id}` },
|
|
82
|
+
{ pattern: /vm\.tiktok\.com\/([0-9a-zA-Z]+)/gi, type: 'tiktok', buildUrl: (id) => `https://vm.tiktok.com/${id}` },
|
|
83
|
+
{ pattern: /acfun\.cn\/v\/(ac\d+)/gi, type: 'acfun', buildUrl: (id) => `https://www.acfun.cn/v/${id}` },
|
|
84
|
+
{ pattern: /zhihu\.com\/video\/(\d+)/gi, type: 'zhihu', buildUrl: (id) => `https://www.zhihu.com/video/${id}` },
|
|
85
|
+
{ pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z]+)/gi, type: 'weishi', buildUrl: (id) => `https://weishi.qq.com/weishi/feed/${id}` },
|
|
86
|
+
{ pattern: /huya\.com\/video\/([0-9a-zA-Z]+)/gi, type: 'huya', buildUrl: (id) => `https://www.huya.com/video/${id}` },
|
|
87
|
+
{ pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z]+)/gi, type: 'haokan', buildUrl: (id) => `https://haokan.baidu.com/v?vid=${id}` },
|
|
88
|
+
{ pattern: /meipai\.com\/media\/(\d+)/gi, type: 'meipai', buildUrl: (id) => `https://www.meipai.com/media/${id}` },
|
|
89
|
+
{ pattern: /twitter\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://twitter.com/i/status/${id}` },
|
|
90
|
+
{ pattern: /x\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://x.com/i/status/${id}` },
|
|
91
|
+
{ pattern: /instagram\.com\/p\/([0-9a-zA-Z_-]+)/gi, type: 'instagram', buildUrl: (id) => `https://www.instagram.com/p/${id}` },
|
|
92
|
+
{ pattern: /doubao\.com\/video\/(\d+)/gi, type: 'doubao', buildUrl: (id) => `https://www.doubao.com/video/${id}` },
|
|
93
|
+
];
|
|
94
|
+
const matches = [];
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
for (const rule of rules) {
|
|
97
|
+
let match;
|
|
98
|
+
while ((match = rule.pattern.exec(content)) !== null) {
|
|
99
|
+
const id = match[1];
|
|
100
|
+
if (seen.has(id))
|
|
101
|
+
continue;
|
|
102
|
+
seen.add(id);
|
|
103
|
+
const url = rule.buildUrl(id);
|
|
104
|
+
matches.push({ type: rule.type, url, id });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return matches;
|
|
88
108
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
function extractUrlsFromXml(xml) {
|
|
98
|
-
const urls = [];
|
|
99
|
-
try {
|
|
100
|
-
const parsed = xmlParser.parse(xml);
|
|
101
|
-
const msg = parsed?.msg;
|
|
102
|
-
if (!msg)
|
|
103
|
-
return urls;
|
|
104
|
-
if (msg.source && typeof msg.source === 'object') {
|
|
105
|
-
const sourceUrl = msg.source['@_url'];
|
|
106
|
-
if (sourceUrl && typeof sourceUrl === 'string')
|
|
107
|
-
urls.push(sourceUrl);
|
|
109
|
+
function extractUrl(content) {
|
|
110
|
+
const urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
|
|
111
|
+
return urlMatches.filter(url => {
|
|
112
|
+
try {
|
|
113
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
114
|
+
if (hostname === 'multimedia.nt.qq.com.cn')
|
|
115
|
+
return false;
|
|
116
|
+
return true;
|
|
108
117
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const pictures = Array.isArray(item.picture) ? item.picture : (item.picture ? [item.picture] : []);
|
|
112
|
-
for (const pic of pictures) {
|
|
113
|
-
if (pic['@_cover'] && typeof pic['@_cover'] === 'string')
|
|
114
|
-
urls.push(pic['@_cover']);
|
|
115
|
-
}
|
|
116
|
-
if (item.title && typeof item.title === 'string') {
|
|
117
|
-
const match = item.title.match(/https?:\/\/[^\s<>"']+/i);
|
|
118
|
-
if (match)
|
|
119
|
-
urls.push(match[0]);
|
|
120
|
-
}
|
|
121
|
-
if (item.summary && typeof item.summary === 'string') {
|
|
122
|
-
const matches = item.summary.match(/https?:\/\/[^\s<>"']+/gi);
|
|
123
|
-
if (matches)
|
|
124
|
-
urls.push(...matches);
|
|
125
|
-
}
|
|
118
|
+
catch {
|
|
119
|
+
return false;
|
|
126
120
|
}
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
debugLog('WARN', `解析 XML 卡片失败: ${getErrorMessage(err)}`);
|
|
130
|
-
}
|
|
131
|
-
return urls;
|
|
121
|
+
});
|
|
132
122
|
}
|
|
133
123
|
function extractAllUrlsFromMessage(session) {
|
|
134
|
-
const urls = [];
|
|
135
124
|
const content = session.content?.trim() || '';
|
|
125
|
+
const urls = [];
|
|
126
|
+
const linkMatches = linkTypeParser(content);
|
|
127
|
+
if (linkMatches.length > 0) {
|
|
128
|
+
for (const match of linkMatches) {
|
|
129
|
+
urls.push(match.url);
|
|
130
|
+
}
|
|
131
|
+
return [...new Set(urls)];
|
|
132
|
+
}
|
|
136
133
|
if (content) {
|
|
137
134
|
const textUrls = extractUrl(content);
|
|
138
135
|
urls.push(...textUrls);
|
|
@@ -140,8 +137,11 @@ function extractAllUrlsFromMessage(session) {
|
|
|
140
137
|
if (session.elements) {
|
|
141
138
|
for (const elem of session.elements) {
|
|
142
139
|
if (elem.type === 'xml' && elem.data) {
|
|
143
|
-
const
|
|
144
|
-
|
|
140
|
+
const urlRegex = /https?:\/\/[^\s<>"']+/gi;
|
|
141
|
+
let match;
|
|
142
|
+
while ((match = urlRegex.exec(elem.data)) !== null) {
|
|
143
|
+
urls.push(match[0]);
|
|
144
|
+
}
|
|
145
145
|
}
|
|
146
146
|
else if (elem.type === 'json' && elem.data) {
|
|
147
147
|
try {
|
|
@@ -167,34 +167,6 @@ function extractAllUrlsFromMessage(session) {
|
|
|
167
167
|
}
|
|
168
168
|
return [...new Set(urls)];
|
|
169
169
|
}
|
|
170
|
-
function extractUrl(content) {
|
|
171
|
-
const urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
|
|
172
|
-
return urlMatches.filter(url => {
|
|
173
|
-
try {
|
|
174
|
-
const hostname = new URL(url).hostname.toLowerCase();
|
|
175
|
-
if (hostname === 'multimedia.nt.qq.com.cn')
|
|
176
|
-
return false;
|
|
177
|
-
return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => hostname.includes(keyword) || (!keyword.includes('.') && url.toLowerCase().includes(keyword))));
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
const lower = url.toLowerCase();
|
|
181
|
-
return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
function getPlatformType(url) {
|
|
186
|
-
try {
|
|
187
|
-
const hostname = new URL(url).hostname.toLowerCase();
|
|
188
|
-
if (hostname === 'multimedia.nt.qq.com.cn')
|
|
189
|
-
return null;
|
|
190
|
-
for (const [platform, keywords] of Object.entries(PLATFORM_KEYWORDS)) {
|
|
191
|
-
if (keywords.some(k => hostname.includes(k) || (!k.includes('.') && url.toLowerCase().includes(k))))
|
|
192
|
-
return platform;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
catch { }
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
170
|
function cleanUrl(url) {
|
|
199
171
|
try {
|
|
200
172
|
url = url.replace(/&/g, '&');
|
|
@@ -219,7 +191,7 @@ async function resolveShortUrl(url) {
|
|
|
219
191
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
220
192
|
'Referer': 'https://www.baidu.com/',
|
|
221
193
|
},
|
|
222
|
-
validateStatus: status => status >= 200 && status < 400,
|
|
194
|
+
validateStatus: (status) => status >= 200 && status < 400,
|
|
223
195
|
});
|
|
224
196
|
const finalUrl = res.request?.res?.responseUrl || url;
|
|
225
197
|
return cleanUrl(finalUrl);
|
|
@@ -390,6 +362,44 @@ function buildForwardNode(session, content, botName) {
|
|
|
390
362
|
}
|
|
391
363
|
const urlCache = new Map();
|
|
392
364
|
const CACHE_TTL = 10 * 60 * 1000;
|
|
365
|
+
async function downloadVideoFile(videoUrl, tempDir, timeout, maxSize) {
|
|
366
|
+
await promises_1.default.mkdir(tempDir, { recursive: true });
|
|
367
|
+
const fileName = `video_${Date.now()}_${Math.random().toString(36).substring(2, 8)}.mp4`;
|
|
368
|
+
const filePath = path_1.default.join(tempDir, fileName);
|
|
369
|
+
const writer = (0, fs_1.createWriteStream)(filePath);
|
|
370
|
+
const response = await (0, axios_1.default)({
|
|
371
|
+
method: 'GET',
|
|
372
|
+
url: videoUrl,
|
|
373
|
+
responseType: 'stream',
|
|
374
|
+
timeout: timeout,
|
|
375
|
+
headers: {
|
|
376
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
const contentLength = Number(response.headers['content-length'] || 0);
|
|
380
|
+
if (contentLength > maxSize) {
|
|
381
|
+
writer.destroy();
|
|
382
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
383
|
+
throw new Error(`视频文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${Math.round(maxSize / 1024 / 1024)}MB)`);
|
|
384
|
+
}
|
|
385
|
+
let downloadedSize = 0;
|
|
386
|
+
response.data.on('data', (chunk) => {
|
|
387
|
+
downloadedSize += chunk.length;
|
|
388
|
+
if (downloadedSize > maxSize) {
|
|
389
|
+
response.data.destroy();
|
|
390
|
+
writer.destroy();
|
|
391
|
+
promises_1.default.unlink(filePath).catch(() => { });
|
|
392
|
+
throw new Error(`视频文件过大,超过限制(${Math.round(maxSize / 1024 / 1024)}MB)`);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
await (0, promises_2.pipeline)(response.data, writer);
|
|
396
|
+
return filePath;
|
|
397
|
+
}
|
|
398
|
+
function getErrorMessage(error) {
|
|
399
|
+
if (error instanceof Error)
|
|
400
|
+
return error.message;
|
|
401
|
+
return String(error);
|
|
402
|
+
}
|
|
393
403
|
function apply(ctx, config) {
|
|
394
404
|
debugEnabled = config.debug || false;
|
|
395
405
|
debugLog('INFO', '插件初始化开始');
|
|
@@ -443,10 +453,6 @@ function apply(ctx, config) {
|
|
|
443
453
|
}
|
|
444
454
|
async function parseUrl(url) {
|
|
445
455
|
const realUrl = await resolveShortUrl(url);
|
|
446
|
-
const platform = getPlatformType(realUrl);
|
|
447
|
-
if (!platform) {
|
|
448
|
-
return { success: false, msg: texts.unsupportedPlatformText };
|
|
449
|
-
}
|
|
450
456
|
const candidates = [realUrl, url];
|
|
451
457
|
for (const candidate of [...new Set(candidates)]) {
|
|
452
458
|
try {
|
|
@@ -457,7 +463,7 @@ function apply(ctx, config) {
|
|
|
457
463
|
debugLog('ERROR', `候选链接解析失败: ${candidate}`, getErrorMessage(error));
|
|
458
464
|
}
|
|
459
465
|
}
|
|
460
|
-
return { success: false, msg:
|
|
466
|
+
return { success: false, msg: texts.unsupportedPlatformText };
|
|
461
467
|
}
|
|
462
468
|
async function processSingleUrl(url) {
|
|
463
469
|
const result = await parseUrl(url);
|
|
@@ -504,6 +510,29 @@ function apply(ctx, config) {
|
|
|
504
510
|
}
|
|
505
511
|
return null;
|
|
506
512
|
}
|
|
513
|
+
async function sendVideoFile(session, videoUrl) {
|
|
514
|
+
if (!videoUrl)
|
|
515
|
+
throw new Error('视频链接为空');
|
|
516
|
+
try {
|
|
517
|
+
debugLog('INFO', `尝试直接发送视频URL: ${videoUrl.substring(0, 100)}...`);
|
|
518
|
+
return await sendWithTimeout(session, koishi_1.h.video(videoUrl));
|
|
519
|
+
}
|
|
520
|
+
catch (err) {
|
|
521
|
+
debugLog('ERROR', `直接发送URL失败,开始下载视频: ${getErrorMessage(err)}`);
|
|
522
|
+
let tempFilePath = null;
|
|
523
|
+
try {
|
|
524
|
+
tempFilePath = await downloadVideoFile(videoUrl, config.tempDir || './temp_videos', config.videoDownloadTimeout || 120000, config.maxVideoSize || 100 * 1024 * 1024);
|
|
525
|
+
const localFile = `file://${path_1.default.resolve(tempFilePath)}`;
|
|
526
|
+
debugLog('INFO', `视频下载完成,发送本地文件: ${localFile}`);
|
|
527
|
+
return await sendWithTimeout(session, koishi_1.h.video(localFile));
|
|
528
|
+
}
|
|
529
|
+
finally {
|
|
530
|
+
if (tempFilePath) {
|
|
531
|
+
promises_1.default.unlink(tempFilePath).catch(e => debugLog('WARN', `删除临时文件失败: ${e}`));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
507
536
|
async function flush(session, urls) {
|
|
508
537
|
const uniqueUrls = [...new Set(urls)];
|
|
509
538
|
const items = [];
|
|
@@ -537,44 +566,75 @@ function apply(ctx, config) {
|
|
|
537
566
|
return;
|
|
538
567
|
const enableForward = config.enableForward && session.platform === 'onebot';
|
|
539
568
|
const botName = config.botName || '视频解析机器人';
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (
|
|
569
|
+
if (enableForward) {
|
|
570
|
+
const forwardMessages = [];
|
|
571
|
+
for (const item of items) {
|
|
572
|
+
const p = item.parsed;
|
|
573
|
+
const text = item.text;
|
|
574
|
+
if (text && config.showImageText) {
|
|
546
575
|
forwardMessages.push(buildForwardNode(session, text, botName));
|
|
547
|
-
else {
|
|
548
|
-
await sendWithTimeout(session, text);
|
|
549
|
-
await delay(300);
|
|
550
576
|
}
|
|
551
|
-
|
|
552
|
-
if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
553
|
-
if (enableForward)
|
|
577
|
+
if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
554
578
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.cover), botName));
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
579
|
+
}
|
|
580
|
+
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
581
|
+
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
582
|
+
for (const imgUrl of imageUrls) {
|
|
583
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
|
|
584
|
+
}
|
|
558
585
|
}
|
|
559
586
|
}
|
|
560
|
-
if (
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
587
|
+
if (forwardMessages.length) {
|
|
588
|
+
const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100));
|
|
589
|
+
try {
|
|
590
|
+
await sendWithTimeout(session, forwardMsg, config.retryTimes);
|
|
564
591
|
}
|
|
565
|
-
|
|
566
|
-
|
|
592
|
+
catch (err) {
|
|
593
|
+
debugLog('ERROR', '合并转发发送失败,降级为逐条发送:', err);
|
|
594
|
+
for (const node of forwardMessages) {
|
|
595
|
+
await sendWithTimeout(session, node.data.content).catch(() => { });
|
|
596
|
+
await delay(300);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
for (const item of items) {
|
|
601
|
+
const p = item.parsed;
|
|
602
|
+
if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
|
|
603
|
+
try {
|
|
604
|
+
await sendVideoFile(session, p.video);
|
|
605
|
+
}
|
|
606
|
+
catch (err) {
|
|
607
|
+
debugLog('ERROR', `视频发送失败(降级发送链接): ${getErrorMessage(err)}`);
|
|
608
|
+
await sendWithTimeout(session, `视频链接:${p.video}`).catch(() => { });
|
|
609
|
+
}
|
|
567
610
|
await delay(500);
|
|
568
611
|
}
|
|
569
612
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
for (const item of items) {
|
|
616
|
+
const p = item.parsed;
|
|
617
|
+
const text = item.text;
|
|
618
|
+
if (text && config.showImageText) {
|
|
619
|
+
await sendWithTimeout(session, text);
|
|
620
|
+
await delay(300);
|
|
621
|
+
}
|
|
622
|
+
if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
623
|
+
await sendWithTimeout(session, koishi_1.h.image(p.cover)).catch(() => { });
|
|
624
|
+
await delay(300);
|
|
625
|
+
}
|
|
626
|
+
if (p.video && config.showVideoFile && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
|
|
627
|
+
try {
|
|
628
|
+
await sendVideoFile(session, p.video);
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
debugLog('ERROR', `视频发送失败(降级发送链接): ${getErrorMessage(err)}`);
|
|
632
|
+
await sendWithTimeout(session, `视频链接:${p.video}`).catch(() => { });
|
|
575
633
|
}
|
|
634
|
+
await delay(500);
|
|
576
635
|
}
|
|
577
|
-
|
|
636
|
+
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
637
|
+
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
578
638
|
for (const imgUrl of imageUrls) {
|
|
579
639
|
await sendWithTimeout(session, koishi_1.h.image(imgUrl)).catch(() => { });
|
|
580
640
|
await delay(200);
|
|
@@ -582,15 +642,6 @@ function apply(ctx, config) {
|
|
|
582
642
|
}
|
|
583
643
|
}
|
|
584
644
|
}
|
|
585
|
-
if (enableForward && forwardMessages.length) {
|
|
586
|
-
const forwardMsg = (0, koishi_1.h)('message', { forward: true }, forwardMessages.slice(0, 100));
|
|
587
|
-
await sendWithTimeout(session, forwardMsg, config.retryTimes).catch(() => {
|
|
588
|
-
debugLog('ERROR', '合并转发发送最终失败,降级为逐条发送');
|
|
589
|
-
for (const node of forwardMessages) {
|
|
590
|
-
sendWithTimeout(session, node.data.content).catch(() => { });
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
645
|
}
|
|
595
646
|
ctx.on('message', async (session) => {
|
|
596
647
|
if (!config.enable)
|
|
@@ -621,5 +672,17 @@ function apply(ctx, config) {
|
|
|
621
672
|
urlCache.delete(key);
|
|
622
673
|
}
|
|
623
674
|
}, 60000);
|
|
675
|
+
process.on('exit', async () => {
|
|
676
|
+
try {
|
|
677
|
+
const tempDir = config.tempDir || './temp_videos';
|
|
678
|
+
const files = await promises_1.default.readdir(tempDir);
|
|
679
|
+
for (const file of files) {
|
|
680
|
+
if (file.startsWith('video_') && file.endsWith('.mp4')) {
|
|
681
|
+
await promises_1.default.unlink(path_1.default.join(tempDir, file)).catch(() => { });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch { }
|
|
686
|
+
});
|
|
624
687
|
debugLog('INFO', '插件初始化完成');
|
|
625
688
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-video-parser-all",
|
|
3
|
-
"description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20
|
|
4
|
-
"version": "0.9.
|
|
3
|
+
"description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20+平台",
|
|
4
|
+
"version": "0.9.9",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|