koishi-plugin-video-parser-all 0.9.7 → 0.9.8
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 +70 -98
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -7,7 +7,6 @@ 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 fast_xml_parser_1 = require("fast-xml-parser");
|
|
11
10
|
exports.name = 'video-parser-all';
|
|
12
11
|
exports.Config = koishi_1.Schema.intersect([
|
|
13
12
|
koishi_1.Schema.object({
|
|
@@ -54,85 +53,62 @@ function debugLog(level, ...args) {
|
|
|
54
53
|
const message = `[${timestamp}] [${level}] ${args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')}`;
|
|
55
54
|
logger.info(message);
|
|
56
55
|
}
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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);
|
|
108
|
-
}
|
|
109
|
-
const items = Array.isArray(msg.item) ? msg.item : (msg.item ? [msg.item] : []);
|
|
110
|
-
for (const item of items) {
|
|
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
|
-
}
|
|
56
|
+
function linkTypeParser(content) {
|
|
57
|
+
content = content.replace(/\\\//g, '/');
|
|
58
|
+
const rules = [
|
|
59
|
+
{ pattern: /bilibili\.com\/video\/([ab]v[0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://www.bilibili.com/video/${id}` },
|
|
60
|
+
{ pattern: /b23\.tv(?:\\)?\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://b23.tv/${id}` },
|
|
61
|
+
{ pattern: /bili(?:22|23|33)\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili23.cn/${id}` },
|
|
62
|
+
{ pattern: /bili2233\.cn\/([0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://bili2233.cn/${id}` },
|
|
63
|
+
{ pattern: /douyin\.com\/video\/(\d+)/gi, type: 'douyin', buildUrl: (id) => `https://www.douyin.com/video/${id}` },
|
|
64
|
+
{ pattern: /v\.douyin\.com\/([0-9a-zA-Z]+)/gi, type: 'douyin', buildUrl: (id) => `https://v.douyin.com/${id}` },
|
|
65
|
+
{ pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://www.kuaishou.com/short-video/${id}` },
|
|
66
|
+
{ pattern: /v\.kuaishou\.com\/([0-9a-zA-Z]+)/gi, type: 'kuaishou', buildUrl: (id) => `https://v.kuaishou.com/${id}` },
|
|
67
|
+
{ pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://www.xiaohongshu.com/discovery/item/${id}` },
|
|
68
|
+
{ pattern: /xhslink\.com\/([0-9a-zA-Z]+)/gi, type: 'xiaohongshu', buildUrl: (id) => `https://xhslink.com/${id}` },
|
|
69
|
+
{ pattern: /weibo\.com\/\d+\/([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://weibo.com/${id}` },
|
|
70
|
+
{ pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z]+)/gi, type: 'weibo', buildUrl: (id) => `https://video.weibo.com/show?fid=${id}` },
|
|
71
|
+
{ pattern: /ixigua\.com\/(\d+)/gi, type: 'xigua', buildUrl: (id) => `https://www.ixigua.com/${id}` },
|
|
72
|
+
{ pattern: /youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://www.youtube.com/watch?v=${id}` },
|
|
73
|
+
{ pattern: /youtu\.be\/([a-zA-Z0-9_-]+)/gi, type: 'youtube', buildUrl: (id) => `https://youtu.be/${id}` },
|
|
74
|
+
{ pattern: /tiktok\.com\/@[\w.]+\/video\/(\d+)/gi, type: 'tiktok', buildUrl: (id) => `https://www.tiktok.com/@user/video/${id}` },
|
|
75
|
+
{ pattern: /vm\.tiktok\.com\/([0-9a-zA-Z]+)/gi, type: 'tiktok', buildUrl: (id) => `https://vm.tiktok.com/${id}` },
|
|
76
|
+
{ pattern: /acfun\.cn\/v\/(ac\d+)/gi, type: 'acfun', buildUrl: (id) => `https://www.acfun.cn/v/${id}` },
|
|
77
|
+
{ pattern: /zhihu\.com\/video\/(\d+)/gi, type: 'zhihu', buildUrl: (id) => `https://www.zhihu.com/video/${id}` },
|
|
78
|
+
{ pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z]+)/gi, type: 'weishi', buildUrl: (id) => `https://weishi.qq.com/weishi/feed/${id}` },
|
|
79
|
+
{ pattern: /huya\.com\/video\/([0-9a-zA-Z]+)/gi, type: 'huya', buildUrl: (id) => `https://www.huya.com/video/${id}` },
|
|
80
|
+
{ pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z]+)/gi, type: 'haokan', buildUrl: (id) => `https://haokan.baidu.com/v?vid=${id}` },
|
|
81
|
+
{ pattern: /meipai\.com\/media\/(\d+)/gi, type: 'meipai', buildUrl: (id) => `https://www.meipai.com/media/${id}` },
|
|
82
|
+
{ pattern: /twitter\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://twitter.com/i/status/${id}` },
|
|
83
|
+
{ pattern: /x\.com\/\w+\/status\/(\d+)/gi, type: 'twitter', buildUrl: (id) => `https://x.com/i/status/${id}` },
|
|
84
|
+
{ pattern: /instagram\.com\/p\/([0-9a-zA-Z_-]+)/gi, type: 'instagram', buildUrl: (id) => `https://www.instagram.com/p/${id}` },
|
|
85
|
+
{ pattern: /doubao\.com\/video\/(\d+)/gi, type: 'doubao', buildUrl: (id) => `https://www.doubao.com/video/${id}` },
|
|
86
|
+
];
|
|
87
|
+
const matches = [];
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
for (const rule of rules) {
|
|
90
|
+
let match;
|
|
91
|
+
while ((match = rule.pattern.exec(content)) !== null) {
|
|
92
|
+
const id = match[1];
|
|
93
|
+
if (seen.has(id))
|
|
94
|
+
continue;
|
|
95
|
+
seen.add(id);
|
|
96
|
+
const url = rule.buildUrl(id);
|
|
97
|
+
matches.push({ type: rule.type, url, id });
|
|
126
98
|
}
|
|
127
99
|
}
|
|
128
|
-
|
|
129
|
-
debugLog('WARN', `解析 XML 卡片失败: ${getErrorMessage(err)}`);
|
|
130
|
-
}
|
|
131
|
-
return urls;
|
|
100
|
+
return matches;
|
|
132
101
|
}
|
|
133
102
|
function extractAllUrlsFromMessage(session) {
|
|
134
|
-
const urls = [];
|
|
135
103
|
const content = session.content?.trim() || '';
|
|
104
|
+
const urls = [];
|
|
105
|
+
const linkMatches = linkTypeParser(content);
|
|
106
|
+
if (linkMatches.length > 0) {
|
|
107
|
+
for (const match of linkMatches) {
|
|
108
|
+
urls.push(match.url);
|
|
109
|
+
}
|
|
110
|
+
return [...new Set(urls)];
|
|
111
|
+
}
|
|
136
112
|
if (content) {
|
|
137
113
|
const textUrls = extractUrl(content);
|
|
138
114
|
urls.push(...textUrls);
|
|
@@ -140,7 +116,7 @@ function extractAllUrlsFromMessage(session) {
|
|
|
140
116
|
if (session.elements) {
|
|
141
117
|
for (const elem of session.elements) {
|
|
142
118
|
if (elem.type === 'xml' && elem.data) {
|
|
143
|
-
const xmlUrls =
|
|
119
|
+
const xmlUrls = extractUrlsFromXmlLegacy(elem.data);
|
|
144
120
|
urls.push(...xmlUrls);
|
|
145
121
|
}
|
|
146
122
|
else if (elem.type === 'json' && elem.data) {
|
|
@@ -167,6 +143,15 @@ function extractAllUrlsFromMessage(session) {
|
|
|
167
143
|
}
|
|
168
144
|
return [...new Set(urls)];
|
|
169
145
|
}
|
|
146
|
+
function extractUrlsFromXmlLegacy(xml) {
|
|
147
|
+
const urls = [];
|
|
148
|
+
const urlRegex = /https?:\/\/[^\s<>"']+/gi;
|
|
149
|
+
let match;
|
|
150
|
+
while ((match = urlRegex.exec(xml)) !== null) {
|
|
151
|
+
urls.push(match[0]);
|
|
152
|
+
}
|
|
153
|
+
return urls;
|
|
154
|
+
}
|
|
170
155
|
function extractUrl(content) {
|
|
171
156
|
const urlMatches = content.match(/https?:\/\/[^\s\"\'\>]+/gi) || [];
|
|
172
157
|
return urlMatches.filter(url => {
|
|
@@ -174,27 +159,13 @@ function extractUrl(content) {
|
|
|
174
159
|
const hostname = new URL(url).hostname.toLowerCase();
|
|
175
160
|
if (hostname === 'multimedia.nt.qq.com.cn')
|
|
176
161
|
return false;
|
|
177
|
-
return
|
|
162
|
+
return true;
|
|
178
163
|
}
|
|
179
164
|
catch {
|
|
180
|
-
|
|
181
|
-
return Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => lower.includes(keyword)));
|
|
165
|
+
return false;
|
|
182
166
|
}
|
|
183
167
|
});
|
|
184
168
|
}
|
|
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
169
|
function cleanUrl(url) {
|
|
199
170
|
try {
|
|
200
171
|
url = url.replace(/&/g, '&');
|
|
@@ -219,7 +190,7 @@ async function resolveShortUrl(url) {
|
|
|
219
190
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
220
191
|
'Referer': 'https://www.baidu.com/',
|
|
221
192
|
},
|
|
222
|
-
validateStatus: status => status >= 200 && status < 400,
|
|
193
|
+
validateStatus: (status) => status >= 200 && status < 400,
|
|
223
194
|
});
|
|
224
195
|
const finalUrl = res.request?.res?.responseUrl || url;
|
|
225
196
|
return cleanUrl(finalUrl);
|
|
@@ -443,10 +414,6 @@ function apply(ctx, config) {
|
|
|
443
414
|
}
|
|
444
415
|
async function parseUrl(url) {
|
|
445
416
|
const realUrl = await resolveShortUrl(url);
|
|
446
|
-
const platform = getPlatformType(realUrl);
|
|
447
|
-
if (!platform) {
|
|
448
|
-
return { success: false, msg: texts.unsupportedPlatformText };
|
|
449
|
-
}
|
|
450
417
|
const candidates = [realUrl, url];
|
|
451
418
|
for (const candidate of [...new Set(candidates)]) {
|
|
452
419
|
try {
|
|
@@ -457,7 +424,7 @@ function apply(ctx, config) {
|
|
|
457
424
|
debugLog('ERROR', `候选链接解析失败: ${candidate}`, getErrorMessage(error));
|
|
458
425
|
}
|
|
459
426
|
}
|
|
460
|
-
return { success: false, msg:
|
|
427
|
+
return { success: false, msg: texts.unsupportedPlatformText };
|
|
461
428
|
}
|
|
462
429
|
async function processSingleUrl(url) {
|
|
463
430
|
const result = await parseUrl(url);
|
|
@@ -623,3 +590,8 @@ function apply(ctx, config) {
|
|
|
623
590
|
}, 60000);
|
|
624
591
|
debugLog('INFO', '插件初始化完成');
|
|
625
592
|
}
|
|
593
|
+
function getErrorMessage(error) {
|
|
594
|
+
if (error instanceof Error)
|
|
595
|
+
return error.message;
|
|
596
|
+
return String(error);
|
|
597
|
+
}
|
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.8",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|