koishi-plugin-video-parser-all 0.1.1 → 0.1.2

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
@@ -8,6 +8,7 @@ export interface Config {
8
8
  imageParseFormat: string;
9
9
  showVideoUrl: boolean;
10
10
  maxDescLength: number;
11
+ bugpkUniversalApi: string;
11
12
  bugpkDouyinMainApi: string;
12
13
  bugpkDouyinBackupApi: string;
13
14
  bugpkKuaishouApi: string;
package/lib/index.js CHANGED
@@ -18,13 +18,11 @@ exports.Config = koishi_1.Schema.object({
18
18
  .role('textarea')
19
19
  .default(`\${标题} \${tab} \${UP主}
20
20
  \${简介}
21
- 点赞:\${点赞} \${tab} 投币:\${投币}
22
- 收藏:\${收藏} \${tab} 转发:\${转发}
23
- 观看:\${观看} \${tab} 弹幕:\${弹幕}
24
21
  \${~~~}
25
22
  \${封面}`),
26
23
  showVideoUrl: koishi_1.Schema.boolean().default(false),
27
24
  maxDescLength: koishi_1.Schema.number().default(200),
25
+ bugpkUniversalApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos'),
28
26
  bugpkDouyinMainApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/douyin'),
29
27
  bugpkDouyinBackupApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/dyjx'),
30
28
  bugpkKuaishouApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/ksjx'),
@@ -33,20 +31,103 @@ exports.Config = koishi_1.Schema.object({
33
31
  ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略消息发送失败的错误(避免日志刷屏)'),
34
32
  });
35
33
  const processed = new Map();
36
- // 新增:定义目标平台的正则匹配规则
37
- const PLATFORM_REGEX = {
38
- douyin: /(douyin\.com|v\.douyin\.com|抖音)/i,
39
- kuaishou: /(kuaishou\.com|kwaishou\.com|快手)/i,
40
- bilibili: /(bilibili\.com|b23\.tv|哔哩哔哩|B站)/i
34
+ const PLATFORM_KEYWORDS = {
35
+ bilibili: ['bilibili', 'b23', 'B站'],
36
+ kuaishou: ['kuaishou', '快手'],
37
+ douyin: ['douyin', '抖音']
41
38
  };
42
- // 新增:检查链接是否属于目标平台
43
- function isTargetPlatformUrl(url) {
44
- return PLATFORM_REGEX.douyin.test(url) ||
45
- PLATFORM_REGEX.kuaishou.test(url) ||
46
- PLATFORM_REGEX.bilibili.test(url);
39
+ function extractUrl(content) {
40
+ const urlMatch = content.match(/https?:\/\/[^\s]+/i);
41
+ return urlMatch ? urlMatch[0] : null;
42
+ }
43
+ function hasPlatformKeyword(content) {
44
+ const lowerContent = content.toLowerCase();
45
+ return Object.values(PLATFORM_KEYWORDS).some(keywords => keywords.some(keyword => {
46
+ const target = /[\u4e00-\u9fa5]/.test(keyword) ? keyword : keyword.toLowerCase();
47
+ return lowerContent.includes(target);
48
+ }));
49
+ }
50
+ function getPlatformType(url) {
51
+ const lowerUrl = url.toLowerCase();
52
+ if (PLATFORM_KEYWORDS.douyin.some(k => lowerUrl.includes(k.toLowerCase())))
53
+ return 'douyin';
54
+ if (PLATFORM_KEYWORDS.kuaishou.some(k => lowerUrl.includes(k.toLowerCase())))
55
+ return 'kuaishou';
56
+ if (PLATFORM_KEYWORDS.bilibili.some(k => lowerUrl.includes(k.toLowerCase())))
57
+ return 'bilibili';
58
+ return null;
59
+ }
60
+ function parseUniversalApiData(data) {
61
+ return {
62
+ title: data.title || '无标题',
63
+ author: data.author || '未知作者',
64
+ desc: data.title || '无简介',
65
+ digg: data.like || 0,
66
+ coin: 0,
67
+ collect: 0,
68
+ share: 0,
69
+ play: 0,
70
+ danmaku: 0,
71
+ cover: data.cover || '',
72
+ video: data.url || ''
73
+ };
74
+ }
75
+ function parseDouyin(data) {
76
+ const videoUrl = data.url || (data.live_photo?.[0]?.video || '');
77
+ return {
78
+ title: data.title || '无标题',
79
+ author: data.author?.name || '未知作者',
80
+ desc: data.desc || data.title || '无简介',
81
+ digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
82
+ cover: data.cover || '',
83
+ video: videoUrl
84
+ };
85
+ }
86
+ function parseKuaishou(data) {
87
+ return {
88
+ title: data.title || '无标题',
89
+ author: '未知作者',
90
+ desc: data.title || '无简介',
91
+ digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
92
+ cover: data.cover || '',
93
+ video: data.url || ''
94
+ };
95
+ }
96
+ function parseBilibili(data) {
97
+ const title = data.title || data.Title || '无标题';
98
+ const cover = data.cover || data.imgurl || data.cover_url || data.pic || '';
99
+ const author = data.user?.name || data.author || '未知UP主';
100
+ const desc = data.description || data.desc || title || '无简介';
101
+ let videoUrl = '';
102
+ if (data.url)
103
+ videoUrl = data.url;
104
+ else if (data.video_url)
105
+ videoUrl = data.video_url;
106
+ else if (data.videos && Array.isArray(data.videos) && data.videos.length > 0) {
107
+ videoUrl = data.videos.reduce((prev, curr) => {
108
+ return (prev.size || 0) > (curr.size || 0) ? prev : curr;
109
+ }).url || '';
110
+ }
111
+ else if (data.play)
112
+ videoUrl = data.play;
113
+ else if (data.durl && Array.isArray(data.durl))
114
+ videoUrl = data.durl[0]?.url || '';
115
+ const digg = data.like || data.digg || 0;
116
+ const coin = data.coin || 0;
117
+ const collect = data.favorite || data.collect || 0;
118
+ const share = data.share || 0;
119
+ const play = data.play || data.view || 0;
120
+ const danmaku = data.danmaku || data.comment || 0;
121
+ return {
122
+ title,
123
+ author,
124
+ desc,
125
+ digg, coin, collect, share, play, danmaku,
126
+ cover,
127
+ video: videoUrl
128
+ };
47
129
  }
48
130
  function apply(ctx, config) {
49
- // 修复:移除axios不支持的retry配置,保留核心请求配置
50
131
  const request = axios_1.default.create({
51
132
  timeout: config.timeout,
52
133
  headers: {
@@ -55,50 +136,21 @@ function apply(ctx, config) {
55
136
  'Accept': 'application/json, text/plain, */*'
56
137
  }
57
138
  });
58
- // 抖音解析
59
- function parseDouyin(data) {
60
- const videoUrl = data.url || (data.live_photo?.[0]?.video || '');
61
- return {
62
- title: data.title || '无标题',
63
- author: data.author?.name || '未知作者',
64
- desc: data.desc || data.title || '无简介',
65
- digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
66
- cover: data.cover || '',
67
- video: videoUrl
68
- };
69
- }
70
- // 快手解析
71
- function parseKuaishou(data) {
72
- return {
73
- title: data.title || '无标题',
74
- author: '未知作者',
75
- desc: data.title || '无简介',
76
- digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
77
- cover: data.cover || '',
78
- video: data.url || ''
79
- };
80
- }
81
- // B站解析(增强:兼容更多返回格式)
82
- function parseBilibili(data) {
83
- const title = data.title || data.Title || '';
84
- const cover = data.cover || data.imgurl || data.cover_url || '';
85
- const author = data.user?.name || data.author || '未知UP主';
86
- const desc = data.description || data.desc || title || '无简介';
87
- const videoUrl = data.url || (data.videos?.length > 0 ? data.videos[0].url : data.video_url || '');
88
- return {
89
- title,
90
- author,
91
- desc,
92
- digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
93
- cover,
94
- video: videoUrl
95
- };
96
- }
97
- // 核心:只走专属接口,增强异常捕获
98
139
  async function parseVideo(url) {
99
140
  try {
100
- // 抖音
101
- if (PLATFORM_REGEX.douyin.test(url)) {
141
+ ctx.logger.debug(`优先调用通用API: ${config.bugpkUniversalApi}`);
142
+ const res = await request.get(config.bugpkUniversalApi, { params: { url } });
143
+ if (res.data.code === 200 && res.data.data) {
144
+ ctx.logger.debug('通用API解析成功');
145
+ return parseUniversalApiData(res.data.data);
146
+ }
147
+ }
148
+ catch (e) {
149
+ ctx.logger.debug(`通用API调用失败: ${e.message},尝试平台专属API`);
150
+ }
151
+ try {
152
+ const platform = getPlatformType(url);
153
+ if (platform === 'douyin') {
102
154
  try {
103
155
  const res = await request.get(config.bugpkDouyinMainApi, { params: { url } });
104
156
  if (res.data.code === 200 && res.data.data)
@@ -116,8 +168,7 @@ function apply(ctx, config) {
116
168
  ctx.logger.debug(`抖音备用接口失败: ${e.message}`);
117
169
  }
118
170
  }
119
- // 快手
120
- if (PLATFORM_REGEX.kuaishou.test(url)) {
171
+ if (platform === 'kuaishou') {
121
172
  try {
122
173
  const res = await request.get(config.bugpkKuaishouApi, { params: { url } });
123
174
  if (res.data.code === 200 && res.data.data)
@@ -127,8 +178,7 @@ function apply(ctx, config) {
127
178
  ctx.logger.debug(`快手接口失败: ${e.message}`);
128
179
  }
129
180
  }
130
- // B站
131
- if (PLATFORM_REGEX.bilibili.test(url)) {
181
+ if (platform === 'bilibili') {
132
182
  try {
133
183
  const res = await request.get(config.bugpkBilibiliApi, { params: { url } });
134
184
  if (res.data.code === 200 && res.data.data)
@@ -140,18 +190,16 @@ function apply(ctx, config) {
140
190
  }
141
191
  }
142
192
  catch (e) {
143
- ctx.logger.error(`解析核心逻辑异常: ${e.message}`);
193
+ ctx.logger.error(`平台专属API解析异常: ${e.message}`);
144
194
  }
145
- // 兜底返回(更友好的提示)
146
195
  return {
147
196
  title: '解析失败',
148
197
  author: '',
149
- desc: '接口暂不可用或链接格式不支持,请稍后重试',
198
+ desc: '通用接口和平台专属接口均解析失败,请稍后重试',
150
199
  digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
151
200
  cover: '', video: ''
152
201
  };
153
202
  }
154
- // 发送结果(核心修复:所有send操作加try/catch,避免中断)
155
203
  async function sendResult(session, data) {
156
204
  try {
157
205
  let text = config.imageParseFormat
@@ -167,37 +215,26 @@ function apply(ctx, config) {
167
215
  .replace(/\${tab}/g, '\t')
168
216
  .replace(/\${~~~}/g, '——————————————');
169
217
  const [beforeCover, afterCover] = text.split('\${封面}');
170
- // 分段发送,每一步都加异常捕获
171
- if (beforeCover && beforeCover.trim()) {
172
- await session.send(beforeCover.trim()).catch(e => {
173
- if (!config.ignoreSendError)
174
- ctx.logger.warn(`发送文本失败: ${e.message}`);
175
- });
176
- }
177
- if (data.cover) {
178
- await session.send(koishi_1.h.image(data.cover)).catch(e => {
179
- if (!config.ignoreSendError)
180
- ctx.logger.warn(`发送封面失败: ${e.message}`);
181
- });
182
- }
183
- if (afterCover && afterCover.trim()) {
184
- await session.send(afterCover.trim()).catch(e => {
218
+ const msgParts = [];
219
+ if (beforeCover && beforeCover.trim())
220
+ msgParts.push(beforeCover.trim());
221
+ if (data.cover)
222
+ msgParts.push(koishi_1.h.image(data.cover));
223
+ if (afterCover && afterCover.trim())
224
+ msgParts.push(afterCover.trim());
225
+ if (data.video && config.showVideoUrl)
226
+ msgParts.push(`🔗 无水印链接:${data.video}`);
227
+ if (msgParts.length > 0) {
228
+ await session.send(msgParts.join('\n')).catch(e => {
185
229
  if (!config.ignoreSendError)
186
- ctx.logger.warn(`发送文本失败: ${e.message}`);
230
+ ctx.logger.warn(`发送合并消息失败: ${e.message}`);
187
231
  });
188
232
  }
189
- if (data.video) {
233
+ if (data.video && !config.showVideoUrl) {
190
234
  try {
191
235
  await session.send(koishi_1.h.video(data.video)).catch(e => {
192
- // 视频发送失败则回退为文本链接
193
- session.send(`📥 无水印视频链接:${data.video}`).catch(() => { });
236
+ session.send(`📥 无水印视频:${data.video}`).catch(() => { });
194
237
  });
195
- if (config.showVideoUrl) {
196
- await session.send(`🔗 无水印链接:${data.video}`).catch(e => {
197
- if (!config.ignoreSendError)
198
- ctx.logger.warn(`发送链接失败: ${e.message}`);
199
- });
200
- }
201
238
  }
202
239
  catch (e) {
203
240
  await session.send(`📥 无水印视频:${data.video}`).catch(() => { });
@@ -209,34 +246,27 @@ function apply(ctx, config) {
209
246
  ctx.logger.error(`发送结果异常: ${e.message}`);
210
247
  }
211
248
  }
212
- // 消息监听(增强:加全局异常捕获 + 严格的平台链接校验)
213
249
  ctx.on('message', async (session) => {
214
250
  try {
215
251
  if (!config.enable)
216
252
  return;
217
253
  const content = session.content.trim();
218
- // 提取纯链接
219
- const urlMatch = content.match(/https?:\/\/[^\s]+/);
220
- if (!urlMatch)
254
+ if (!hasPlatformKeyword(content))
221
255
  return;
222
- const url = urlMatch[0];
223
- // 核心优化:只处理目标平台的链接
224
- if (!isTargetPlatformUrl(url))
256
+ const url = extractUrl(content);
257
+ if (!url)
225
258
  return;
226
- // 去重逻辑
227
259
  const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
228
260
  const now = Date.now();
229
261
  if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
230
262
  return;
231
263
  processed.set(hash, now);
232
- // 发送等待提示(加异常捕获)
233
264
  if (config.showWaitingTip) {
234
265
  await session.send(config.waitingTipText).catch(e => {
235
266
  if (!config.ignoreSendError)
236
267
  ctx.logger.warn(`发送等待提示失败: ${e.message}`);
237
268
  });
238
269
  }
239
- // 解析并发送结果
240
270
  const data = await parseVideo(url);
241
271
  await sendResult(session, data);
242
272
  }
@@ -244,10 +274,9 @@ function apply(ctx, config) {
244
274
  ctx.logger.error(`消息处理异常: ${e.message}`);
245
275
  }
246
276
  });
247
- // 定时清理缓存
248
277
  setInterval(() => {
249
278
  const now = Date.now();
250
279
  processed.forEach((t, k) => now - t > 86400000 && processed.delete(k));
251
280
  }, 3600000);
252
- ctx.logger.info('✅ 视频解析插件(修复版)加载完成');
281
+ ctx.logger.info('视频解析插件加载完成');
253
282
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-video-parser-all",
3
3
  "description": "Koishi 视频解析插件,支持抖音/快手/B站链接解析,可自定义API和解析规则",
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [