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.
Files changed (2) hide show
  1. package/lib/index.js +70 -98
  2. 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
- const PLATFORM_KEYWORDS = {
58
- bilibili: ['bilibili', 'b23', 'www.bilibili.com', 'm.bilibili.com', 'b23.tv', 't.bilibili.com', 'bilibili.com/video', 'bilibili.com/opus', 'bilibili.com/bangumi'],
59
- kuaishou: ['kuaishou', 'v.kuaishou.com', 'www.kuaishou.com', 'kwimgs.com'],
60
- weibo: ['weibo', 'weibo.com', 'video.weibo.com', 'm.weibo.cn', 'weibo.com/tv/show', 'weibo.com/feed'],
61
- toutiao: ['toutiao', 'm.toutiao.com', 'toutiao.com', 'ixigua.com', 'toutiao.com/video'],
62
- pipigx: ['pipigx', 'h5.pipigx.com', 'ippzone.com'],
63
- pipixia: ['pipixia', 'pipix', 'h5.pipix.com', 'ppxsign.byteimg.com', 'pipix.com'],
64
- douyin: ['douyin', 'v.douyin.com', 'douyinpic.com', 'douyinvod.com', 'douyin.com/video', 'douyin.com/note', 'www.douyin.com'],
65
- zuiyou: ['zuiyou', 'xiaochuankeji.cn', 'izuiyou.com'],
66
- xiaohongshu: ['xiaohongshu', 'xhslink.com', 'www.xiaohongshu.com'],
67
- jianying: ['jianying', 'jimeng.jianying.com', 'lv.ulikecam.com'],
68
- acfun: ['acfun', 'acfun.cn', 'www.acfun.cn'],
69
- zhihu: ['zhihu', 'zhihu.com', 'www.zhihu.com'],
70
- weishi: ['weishi', 'weishi.qq.com'],
71
- huya: ['huya', 'huya.com', 'www.huya.com'],
72
- youtube: ['youtube', 'youtube.com', 'youtu.be', 'www.youtube.com'],
73
- tiktok: ['tiktok', 'tiktok.com', 'www.tiktok.com'],
74
- xigua: ['xigua', 'ixigua.com'],
75
- haokan: ['haokan', 'haokan.baidu.com'],
76
- li: ['video.li'],
77
- meipai: ['meipai', 'meipai.com'],
78
- quanmin: ['quanmin', 'quanmin.tv'],
79
- twitter: ['twitter', 'x.com'],
80
- instagram: ['instagram', 'instagram.com'],
81
- doubao: ['doubao', 'doubao.com'],
82
- jimeng: ['jimeng', 'jimeng.ai'],
83
- };
84
- function getErrorMessage(error) {
85
- if (error instanceof Error)
86
- return error.message;
87
- return String(error);
88
- }
89
- const xmlParser = new fast_xml_parser_1.XMLParser({
90
- ignoreAttributes: false,
91
- attributeNamePrefix: '@_',
92
- allowBooleanAttributes: true,
93
- trimValues: true,
94
- parseTagValue: false,
95
- isArray: (name) => name === 'item' || name === 'picture',
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);
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
- catch (err) {
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 = extractUrlsFromXml(elem.data);
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 Object.values(PLATFORM_KEYWORDS).some(group => group.some(keyword => hostname.includes(keyword) || (!keyword.includes('.') && url.toLowerCase().includes(keyword))));
162
+ return true;
178
163
  }
179
164
  catch {
180
- const lower = url.toLowerCase();
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(/&amp;/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+平台,新增XML卡片链接提取",
4
- "version": "0.9.7",
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": [