koishi-plugin-video-parser-all 1.0.9 → 1.1.1

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 CHANGED
@@ -29,10 +29,10 @@ export declare const Config: Schema<{
29
29
  primaryApiUrl?: string | null | undefined;
30
30
  backupApiUrl?: string | null | undefined;
31
31
  useDedicatedApiFirst?: boolean | null | undefined;
32
+ platformDedicatedFirst?: import("cosmokit").Dict<boolean, string> | null | undefined;
32
33
  customApis?: ({
33
34
  platform?: "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | null | undefined;
34
35
  apiUrl?: string | null | undefined;
35
- useDedicatedFirst?: boolean | null | undefined;
36
36
  } & import("cosmokit").Dict)[] | null | undefined;
37
37
  } & {
38
38
  waitingTipText?: string | null | undefined;
@@ -69,10 +69,10 @@ export declare const Config: Schema<{
69
69
  primaryApiUrl: string;
70
70
  backupApiUrl: string;
71
71
  useDedicatedApiFirst: boolean;
72
+ platformDedicatedFirst: import("cosmokit").Dict<boolean, string>;
72
73
  customApis: Schemastery.ObjectT<{
73
74
  platform: Schema<"bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao", "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao">;
74
75
  apiUrl: Schema<string, string>;
75
- useDedicatedFirst: Schema<boolean, boolean>;
76
76
  }>[];
77
77
  } & {
78
78
  waitingTipText: string;
package/lib/index.js CHANGED
@@ -10,7 +10,6 @@ const axios_1 = __importDefault(require("axios"));
10
10
  const promises_1 = __importDefault(require("fs/promises"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const fs_1 = require("fs");
13
- const promises_2 = require("stream/promises");
14
13
  const lru_cache_1 = require("lru-cache");
15
14
  exports.name = 'video-parser-all';
16
15
  exports.Config = koishi_1.Schema.intersect([
@@ -49,6 +48,27 @@ exports.Config = koishi_1.Schema.intersect([
49
48
  primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('主 API 地址'),
50
49
  backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').description('备用主 API 地址(仅支持抖音/小红书/ins/即梦)'),
51
50
  useDedicatedApiFirst: koishi_1.Schema.boolean().default(false).description('全局默认:是否优先使用平台专属 API(各平台可单独覆盖)'),
51
+ platformDedicatedFirst: koishi_1.Schema.dict(koishi_1.Schema.boolean())
52
+ .default({
53
+ bilibili: false,
54
+ douyin: false,
55
+ kuaishou: false,
56
+ xiaohongshu: false,
57
+ weibo: false,
58
+ xigua: false,
59
+ youtube: false,
60
+ tiktok: false,
61
+ acfun: false,
62
+ zhihu: false,
63
+ weishi: false,
64
+ huya: false,
65
+ haokan: false,
66
+ meipai: false,
67
+ twitter: false,
68
+ instagram: false,
69
+ doubao: false,
70
+ })
71
+ .description('各平台独立开关:是否优先使用专属 API(key 为平台名)'),
52
72
  customApis: koishi_1.Schema.array(koishi_1.Schema.object({
53
73
  platform: koishi_1.Schema.union([
54
74
  koishi_1.Schema.const('bilibili').description('哔哩哔哩'),
@@ -70,8 +90,7 @@ exports.Config = koishi_1.Schema.intersect([
70
90
  koishi_1.Schema.const('doubao').description('豆包'),
71
91
  ]).description('选择平台'),
72
92
  apiUrl: koishi_1.Schema.string().description('API 地址'),
73
- useDedicatedFirst: koishi_1.Schema.boolean().default(false).description('该平台优先使用此专属 API'),
74
- })).default([]).description('自定义平台专属 API,可覆盖默认专属 API,并可单独设定优先策略'),
93
+ })).default([]).description('自定义平台专属 API 地址,留空则使用内置默认专属 API'),
75
94
  }).description('API 选择设置'),
76
95
  koishi_1.Schema.object({
77
96
  waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('解析等待提示'),
@@ -109,27 +128,26 @@ function linkTypeParser(content) {
109
128
  content = content.replace(/\\\//g, '/');
110
129
  const rules = [
111
130
  { pattern: /bilibili\.com\/video\/([ab]v[0-9a-zA-Z]+)/gi, type: 'bilibili', buildUrl: (id) => `https://www.bilibili.com/video/${id}` },
112
- { pattern: /b23\.tv\/([0-9a-zA-Z]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://b23.tv/${id}` },
113
- { pattern: /bili(?:22|23|33)\.cn\/([0-9a-zA-Z]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://bili23.cn/${id}` },
114
- { pattern: /bili2233\.cn\/([0-9a-zA-Z]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://bili2233.cn/${id}` },
131
+ { pattern: /b23\.tv\/([0-9a-zA-Z_-]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://b23.tv/${id}` },
132
+ { pattern: /bili(?:22|23|33|2233)\.cn\/([0-9a-zA-Z_-]{5,})/gi, type: 'bilibili', buildUrl: (id) => `https://bili${RegExp.$1 || ''}.cn/${id}` },
115
133
  { pattern: /douyin\.com\/video\/(\d{10,})/gi, type: 'douyin', buildUrl: (id) => `https://www.douyin.com/video/${id}` },
116
- { pattern: /v\.douyin\.com\/([0-9a-zA-Z]{8,})/gi, type: 'douyin', buildUrl: (id) => `https://v.douyin.com/${id}/` },
117
- { pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z]{10,})/gi, type: 'kuaishou', buildUrl: (id) => `https://www.kuaishou.com/short-video/${id}` },
118
- { pattern: /v\.kuaishou\.com\/([0-9a-zA-Z]{8,})/gi, type: 'kuaishou', buildUrl: (id) => `https://v.kuaishou.com/${id}` },
119
- { pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z]{10,})/gi, type: 'xiaohongshu', buildUrl: (id) => `https://www.xiaohongshu.com/discovery/item/${id}` },
120
- { pattern: /xhslink\.com\/([0-9a-zA-Z]{8,})/gi, type: 'xiaohongshu', buildUrl: (id) => `https://xhslink.com/${id}` },
121
- { pattern: /weibo\.com\/\d+\/([0-9a-zA-Z]{10,})/gi, type: 'weibo', buildUrl: (id) => `https://weibo.com/${id}` },
122
- { pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z]{10,})/gi, type: 'weibo', buildUrl: (id) => `https://video.weibo.com/show?fid=${id}` },
134
+ { pattern: /v\.douyin\.com\/([0-9a-zA-Z_-]{8,})/gi, type: 'douyin', buildUrl: (id) => `https://v.douyin.com/${id}/` },
135
+ { pattern: /kuaishou\.com\/short-video\/([0-9a-zA-Z_-]{10,})/gi, type: 'kuaishou', buildUrl: (id) => `https://www.kuaishou.com/short-video/${id}` },
136
+ { pattern: /v\.kuaishou\.com\/([0-9a-zA-Z_-]{8,})/gi, type: 'kuaishou', buildUrl: (id) => `https://v.kuaishou.com/${id}` },
137
+ { pattern: /xiaohongshu\.com\/discovery\/item\/([0-9a-zA-Z_-]{10,})/gi, type: 'xiaohongshu', buildUrl: (id) => `https://www.xiaohongshu.com/discovery/item/${id}` },
138
+ { pattern: /xhslink\.com\/([0-9a-zA-Z_-]{8,})/gi, type: 'xiaohongshu', buildUrl: (id) => `https://xhslink.com/${id}` },
139
+ { pattern: /weibo\.com\/\d+\/([0-9a-zA-Z_-]{10,})/gi, type: 'weibo', buildUrl: (id) => `https://weibo.com/${id}` },
140
+ { pattern: /video\.weibo\.com\/show\?fid=([0-9a-zA-Z_-]{10,})/gi, type: 'weibo', buildUrl: (id) => `https://video.weibo.com/show?fid=${id}` },
123
141
  { pattern: /ixigua\.com\/(\d{10,})/gi, type: 'xigua', buildUrl: (id) => `https://www.ixigua.com/${id}` },
124
142
  { pattern: /youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/gi, type: 'youtube', buildUrl: (id) => `https://www.youtube.com/watch?v=${id}` },
125
143
  { pattern: /youtu\.be\/([a-zA-Z0-9_-]{11})/gi, type: 'youtube', buildUrl: (id) => `https://youtu.be/${id}` },
126
144
  { pattern: /tiktok\.com\/@[\w.]+\/video\/(\d{10,})/gi, type: 'tiktok', buildUrl: (id) => `https://www.tiktok.com/@user/video/${id}` },
127
- { pattern: /vm\.tiktok\.com\/([0-9a-zA-Z]{8,})/gi, type: 'tiktok', buildUrl: (id) => `https://vm.tiktok.com/${id}` },
145
+ { pattern: /vm\.tiktok\.com\/([0-9a-zA-Z_-]{8,})/gi, type: 'tiktok', buildUrl: (id) => `https://vm.tiktok.com/${id}` },
128
146
  { pattern: /acfun\.cn\/v\/(ac\d{10,})/gi, type: 'acfun', buildUrl: (id) => `https://www.acfun.cn/v/${id}` },
129
147
  { pattern: /zhihu\.com\/video\/(\d{10,})/gi, type: 'zhihu', buildUrl: (id) => `https://www.zhihu.com/video/${id}` },
130
- { pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z]{10,})/gi, type: 'weishi', buildUrl: (id) => `https://weishi.qq.com/weishi/feed/${id}` },
131
- { pattern: /huya\.com\/video\/([0-9a-zA-Z]{10,})/gi, type: 'huya', buildUrl: (id) => `https://www.huya.com/video/${id}` },
132
- { pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z]{10,})/gi, type: 'haokan', buildUrl: (id) => `https://haokan.baidu.com/v?vid=${id}` },
148
+ { pattern: /weishi\.qq\.com\/weishi\/feed\/([0-9a-zA-Z_-]{10,})/gi, type: 'weishi', buildUrl: (id) => `https://weishi.qq.com/weishi/feed/${id}` },
149
+ { pattern: /huya\.com\/video\/([0-9a-zA-Z_-]{10,})/gi, type: 'huya', buildUrl: (id) => `https://www.huya.com/video/${id}` },
150
+ { pattern: /haokan\.baidu\.com\/v\?vid=([0-9a-zA-Z_-]{10,})/gi, type: 'haokan', buildUrl: (id) => `https://haokan.baidu.com/v?vid=${id}` },
133
151
  { pattern: /meipai\.com\/media\/(\d{10,})/gi, type: 'meipai', buildUrl: (id) => `https://www.meipai.com/media/${id}` },
134
152
  { pattern: /twitter\.com\/\w+\/status\/(\d{10,})/gi, type: 'twitter', buildUrl: (id) => `https://twitter.com/i/status/${id}` },
135
153
  { pattern: /x\.com\/\w+\/status\/(\d{10,})/gi, type: 'twitter', buildUrl: (id) => `https://x.com/i/status/${id}` },
@@ -140,6 +158,7 @@ function linkTypeParser(content) {
140
158
  const seen = new Set();
141
159
  for (const rule of rules) {
142
160
  let match;
161
+ rule.pattern.lastIndex = 0;
143
162
  while ((match = rule.pattern.exec(content)) !== null) {
144
163
  const id = match[1];
145
164
  if (seen.has(id))
@@ -153,16 +172,12 @@ function linkTypeParser(content) {
153
172
  }
154
173
  function extractAllUrlsFromMessage(session) {
155
174
  const content = session.content?.trim() || '';
156
- const rawUrls = [];
157
- const textMatch = content.match(/https?:\/\/[^\s<>"'(){}[\]]+/gi);
158
- if (textMatch)
159
- rawUrls.push(...textMatch);
175
+ const matchedLinks = linkTypeParser(content);
176
+ const cardsContent = [];
160
177
  if (session.elements) {
161
178
  for (const elem of session.elements) {
162
179
  if (elem.type === 'xml' && elem.data) {
163
- const matches = elem.data.match(/https?:\/\/[^\s<>"'(){}[\]]+/gi);
164
- if (matches)
165
- rawUrls.push(...matches);
180
+ cardsContent.push(elem.data);
166
181
  }
167
182
  else if (elem.type === 'json' && elem.data) {
168
183
  try {
@@ -172,9 +187,7 @@ function extractAllUrlsFromMessage(session) {
172
187
  return;
173
188
  for (const val of Object.values(obj)) {
174
189
  if (typeof val === 'string') {
175
- const matches = val.match(/https?:\/\/[^\s<>"'(){}[\]]+/gi);
176
- if (matches)
177
- rawUrls.push(...matches);
190
+ cardsContent.push(val);
178
191
  }
179
192
  else if (typeof val === 'object')
180
193
  extract(val);
@@ -186,18 +199,16 @@ function extractAllUrlsFromMessage(session) {
186
199
  }
187
200
  }
188
201
  }
202
+ for (const cardContent of cardsContent) {
203
+ const cardLinks = linkTypeParser(cardContent);
204
+ matchedLinks.push(...cardLinks);
205
+ }
189
206
  const seen = new Set();
190
207
  const result = [];
191
- for (const rawUrl of rawUrls) {
192
- const cleanedUrl = rawUrl.replace(/&amp;/g, '&').replace(/[.,;:!?)]+$/, '');
193
- if (seen.has(cleanedUrl))
194
- continue;
195
- const matches = linkTypeParser(cleanedUrl);
196
- for (const match of matches) {
197
- if (!seen.has(match.url)) {
198
- seen.add(match.url);
199
- result.push(match);
200
- }
208
+ for (const link of matchedLinks) {
209
+ if (!seen.has(link.url)) {
210
+ seen.add(link.url);
211
+ result.push(link);
201
212
  }
202
213
  }
203
214
  return result;
@@ -484,7 +495,19 @@ async function downloadVideoFile(videoUrl, tempDir, timeout, maxSizeMB) {
484
495
  }
485
496
  });
486
497
  try {
487
- await (0, promises_2.pipeline)(response.data, writer);
498
+ await new Promise((resolve, reject) => {
499
+ const stream = require('stream');
500
+ const pipeline = stream.promises?.pipeline || require('util').promisify(stream.pipeline);
501
+ if (typeof pipeline === 'function') {
502
+ pipeline(response.data, writer).then(resolve).catch(reject);
503
+ }
504
+ else {
505
+ response.data.pipe(writer);
506
+ writer.on('finish', resolve);
507
+ writer.on('error', reject);
508
+ response.data.on('error', reject);
509
+ }
510
+ });
488
511
  debugLog('INFO', `视频下载完成,大小: ${Math.round(downloadedSize / 1024 / 1024)}MB`);
489
512
  return filePath;
490
513
  }
@@ -533,11 +556,12 @@ function apply(ctx, config) {
533
556
  const backupSupportedPlatforms = new Set(['douyin', 'xiaohongshu', 'instagram', 'jimeng']);
534
557
  function getPlatformConfig(type) {
535
558
  const custom = config.customApis?.find((item) => item.platform === type);
559
+ let apiUrl = defaultDedicatedApis[type] || null;
536
560
  if (custom && custom.apiUrl) {
537
- return { apiUrl: custom.apiUrl, dedicatedFirst: custom.useDedicatedFirst ?? false };
561
+ apiUrl = custom.apiUrl;
538
562
  }
539
- const defaultUrl = defaultDedicatedApis[type] || null;
540
- return { apiUrl: defaultUrl, dedicatedFirst: config.useDedicatedApiFirst ?? false };
563
+ const dedicatedFirst = config.platformDedicatedFirst?.[type] ?? config.useDedicatedApiFirst ?? false;
564
+ return { apiUrl, dedicatedFirst };
541
565
  }
542
566
  async function fetchApi(url, type) {
543
567
  const cacheKey = url;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-video-parser-all",
3
3
  "description": "Koishi 全平台视频解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20+平台",
4
- "version": "1.0.9",
4
+ "version": "1.1.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -56,8 +56,9 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
56
56
  |--------|------|--------|------|
57
57
  | `primaryApiUrl` | string | `https://api.bugpk.com/api/short_videos` | 主 API 地址,解析时优先使用 |
58
58
  | `backupApiUrl` | string | `https://api.bugpk.com/api/svparse` | 备用主 API 地址,仅支持抖音、小红书、Instagram、即梦平台解析 |
59
- | `useDedicatedApiFirst` | boolean | false | 是否优先使用平台专属 API,失败后依次回退到主 API、备用主 API |
60
- | `customApis` | array | [] | 自定义平台专属 API 列表,每项需选择平台并填写 API 地址,可覆盖内置的默认专属 API |
59
+ | `useDedicatedApiFirst` | boolean | false | 全局默认值:是否优先使用平台专属 API,各平台可单独覆盖该行为 |
60
+ | `platformDedicatedFirst` | object | 见下方说明 | 各平台独立开关:是否优先使用专属 API(key 为平台名)。支持的 key:bilibili、douyin、kuaishou、xiaohongshu、weibo、xigua、youtube、tiktok、acfun、zhihu、weishi、huya、haokan、meipai、twitter、instagram、doubao |
61
+ | `customApis` | array | [] | 自定义平台专属 API 列表。每项包含:`platform`(平台类型)、`apiUrl`(API 地址)。可覆盖内置默认专属 API |
61
62
 
62
63
  ### 错误与重试设置
63
64
  | 配置项 | 类型 | 默认值 | 说明 |