koishi-plugin-video-parser-all 0.5.3 → 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.js +74 -19
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -38,8 +38,8 @@ 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平台)'),
|
|
@@ -115,7 +115,7 @@ const logger = new koishi_1.Logger(exports.name);
|
|
|
115
115
|
const PLATFORM_KEYWORDS = {
|
|
116
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'],
|
|
117
117
|
kuaishou: ['kuaishou', '快手', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com', 'kuaishou.com/app'],
|
|
118
|
-
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'],
|
|
119
119
|
weibo: ['weibo', '微博', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
|
|
120
120
|
toutiao: ['toutiao', '今日头条', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video', 'ixigua.com/i'],
|
|
121
121
|
pipigx: ['pipigx', '皮皮搞笑', 'h5.pipigx.com', 'ippzone.com', 'pipigx.com/share'],
|
|
@@ -139,7 +139,11 @@ const PLATFORM_ERROR_CODE_MAP = {
|
|
|
139
139
|
xiaohongshu: ErrorCode.XIAOHONGSHU_PARSE_FAILED,
|
|
140
140
|
bilibili: ErrorCode.BILIBILI_PARSE_FAILED,
|
|
141
141
|
kuaishou: ErrorCode.KUAISHOU_PARSE_FAILED,
|
|
142
|
-
weibo: ErrorCode.WEIBO_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
|
|
143
147
|
};
|
|
144
148
|
const VARIABLE_MAPPING = {
|
|
145
149
|
'标题': ['title', 'Title', 'TITLE'],
|
|
@@ -315,19 +319,41 @@ function getPlatformType(url) {
|
|
|
315
319
|
return 'zuiyou';
|
|
316
320
|
return null;
|
|
317
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
|
+
}
|
|
318
344
|
async function resolveShortUrl(url) {
|
|
319
345
|
try {
|
|
320
346
|
const res = await axios_1.default.head(url, {
|
|
321
|
-
timeout:
|
|
322
|
-
maxRedirects:
|
|
347
|
+
timeout: 10000,
|
|
348
|
+
maxRedirects: 10,
|
|
323
349
|
headers: {
|
|
324
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'
|
|
325
351
|
}
|
|
326
352
|
});
|
|
327
|
-
return res.request.res?.responseUrl || url;
|
|
353
|
+
return cleanUrl(res.request.res?.responseUrl || url);
|
|
328
354
|
}
|
|
329
355
|
catch (e) {
|
|
330
|
-
return url;
|
|
356
|
+
return cleanUrl(url);
|
|
331
357
|
}
|
|
332
358
|
}
|
|
333
359
|
function formatDuration(input) {
|
|
@@ -512,8 +538,28 @@ function apply(ctx, config) {
|
|
|
512
538
|
timeout: config.timeout,
|
|
513
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' }
|
|
514
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
|
+
}
|
|
515
560
|
async function parse(url) {
|
|
516
|
-
|
|
561
|
+
let realUrl = await resolveShortUrl(url);
|
|
562
|
+
realUrl = cleanUrl(realUrl);
|
|
517
563
|
const platform = getPlatformType(realUrl);
|
|
518
564
|
if (!platform) {
|
|
519
565
|
const code = ErrorCode.UNSUPPORTED_PLATFORM;
|
|
@@ -529,23 +575,32 @@ function apply(ctx, config) {
|
|
|
529
575
|
return { data: null, code, msg };
|
|
530
576
|
}
|
|
531
577
|
try {
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (
|
|
537
|
-
const apiErrorMsg =
|
|
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 || '解析失败';
|
|
538
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
|
+
}
|
|
539
589
|
const code = platformCode;
|
|
540
|
-
const msg = getErrorInfo(code,
|
|
590
|
+
const msg = getErrorInfo(code, detailedMsg);
|
|
541
591
|
logger.error(`[${code}] API返回错误: ${platform}, URL: ${url}, 错误: ${apiErrorMsg}`);
|
|
542
592
|
return { data: null, code, msg };
|
|
543
593
|
}
|
|
544
594
|
try {
|
|
545
|
-
const parseResult = parseData(
|
|
546
|
-
|
|
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) {
|
|
547
602
|
const code = ErrorCode.NO_VIDEO_FOUND;
|
|
548
|
-
const msg = getErrorInfo(code, '
|
|
603
|
+
const msg = getErrorInfo(code, '链接有效但未找到视频/图片内容(可能是直播、私密内容或已删除)');
|
|
549
604
|
logger.warn(`[${code}] 解析成功但无有效内容: ${platform}, URL: ${url}`);
|
|
550
605
|
return { data: null, code, msg };
|
|
551
606
|
}
|
package/package.json
CHANGED