koishi-plugin-video-parser-all 0.1.3 → 0.1.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.
Files changed (3) hide show
  1. package/lib/index.d.ts +22 -6
  2. package/lib/index.js +199 -301
  3. package/package.json +1 -1
package/lib/index.d.ts CHANGED
@@ -3,21 +3,37 @@ export declare const name = "video-parser-all";
3
3
  export interface Config {
4
4
  enable: boolean;
5
5
  showWaitingTip: boolean;
6
+ revokeWaitingTip: boolean;
6
7
  waitingTipText: string;
7
8
  sameLinkInterval: number;
8
9
  imageParseFormat: string;
9
- showVideoUrl: boolean;
10
+ returnContent: {
11
+ showImageText: boolean;
12
+ showVideoUrl: boolean;
13
+ showVideoFile: boolean;
14
+ };
10
15
  maxDescLength: number;
11
- bugpkUniversalApi: string;
12
- bugpkDouyinMainApi: string;
13
- bugpkDouyinBackupApi: string;
14
- bugpkKuaishouApi: string;
15
- bugpkBilibiliApi: string;
16
16
  timeout: number;
17
17
  ignoreSendError: boolean;
18
18
  enableForward: boolean;
19
19
  downloadVideoBeforeSend: boolean;
20
20
  messageBufferDelay: number;
21
+ commonApi: string;
22
+ douyin: {
23
+ mode: 'common' | 'own' | 'custom';
24
+ ownApi: string;
25
+ customApi: string;
26
+ };
27
+ kuaishou: {
28
+ mode: 'common' | 'own' | 'custom';
29
+ ownApi: string;
30
+ customApi: string;
31
+ };
32
+ bilibili: {
33
+ mode: 'common' | 'own' | 'custom';
34
+ ownApi: string;
35
+ customApi: string;
36
+ };
21
37
  }
22
38
  export declare const Config: Schema<Config>;
23
39
  export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js CHANGED
@@ -15,375 +15,273 @@ exports.name = 'video-parser-all';
15
15
  exports.Config = koishi_1.Schema.object({
16
16
  enable: koishi_1.Schema.boolean().default(true).description('是否启用插件'),
17
17
  showWaitingTip: koishi_1.Schema.boolean().default(true).description('是否显示解析等待提示'),
18
- waitingTipText: koishi_1.Schema.string().default('正在解析视频…').description('解析等待提示的文本内容'),
19
- sameLinkInterval: koishi_1.Schema.number().default(180).description('相同链接重复解析间隔(秒),避免频繁解析同一链接'),
18
+ revokeWaitingTip: koishi_1.Schema.boolean().default(true).description('是否撤回等待提示文本'),
19
+ waitingTipText: koishi_1.Schema.string().default('正在解析视频…').description('等待提示文本'),
20
+ sameLinkInterval: koishi_1.Schema.number().default(180).description('相同链接重复间隔秒'),
20
21
  imageParseFormat: koishi_1.Schema.string()
21
22
  .role('textarea')
22
- .default(`\${标题} \${tab} \${UP主}
23
- \${简介}
24
- \${~~~}
25
- \${封面}`)
26
- .description('解析结果的输出格式,支持占位符:${标题}、${UP主}、${简介}、${点赞}、${投币}、${收藏}、${转发}、${观看}、${弹幕}、${tab}、${~~~}、${封面}'),
27
- showVideoUrl: koishi_1.Schema.boolean().default(false).description('是否在消息中显示无水印视频链接(false则直接发送视频文件)'),
28
- maxDescLength: koishi_1.Schema.number().default(200).description('简介内容的最大长度,超出部分会被截断'),
29
- bugpkUniversalApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('通用视频解析API地址(优先调用)'),
30
- bugpkDouyinMainApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/douyin').description('抖音主解析API地址(备用)'),
31
- bugpkDouyinBackupApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/dyjx').description('抖音备用解析API地址(备用)'),
32
- bugpkKuaishouApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/ksjx').description('快手解析API地址(备用)'),
33
- bugpkBilibiliApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/bilibili').description('B站解析API地址(备用)'),
34
- timeout: koishi_1.Schema.number().default(15000).description('API请求超时时间(毫秒)'),
35
- ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略消息发送失败的错误,避免日志刷屏'),
36
- enableForward: koishi_1.Schema.boolean().default(false).description('是否开启合并转发 仅支持 onebot 适配器 其他平台开启 无效'),
37
- downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('是否将视频链接下载后再发送 (以解决部分onebot协议端的问题)否则使用视频直链发送'),
38
- messageBufferDelay: koishi_1.Schema.number().default(1).description('消息接收缓冲延迟(秒)收到链接后等待指定时间,收集同时发送的多个链接后再逐个处理'),
23
+ .default('${标题} ${tab} ${UP主}\n${简介}\n${~~~}\n${封面}')
24
+ .description('图文格式:${标题} ${UP主} ${简介} ${点赞} ${投币} ${收藏} ${转发} ${观看} ${弹幕} ${tab} ${~~~} ${封面}'),
25
+ returnContent: koishi_1.Schema.object({
26
+ showImageText: koishi_1.Schema.boolean().default(true).description('返回图文'),
27
+ showVideoUrl: koishi_1.Schema.boolean().default(false).description('返回视频直链'),
28
+ showVideoFile: koishi_1.Schema.boolean().default(true).description('返回视频'),
29
+ }).description('内容组件'),
30
+ maxDescLength: koishi_1.Schema.number().default(200).description('简介最大长度'),
31
+ timeout: koishi_1.Schema.number().default(15000).description('请求超时毫秒'),
32
+ ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略发送错误'),
33
+ enableForward: koishi_1.Schema.boolean().default(false).description('合并转发(仅onebot)'),
34
+ downloadVideoBeforeSend: koishi_1.Schema.boolean().default(false).description('先下载视频再发送(解决onebot问题)'),
35
+ messageBufferDelay: koishi_1.Schema.number().default(1).description('消息缓冲延迟秒'),
36
+ commonApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('通用解析API'),
37
+ douyin: koishi_1.Schema.object({
38
+ mode: koishi_1.Schema.union([
39
+ koishi_1.Schema.const('common').description('使用通用API'),
40
+ koishi_1.Schema.const('own').description('使用平台专属API'),
41
+ koishi_1.Schema.const('custom').description('使用自定义API'),
42
+ ]).default('common').description('解析模式'),
43
+ ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/douyin').description('抖音专属API'),
44
+ customApi: koishi_1.Schema.string().description('抖音自定义API'),
45
+ }).description('抖音配置'),
46
+ kuaishou: koishi_1.Schema.object({
47
+ mode: koishi_1.Schema.union([
48
+ koishi_1.Schema.const('common').description('使用通用API'),
49
+ koishi_1.Schema.const('own').description('使用平台专属API'),
50
+ koishi_1.Schema.const('custom').description('使用自定义API'),
51
+ ]).default('common').description('解析模式'),
52
+ ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/ksjx').description('快手专属API'),
53
+ customApi: koishi_1.Schema.string().description('快手自定义API'),
54
+ }).description('快手配置'),
55
+ bilibili: koishi_1.Schema.object({
56
+ mode: koishi_1.Schema.union([
57
+ koishi_1.Schema.const('common').description('使用通用API'),
58
+ koishi_1.Schema.const('own').description('使用平台专属API'),
59
+ koishi_1.Schema.const('custom').description('使用自定义API'),
60
+ ]).default('common').description('解析模式'),
61
+ ownApi: koishi_1.Schema.string().default('https://api.bugpk.com/api/bilibili').description('B站专属API'),
62
+ customApi: koishi_1.Schema.string().description('B站自定义API'),
63
+ }).description('B站配置'),
39
64
  });
40
65
  const processed = new Map();
41
66
  const linkBuffer = new Map();
42
67
  const PLATFORM_KEYWORDS = {
43
68
  bilibili: ['bilibili', 'b23', 'B站'],
44
69
  kuaishou: ['kuaishou', '快手'],
45
- douyin: ['douyin', '抖音']
70
+ douyin: ['douyin', '抖音'],
46
71
  };
47
72
  function extractUrl(content) {
48
73
  const urlMatches = content.match(/https?:\/\/[^\s]+/gi) || [];
49
74
  return urlMatches.filter(url => {
50
- const lowerUrl = url.toLowerCase();
51
- return Object.values(PLATFORM_KEYWORDS).some(keywords => keywords.some(keyword => lowerUrl.includes(keyword.toLowerCase())));
75
+ const lower = url.toLowerCase();
76
+ return Object.values(PLATFORM_KEYWORDS).some(g => g.some(k => lower.includes(k.toLowerCase())));
52
77
  });
53
78
  }
54
79
  function hasPlatformKeyword(content) {
55
- const lowerContent = content.toLowerCase();
56
- return Object.values(PLATFORM_KEYWORDS).some(keywords => keywords.some(keyword => {
57
- const target = /[\u4e00-\u9fa5]/.test(keyword) ? keyword : keyword.toLowerCase();
58
- return lowerContent.includes(target);
59
- }));
80
+ const lower = content.toLowerCase();
81
+ return Object.values(PLATFORM_KEYWORDS).some(g => g.some(k => lower.includes(k.toLowerCase())));
60
82
  }
61
83
  function getPlatformType(url) {
62
- const lowerUrl = url.toLowerCase();
63
- if (PLATFORM_KEYWORDS.douyin.some(k => lowerUrl.includes(k.toLowerCase())))
84
+ const lower = url.toLowerCase();
85
+ if (PLATFORM_KEYWORDS.douyin.some(k => lower.includes(k.toLowerCase())))
64
86
  return 'douyin';
65
- if (PLATFORM_KEYWORDS.kuaishou.some(k => lowerUrl.includes(k.toLowerCase())))
87
+ if (PLATFORM_KEYWORDS.kuaishou.some(k => lower.includes(k.toLowerCase())))
66
88
  return 'kuaishou';
67
- if (PLATFORM_KEYWORDS.bilibili.some(k => lowerUrl.includes(k.toLowerCase())))
89
+ if (PLATFORM_KEYWORDS.bilibili.some(k => lower.includes(k.toLowerCase())))
68
90
  return 'bilibili';
69
91
  return null;
70
92
  }
71
93
  async function downloadVideo(url, filename) {
72
- const downloadPath = path_1.default.join(process.cwd(), 'temp_videos');
73
- if (!fs_1.default.existsSync(downloadPath)) {
74
- fs_1.default.mkdirSync(downloadPath, { recursive: true });
75
- }
76
- const filePath = path_1.default.join(downloadPath, `${filename}.mp4`);
77
- const response = await (0, axios_1.default)({
78
- url,
79
- method: 'GET',
80
- responseType: 'stream',
81
- timeout: 30000
82
- });
83
- await (0, promises_1.pipeline)(response.data, fs_1.default.createWriteStream(filePath));
84
- return filePath;
85
- }
86
- function parseUniversalApiData(data) {
87
- return {
88
- title: data.title || '无标题',
89
- author: data.author || '未知作者',
90
- desc: data.title || '无简介',
91
- digg: data.like || 0,
92
- coin: 0,
93
- collect: 0,
94
- share: 0,
95
- play: 0,
96
- danmaku: 0,
97
- cover: data.cover || '',
98
- video: data.url || ''
99
- };
100
- }
101
- function parseDouyin(data) {
102
- const videoUrl = data.url || (data.live_photo?.[0]?.video || '');
103
- return {
104
- title: data.title || '无标题',
105
- author: data.author?.name || '未知作者',
106
- desc: data.desc || data.title || '无简介',
107
- digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
108
- cover: data.cover || '',
109
- video: videoUrl
110
- };
94
+ const dir = path_1.default.join(process.cwd(), 'temp_videos');
95
+ if (!fs_1.default.existsSync(dir))
96
+ fs_1.default.mkdirSync(dir, { recursive: true });
97
+ const file = path_1.default.join(dir, `${filename}.mp4`);
98
+ const res = await (0, axios_1.default)({ url, method: 'GET', responseType: 'stream', timeout: 30000 });
99
+ await (0, promises_1.pipeline)(res.data, fs_1.default.createWriteStream(file));
100
+ return file;
111
101
  }
112
- function parseKuaishou(data) {
102
+ function parseData(data) {
113
103
  return {
114
104
  title: data.title || '无标题',
115
- author: '未知作者',
116
- desc: data.title || '无简介',
117
- digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
118
- cover: data.cover || '',
119
- video: data.url || ''
120
- };
121
- }
122
- function parseBilibili(data) {
123
- const title = data.title || data.Title || '无标题';
124
- const cover = data.cover || data.imgurl || data.cover_url || data.pic || '';
125
- const author = data.user?.name || data.author || '未知UP主';
126
- const desc = data.description || data.desc || title || '无简介';
127
- let videoUrl = '';
128
- if (data.url)
129
- videoUrl = data.url;
130
- else if (data.video_url)
131
- videoUrl = data.video_url;
132
- else if (data.videos && Array.isArray(data.videos) && data.videos.length > 0) {
133
- videoUrl = data.videos.reduce((prev, curr) => {
134
- return (prev.size || 0) > (curr.size || 0) ? prev : curr;
135
- }).url || '';
136
- }
137
- else if (data.play)
138
- videoUrl = data.play;
139
- else if (data.durl && Array.isArray(data.durl))
140
- videoUrl = data.durl[0]?.url || '';
141
- else if (data.hd_url)
142
- videoUrl = data.hd_url;
143
- else if (data.sd_url)
144
- videoUrl = data.sd_url;
145
- const digg = data.like || data.digg || 0;
146
- const coin = data.coin || 0;
147
- const collect = data.favorite || data.collect || 0;
148
- const share = data.share || 0;
149
- const play = data.play || data.view || 0;
150
- const danmaku = data.danmaku || data.comment || 0;
151
- return {
152
- title,
153
- author,
154
- desc,
155
- digg, coin, collect, share, play, danmaku,
156
- cover,
157
- video: videoUrl
105
+ author: data.author || data.user?.name || '未知作者',
106
+ desc: (data.desc || data.title || '无简介').slice(0, 200),
107
+ digg: data.like || data.digg || 0,
108
+ coin: data.coin || 0,
109
+ collect: data.collect || data.favorite || 0,
110
+ share: data.share || 0,
111
+ play: data.play || data.view || 0,
112
+ danmaku: data.danmaku || data.comment || 0,
113
+ cover: data.cover || data.imgurl || data.pic || '',
114
+ video: data.url || data.video_url || data.play || '',
158
115
  };
159
116
  }
160
117
  function apply(ctx, config) {
161
- const request = axios_1.default.create({
162
- timeout: config.timeout,
163
- headers: {
164
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
165
- 'Referer': 'https://www.bugpk.com/',
166
- 'Accept': 'application/json, text/plain, */*'
167
- }
168
- });
169
- async function parseVideo(url) {
118
+ const http = axios_1.default.create({ timeout: config.timeout });
119
+ function getApi(platform) {
120
+ const conf = config[platform];
121
+ if (conf.mode === 'custom' && conf.customApi)
122
+ return conf.customApi;
123
+ if (conf.mode === 'own')
124
+ return conf.ownApi;
125
+ return config.commonApi;
126
+ }
127
+ async function parse(url) {
128
+ const pt = getPlatformType(url);
129
+ if (!pt)
130
+ return null;
131
+ const api = getApi(pt);
132
+ if (!api)
133
+ return null;
170
134
  try {
171
- ctx.logger.debug(`优先调用通用API: ${config.bugpkUniversalApi}`);
172
- const res = await request.get(config.bugpkUniversalApi, { params: { url } });
173
- if (res.data.code === 200 && res.data.data) {
174
- ctx.logger.debug('通用API解析成功');
175
- return parseUniversalApiData(res.data.data);
176
- }
177
- }
178
- catch (e) {
179
- ctx.logger.debug(`通用API调用失败: ${e.message},尝试平台专属API`);
135
+ const { data } = await http.get(api, { params: { url } });
136
+ if (data.code === 200 && data.data)
137
+ return parseData(data.data);
180
138
  }
139
+ catch { }
140
+ return null;
141
+ }
142
+ async function revoke(session, key) {
143
+ if (!config.revokeWaitingTip)
144
+ return;
145
+ const buf = linkBuffer.get(key);
146
+ if (!buf?.tipMsgId)
147
+ return;
181
148
  try {
182
- const platform = getPlatformType(url);
183
- if (platform === 'douyin') {
184
- try {
185
- const res = await request.get(config.bugpkDouyinMainApi, { params: { url } });
186
- if (res.data.code === 200 && res.data.data)
187
- return parseDouyin(res.data.data);
188
- }
189
- catch (e) {
190
- ctx.logger.debug(`抖音主接口失败: ${e.message}`);
191
- }
192
- try {
193
- const res = await request.get(config.bugpkDouyinBackupApi, { params: { url } });
194
- if (res.data.code === 200 && res.data.data)
195
- return parseDouyin(res.data.data);
196
- }
197
- catch (e) {
198
- ctx.logger.debug(`抖音备用接口失败: ${e.message}`);
199
- }
200
- }
201
- if (platform === 'kuaishou') {
202
- try {
203
- const res = await request.get(config.bugpkKuaishouApi, { params: { url } });
204
- if (res.data.code === 200 && res.data.data)
205
- return parseKuaishou(res.data.data);
206
- }
207
- catch (e) {
208
- ctx.logger.debug(`快手接口失败: ${e.message}`);
209
- }
210
- }
211
- if (platform === 'bilibili') {
212
- try {
213
- const res = await request.get(config.bugpkBilibiliApi, { params: { url } });
214
- if (res.data.code === 200 && res.data.data)
215
- return parseBilibili(res.data.data);
216
- }
217
- catch (e) {
218
- ctx.logger.debug(`B站接口失败: ${e.message}`);
219
- }
220
- }
221
- }
222
- catch (e) {
223
- ctx.logger.error(`平台专属API解析异常: ${e.message}`);
149
+ await session.bot.deleteMessage(session.channelId, buf.tipMsgId);
224
150
  }
225
- return {
226
- title: '解析失败',
227
- author: '',
228
- desc: '通用接口和平台专属接口均解析失败,请稍后重试',
229
- digg: 0, coin: 0, collect: 0, share: 0, play: 0, danmaku: 0,
230
- cover: '', video: ''
231
- };
151
+ catch { }
232
152
  }
233
- async function processSingleUrl(session, url) {
153
+ async function processOne(session, url) {
234
154
  const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
235
155
  const now = Date.now();
236
156
  if (processed.get(hash) && now - processed.get(hash) < config.sameLinkInterval * 1000)
237
- return;
157
+ return null;
238
158
  processed.set(hash, now);
239
- const data = await parseVideo(url);
159
+ const data = await parse(url);
160
+ if (!data)
161
+ return null;
240
162
  let text = config.imageParseFormat
241
163
  .replace(/\${标题}/g, data.title)
242
164
  .replace(/\${UP主}/g, data.author)
243
- .replace(/\${简介}/g, data.desc.slice(0, config.maxDescLength))
244
- .replace(/\${点赞}/g, data.digg.toString())
245
- .replace(/\${投币}/g, data.coin.toString())
246
- .replace(/\${收藏}/g, data.collect.toString())
247
- .replace(/\${转发}/g, data.share.toString())
248
- .replace(/\${观看}/g, data.play.toString())
249
- .replace(/\${弹幕}/g, data.danmaku.toString())
165
+ .replace(/\${简介}/g, data.desc)
166
+ .replace(/\${点赞}/g, String(data.digg))
167
+ .replace(/\${投币}/g, String(data.coin))
168
+ .replace(/\${收藏}/g, String(data.collect))
169
+ .replace(/\${转发}/g, String(data.share))
170
+ .replace(/\${观看}/g, String(data.play))
171
+ .replace(/\${弹幕}/g, String(data.danmaku))
250
172
  .replace(/\${tab}/g, '\t')
251
- .replace(/\${~~~}/g, '——————————————');
252
- const [beforeCover, afterCover] = text.split('\${封面}');
253
- const msgParts = [];
254
- if (beforeCover && beforeCover.trim())
255
- msgParts.push(beforeCover.trim());
256
- if (data.cover)
257
- msgParts.push(koishi_1.h.image(data.cover));
258
- if (afterCover && afterCover.trim())
259
- msgParts.push(afterCover.trim());
260
- if (data.video && config.showVideoUrl)
261
- msgParts.push(`🔗 无水印链接:${data.video}`);
262
- let videoMsg = '';
263
- if (data.video && !config.showVideoUrl) {
264
- if (config.downloadVideoBeforeSend && data.video) {
173
+ .replace(/\${~~~}/g, '\n');
174
+ const parts = [];
175
+ if (config.returnContent.showImageText) {
176
+ const [a, b] = text.split('${封面}');
177
+ if (a?.trim())
178
+ parts.push(a.trim());
179
+ if (data.cover)
180
+ parts.push(koishi_1.h.image(data.cover));
181
+ if (b?.trim())
182
+ parts.push(b.trim());
183
+ }
184
+ let videoPart = '';
185
+ if (config.returnContent.showVideoFile && data.video && !config.returnContent.showVideoUrl) {
186
+ if (config.downloadVideoBeforeSend) {
265
187
  try {
266
- const filename = crypto_1.default.createHash('md5').update(data.video).digest('hex');
267
- const filePath = await downloadVideo(data.video, filename);
268
- videoMsg = koishi_1.h.video(`file://${filePath}`);
188
+ const fn = crypto_1.default.createHash('md5').update(data.video).digest('hex');
189
+ const fp = await downloadVideo(data.video, fn);
190
+ videoPart = koishi_1.h.video(`file://${fp}`);
269
191
  }
270
- catch (e) {
271
- ctx.logger.error(`视频下载失败: ${e.message}`);
272
- videoMsg = `📥 无水印视频:${data.video}`;
192
+ catch {
193
+ videoPart = `视频:${data.video}`;
273
194
  }
274
195
  }
275
196
  else {
276
- videoMsg = koishi_1.h.video(data.video);
197
+ videoPart = koishi_1.h.video(data.video);
277
198
  }
278
199
  }
279
- return {
280
- content: msgParts.join('\n'),
281
- video: videoMsg,
282
- data
283
- };
200
+ if (config.returnContent.showVideoUrl && data.video) {
201
+ parts.push(`直链:${data.video}`);
202
+ }
203
+ return { content: parts.join('\n'), video: videoPart };
284
204
  }
285
- async function processBufferedUrls(session) {
286
- const sessionKey = `${session.platform}:${session.userId}:${session.channelId}`;
287
- const bufferData = linkBuffer.get(sessionKey);
288
- if (!bufferData)
205
+ async function flush(session) {
206
+ const key = `${session.platform}:${session.userId}:${session.channelId}`;
207
+ const buf = linkBuffer.get(key);
208
+ if (!buf)
289
209
  return;
290
- clearTimeout(bufferData.timer);
291
- linkBuffer.delete(sessionKey);
292
- const results = [];
293
- for (const url of bufferData.urls) {
294
- const result = await processSingleUrl(session, url);
295
- if (result)
296
- results.push(result);
210
+ clearTimeout(buf.timer);
211
+ linkBuffer.delete(key);
212
+ await revoke(session, key);
213
+ const items = [];
214
+ for (const u of buf.urls) {
215
+ const it = await processOne(session, u);
216
+ if (it)
217
+ items.push(it);
297
218
  }
298
- if (results.length === 0)
219
+ if (!items.length)
299
220
  return;
300
- // 修复:通过平台判断是否为onebot,而非直接访问session.adapter
301
221
  if (config.enableForward && session.platform === 'onebot') {
302
- const forwardMessages = results.map(result => {
303
- const messages = [
304
- (0, koishi_1.h)('message', { user_id: session.selfId }, [result.content]),
305
- ];
306
- if (result.video)
307
- messages.push((0, koishi_1.h)('message', { user_id: session.selfId }, [result.video]));
308
- return messages;
309
- }).flat();
310
- await session.send((0, koishi_1.h)('forward', {
311
- messages: forwardMessages,
312
- title: '视频解析结果',
313
- brief: `共解析${results.length}个视频链接`,
314
- source: '视频解析插件',
315
- preview: [results[0].content.substring(0, 10) + '...'],
316
- summary: `查看${results.length}条解析结果`
317
- })).catch(e => {
318
- if (!config.ignoreSendError)
319
- ctx.logger.warn(`合并转发发送失败: ${e.message}`);
320
- sendIndividualMessages(session, results);
321
- });
322
- }
323
- else {
324
- sendIndividualMessages(session, results);
325
- }
326
- }
327
- async function sendIndividualMessages(session, results) {
328
- for (const result of results) {
222
+ const msgs = items.map(it => [
223
+ it.content ? (0, koishi_1.h)('message', { user_id: session.selfId }, [it.content]) : null,
224
+ it.video ? (0, koishi_1.h)('message', { user_id: session.selfId }, [it.video]) : null,
225
+ ].filter(Boolean)).flat();
329
226
  try {
330
- if (result.content)
331
- await session.send(result.content);
332
- if (result.video)
333
- await session.send(result.video);
334
- }
335
- catch (e) {
336
- if (!config.ignoreSendError)
337
- ctx.logger.warn(`单条消息发送失败: ${e.message}`);
227
+ await session.send((0, koishi_1.h)('forward', { messages: msgs }));
228
+ return;
338
229
  }
230
+ catch { }
231
+ }
232
+ for (const it of items) {
233
+ if (it.content)
234
+ await session.send(it.content).catch(() => { });
235
+ if (it.video)
236
+ await session.send(it.video).catch(() => { });
339
237
  }
340
238
  }
341
239
  ctx.on('message', async (session) => {
342
- try {
343
- if (!config.enable)
344
- return;
345
- const content = session.content.trim();
346
- if (!hasPlatformKeyword(content))
347
- return;
348
- const urls = extractUrl(content);
349
- if (urls.length === 0)
350
- return;
351
- if (config.showWaitingTip && !linkBuffer.has(`${session.platform}:${session.userId}:${session.channelId}`)) {
352
- await session.send(config.waitingTipText).catch(e => {
353
- if (!config.ignoreSendError)
354
- ctx.logger.warn(`发送等待提示失败: ${e.message}`);
355
- });
356
- }
357
- const sessionKey = `${session.platform}:${session.userId}:${session.channelId}`;
358
- if (linkBuffer.has(sessionKey)) {
359
- const bufferData = linkBuffer.get(sessionKey);
360
- bufferData.urls.push(...urls);
361
- clearTimeout(bufferData.timer);
362
- bufferData.timer = setTimeout(() => processBufferedUrls(session), config.messageBufferDelay * 1000);
363
- linkBuffer.set(sessionKey, bufferData);
240
+ if (!config.enable)
241
+ return;
242
+ const content = session.content.trim();
243
+ if (!hasPlatformKeyword(content))
244
+ return;
245
+ const urls = extractUrl(content);
246
+ if (!urls.length)
247
+ return;
248
+ const key = `${session.platform}:${session.userId}:${session.channelId}`;
249
+ if (config.showWaitingTip && !linkBuffer.has(key)) {
250
+ const tip = await session.send(config.waitingTipText).catch(() => null);
251
+ const mid = tip?.messageId || tip?.id;
252
+ linkBuffer.set(key, {
253
+ urls,
254
+ timer: setTimeout(() => flush(session), config.messageBufferDelay * 1000),
255
+ tipMsgId: mid,
256
+ });
257
+ }
258
+ else {
259
+ const ex = linkBuffer.get(key);
260
+ if (ex) {
261
+ clearTimeout(ex.timer);
262
+ ex.urls.push(...urls);
263
+ ex.timer = setTimeout(() => flush(session), config.messageBufferDelay * 1000);
264
+ linkBuffer.set(key, ex);
364
265
  }
365
266
  else {
366
- const timer = setTimeout(() => processBufferedUrls(session), config.messageBufferDelay * 1000);
367
- linkBuffer.set(sessionKey, { urls, timer });
267
+ linkBuffer.set(key, {
268
+ urls,
269
+ timer: setTimeout(() => flush(session), config.messageBufferDelay * 1000),
270
+ });
368
271
  }
369
272
  }
370
- catch (e) {
371
- ctx.logger.error(`消息处理异常: ${e.message}`);
372
- }
373
273
  });
374
274
  setInterval(() => {
375
275
  const now = Date.now();
376
276
  processed.forEach((t, k) => now - t > 86400000 && processed.delete(k));
377
- const tempPath = path_1.default.join(process.cwd(), 'temp_videos');
378
- if (fs_1.default.existsSync(tempPath)) {
379
- fs_1.default.readdirSync(tempPath).forEach(file => {
380
- const filePath = path_1.default.join(tempPath, file);
381
- const stat = fs_1.default.statSync(filePath);
382
- if (now - stat.ctimeMs > 3600000) {
383
- fs_1.default.unlinkSync(filePath);
384
- }
277
+ const dir = path_1.default.join(process.cwd(), 'temp_videos');
278
+ if (fs_1.default.existsSync(dir)) {
279
+ fs_1.default.readdirSync(dir).forEach(f => {
280
+ const p = path_1.default.join(dir, f);
281
+ if (now - fs_1.default.statSync(p).ctimeMs > 3600000)
282
+ fs_1.default.unlinkSync(p);
385
283
  });
386
284
  }
387
285
  }, 3600000);
388
- ctx.logger.info('视频解析插件加载完成');
286
+ ctx.logger.info('video-parser-all 加载完成');
389
287
  }
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.3",
4
+ "version": "0.1.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [