koishi-plugin-video-parser-all 0.5.2 → 0.5.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 +28 -0
- package/lib/index.js +349 -103
- package/package.json +1 -1
- package/readme.md +0 -4
package/lib/index.d.ts
CHANGED
|
@@ -61,4 +61,32 @@ export declare const Config: Schema<{
|
|
|
61
61
|
} & {
|
|
62
62
|
autoClearCacheInterval: number;
|
|
63
63
|
}>;
|
|
64
|
+
export declare enum ErrorCode {
|
|
65
|
+
SUCCESS = 0,
|
|
66
|
+
UNKNOWN_ERROR = 1000,
|
|
67
|
+
UNSUPPORTED_PLATFORM = 1001,
|
|
68
|
+
PLATFORM_API_NOT_CONFIGURED = 1002,
|
|
69
|
+
REQUEST_TIMEOUT = 1003,
|
|
70
|
+
NETWORK_ERROR = 1004,
|
|
71
|
+
DUPLICATE_PARSE = 1005,
|
|
72
|
+
INVALID_URL = 1006,
|
|
73
|
+
API_RETURN_ERROR = 2000,
|
|
74
|
+
API_DATA_PARSE_FAILED = 2001,
|
|
75
|
+
API_EMPTY_RESPONSE = 2002,
|
|
76
|
+
API_INVALID_RESPONSE = 2003,
|
|
77
|
+
VIDEO_DOWNLOAD_FAILED = 3000,
|
|
78
|
+
VIDEO_SIZE_EXCEEDED = 3001,
|
|
79
|
+
UNSUPPORTED_CONTENT_TYPE = 3002,
|
|
80
|
+
NO_VIDEO_FOUND = 3003,
|
|
81
|
+
NO_IMAGE_FOUND = 3004,
|
|
82
|
+
MESSAGE_SEND_FAILED = 4000,
|
|
83
|
+
MESSAGE_SEND_TIMEOUT = 4001,
|
|
84
|
+
FORWARD_MESSAGE_FAILED = 4002,
|
|
85
|
+
DOUYIN_PARSE_FAILED = 5001,
|
|
86
|
+
XIAOHONGSHU_PARSE_FAILED = 5002,
|
|
87
|
+
BILIBILI_PARSE_FAILED = 5003,
|
|
88
|
+
KUAISHOU_PARSE_FAILED = 5004,
|
|
89
|
+
WEIBO_PARSE_FAILED = 5005
|
|
90
|
+
}
|
|
91
|
+
export declare const ErrorMessageMap: Record<ErrorCode, string>;
|
|
64
92
|
export declare function apply(ctx: Context, config: any): void;
|
package/lib/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Config = exports.name = void 0;
|
|
6
|
+
exports.ErrorMessageMap = exports.ErrorCode = 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"));
|
|
@@ -22,7 +22,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
22
22
|
sameLinkInterval: koishi_1.Schema.number().min(0).default(180).description('相同链接重复解析间隔(秒)'),
|
|
23
23
|
}).description('基础设置'),
|
|
24
24
|
koishi_1.Schema.object({
|
|
25
|
-
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n音乐:${音乐名}').description('
|
|
25
|
+
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n时长:${视频时长}\n点赞:${点赞数}\n投币:${投币数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n音乐:${音乐名}').description('统一消息格式(无法获取的变量会自动隐藏)\n变量介绍:\n${标题} - 内容标题\n${作者} - 作者名称\n${简介} - 内容简介\n${视频时长} - 视频时长\n${点赞数} - 点赞数量\n${投币数} - 投币数量(仅B站)\n${收藏数} - 收藏数量\n${转发数} - 转发/分享数量\n${播放数} - 播放数量\n${评论数} - 评论数量\n${音乐名} - 背景音乐名称'),
|
|
26
26
|
}).description('统一消息格式'),
|
|
27
27
|
koishi_1.Schema.object({
|
|
28
28
|
showImageText: koishi_1.Schema.boolean().default(true).description('显示图文内容'),
|
|
@@ -38,29 +38,84 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
38
38
|
}).description('网络与API设置'),
|
|
39
39
|
koishi_1.Schema.object({
|
|
40
40
|
ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略发送失败错误'),
|
|
41
|
-
retryTimes: koishi_1.Schema.number().min(0).default(
|
|
42
|
-
retryInterval: koishi_1.Schema.number().min(0).default(
|
|
41
|
+
retryTimes: koishi_1.Schema.number().min(0).default(3).description('API请求重试次数'),
|
|
42
|
+
retryInterval: koishi_1.Schema.number().min(0).default(1000).description('重试间隔时间(毫秒)'),
|
|
43
43
|
}).description('错误与重试设置'),
|
|
44
44
|
koishi_1.Schema.object({
|
|
45
45
|
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅OneBot平台)'),
|
|
46
|
-
downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('
|
|
46
|
+
downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('发送前先下载视频'),
|
|
47
47
|
maxVideoSize: koishi_1.Schema.number().min(0).default(0).description('最大视频大小限制(MB,0为不限制)'),
|
|
48
48
|
downloadThreads: koishi_1.Schema.number().min(0).max(10).default(0).description('多线程下载线程数(0为不使用多线程,1-10为启用对应线程数)'),
|
|
49
|
-
}).description('
|
|
49
|
+
}).description('发送方式设置'),
|
|
50
50
|
koishi_1.Schema.object({
|
|
51
51
|
messageBufferDelay: koishi_1.Schema.number().min(0).default(0).description('消息缓冲延迟(毫秒,批量处理链接)'),
|
|
52
52
|
}).description('消息处理设置'),
|
|
53
53
|
koishi_1.Schema.object({
|
|
54
54
|
autoClearCacheInterval: koishi_1.Schema.number().min(0).default(0).description('自动清理缓存间隔(分钟,0为关闭)'),
|
|
55
|
-
}).description('
|
|
55
|
+
}).description('缓存清理设置'),
|
|
56
56
|
]);
|
|
57
|
+
var ErrorCode;
|
|
58
|
+
(function (ErrorCode) {
|
|
59
|
+
ErrorCode[ErrorCode["SUCCESS"] = 0] = "SUCCESS";
|
|
60
|
+
ErrorCode[ErrorCode["UNKNOWN_ERROR"] = 1000] = "UNKNOWN_ERROR";
|
|
61
|
+
ErrorCode[ErrorCode["UNSUPPORTED_PLATFORM"] = 1001] = "UNSUPPORTED_PLATFORM";
|
|
62
|
+
ErrorCode[ErrorCode["PLATFORM_API_NOT_CONFIGURED"] = 1002] = "PLATFORM_API_NOT_CONFIGURED";
|
|
63
|
+
ErrorCode[ErrorCode["REQUEST_TIMEOUT"] = 1003] = "REQUEST_TIMEOUT";
|
|
64
|
+
ErrorCode[ErrorCode["NETWORK_ERROR"] = 1004] = "NETWORK_ERROR";
|
|
65
|
+
ErrorCode[ErrorCode["DUPLICATE_PARSE"] = 1005] = "DUPLICATE_PARSE";
|
|
66
|
+
ErrorCode[ErrorCode["INVALID_URL"] = 1006] = "INVALID_URL";
|
|
67
|
+
ErrorCode[ErrorCode["API_RETURN_ERROR"] = 2000] = "API_RETURN_ERROR";
|
|
68
|
+
ErrorCode[ErrorCode["API_DATA_PARSE_FAILED"] = 2001] = "API_DATA_PARSE_FAILED";
|
|
69
|
+
ErrorCode[ErrorCode["API_EMPTY_RESPONSE"] = 2002] = "API_EMPTY_RESPONSE";
|
|
70
|
+
ErrorCode[ErrorCode["API_INVALID_RESPONSE"] = 2003] = "API_INVALID_RESPONSE";
|
|
71
|
+
ErrorCode[ErrorCode["VIDEO_DOWNLOAD_FAILED"] = 3000] = "VIDEO_DOWNLOAD_FAILED";
|
|
72
|
+
ErrorCode[ErrorCode["VIDEO_SIZE_EXCEEDED"] = 3001] = "VIDEO_SIZE_EXCEEDED";
|
|
73
|
+
ErrorCode[ErrorCode["UNSUPPORTED_CONTENT_TYPE"] = 3002] = "UNSUPPORTED_CONTENT_TYPE";
|
|
74
|
+
ErrorCode[ErrorCode["NO_VIDEO_FOUND"] = 3003] = "NO_VIDEO_FOUND";
|
|
75
|
+
ErrorCode[ErrorCode["NO_IMAGE_FOUND"] = 3004] = "NO_IMAGE_FOUND";
|
|
76
|
+
ErrorCode[ErrorCode["MESSAGE_SEND_FAILED"] = 4000] = "MESSAGE_SEND_FAILED";
|
|
77
|
+
ErrorCode[ErrorCode["MESSAGE_SEND_TIMEOUT"] = 4001] = "MESSAGE_SEND_TIMEOUT";
|
|
78
|
+
ErrorCode[ErrorCode["FORWARD_MESSAGE_FAILED"] = 4002] = "FORWARD_MESSAGE_FAILED";
|
|
79
|
+
ErrorCode[ErrorCode["DOUYIN_PARSE_FAILED"] = 5001] = "DOUYIN_PARSE_FAILED";
|
|
80
|
+
ErrorCode[ErrorCode["XIAOHONGSHU_PARSE_FAILED"] = 5002] = "XIAOHONGSHU_PARSE_FAILED";
|
|
81
|
+
ErrorCode[ErrorCode["BILIBILI_PARSE_FAILED"] = 5003] = "BILIBILI_PARSE_FAILED";
|
|
82
|
+
ErrorCode[ErrorCode["KUAISHOU_PARSE_FAILED"] = 5004] = "KUAISHOU_PARSE_FAILED";
|
|
83
|
+
ErrorCode[ErrorCode["WEIBO_PARSE_FAILED"] = 5005] = "WEIBO_PARSE_FAILED";
|
|
84
|
+
})(ErrorCode || (exports.ErrorCode = ErrorCode = {}));
|
|
85
|
+
exports.ErrorMessageMap = {
|
|
86
|
+
[ErrorCode.SUCCESS]: '操作成功',
|
|
87
|
+
[ErrorCode.UNKNOWN_ERROR]: '未知错误',
|
|
88
|
+
[ErrorCode.UNSUPPORTED_PLATFORM]: '不支持该平台链接',
|
|
89
|
+
[ErrorCode.PLATFORM_API_NOT_CONFIGURED]: '该平台暂未配置解析接口',
|
|
90
|
+
[ErrorCode.REQUEST_TIMEOUT]: '请求超时',
|
|
91
|
+
[ErrorCode.NETWORK_ERROR]: '网络请求失败',
|
|
92
|
+
[ErrorCode.DUPLICATE_PARSE]: '请勿重复解析(相同链接解析间隔未到)',
|
|
93
|
+
[ErrorCode.INVALID_URL]: '无效的链接格式',
|
|
94
|
+
[ErrorCode.API_RETURN_ERROR]: 'API返回错误',
|
|
95
|
+
[ErrorCode.API_DATA_PARSE_FAILED]: '数据解析异常',
|
|
96
|
+
[ErrorCode.API_EMPTY_RESPONSE]: 'API返回空数据',
|
|
97
|
+
[ErrorCode.API_INVALID_RESPONSE]: 'API返回无效格式',
|
|
98
|
+
[ErrorCode.VIDEO_DOWNLOAD_FAILED]: '视频下载失败',
|
|
99
|
+
[ErrorCode.VIDEO_SIZE_EXCEEDED]: '视频大小超过限制',
|
|
100
|
+
[ErrorCode.UNSUPPORTED_CONTENT_TYPE]: '不支持的内容类型',
|
|
101
|
+
[ErrorCode.NO_VIDEO_FOUND]: '未找到视频内容',
|
|
102
|
+
[ErrorCode.NO_IMAGE_FOUND]: '未找到图片内容',
|
|
103
|
+
[ErrorCode.MESSAGE_SEND_FAILED]: '消息发送失败',
|
|
104
|
+
[ErrorCode.MESSAGE_SEND_TIMEOUT]: '消息发送超时',
|
|
105
|
+
[ErrorCode.FORWARD_MESSAGE_FAILED]: '合并转发失败',
|
|
106
|
+
[ErrorCode.DOUYIN_PARSE_FAILED]: '抖音链接解析失败',
|
|
107
|
+
[ErrorCode.XIAOHONGSHU_PARSE_FAILED]: '小红书链接解析失败',
|
|
108
|
+
[ErrorCode.BILIBILI_PARSE_FAILED]: 'B站链接解析失败',
|
|
109
|
+
[ErrorCode.KUAISHOU_PARSE_FAILED]: '快手链接解析失败',
|
|
110
|
+
[ErrorCode.WEIBO_PARSE_FAILED]: '微博链接解析失败',
|
|
111
|
+
};
|
|
57
112
|
const processed = new Map();
|
|
58
113
|
const linkBuffer = new Map();
|
|
59
114
|
const logger = new koishi_1.Logger(exports.name);
|
|
60
115
|
const PLATFORM_KEYWORDS = {
|
|
61
116
|
bilibili: ['bilibili', 'b23', 'B站', 'www.bilibili.com', 'm.bilibili.com', '哔哩哔哩', 'bilibili.com/opus', 'bilibili.com/video', 'b23.tv', 't.bilibili.com', 'bilibili.com/bangumi'],
|
|
62
117
|
kuaishou: ['kuaishou', '快手', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com', 'kuaishou.com/app'],
|
|
63
|
-
xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com', 'xiaohongshu.com/explore', 'xhslink.com/'],
|
|
118
|
+
xiaohongshu: ['xiaohongshu', '小红书', 'xhslink.com', 'xiaohongshu.com', 'xhscdn.com', 'xiaohongshu.com/explore', 'xhslink.com/', 'xiaohongshu.com/discovery/item'],
|
|
64
119
|
weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
|
|
65
120
|
toutiao: ['toutiao', '今日头条', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video', 'ixigua.com/i'],
|
|
66
121
|
pipigx: ['pipigx', '皮皮搞笑', 'h5.pipigx.com', 'ippzone.com', 'pipigx.com/share'],
|
|
@@ -79,9 +134,20 @@ const API_CONFIG = {
|
|
|
79
134
|
pipixia: 'https://api.bugpk.com/api/ppx',
|
|
80
135
|
zuiyou: 'https://api.bugpk.com/api/zuiyou'
|
|
81
136
|
};
|
|
137
|
+
const PLATFORM_ERROR_CODE_MAP = {
|
|
138
|
+
douyin: ErrorCode.DOUYIN_PARSE_FAILED,
|
|
139
|
+
xiaohongshu: ErrorCode.XIAOHONGSHU_PARSE_FAILED,
|
|
140
|
+
bilibili: ErrorCode.BILIBILI_PARSE_FAILED,
|
|
141
|
+
kuaishou: ErrorCode.KUAISHOU_PARSE_FAILED,
|
|
142
|
+
weibo: ErrorCode.WEIBO_PARSE_FAILED,
|
|
143
|
+
toutiao: ErrorCode.API_RETURN_ERROR,
|
|
144
|
+
pipigx: ErrorCode.API_RETURN_ERROR,
|
|
145
|
+
pipixia: ErrorCode.API_RETURN_ERROR,
|
|
146
|
+
zuiyou: ErrorCode.API_RETURN_ERROR
|
|
147
|
+
};
|
|
82
148
|
const VARIABLE_MAPPING = {
|
|
83
149
|
'标题': ['title', 'Title', 'TITLE'],
|
|
84
|
-
'作者': ['author', 'name', 'Author', 'Name'],
|
|
150
|
+
'作者': ['author.name', 'author', 'name', 'Author', 'Name'],
|
|
85
151
|
'简介': ['desc', 'description', 'Desc', 'Description', 'content', 'Content'],
|
|
86
152
|
'视频时长': ['duration', 'Duration', 'time', 'Time'],
|
|
87
153
|
'点赞数': ['like', 'Like', 'attitudes_count', 'digg_count', 'praise'],
|
|
@@ -92,6 +158,10 @@ const VARIABLE_MAPPING = {
|
|
|
92
158
|
'评论数': ['comment', 'Comment', 'comments_count', 'comment_count', 'discuss'],
|
|
93
159
|
'音乐名': ['music.title', 'music_name', 'audio_name', 'sound_name']
|
|
94
160
|
};
|
|
161
|
+
function getErrorInfo(code, detail) {
|
|
162
|
+
const baseMsg = exports.ErrorMessageMap[code] || exports.ErrorMessageMap[ErrorCode.UNKNOWN_ERROR];
|
|
163
|
+
return detail ? `[错误码: ${code}] ${baseMsg}:${detail}` : `[错误码: ${code}] ${baseMsg}`;
|
|
164
|
+
}
|
|
95
165
|
function getErrorMessage(error) {
|
|
96
166
|
if (error instanceof Error)
|
|
97
167
|
return error.message;
|
|
@@ -156,11 +226,11 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
|
|
|
156
226
|
const filePath = path_1.default.join(dir, `${filename}.mp4`);
|
|
157
227
|
try {
|
|
158
228
|
if (url.endsWith('.m4a') || url.endsWith('.mp3')) {
|
|
159
|
-
|
|
229
|
+
return { filePath: '', code: ErrorCode.UNSUPPORTED_CONTENT_TYPE };
|
|
160
230
|
}
|
|
161
231
|
const fileSize = await getFileSize(url, userAgent);
|
|
162
232
|
if (maxSize > 0 && fileSize > maxSize) {
|
|
163
|
-
|
|
233
|
+
return { filePath: '', code: ErrorCode.VIDEO_SIZE_EXCEEDED };
|
|
164
234
|
}
|
|
165
235
|
if (threads <= 0 || fileSize === 0) {
|
|
166
236
|
const response = await (0, axios_1.default)({
|
|
@@ -174,7 +244,7 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
|
|
|
174
244
|
});
|
|
175
245
|
const writeStream = fs_1.default.createWriteStream(filePath);
|
|
176
246
|
await (0, promises_1.pipeline)(response.data, writeStream);
|
|
177
|
-
return filePath;
|
|
247
|
+
return { filePath, code: ErrorCode.SUCCESS };
|
|
178
248
|
}
|
|
179
249
|
const totalSize = fileSize * 1024 * 1024;
|
|
180
250
|
const chunkSize = Math.ceil(totalSize / threads);
|
|
@@ -200,7 +270,7 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
|
|
|
200
270
|
fs_1.default.unlinkSync(result.filePath);
|
|
201
271
|
}
|
|
202
272
|
writeStream.end();
|
|
203
|
-
return filePath;
|
|
273
|
+
return { filePath, code: ErrorCode.SUCCESS };
|
|
204
274
|
}
|
|
205
275
|
catch (error) {
|
|
206
276
|
if (fs_1.default.existsSync(filePath)) {
|
|
@@ -213,7 +283,7 @@ async function downloadVideo(url, filename, userAgent, maxSize, threads) {
|
|
|
213
283
|
}
|
|
214
284
|
catch (e) { }
|
|
215
285
|
});
|
|
216
|
-
|
|
286
|
+
return { filePath: '', code: ErrorCode.VIDEO_DOWNLOAD_FAILED };
|
|
217
287
|
}
|
|
218
288
|
}
|
|
219
289
|
function extractUrl(content) {
|
|
@@ -249,19 +319,41 @@ function getPlatformType(url) {
|
|
|
249
319
|
return 'zuiyou';
|
|
250
320
|
return null;
|
|
251
321
|
}
|
|
322
|
+
function cleanUrl(url) {
|
|
323
|
+
try {
|
|
324
|
+
// 处理HTML实体编码
|
|
325
|
+
url = url.replace(/&/g, '&');
|
|
326
|
+
const urlObj = new URL(url);
|
|
327
|
+
if (urlObj.hostname.includes('xiaohongshu.com')) {
|
|
328
|
+
urlObj.searchParams.delete('source');
|
|
329
|
+
urlObj.searchParams.delete('xhsshare');
|
|
330
|
+
urlObj.searchParams.delete('xsec_token');
|
|
331
|
+
urlObj.searchParams.delete('xsec_source');
|
|
332
|
+
return urlObj.origin + urlObj.pathname + urlObj.search;
|
|
333
|
+
}
|
|
334
|
+
if (urlObj.hostname.includes('douyin.com') || urlObj.hostname.includes('v.douyin.com')) {
|
|
335
|
+
return urlObj.origin + urlObj.pathname;
|
|
336
|
+
}
|
|
337
|
+
return url;
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
// 处理HTML实体编码
|
|
341
|
+
return url.replace(/&/g, '&');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
252
344
|
async function resolveShortUrl(url) {
|
|
253
345
|
try {
|
|
254
346
|
const res = await axios_1.default.head(url, {
|
|
255
|
-
timeout:
|
|
256
|
-
maxRedirects:
|
|
347
|
+
timeout: 10000,
|
|
348
|
+
maxRedirects: 10,
|
|
257
349
|
headers: {
|
|
258
350
|
'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'
|
|
259
351
|
}
|
|
260
352
|
});
|
|
261
|
-
return res.request.res?.responseUrl || url;
|
|
353
|
+
return cleanUrl(res.request.res?.responseUrl || url);
|
|
262
354
|
}
|
|
263
355
|
catch (e) {
|
|
264
|
-
return url;
|
|
356
|
+
return cleanUrl(url);
|
|
265
357
|
}
|
|
266
358
|
}
|
|
267
359
|
function formatDuration(input) {
|
|
@@ -328,9 +420,9 @@ function parseData(rawResponse, maxDescLength, platform) {
|
|
|
328
420
|
stat[varName] = value;
|
|
329
421
|
}
|
|
330
422
|
});
|
|
331
|
-
let type = 'video';
|
|
423
|
+
let type = data.type || 'video';
|
|
332
424
|
const title = findValueInObject(data, ['title']) || '无标题';
|
|
333
|
-
const author = findValueInObject(data, ['author', '
|
|
425
|
+
const author = findValueInObject(data, ['author.name', 'author']) || '未知作者';
|
|
334
426
|
const desc = (findValueInObject(data, ['desc', 'description', 'content']) || title).toString().slice(0, maxDescLength);
|
|
335
427
|
const cover = findValueInObject(data, ['cover', 'imgurl', 'pic', 'thumbnail']) || '';
|
|
336
428
|
let images = findValueInObject(data, ['imgurl', 'images', 'pics']) || [];
|
|
@@ -344,24 +436,23 @@ function parseData(rawResponse, maxDescLength, platform) {
|
|
|
344
436
|
findValueInObject(data, ['video_url'])
|
|
345
437
|
];
|
|
346
438
|
let video = '';
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
439
|
+
if (Array.isArray(videoUrls[2])) {
|
|
440
|
+
video = videoUrls[2][0]?.url || '';
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
for (const url of videoUrls) {
|
|
444
|
+
if (url && typeof url === 'string' && url.trim() !== '') {
|
|
445
|
+
video = url;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
351
448
|
}
|
|
352
449
|
}
|
|
353
450
|
const durationValue = findValueInObject(data, ['duration']);
|
|
354
451
|
const duration = typeof durationValue === 'number' ? durationValue : parseInt(durationValue) || 0;
|
|
355
452
|
const durationFormatted = formatDuration(durationValue || 0);
|
|
356
|
-
const
|
|
357
|
-
if (dataType === 'image' || (images.length > 0 && !video)) {
|
|
358
|
-
type = 'image';
|
|
359
|
-
}
|
|
360
|
-
if (video && (video.endsWith('.m4a') || video.endsWith('.mp3'))) {
|
|
361
|
-
video = '';
|
|
362
|
-
}
|
|
453
|
+
const live_photo = data.live_photo || [];
|
|
363
454
|
return {
|
|
364
|
-
type,
|
|
455
|
+
type: type,
|
|
365
456
|
rawData: rawResponse,
|
|
366
457
|
title,
|
|
367
458
|
author,
|
|
@@ -371,7 +462,8 @@ function parseData(rawResponse, maxDescLength, platform) {
|
|
|
371
462
|
video,
|
|
372
463
|
duration,
|
|
373
464
|
durationFormatted,
|
|
374
|
-
stat
|
|
465
|
+
stat,
|
|
466
|
+
live_photo
|
|
375
467
|
};
|
|
376
468
|
}
|
|
377
469
|
function generateFormattedText(platform, parseData, config) {
|
|
@@ -401,6 +493,9 @@ function generateFormattedText(platform, parseData, config) {
|
|
|
401
493
|
}
|
|
402
494
|
});
|
|
403
495
|
result = validLines.join('\n').trim();
|
|
496
|
+
if (!result) {
|
|
497
|
+
result = `标题:${parseData.title}\n作者:${parseData.author}\n简介:${parseData.desc}`;
|
|
498
|
+
}
|
|
404
499
|
return result;
|
|
405
500
|
}
|
|
406
501
|
function clearAllCache() {
|
|
@@ -443,58 +538,139 @@ function apply(ctx, config) {
|
|
|
443
538
|
timeout: config.timeout,
|
|
444
539
|
headers: { 'User-Agent': config.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' }
|
|
445
540
|
});
|
|
541
|
+
async function parseWithRetry(url, platform, retryTimes) {
|
|
542
|
+
let lastError = null;
|
|
543
|
+
for (let i = 0; i <= retryTimes; i++) {
|
|
544
|
+
try {
|
|
545
|
+
const res = await http.get(API_CONFIG[platform], {
|
|
546
|
+
params: { url },
|
|
547
|
+
timeout: config.timeout
|
|
548
|
+
});
|
|
549
|
+
return res.data;
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
lastError = error;
|
|
553
|
+
if (i < retryTimes) {
|
|
554
|
+
await delay(config.retryInterval);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
throw lastError;
|
|
559
|
+
}
|
|
446
560
|
async function parse(url) {
|
|
447
|
-
|
|
561
|
+
let realUrl = await resolveShortUrl(url);
|
|
562
|
+
realUrl = cleanUrl(realUrl);
|
|
448
563
|
const platform = getPlatformType(realUrl);
|
|
449
564
|
if (!platform) {
|
|
450
|
-
|
|
451
|
-
|
|
565
|
+
const code = ErrorCode.UNSUPPORTED_PLATFORM;
|
|
566
|
+
const msg = getErrorInfo(code, url);
|
|
567
|
+
logger.error(`[${code}] ${exports.ErrorMessageMap[code]}: ${url}`);
|
|
568
|
+
return { data: null, code, msg };
|
|
452
569
|
}
|
|
453
570
|
const apiUrl = API_CONFIG[platform];
|
|
454
571
|
if (!apiUrl) {
|
|
455
|
-
|
|
456
|
-
|
|
572
|
+
const code = ErrorCode.PLATFORM_API_NOT_CONFIGURED;
|
|
573
|
+
const msg = getErrorInfo(code, platform);
|
|
574
|
+
logger.error(`[${code}] ${exports.ErrorMessageMap[code]}: ${platform}`);
|
|
575
|
+
return { data: null, code, msg };
|
|
457
576
|
}
|
|
458
577
|
try {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
|
|
578
|
+
const resData = await parseWithRetry(realUrl, platform, config.retryTimes);
|
|
579
|
+
// 正确的成功判断逻辑:code为200或0,或者msg包含"解析成功"
|
|
580
|
+
const isSuccess = resData.code === 200 || resData.code === 0 ||
|
|
581
|
+
(resData.msg && resData.msg.includes('解析成功'));
|
|
582
|
+
if (!isSuccess) {
|
|
583
|
+
const apiErrorMsg = resData.msg || '解析失败';
|
|
584
|
+
const platformCode = PLATFORM_ERROR_CODE_MAP[platform] || ErrorCode.API_RETURN_ERROR;
|
|
585
|
+
let detailedMsg = apiErrorMsg;
|
|
586
|
+
if (apiErrorMsg.includes('无法识别解析类型') || apiErrorMsg.includes('未找到有效内容')) {
|
|
587
|
+
detailedMsg = `链接格式不支持或内容已失效:${apiErrorMsg}`;
|
|
588
|
+
}
|
|
589
|
+
const code = platformCode;
|
|
590
|
+
const msg = getErrorInfo(code, detailedMsg);
|
|
591
|
+
logger.error(`[${code}] API返回错误: ${platform}, URL: ${url}, 错误: ${apiErrorMsg}`);
|
|
592
|
+
return { data: null, code, msg };
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
const parseResult = parseData(resData, config.maxDescLength, platform);
|
|
596
|
+
// 修正内容判断逻辑:支持live类型和live_photo
|
|
597
|
+
const hasValidContent = parseResult.video ||
|
|
598
|
+
(parseResult.images && parseResult.images.length > 0) ||
|
|
599
|
+
(parseResult.live_photo && parseResult.live_photo.length > 0) ||
|
|
600
|
+
parseResult.type === 'live';
|
|
601
|
+
if (!hasValidContent) {
|
|
602
|
+
const code = ErrorCode.NO_VIDEO_FOUND;
|
|
603
|
+
const msg = getErrorInfo(code, '链接有效但未找到视频/图片内容(可能是直播、私密内容或已删除)');
|
|
604
|
+
logger.warn(`[${code}] 解析成功但无有效内容: ${platform}, URL: ${url}`);
|
|
605
|
+
return { data: null, code, msg };
|
|
606
|
+
}
|
|
607
|
+
logger.info(`[${ErrorCode.SUCCESS}] ${platform}解析成功: ${url}`);
|
|
608
|
+
return {
|
|
609
|
+
data: parseResult,
|
|
610
|
+
code: ErrorCode.SUCCESS,
|
|
611
|
+
msg: getErrorInfo(ErrorCode.SUCCESS)
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
catch (parseError) {
|
|
615
|
+
const errorMsg = getErrorMessage(parseError);
|
|
616
|
+
const code = ErrorCode.API_DATA_PARSE_FAILED;
|
|
617
|
+
const msg = getErrorInfo(code, errorMsg);
|
|
618
|
+
logger.error(`[${code}] 解析数据失败: ${platform}, URL: ${url}, 错误: ${errorMsg}`);
|
|
619
|
+
return { data: null, code, msg };
|
|
466
620
|
}
|
|
467
|
-
const parseResult = parseData(res.data, config.maxDescLength, platform);
|
|
468
|
-
return { data: parseResult, msg: `${platform}解析成功` };
|
|
469
621
|
}
|
|
470
622
|
catch (error) {
|
|
471
|
-
|
|
472
|
-
|
|
623
|
+
const errorMsg = getErrorMessage(error);
|
|
624
|
+
let code = ErrorCode.UNKNOWN_ERROR;
|
|
625
|
+
if (errorMsg.includes('timeout')) {
|
|
626
|
+
code = ErrorCode.REQUEST_TIMEOUT;
|
|
627
|
+
}
|
|
628
|
+
else if (errorMsg.includes('Network') || errorMsg.includes('network')) {
|
|
629
|
+
code = ErrorCode.NETWORK_ERROR;
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
code = ErrorCode.NETWORK_ERROR;
|
|
633
|
+
}
|
|
634
|
+
const msg = getErrorInfo(code, errorMsg);
|
|
635
|
+
logger.error(`[${code}] 解析请求失败: ${platform}, URL: ${url}, 错误: ${errorMsg}`);
|
|
636
|
+
return { data: null, code, msg };
|
|
473
637
|
}
|
|
474
638
|
}
|
|
475
639
|
async function processSingleUrl(session, url) {
|
|
476
640
|
const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
|
|
477
641
|
const now = Date.now();
|
|
478
642
|
if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000) {
|
|
479
|
-
|
|
643
|
+
const code = ErrorCode.DUPLICATE_PARSE;
|
|
644
|
+
const msg = getErrorInfo(code);
|
|
645
|
+
return { data: null, code, msg };
|
|
480
646
|
}
|
|
481
647
|
processed.set(hash, now);
|
|
482
648
|
const result = await parse(url);
|
|
483
649
|
if (!result.data)
|
|
484
|
-
return { data: null, msg: result.msg };
|
|
650
|
+
return { data: null, code: result.code, msg: result.msg };
|
|
485
651
|
const parseData = result.data;
|
|
486
652
|
const platform = getPlatformType(url);
|
|
487
653
|
const text = generateFormattedText(platform, parseData, config);
|
|
488
654
|
return {
|
|
489
|
-
data: {
|
|
490
|
-
|
|
655
|
+
data: {
|
|
656
|
+
text,
|
|
657
|
+
cover: parseData.cover,
|
|
658
|
+
images: parseData.images,
|
|
659
|
+
video: parseData.video,
|
|
660
|
+
type: parseData.type,
|
|
661
|
+
live_photo: parseData.live_photo
|
|
662
|
+
},
|
|
663
|
+
code: ErrorCode.SUCCESS,
|
|
664
|
+
msg: getErrorInfo(ErrorCode.SUCCESS)
|
|
491
665
|
};
|
|
492
666
|
}
|
|
493
667
|
async function sendTimeout(session, content) {
|
|
494
668
|
if (config.videoSendTimeout <= 0) {
|
|
495
669
|
return session.send(content).catch((err) => {
|
|
670
|
+
const errorMsg = getErrorMessage(err);
|
|
671
|
+
logger.error(`[${ErrorCode.MESSAGE_SEND_FAILED}] 发送消息失败: ${errorMsg}`);
|
|
496
672
|
if (!config.ignoreSendError)
|
|
497
|
-
|
|
673
|
+
return null;
|
|
498
674
|
return null;
|
|
499
675
|
});
|
|
500
676
|
}
|
|
@@ -502,8 +678,11 @@ function apply(ctx, config) {
|
|
|
502
678
|
session.send(content),
|
|
503
679
|
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), config.videoSendTimeout))
|
|
504
680
|
]).catch((err) => {
|
|
681
|
+
const errorMsg = getErrorMessage(err);
|
|
682
|
+
const code = errorMsg.includes('timeout') ? ErrorCode.MESSAGE_SEND_TIMEOUT : ErrorCode.MESSAGE_SEND_FAILED;
|
|
683
|
+
logger.error(`[${code}] 发送消息失败: ${errorMsg}`);
|
|
505
684
|
if (!config.ignoreSendError)
|
|
506
|
-
|
|
685
|
+
return null;
|
|
507
686
|
return null;
|
|
508
687
|
});
|
|
509
688
|
}
|
|
@@ -516,43 +695,32 @@ function apply(ctx, config) {
|
|
|
516
695
|
linkBuffer.delete(key);
|
|
517
696
|
}
|
|
518
697
|
const items = [];
|
|
519
|
-
const
|
|
698
|
+
const errors = [];
|
|
520
699
|
for (const url of urls) {
|
|
521
700
|
const result = await processSingleUrl(session, url);
|
|
522
701
|
if (result.data) {
|
|
523
702
|
items.push(result.data);
|
|
524
703
|
}
|
|
525
704
|
else {
|
|
526
|
-
|
|
527
|
-
logger.error(
|
|
705
|
+
errors.push({ url, code: result.code, msg: result.msg });
|
|
706
|
+
logger.error(`[${result.code}] 解析失败: ${url}, 原因: ${result.msg}`);
|
|
528
707
|
}
|
|
529
708
|
}
|
|
530
|
-
if (
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (errs.length) {
|
|
537
|
-
const errorMsg = `⚠ 部分解析失败\n${errs.join('\n')}`;
|
|
538
|
-
if (enableForward) {
|
|
539
|
-
forwardMessages.push(buildForwardNode(session, errorMsg, botName));
|
|
540
|
-
}
|
|
541
|
-
else {
|
|
542
|
-
await sendTimeout(session, errorMsg);
|
|
543
|
-
await delay(600);
|
|
544
|
-
}
|
|
709
|
+
if (errors.length > 0) {
|
|
710
|
+
const errorLines = errors.map(err => `【${err.url}】: ${err.msg}`);
|
|
711
|
+
const errorMsg = `❌ 解析失败列表(共${errors.length}个链接):\n${errorLines.join('\n')}`;
|
|
712
|
+
logger.error(`解析失败数量: ${errors.length}, 错误码列表: ${errors.map(e => e.code).join(', ')}`);
|
|
713
|
+
await sendTimeout(session, errorMsg);
|
|
714
|
+
await delay(500);
|
|
545
715
|
}
|
|
546
716
|
if (items.length === 0) {
|
|
547
|
-
const failMsg =
|
|
548
|
-
|
|
549
|
-
forwardMessages.push(buildForwardNode(session, failMsg, botName));
|
|
550
|
-
}
|
|
551
|
-
else {
|
|
552
|
-
await sendTimeout(session, failMsg);
|
|
553
|
-
}
|
|
717
|
+
const failMsg = getErrorInfo(ErrorCode.UNKNOWN_ERROR, '所有链接均解析失败,请检查链接是否有效或稍后重试');
|
|
718
|
+
await sendTimeout(session, `⚠ ${failMsg}`);
|
|
554
719
|
return;
|
|
555
720
|
}
|
|
721
|
+
const enableForward = config.enableForward && session.platform === 'onebot';
|
|
722
|
+
const forwardMessages = [];
|
|
723
|
+
const botName = config.botName || '视频解析机器人';
|
|
556
724
|
for (const item of items) {
|
|
557
725
|
try {
|
|
558
726
|
if (enableForward) {
|
|
@@ -565,28 +733,60 @@ function apply(ctx, config) {
|
|
|
565
733
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.images[i]), botName));
|
|
566
734
|
}
|
|
567
735
|
}
|
|
736
|
+
if (item.type === 'live' || item.type === 'live_photo') {
|
|
737
|
+
if (item.live_photo?.length) {
|
|
738
|
+
for (let i = 0; i < item.live_photo.length && forwardMessages.length < 100; i++) {
|
|
739
|
+
const liveItem = item.live_photo[i];
|
|
740
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(liveItem.image), botName));
|
|
741
|
+
if (liveItem.video) {
|
|
742
|
+
try {
|
|
743
|
+
const videoElem = koishi_1.h.video(liveItem.video);
|
|
744
|
+
forwardMessages.push(buildForwardNode(session, videoElem, botName));
|
|
745
|
+
}
|
|
746
|
+
catch (e) {
|
|
747
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.text(`视频链接: ${liveItem.video}`), botName));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
else if (item.images?.length) {
|
|
753
|
+
for (let i = 0; i < item.images.length && forwardMessages.length < 100; i++) {
|
|
754
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(item.images[i]), botName));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
568
758
|
if (item.video && config.showVideoFile && forwardMessages.length < 100) {
|
|
569
759
|
let videoElem;
|
|
570
760
|
try {
|
|
571
761
|
if (config.downloadVideoBeforeSend) {
|
|
572
762
|
const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
|
|
573
|
-
const
|
|
574
|
-
|
|
763
|
+
const downloadResult = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
|
|
764
|
+
if (downloadResult.code !== ErrorCode.SUCCESS) {
|
|
765
|
+
const errorMsg = getErrorInfo(downloadResult.code);
|
|
766
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.text(`${errorMsg}\n链接:${item.video}`), botName));
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
videoElem = koishi_1.h.file(downloadResult.filePath);
|
|
770
|
+
forwardMessages.push(buildForwardNode(session, videoElem, botName));
|
|
771
|
+
}
|
|
575
772
|
}
|
|
576
773
|
else {
|
|
577
774
|
const fileSize = await getFileSize(item.video, config.userAgent);
|
|
578
775
|
if (config.maxVideoSize > 0 && fileSize > config.maxVideoSize) {
|
|
579
|
-
|
|
776
|
+
const code = ErrorCode.VIDEO_SIZE_EXCEEDED;
|
|
777
|
+
const errorMsg = getErrorInfo(code, `${fileSize}MB超过限制${config.maxVideoSize}MB`);
|
|
778
|
+
videoElem = koishi_1.h.text(`${errorMsg},仅发送链接:${item.video}`);
|
|
580
779
|
}
|
|
581
780
|
else {
|
|
582
781
|
videoElem = koishi_1.h.video(item.video);
|
|
583
782
|
}
|
|
584
783
|
}
|
|
585
|
-
forwardMessages.push(buildForwardNode(session, videoElem, botName));
|
|
586
784
|
}
|
|
587
785
|
catch (error) {
|
|
588
|
-
|
|
589
|
-
|
|
786
|
+
const errorMsg = getErrorMessage(error);
|
|
787
|
+
const code = ErrorCode.VIDEO_DOWNLOAD_FAILED;
|
|
788
|
+
logger.error(`[${code}] 视频处理失败: ${errorMsg}`);
|
|
789
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.text(getErrorInfo(code, errorMsg) + `\n链接:${item.video}`), botName));
|
|
590
790
|
}
|
|
591
791
|
}
|
|
592
792
|
}
|
|
@@ -595,7 +795,28 @@ function apply(ctx, config) {
|
|
|
595
795
|
await sendTimeout(session, item.text);
|
|
596
796
|
await delay(300);
|
|
597
797
|
}
|
|
598
|
-
if (item.type === '
|
|
798
|
+
if (item.type === 'live' || item.type === 'live_photo') {
|
|
799
|
+
if (item.live_photo?.length) {
|
|
800
|
+
for (const liveItem of item.live_photo) {
|
|
801
|
+
await sendTimeout(session, koishi_1.h.image(liveItem.image));
|
|
802
|
+
await delay(200);
|
|
803
|
+
if (liveItem.video) {
|
|
804
|
+
try {
|
|
805
|
+
await sendTimeout(session, koishi_1.h.video(liveItem.video));
|
|
806
|
+
}
|
|
807
|
+
catch (e) {
|
|
808
|
+
await sendTimeout(session, koishi_1.h.text(`Live Photo 视频链接: ${liveItem.video}`));
|
|
809
|
+
}
|
|
810
|
+
await delay(300);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
else if (item.images?.length) {
|
|
815
|
+
const imgMsg = (0, koishi_1.h)('message', ...item.images.map((url) => koishi_1.h.image(url)));
|
|
816
|
+
await sendTimeout(session, imgMsg);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
else if (item.type === 'image' && item.images?.length) {
|
|
599
820
|
const imgMsg = (0, koishi_1.h)('message', ...item.images.map((url) => koishi_1.h.image(url)));
|
|
600
821
|
await sendTimeout(session, imgMsg);
|
|
601
822
|
}
|
|
@@ -609,13 +830,22 @@ function apply(ctx, config) {
|
|
|
609
830
|
let videoElem;
|
|
610
831
|
if (config.downloadVideoBeforeSend) {
|
|
611
832
|
const filename = crypto_1.default.createHash('md5').update(item.video).digest('hex');
|
|
612
|
-
const
|
|
613
|
-
|
|
833
|
+
const downloadResult = await downloadVideo(item.video, filename, config.userAgent, config.maxVideoSize, config.downloadThreads);
|
|
834
|
+
if (downloadResult.code !== ErrorCode.SUCCESS) {
|
|
835
|
+
const errorMsg = getErrorInfo(downloadResult.code);
|
|
836
|
+
await sendTimeout(session, koishi_1.h.text(`${errorMsg}\n链接:${item.video}`));
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
videoElem = koishi_1.h.file(downloadResult.filePath);
|
|
841
|
+
}
|
|
614
842
|
}
|
|
615
843
|
else {
|
|
616
844
|
const fileSize = await getFileSize(item.video, config.userAgent);
|
|
617
845
|
if (config.maxVideoSize > 0 && fileSize > config.maxVideoSize) {
|
|
618
|
-
|
|
846
|
+
const code = ErrorCode.VIDEO_SIZE_EXCEEDED;
|
|
847
|
+
const errorMsg = getErrorInfo(code, `${fileSize}MB超过限制${config.maxVideoSize}MB`);
|
|
848
|
+
videoElem = koishi_1.h.text(`${errorMsg},仅发送链接:${item.video}`);
|
|
619
849
|
}
|
|
620
850
|
else {
|
|
621
851
|
videoElem = koishi_1.h.video(item.video);
|
|
@@ -624,8 +854,10 @@ function apply(ctx, config) {
|
|
|
624
854
|
await sendTimeout(session, videoElem);
|
|
625
855
|
}
|
|
626
856
|
catch (error) {
|
|
627
|
-
|
|
628
|
-
|
|
857
|
+
const errorMsg = getErrorMessage(error);
|
|
858
|
+
const code = ErrorCode.VIDEO_DOWNLOAD_FAILED;
|
|
859
|
+
logger.error(`[${code}] 视频处理失败: ${errorMsg}`);
|
|
860
|
+
await sendTimeout(session, koishi_1.h.text(getErrorInfo(code, errorMsg) + `\n链接:${item.video}`));
|
|
629
861
|
}
|
|
630
862
|
}
|
|
631
863
|
}
|
|
@@ -633,8 +865,10 @@ function apply(ctx, config) {
|
|
|
633
865
|
}
|
|
634
866
|
}
|
|
635
867
|
catch (error) {
|
|
636
|
-
|
|
637
|
-
|
|
868
|
+
const errorMsg = getErrorMessage(error);
|
|
869
|
+
const code = ErrorCode.UNKNOWN_ERROR;
|
|
870
|
+
logger.error(`[${code}] 处理内容失败: ${errorMsg}`);
|
|
871
|
+
await sendTimeout(session, koishi_1.h.text(getErrorInfo(code, `处理${item.type}内容失败: ${errorMsg}`)));
|
|
638
872
|
}
|
|
639
873
|
}
|
|
640
874
|
if (enableForward && forwardMessages.length) {
|
|
@@ -644,7 +878,9 @@ function apply(ctx, config) {
|
|
|
644
878
|
await sendTimeout(session, forwardMsg);
|
|
645
879
|
}
|
|
646
880
|
catch (error) {
|
|
647
|
-
|
|
881
|
+
const errorMsg = getErrorMessage(error);
|
|
882
|
+
const code = ErrorCode.FORWARD_MESSAGE_FAILED;
|
|
883
|
+
logger.error(`[${code}] 合并转发失败: ${errorMsg}`);
|
|
648
884
|
for (const node of forwardMessages) {
|
|
649
885
|
await sendTimeout(session, node.data.content);
|
|
650
886
|
await delay(500);
|
|
@@ -687,21 +923,25 @@ function apply(ctx, config) {
|
|
|
687
923
|
});
|
|
688
924
|
ctx.command('parse <url>', '手动解析视频链接')
|
|
689
925
|
.action(async ({ session }, url) => {
|
|
690
|
-
if (!url)
|
|
691
|
-
|
|
926
|
+
if (!url) {
|
|
927
|
+
const code = ErrorCode.INVALID_URL;
|
|
928
|
+
return getErrorInfo(code, '请输入视频链接');
|
|
929
|
+
}
|
|
692
930
|
let urls = extractUrl(url);
|
|
693
931
|
if (urls.length === 0 && hasPlatformKeyword(url)) {
|
|
694
932
|
const allLinks = url.match(/https?:\/\/[^\s\"\'\>\]]+/gi) || [];
|
|
695
933
|
urls = allLinks.filter((u) => getPlatformType(u));
|
|
696
934
|
}
|
|
697
|
-
if (urls.length === 0)
|
|
698
|
-
|
|
935
|
+
if (urls.length === 0) {
|
|
936
|
+
const code = ErrorCode.UNSUPPORTED_PLATFORM;
|
|
937
|
+
return getErrorInfo(code, '不支持该链接');
|
|
938
|
+
}
|
|
699
939
|
await flush(session, urls);
|
|
700
940
|
});
|
|
701
941
|
ctx.command('clear-cache', '清空解析缓存与临时文件')
|
|
702
942
|
.action(() => {
|
|
703
943
|
clearAllCache();
|
|
704
|
-
return '
|
|
944
|
+
return getErrorInfo(ErrorCode.SUCCESS, '解析缓存已清空');
|
|
705
945
|
});
|
|
706
946
|
setInterval(() => {
|
|
707
947
|
const now = Date.now();
|
|
@@ -720,18 +960,24 @@ function apply(ctx, config) {
|
|
|
720
960
|
const stat = fs_1.default.statSync(path_1.default.join(tempDir, file));
|
|
721
961
|
if (now - stat.mtimeMs > 3600000) {
|
|
722
962
|
fs_1.default.unlinkSync(path_1.default.join(tempDir, file));
|
|
963
|
+
logger.info(`清理过期临时文件: ${file}`);
|
|
723
964
|
}
|
|
724
965
|
}
|
|
725
966
|
catch (error) {
|
|
726
|
-
|
|
967
|
+
const errorMsg = getErrorMessage(error);
|
|
968
|
+
logger.error(`[${ErrorCode.UNKNOWN_ERROR}] 清理临时文件失败: ${file}, ${errorMsg}`);
|
|
727
969
|
}
|
|
728
970
|
});
|
|
729
971
|
}, 1800000);
|
|
730
972
|
if (config.autoClearCacheInterval > 0) {
|
|
731
973
|
setInterval(() => {
|
|
732
974
|
clearAllCache();
|
|
975
|
+
logger.info(getErrorInfo(ErrorCode.SUCCESS, '自动清理缓存完成'));
|
|
733
976
|
}, config.autoClearCacheInterval * 60000);
|
|
734
977
|
}
|
|
735
|
-
process.on('exit',
|
|
736
|
-
|
|
978
|
+
process.on('exit', () => {
|
|
979
|
+
clearAllCache();
|
|
980
|
+
logger.info(getErrorInfo(ErrorCode.SUCCESS, '进程退出,已清理缓存'));
|
|
981
|
+
});
|
|
982
|
+
logger.info(getErrorInfo(ErrorCode.SUCCESS, '视频解析插件已加载'));
|
|
737
983
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
- 🎨 自定义解析结果格式、返回内容类型(封面/链接/视频)
|
|
9
9
|
- ⚡ 内置防重复解析、接口重试、自动缓存清理等实用功能
|
|
10
10
|
- 📤 支持 OneBot 平台消息合并转发,优化展示体验
|
|
11
|
-
- 🔌 内置多套解析 API,自动降级容错,提升解析成功率
|
|
12
11
|
|
|
13
12
|
### English
|
|
14
13
|
This is a **video parsing plugin** developed for the Koishi bot framework, supporting automatic recognition and parsing of short video links from mainstream platforms such as Douyin, Kuaishou, Bilibili, Xiaohongshu, Weibo, Toutiao, Pipi Funny, Pipi Shrimp, and Zuiyou. Core features:
|
|
@@ -16,7 +15,6 @@ This is a **video parsing plugin** developed for the Koishi bot framework, suppo
|
|
|
16
15
|
- 🎨 Customize the parsing result format and return content type (cover/link/video)
|
|
17
16
|
- ⚡ Built-in duplicate prevention, retry logic, auto cache cleanup
|
|
18
17
|
- 📤 Support OneBot message forwarding for better display experience
|
|
19
|
-
- 🔌 Multiple built-in parsing APIs with automatic failover
|
|
20
18
|
|
|
21
19
|
## 项目仓库 (Repository)
|
|
22
20
|
- GitHub: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all`
|
|
@@ -76,8 +74,6 @@ This is a **video parsing plugin** developed for the Koishi bot framework, suppo
|
|
|
76
74
|
|----------------------|-------------------------|
|
|
77
75
|
| Minecraft-1314 | 插件完整开发 (Complete plugin development) |
|
|
78
76
|
| JH-Ahua | BugPk-Api 支持 |
|
|
79
|
-
| 素颜API | 素颜API 支持 |
|
|
80
|
-
| 星之阁API | 星之阁API 支持 |
|
|
81
77
|
| (欢迎提交 PR 加入贡献者列表) | (Welcome to submit PR to join the contributor list) |
|
|
82
78
|
|
|
83
79
|
## 许可协议 (License)
|