koishi-plugin-video-parser-all 1.3.5 → 1.3.7

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
@@ -1,4 +1,11 @@
1
1
  import { Context, Schema } from 'koishi';
2
+ declare module 'koishi' {
3
+ interface Context {
4
+ downloads?: {
5
+ download(url: string, dest: string, options?: Record<string, unknown>): Promise<string>;
6
+ };
7
+ }
8
+ }
2
9
  export declare const name = "video-parser-all";
3
10
  export declare const Config: Schema<{
4
11
  enable?: boolean | null | undefined;
@@ -23,9 +30,13 @@ export declare const Config: Schema<{
23
30
  twitter?: boolean | null | undefined;
24
31
  instagram?: boolean | null | undefined;
25
32
  doubao?: boolean | null | undefined;
26
- doubao_chat?: boolean | null | undefined;
27
33
  oasis?: boolean | null | undefined;
28
34
  wechat_channel?: boolean | null | undefined;
35
+ lishi?: boolean | null | undefined;
36
+ quanmin?: boolean | null | undefined;
37
+ pipigx?: boolean | null | undefined;
38
+ pipixia?: boolean | null | undefined;
39
+ zuiyou?: boolean | null | undefined;
29
40
  } & import("cosmokit").Dict) | null | undefined;
30
41
  } & import("cosmokit").Dict & {
31
42
  unifiedMessageFormat?: string | null | undefined;
@@ -34,24 +45,24 @@ export declare const Config: Schema<{
34
45
  showCoverImage?: boolean | null | undefined;
35
46
  showMusicCover?: boolean | null | undefined;
36
47
  showImageFile?: boolean | null | undefined;
37
- forceDownloadImage?: boolean | null | undefined;
38
- imageDownloadTimeout?: number | null | undefined;
39
- imageTempDir?: string | null | undefined;
40
- maxImageSize?: number | null | undefined;
41
48
  showVideoFile?: boolean | null | undefined;
49
+ forceDownloadImage?: boolean | null | undefined;
42
50
  forceDownloadVideo?: boolean | null | undefined;
43
- videoDownloadTimeout?: number | null | undefined;
44
- tempDir?: string | null | undefined;
45
- maxVideoSize?: number | null | undefined;
46
- maxDescLength?: number | null | undefined;
47
- maxConcurrent?: number | null | undefined;
48
- downloadConcurrency?: number | null | undefined;
51
+ } & {
49
52
  showMusicVoice?: boolean | null | undefined;
50
53
  showMusicVoiceFile?: boolean | null | undefined;
51
54
  forceDownloadMusicVoice?: boolean | null | undefined;
52
- musicDownloadTimeout?: number | null | undefined;
53
- musicTempDir?: string | null | undefined;
54
- maxMusicSize?: number | null | undefined;
55
+ } & {
56
+ maxDescLength?: number | null | undefined;
57
+ maxConcurrent?: number | null | undefined;
58
+ downloadConcurrency?: number | null | undefined;
59
+ mediaDownloadTimeout?: number | null | undefined;
60
+ maxMediaSize?: number | null | undefined;
61
+ downloadEngine?: "internal" | "aria2" | null | undefined;
62
+ aria2Host?: string | null | undefined;
63
+ aria2Port?: number | null | undefined;
64
+ aria2Secret?: string | null | undefined;
65
+ resumeDownload?: boolean | null | undefined;
55
66
  } & {
56
67
  timeout?: number | null | undefined;
57
68
  videoSendTimeout?: number | null | undefined;
@@ -74,11 +85,11 @@ export declare const Config: Schema<{
74
85
  ignoreSendError?: boolean | null | undefined;
75
86
  retryTimes?: number | null | undefined;
76
87
  retryInterval?: number | null | undefined;
77
- } & {
78
88
  enableForward?: boolean | null | undefined;
79
89
  } & {
80
90
  deduplicationInterval?: number | null | undefined;
81
91
  cacheTTL?: number | null | undefined;
92
+ cacheDir?: string | null | undefined;
82
93
  } & {
83
94
  primaryApiUrl?: string | null | undefined;
84
95
  backupApiUrl?: string | null | undefined;
@@ -100,12 +111,16 @@ export declare const Config: Schema<{
100
111
  twitter?: boolean | null | undefined;
101
112
  instagram?: boolean | null | undefined;
102
113
  doubao?: boolean | null | undefined;
103
- doubao_chat?: boolean | null | undefined;
104
114
  oasis?: boolean | null | undefined;
105
115
  wechat_channel?: boolean | null | undefined;
116
+ lishi?: boolean | null | undefined;
117
+ quanmin?: boolean | null | undefined;
118
+ pipigx?: boolean | null | undefined;
119
+ pipixia?: boolean | null | undefined;
120
+ zuiyou?: boolean | null | undefined;
106
121
  } & import("cosmokit").Dict) | null | undefined;
107
122
  customApis?: ({
108
- platform?: "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "doubao_chat" | "oasis" | "wechat_channel" | null | undefined;
123
+ platform?: "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "oasis" | "wechat_channel" | null | undefined;
109
124
  apiUrl?: string | null | undefined;
110
125
  apiKey?: string | null | undefined;
111
126
  authHeaderType?: "Bearer" | "X-API-Key" | "Custom" | null | undefined;
@@ -162,9 +177,13 @@ export declare const Config: Schema<{
162
177
  twitter: Schema<boolean, boolean>;
163
178
  instagram: Schema<boolean, boolean>;
164
179
  doubao: Schema<boolean, boolean>;
165
- doubao_chat: Schema<boolean, boolean>;
166
180
  oasis: Schema<boolean, boolean>;
167
181
  wechat_channel: Schema<boolean, boolean>;
182
+ lishi: Schema<boolean, boolean>;
183
+ quanmin: Schema<boolean, boolean>;
184
+ pipigx: Schema<boolean, boolean>;
185
+ pipixia: Schema<boolean, boolean>;
186
+ zuiyou: Schema<boolean, boolean>;
168
187
  }>;
169
188
  } & import("cosmokit").Dict & {
170
189
  unifiedMessageFormat: string;
@@ -173,24 +192,24 @@ export declare const Config: Schema<{
173
192
  showCoverImage: boolean;
174
193
  showMusicCover: boolean;
175
194
  showImageFile: boolean;
176
- forceDownloadImage: boolean;
177
- imageDownloadTimeout: number;
178
- imageTempDir: string;
179
- maxImageSize: number;
180
195
  showVideoFile: boolean;
196
+ forceDownloadImage: boolean;
181
197
  forceDownloadVideo: boolean;
182
- videoDownloadTimeout: number;
183
- tempDir: string;
184
- maxVideoSize: number;
185
- maxDescLength: number;
186
- maxConcurrent: number;
187
- downloadConcurrency: number;
198
+ } & {
188
199
  showMusicVoice: boolean;
189
200
  showMusicVoiceFile: boolean;
190
201
  forceDownloadMusicVoice: boolean;
191
- musicDownloadTimeout: number;
192
- musicTempDir: string;
193
- maxMusicSize: number;
202
+ } & {
203
+ maxDescLength: number;
204
+ maxConcurrent: number;
205
+ downloadConcurrency: number;
206
+ mediaDownloadTimeout: number;
207
+ maxMediaSize: number;
208
+ downloadEngine: "internal" | "aria2";
209
+ aria2Host: string;
210
+ aria2Port: number;
211
+ aria2Secret: string;
212
+ resumeDownload: boolean;
194
213
  } & {
195
214
  timeout: number;
196
215
  videoSendTimeout: number;
@@ -216,11 +235,11 @@ export declare const Config: Schema<{
216
235
  ignoreSendError: boolean;
217
236
  retryTimes: number;
218
237
  retryInterval: number;
219
- } & {
220
238
  enableForward: boolean;
221
239
  } & {
222
240
  deduplicationInterval: number;
223
241
  cacheTTL: number;
242
+ cacheDir: string;
224
243
  } & {
225
244
  primaryApiUrl: string;
226
245
  backupApiUrl: string;
@@ -242,12 +261,16 @@ export declare const Config: Schema<{
242
261
  twitter: Schema<boolean, boolean>;
243
262
  instagram: Schema<boolean, boolean>;
244
263
  doubao: Schema<boolean, boolean>;
245
- doubao_chat: Schema<boolean, boolean>;
246
264
  oasis: Schema<boolean, boolean>;
247
265
  wechat_channel: Schema<boolean, boolean>;
266
+ lishi: Schema<boolean, boolean>;
267
+ quanmin: Schema<boolean, boolean>;
268
+ pipigx: Schema<boolean, boolean>;
269
+ pipixia: Schema<boolean, boolean>;
270
+ zuiyou: Schema<boolean, boolean>;
248
271
  }>;
249
272
  customApis: Schemastery.ObjectT<{
250
- platform: Schema<"bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "doubao_chat" | "oasis" | "wechat_channel", "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "doubao_chat" | "oasis" | "wechat_channel">;
273
+ platform: Schema<"bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "oasis" | "wechat_channel", "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "oasis" | "wechat_channel">;
251
274
  apiUrl: Schema<string, string>;
252
275
  apiKey: Schema<string, string>;
253
276
  authHeaderType: Schema<"Bearer" | "X-API-Key" | "Custom", "Bearer" | "X-API-Key" | "Custom">;
package/lib/index.js CHANGED
@@ -71,9 +71,9 @@ exports.name = 'video-parser-all';
71
71
  exports.Config = koishi_1.Schema.intersect([
72
72
  koishi_1.Schema.object({
73
73
  enable: koishi_1.Schema.boolean().default(true).description('是否启用视频解析插件'),
74
- botName: koishi_1.Schema.string().default('视频解析机器人').description('合并转发消息中显示的机器人名称'),
74
+ botName: koishi_1.Schema.string().default('视频解析机器人').description('合并转发中显示的昵称'),
75
75
  showWaitingTip: koishi_1.Schema.boolean().default(true).description('解析时显示等待提示'),
76
- debug: koishi_1.Schema.boolean().default(false).description('开启调试模式,在控制台输出详细日志'),
76
+ debug: koishi_1.Schema.boolean().default(false).description('开启调试日志'),
77
77
  platformEnabled: koishi_1.Schema.object({
78
78
  bilibili: koishi_1.Schema.boolean().default(true).description('哔哩哔哩'),
79
79
  douyin: koishi_1.Schema.boolean().default(true).description('抖音'),
@@ -92,41 +92,50 @@ exports.Config = koishi_1.Schema.intersect([
92
92
  twitter: koishi_1.Schema.boolean().default(true).description('Twitter/X'),
93
93
  instagram: koishi_1.Schema.boolean().default(true).description('Instagram'),
94
94
  doubao: koishi_1.Schema.boolean().default(true).description('豆包'),
95
- doubao_chat: koishi_1.Schema.boolean().default(true).description('豆包对话'),
96
95
  oasis: koishi_1.Schema.boolean().default(true).description('绿洲'),
97
96
  wechat_channel: koishi_1.Schema.boolean().default(true).description('视频号'),
97
+ lishi: koishi_1.Schema.boolean().default(true).description('梨视频'),
98
+ quanmin: koishi_1.Schema.boolean().default(true).description('全民直播'),
99
+ pipigx: koishi_1.Schema.boolean().default(true).description('皮皮搞笑'),
100
+ pipixia: koishi_1.Schema.boolean().default(true).description('皮皮虾'),
101
+ zuiyou: koishi_1.Schema.boolean().default(true).description('最右'),
98
102
  }).description('各平台解析开关'),
99
- }).description('基础设置'),
103
+ }).description('基本设置'),
100
104
  koishi_1.Schema.object({
101
- unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}').description('文字消息格式,支持变量。某行所有变量为空时自动隐藏。封面及媒体文件由独立开关控制,默认不包含在文字中'),
102
- }).description('消息格式设置'),
105
+ unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}').description('文字格式,支持变量,空行自动隐藏'),
106
+ }).description('消息格式'),
103
107
  koishi_1.Schema.object({
104
- showImageText: koishi_1.Schema.boolean().default(true).description('是否发送文字内容'),
105
- showCoverImage: koishi_1.Schema.boolean().default(true).description('是否发送封面图片(视频/图集封面)'),
106
- showMusicCover: koishi_1.Schema.boolean().default(true).description('是否发送音乐封面图片'),
107
- showImageFile: koishi_1.Schema.boolean().default(true).description('封面/图片是否以文件形式发送(关闭则只发送链接)'),
108
- forceDownloadImage: koishi_1.Schema.boolean().default(false).description('强制下载封面/图片后发送'),
109
- imageDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(60000).description('图片下载超时(毫秒)'),
110
- imageTempDir: koishi_1.Schema.string().default('./temp_images').description('临时封面/图片存储目录'),
111
- maxImageSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载图片大小(MB),0 为不限制'),
112
- showVideoFile: koishi_1.Schema.boolean().default(true).description('视频是否以文件形式发送(关闭则只发送链接)'),
113
- forceDownloadVideo: koishi_1.Schema.boolean().default(false).description('强制下载视频后发送'),
114
- videoDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('视频下载超时(毫秒)'),
115
- tempDir: koishi_1.Schema.string().default('./temp_videos').description('临时视频存储目录'),
116
- maxVideoSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载视频大小(MB),0 为不限制'),
117
- maxDescLength: koishi_1.Schema.number().min(0).step(1).default(200).description('简介最大长度(字符)'),
108
+ showImageText: koishi_1.Schema.boolean().default(true).description('发送文字内容'),
109
+ showCoverImage: koishi_1.Schema.boolean().default(true).description('发送封面图片'),
110
+ showMusicCover: koishi_1.Schema.boolean().default(true).description('发送音乐封面图片'),
111
+ showImageFile: koishi_1.Schema.boolean().default(true).description('封面/图片是否以图片形式发送(关闭则只发送链接)'),
112
+ showVideoFile: koishi_1.Schema.boolean().default(true).description('视频是否以视频形式发送(关闭则只发送链接)'),
113
+ forceDownloadImage: koishi_1.Schema.boolean().default(false).description('强制下载封面/图片'),
114
+ forceDownloadVideo: koishi_1.Schema.boolean().default(false).description('强制下载视频'),
115
+ }).description('媒体发送'),
116
+ koishi_1.Schema.object({
117
+ showMusicVoice: koishi_1.Schema.boolean().default(false).description('音乐链接以语音形式发送'),
118
+ showMusicVoiceFile: koishi_1.Schema.boolean().default(true).description('音乐语音是否以文件形式发送(关闭则只发送链接)'),
119
+ forceDownloadMusicVoice: koishi_1.Schema.boolean().default(false).description('强制下载音乐语音'),
120
+ }).description('音乐语音(需 silk 和 ffmpeg)'),
121
+ koishi_1.Schema.object({
122
+ maxDescLength: koishi_1.Schema.number().min(0).step(1).default(200).description('简介长度上限'),
118
123
  maxConcurrent: koishi_1.Schema.number().min(1).step(1).default(3).description('解析最大并发数'),
119
124
  downloadConcurrency: koishi_1.Schema.number().min(1).step(1).default(3).description('下载线程数'),
120
- showMusicVoice: koishi_1.Schema.boolean().default(false).description('是否发送音乐(转语音)'),
121
- showMusicVoiceFile: koishi_1.Schema.boolean().default(true).description('音乐语音是否以文件形式发送(关闭则只发送链接)'),
122
- forceDownloadMusicVoice: koishi_1.Schema.boolean().default(false).description('强制下载音乐语音后发送'),
123
- musicDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('音乐下载超时(毫秒)'),
124
- musicTempDir: koishi_1.Schema.string().default('./temp_music').description('临时音乐存储目录'),
125
- maxMusicSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载音乐大小(MB),0 为不限制'),
126
- }).description('内容显示设置'),
125
+ mediaDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('统一下载超时 (ms)'),
126
+ maxMediaSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载文件大小 (MB),0 为不限制'),
127
+ downloadEngine: koishi_1.Schema.union([
128
+ koishi_1.Schema.const('internal').description('内置下载'),
129
+ koishi_1.Schema.const('aria2').description('aria2 下载'),
130
+ ]).default('internal').description('下载引擎'),
131
+ aria2Host: koishi_1.Schema.string().default('127.0.0.1').description('aria2 RPC 地址'),
132
+ aria2Port: koishi_1.Schema.number().default(6800).description('aria2 RPC 端口'),
133
+ aria2Secret: koishi_1.Schema.string().default('').description('aria2 RPC 密钥'),
134
+ resumeDownload: koishi_1.Schema.boolean().default(true).description('启用断点续传(仅 aria2 模式)'),
135
+ }).description('性能与限制'),
127
136
  koishi_1.Schema.object({
128
- timeout: koishi_1.Schema.number().min(0).step(1).default(180000).description('API 请求超时(毫秒)'),
129
- videoSendTimeout: koishi_1.Schema.number().min(0).step(1).default(60000).description('消息发送超时(毫秒)'),
137
+ timeout: koishi_1.Schema.number().min(0).step(1).default(180000).description('API 请求超时 (ms)'),
138
+ videoSendTimeout: koishi_1.Schema.number().min(0).step(1).default(180000).description('消息发送超时 (ms)'),
130
139
  userAgent: koishi_1.Schema.string().default('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36').description('User-Agent'),
131
140
  proxy: koishi_1.Schema.object({
132
141
  enabled: koishi_1.Schema.boolean().default(false).description('启用代理'),
@@ -145,19 +154,18 @@ exports.Config = koishi_1.Schema.intersect([
145
154
  name: koishi_1.Schema.string().required().description('头名称'),
146
155
  value: koishi_1.Schema.string().required().description('头值'),
147
156
  })).default([]).description('自定义请求头'),
148
- }).description('网络设置'),
157
+ }).description('网络与请求'),
149
158
  koishi_1.Schema.object({
150
159
  ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略发送失败'),
151
160
  retryTimes: koishi_1.Schema.number().min(0).step(1).default(3).description('重试次数'),
152
- retryInterval: koishi_1.Schema.number().min(0).step(1).default(1000).description('重试间隔(毫秒)'),
153
- }).description('错误与重试'),
154
- koishi_1.Schema.object({
155
- enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(支持 OneBot、Satori 平台)'),
156
- }).description('发送方式'),
161
+ retryInterval: koishi_1.Schema.number().min(0).step(1).default(1000).description('重试间隔 (ms)'),
162
+ enableForward: koishi_1.Schema.boolean().default(false).description('合并转发(OneBot/Satori)'),
163
+ }).description('发送与重试'),
157
164
  koishi_1.Schema.object({
158
- deduplicationInterval: koishi_1.Schema.number().min(0).step(1).default(180).description('去重间隔(秒)'),
159
- cacheTTL: koishi_1.Schema.number().min(0).step(1).default(600).description('缓存时间(秒)'),
160
- }).description('缓存与去重'),
165
+ deduplicationInterval: koishi_1.Schema.number().min(0).step(1).default(180).description('去重间隔 (s)'),
166
+ cacheTTL: koishi_1.Schema.number().min(0).step(1).default(600).description('缓存时间 (s)'),
167
+ cacheDir: koishi_1.Schema.string().default('./temp_cache').description('统一临时目录'),
168
+ }).description('缓存与临时文件'),
161
169
  koishi_1.Schema.object({
162
170
  primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').hidden(),
163
171
  backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').hidden(),
@@ -179,9 +187,13 @@ exports.Config = koishi_1.Schema.intersect([
179
187
  twitter: koishi_1.Schema.boolean().default(false).description('Twitter/X'),
180
188
  instagram: koishi_1.Schema.boolean().default(false).description('Instagram'),
181
189
  doubao: koishi_1.Schema.boolean().default(false).description('豆包'),
182
- doubao_chat: koishi_1.Schema.boolean().default(false).description('豆包对话'),
183
190
  oasis: koishi_1.Schema.boolean().default(false).description('绿洲'),
184
191
  wechat_channel: koishi_1.Schema.boolean().default(false).description('视频号'),
192
+ lishi: koishi_1.Schema.boolean().default(false).description('梨视频'),
193
+ quanmin: koishi_1.Schema.boolean().default(false).description('全民直播'),
194
+ pipigx: koishi_1.Schema.boolean().default(false).description('皮皮搞笑'),
195
+ pipixia: koishi_1.Schema.boolean().default(false).description('皮皮虾'),
196
+ zuiyou: koishi_1.Schema.boolean().default(false).description('最右'),
185
197
  }).description('优先使用专属 API'),
186
198
  customApis: koishi_1.Schema.array(koishi_1.Schema.object({
187
199
  platform: koishi_1.Schema.union([
@@ -202,7 +214,6 @@ exports.Config = koishi_1.Schema.intersect([
202
214
  koishi_1.Schema.const('twitter').description('Twitter/X'),
203
215
  koishi_1.Schema.const('instagram').description('Instagram'),
204
216
  koishi_1.Schema.const('doubao').description('豆包'),
205
- koishi_1.Schema.const('doubao_chat').description('豆包对话'),
206
217
  koishi_1.Schema.const('oasis').description('绿洲'),
207
218
  koishi_1.Schema.const('wechat_channel').description('视频号'),
208
219
  ]).description('平台'),
@@ -215,12 +226,12 @@ exports.Config = koishi_1.Schema.intersect([
215
226
  ]).default('Bearer').description('认证头类型'),
216
227
  customHeaderName: koishi_1.Schema.string().default('X-API-Key').description('自定义头名称'),
217
228
  fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON'),
218
- })).default([]).description('自定义内置平台专属 API'),
229
+ })).default([]).description('覆盖内置平台 API'),
219
230
  customPlatforms: koishi_1.Schema.array(koishi_1.Schema.object({
220
231
  name: koishi_1.Schema.string().required().description('平台名称'),
221
- exampleUrl: koishi_1.Schema.string().description('示例视频链接'),
222
- keywords: koishi_1.Schema.string().required().description('匹配关键词,逗号分隔'),
223
- apiUrl: koishi_1.Schema.string().required().description('解析 API 地址'),
232
+ exampleUrl: koishi_1.Schema.string().description('示例链接'),
233
+ keywords: koishi_1.Schema.string().required().description('关键词(逗号分隔)'),
234
+ apiUrl: koishi_1.Schema.string().required().description('解析 API'),
224
235
  apiKey: koishi_1.Schema.string().default('').description('API Key'),
225
236
  authHeaderType: koishi_1.Schema.union([
226
237
  koishi_1.Schema.const('Bearer').description('Bearer'),
@@ -266,14 +277,14 @@ exports.Config = koishi_1.Schema.intersect([
266
277
  ' "music_cover": "data.music.cover",\n' +
267
278
  ' "music_url": "data.music.url"\n' +
268
279
  '}').description('全局字段映射 JSON'),
269
- }).description('API 选择'),
280
+ }).description('API 与平台'),
270
281
  koishi_1.Schema.object({
271
282
  waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('等待提示'),
272
283
  unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持提示'),
273
284
  invalidLinkText: koishi_1.Schema.string().default('无效的视频链接').description('无效链接提示'),
274
285
  parseErrorPrefix: koishi_1.Schema.string().default('❌ 解析失败:').description('错误前缀'),
275
- parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('错误项格式'),
276
- }).description('界面文字'),
286
+ parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('错误格式'),
287
+ }).description('界面文本'),
277
288
  ]);
278
289
  const logger = new koishi_1.Logger(exports.name);
279
290
  let debugEnabled = false;
@@ -309,10 +320,19 @@ const BUILTIN_LINK_RULES = [
309
320
  { pattern: /https?:\/\/x\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
310
321
  { pattern: /https?:\/\/(?:www\.)?instagram\.com\/p\/[0-9a-zA-Z_-]{10,}/gi, type: 'instagram' },
311
322
  { pattern: /https?:\/\/(?:www\.)?doubao\.com\/video\/\d{10,}/gi, type: 'doubao' },
312
- { pattern: /https?:\/\/(?:www\.)?doubao\.com\/thread\/[0-9a-zA-Z_-]+/gi, type: 'doubao_chat' },
313
323
  { pattern: /https?:\/\/(?:www\.)?oasis\.weibo\.com\/v\/[0-9a-zA-Z_-]+/gi, type: 'oasis' },
314
324
  { pattern: /https?:\/\/channels\.weixin\.qq\.com\/[0-9a-zA-Z_-]+/gi, type: 'wechat_channel' },
315
325
  { pattern: /https?:\/\/weixin\.qq\.com\/sph\/[0-9a-zA-Z_-]+/gi, type: 'wechat_channel' },
326
+ { pattern: /https?:\/\/(?:www\.)?pearvideo\.com\/video_\d+/gi, type: 'lishi' },
327
+ { pattern: /https?:\/\/video\.li\/[0-9a-zA-Z_-]{3,}/gi, type: 'lishi' },
328
+ { pattern: /https?:\/\/(?:www\.)?quanmin\.tv\/\w+/gi, type: 'quanmin' },
329
+ { pattern: /https?:\/\/(?:www\.)?quanmintv\.cn\/\w+/gi, type: 'quanmin' },
330
+ { pattern: /https?:\/\/h5\.pipigx\.com\/pp\/post\/\d+/gi, type: 'pipigx' },
331
+ { pattern: /https?:\/\/(?:www\.)?ippzone\.com\/\w+/gi, type: 'pipigx' },
332
+ { pattern: /https?:\/\/(?:h5|www)\.pipix\.com\/\w+/gi, type: 'pipixia' },
333
+ { pattern: /https?:\/\/(?:www\.)?pipixia\.com\/\w+/gi, type: 'pipixia' },
334
+ { pattern: /https?:\/\/share\.xiaochuankeji\.cn\/hybrid\/share\/post\?pid=\d+/gi, type: 'zuiyou' },
335
+ { pattern: /https?:\/\/(?:h5|www)\.izuiyou\.com\/\w+/gi, type: 'zuiyou' },
316
336
  ];
317
337
  function buildCustomLinkRules(customPlatforms) {
318
338
  if (!Array.isArray(customPlatforms) || customPlatforms.length === 0)
@@ -604,6 +624,10 @@ function generateFormattedText(p, format) {
604
624
  '音乐作者': p.music.author || '',
605
625
  '音乐封面': p.music.cover || '',
606
626
  };
627
+ const varReplacements = Object.entries(vars).map(([key, val]) => ({
628
+ regex: new RegExp(`\\$\\{${key}\\}`, 'g'),
629
+ value: val,
630
+ }));
607
631
  const lines = format.split('\n');
608
632
  const resultLines = [];
609
633
  for (const line of lines) {
@@ -622,8 +646,8 @@ function generateFormattedText(p, format) {
622
646
  continue;
623
647
  }
624
648
  let newLine = line;
625
- for (const [key, val] of Object.entries(vars)) {
626
- newLine = newLine.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), val);
649
+ for (const { regex, value } of varReplacements) {
650
+ newLine = newLine.replace(regex, value);
627
651
  }
628
652
  resultLines.push(newLine);
629
653
  }
@@ -674,6 +698,7 @@ function apply(ctx, config) {
674
698
  parseErrorItemFormat: config.parseErrorItemFormat || '【${url}】: ${msg}',
675
699
  };
676
700
  const proxyConfig = config.proxy || {};
701
+ const cacheDir = config.cacheDir || './temp_cache';
677
702
  const customPlatforms = (config.customPlatforms || []).map((p) => ({
678
703
  name: p.name,
679
704
  apiUrl: p.apiUrl,
@@ -684,6 +709,27 @@ function apply(ctx, config) {
684
709
  proxy: p.proxy || null
685
710
  }));
686
711
  const downloadLimiter = new ConcurrencyLimiter(config.downloadConcurrency || 3);
712
+ const mediaDownloadTimeout = config.mediaDownloadTimeout ?? 120000;
713
+ const maxMediaSize = config.maxMediaSize ?? 0;
714
+ const downloadEngine = config.downloadEngine || 'internal';
715
+ let aria2 = null;
716
+ if (downloadEngine === 'aria2') {
717
+ try {
718
+ const Aria2 = require('aria2');
719
+ aria2 = new Aria2({
720
+ host: config.aria2Host || '127.0.0.1',
721
+ port: config.aria2Port || 6800,
722
+ secure: false,
723
+ secret: config.aria2Secret || '',
724
+ path: '/jsonrpc'
725
+ });
726
+ aria2.open();
727
+ logger.info('aria2 连接成功');
728
+ }
729
+ catch (e) {
730
+ logger.warn('aria2 连接失败,回退到内置下载');
731
+ }
732
+ }
687
733
  function getPlatformConfig(type) {
688
734
  if (type.startsWith('custom_')) {
689
735
  const name = type.slice(7);
@@ -692,7 +738,7 @@ function apply(ctx, config) {
692
738
  return {
693
739
  apiUrl: custom.apiUrl,
694
740
  dedicatedFirst: true,
695
- apiKey: custom.apiKey,
741
+ apiKey: custom.apiKey || '',
696
742
  authHeaderType: custom.authHeaderType,
697
743
  customHeaderName: custom.customHeaderName,
698
744
  fieldMapping: custom.fieldMapping,
@@ -706,7 +752,6 @@ function apply(ctx, config) {
706
752
  bilibili: 'https://api.bugpk.com/api/bilibili',
707
753
  douyin: 'https://api.bugpk.com/api/douyin',
708
754
  doubao: 'https://api.bugpk.com/api/dbvideos',
709
- doubao_chat: 'https://api.bugpk.com/api/dbduihua',
710
755
  kuaishou: 'https://api.bugpk.com/api/kuaishou',
711
756
  xiaohongshu: 'https://api.bugpk.com/api/xhs',
712
757
  jimeng: 'https://api.bugpk.com/api/jimengai',
@@ -730,6 +775,9 @@ function apply(ctx, config) {
730
775
  customHeaderName = custom.customHeaderName || 'X-API-Key';
731
776
  fieldMapping = parseFieldMapping(custom.fieldMapping);
732
777
  }
778
+ else {
779
+ apiKey = '';
780
+ }
733
781
  const dedicatedFirst = config.platformDedicatedFirst?.[type] ?? false;
734
782
  if (!fieldMapping) {
735
783
  fieldMapping = parseFieldMapping(config.globalFieldMapping);
@@ -762,13 +810,77 @@ function apply(ctx, config) {
762
810
  return cleanUrl(url);
763
811
  }
764
812
  }
765
- async function downloadFile(url, timeout, maxSize, tempDir, filePrefix, fileExts) {
813
+ async function downloadFile(url, timeout, maxSize, filePrefix, fileExts) {
766
814
  if (!url)
767
815
  throw new Error('链接为空');
768
- await promises_1.default.mkdir(tempDir, { recursive: true });
769
- const ext = fileExts.find(e => url.match(new RegExp('\\.' + e + '(\\?|$)', 'i'))) || fileExts[0];
816
+ await promises_1.default.mkdir(cacheDir, { recursive: true });
817
+ const extRegexCache = {};
818
+ const ext = fileExts.find(e => {
819
+ const r = extRegexCache[e] || (extRegexCache[e] = new RegExp('\\.' + e + '(\\?|$)', 'i'));
820
+ return r.test(url);
821
+ }) || fileExts[0];
770
822
  const fileName = `${filePrefix}_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.${ext}`;
771
- const filePath = path_1.default.resolve(tempDir, fileName);
823
+ const filePath = path_1.default.resolve(cacheDir, fileName);
824
+ if (ctx.downloads) {
825
+ try {
826
+ const dest = await ctx.downloads.download(url, path_1.default.join(cacheDir, fileName), {
827
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
828
+ timeout
829
+ });
830
+ const stat = await promises_1.default.stat(dest);
831
+ if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
832
+ await promises_1.default.unlink(dest).catch(() => { });
833
+ throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
834
+ }
835
+ return dest;
836
+ }
837
+ catch (e) {
838
+ debugLog('ERROR', `downloads 服务下载失败,回退: ${getErrorMessage(e)}`);
839
+ }
840
+ }
841
+ if (aria2 && config.resumeDownload) {
842
+ try {
843
+ const gid = await aria2.call('aria2.addUri', [url], {
844
+ dir: cacheDir,
845
+ out: fileName,
846
+ split: 4,
847
+ continue: true,
848
+ maxConnectionPerServer: 5,
849
+ timeout: timeout / 1000,
850
+ maxFileNotFound: 5,
851
+ maxTries: 5,
852
+ retryWait: 2,
853
+ header: [`User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36`, `Referer: https://www.baidu.com/`]
854
+ });
855
+ let completed = false;
856
+ const ariaStartTime = Date.now();
857
+ while (!completed) {
858
+ if (Date.now() - ariaStartTime > timeout) {
859
+ await aria2.call('aria2.remove', gid).catch(() => { });
860
+ throw new Error('aria2下载超时');
861
+ }
862
+ const status = await aria2.call('aria2.tellStatus', gid);
863
+ if (status.status === 'complete') {
864
+ completed = true;
865
+ }
866
+ else if (status.status === 'error' || status.status === 'removed') {
867
+ throw new Error('aria2下载失败');
868
+ }
869
+ else {
870
+ await delay(1000);
871
+ }
872
+ }
873
+ const stat = await promises_1.default.stat(filePath);
874
+ if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
875
+ await promises_1.default.unlink(filePath).catch(() => { });
876
+ throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
877
+ }
878
+ return filePath;
879
+ }
880
+ catch (e) {
881
+ debugLog('ERROR', `aria2下载失败,回退内置下载: ${getErrorMessage(e)}`);
882
+ }
883
+ }
772
884
  const writer = (0, fs_1.createWriteStream)(filePath);
773
885
  let response;
774
886
  try {
@@ -803,7 +915,7 @@ function apply(ctx, config) {
803
915
  throw new Error(`写入文件失败: ${getErrorMessage(e)}`);
804
916
  }
805
917
  }
806
- async function sendMedia(session, url, type, forceDownload, showFile, timeout, tempDir, maxSize) {
918
+ async function sendMedia(session, url, type, forceDownload, showFile) {
807
919
  if (!url)
808
920
  return;
809
921
  await downloadLimiter.acquire();
@@ -818,7 +930,7 @@ function apply(ctx, config) {
818
930
  const sendFunc = type === 'audio' ? koishi_1.h.audio : type === 'video' ? koishi_1.h.video : koishi_1.h.image;
819
931
  if (forceDownload) {
820
932
  try {
821
- const localPath = await downloadFile(url, timeout, maxSize, tempDir, prefixMap[type], extMap[type]);
933
+ const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type]);
822
934
  try {
823
935
  await sendWithTimeout(session, sendFunc(`file://${localPath}`));
824
936
  }
@@ -847,7 +959,7 @@ function apply(ctx, config) {
847
959
  }
848
960
  catch {
849
961
  try {
850
- const localPath = await downloadFile(url, timeout, maxSize, tempDir, prefixMap[type], extMap[type]);
962
+ const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type]);
851
963
  try {
852
964
  await sendWithTimeout(session, sendFunc(`file://${localPath}`));
853
965
  }
@@ -957,26 +1069,26 @@ function apply(ctx, config) {
957
1069
  await delay(300);
958
1070
  }
959
1071
  if (p.cover && config.showCoverImage && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
960
- await sendMedia(session, p.cover, 'image', config.forceDownloadImage, config.showImageFile, config.imageDownloadTimeout, config.imageTempDir, config.maxImageSize).catch(() => { });
1072
+ await sendMedia(session, p.cover, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
961
1073
  await delay(300);
962
1074
  }
963
1075
  if (config.showMusicCover && p.music.cover) {
964
- await sendMedia(session, p.music.cover, 'image', config.forceDownloadImage, config.showImageFile, config.imageDownloadTimeout, config.imageTempDir, config.maxImageSize).catch(() => { });
1076
+ await sendMedia(session, p.music.cover, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
965
1077
  await delay(300);
966
1078
  }
967
1079
  if (p.video && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
968
- await sendMedia(session, p.video, 'video', config.forceDownloadVideo, config.showVideoFile, config.videoDownloadTimeout, config.tempDir, config.maxVideoSize).catch(() => { });
1080
+ await sendMedia(session, p.video, 'video', config.forceDownloadVideo, config.showVideoFile).catch(() => { });
969
1081
  await delay(500);
970
1082
  }
971
1083
  if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
972
1084
  const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
973
1085
  for (const imgUrl of imageUrls) {
974
- await sendMedia(session, imgUrl, 'image', config.forceDownloadImage, config.showImageFile, config.imageDownloadTimeout, config.imageTempDir, config.maxImageSize).catch(() => { });
1086
+ await sendMedia(session, imgUrl, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
975
1087
  await delay(200);
976
1088
  }
977
1089
  }
978
1090
  if (config.showMusicVoice && p.music.url) {
979
- await sendMedia(session, p.music.url, 'audio', config.forceDownloadMusicVoice, config.showMusicVoiceFile, config.musicDownloadTimeout, config.musicTempDir, config.maxMusicSize).catch(() => { });
1091
+ await sendMedia(session, p.music.url, 'audio', config.forceDownloadMusicVoice, config.showMusicVoiceFile).catch(() => { });
980
1092
  await delay(300);
981
1093
  }
982
1094
  }
@@ -1170,19 +1282,16 @@ function apply(ctx, config) {
1170
1282
  });
1171
1283
  const tempCleanupInterval = setInterval(async () => {
1172
1284
  try {
1173
- const dirs = [config.tempDir || './temp_videos', config.imageTempDir || './temp_images', config.musicTempDir || './temp_music'];
1174
- for (const dir of dirs) {
1175
- const files = await promises_1.default.readdir(dir);
1176
- const now = Date.now();
1177
- for (const file of files) {
1178
- if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
1179
- (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
1180
- (file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
1181
- const filePath = path_1.default.join(dir, file);
1182
- const stats = await promises_1.default.stat(filePath);
1183
- if (now - stats.mtimeMs > 3600000) {
1184
- await promises_1.default.unlink(filePath).catch(() => { });
1185
- }
1285
+ const files = await promises_1.default.readdir(cacheDir);
1286
+ const now = Date.now();
1287
+ for (const file of files) {
1288
+ if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
1289
+ (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
1290
+ (file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
1291
+ const filePath = path_1.default.join(cacheDir, file);
1292
+ const stats = await promises_1.default.stat(filePath);
1293
+ if (now - stats.mtimeMs > 3600000) {
1294
+ await promises_1.default.unlink(filePath).catch(() => { });
1186
1295
  }
1187
1296
  }
1188
1297
  }
@@ -1193,21 +1302,20 @@ function apply(ctx, config) {
1193
1302
  }, 3600000);
1194
1303
  ctx.on('dispose', () => {
1195
1304
  clearInterval(tempCleanupInterval);
1305
+ if (aria2)
1306
+ aria2.close();
1196
1307
  urlCacheLocal.clear();
1197
1308
  dedupCache.clear();
1198
1309
  debugLog('INFO', '插件已卸载');
1199
1310
  });
1200
1311
  process.on('beforeExit', async () => {
1201
1312
  try {
1202
- const dirs = [config.tempDir || './temp_videos', config.imageTempDir || './temp_images', config.musicTempDir || './temp_music'];
1203
- for (const dir of dirs) {
1204
- const files = await promises_1.default.readdir(dir);
1205
- for (const file of files) {
1206
- if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
1207
- (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
1208
- (file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
1209
- await promises_1.default.unlink(path_1.default.join(dir, file)).catch(() => { });
1210
- }
1313
+ const files = await promises_1.default.readdir(cacheDir);
1314
+ for (const file of files) {
1315
+ if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
1316
+ (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
1317
+ (file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
1318
+ await promises_1.default.unlink(path_1.default.join(cacheDir, file)).catch(() => { });
1211
1319
  }
1212
1320
  }
1213
1321
  }
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.3.5",
4
+ "version": "1.3.7",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -45,7 +45,6 @@
45
45
  "twitter",
46
46
  "instagram",
47
47
  "doubao",
48
- "doubao_chat",
49
48
  "jimeng",
50
49
  "oasis",
51
50
  "wechat_channel",
@@ -94,14 +93,14 @@
94
93
  "typescript": "^5.3.3"
95
94
  },
96
95
  "dependencies": {
97
- "axios": "^1.16.1",
98
- "fast-xml-parser": "^4.5.6"
96
+ "axios": "^1.16.1"
99
97
  },
100
98
  "peerDependencies": {
101
99
  "@koishijs/plugin-console": "^5.30.4",
102
100
  "koishi": "^4.18.7",
103
101
  "koishi-plugin-silk": "^1.0.0",
104
- "koishi-plugin-ffmpeg": "^1.0.0"
102
+ "koishi-plugin-ffmpeg": "^1.0.0",
103
+ "aria2": "^4.1.2"
105
104
  },
106
105
  "peerDependenciesMeta": {
107
106
  "koishi-plugin-silk": {
@@ -109,6 +108,9 @@
109
108
  },
110
109
  "koishi-plugin-ffmpeg": {
111
110
  "optional": true
111
+ },
112
+ "aria2": {
113
+ "optional": true
112
114
  }
113
115
  },
114
116
  "repository": {
package/readme.md CHANGED
@@ -20,89 +20,94 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
20
20
 
21
21
  ## 配置项说明 (Configuration)
22
22
 
23
- ### 基础设置 (Basic Settings)
23
+ ### 基本设置
24
24
  | 配置项 | 类型 | 默认值 | 说明 |
25
25
  |--------|------|--------|------|
26
- | `enable` | boolean | true | 是否启用视频解析插件 |
27
- | `botName` | string | 视频解析机器人 | 合并转发消息中显示的机器人名称 |
28
- | `showWaitingTip` | boolean | true | 解析时是否显示等待提示 |
29
- | `debug` | boolean | false | 是否开启 Debug 模式,在控制台输出详细日志 |
30
- | `platformEnabled` | object | 各平台均为 `true` | 各平台解析开关,可单独关闭某平台 |
26
+ | `enable` | boolean | true | 启用插件 |
27
+ | `botName` | string | 视频解析机器人 | 合并转发中的昵称 |
28
+ | `showWaitingTip` | boolean | true | 显示等待提示 |
29
+ | `debug` | boolean | false | Debug 日志 |
30
+ | `platformEnabled` | object | 全开 | 各平台开关 |
31
31
 
32
- ### 统一消息格式 (Unified Message Format)
32
+ ### 消息格式
33
33
  | 配置项 | 类型 | 默认值 | 说明 |
34
34
  |--------|------|--------|------|
35
- | `unifiedMessageFormat` | string | `标题:${标题}\n作者:${作者}\n简介:${简介}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}` | 文字消息格式,支持变量替换。空行自动隐藏。 |
35
+ | `unifiedMessageFormat` | string | 见预设 | 文字格式,支持变量,空行自动隐藏 |
36
36
 
37
- ### 内容显示设置 (Content Display Settings)
37
+ ### 媒体发送
38
38
  | 配置项 | 类型 | 默认值 | 说明 |
39
39
  |--------|------|--------|------|
40
- | `showImageText` | boolean | true | 是否发送文字内容 |
41
- | `showCoverImage` | boolean | true | 是否发送封面图片(视频/图集封面) |
42
- | `showMusicCover` | boolean | true | 是否发送音乐封面图片 |
43
- | `showImageFile` | boolean | true | 封面/图片是否以文件形式发送(关闭则只发链接) |
44
- | `forceDownloadImage` | boolean | false | 强制下载封面/图片后发送 |
45
- | `imageDownloadTimeout` | number | 60000 | 图片下载超时(毫秒) |
46
- | `imageTempDir` | string | `./temp_images` | 临时封面/图片存储目录 |
47
- | `maxImageSize` | number | 0 | 最大下载图片大小(MB),0 不限制 |
48
- | `showVideoFile` | boolean | true | 视频是否以文件形式发送(关闭则只发链接) |
49
- | `forceDownloadVideo` | boolean | false | 强制下载视频后发送 |
50
- | `videoDownloadTimeout` | number | 120000 | 视频下载超时(毫秒) |
51
- | `tempDir` | string | `./temp_videos` | 临时视频存储目录 |
52
- | `maxVideoSize` | number | 0 | 最大下载视频大小(MB),0 不限制 |
53
- | `maxDescLength` | number | 200 | 简介最大长度(字符) |
54
- | `maxConcurrent` | number | 3 | 解析最大并发数 |
55
- | `downloadConcurrency` | number | 3 | 下载线程数(≥1 整数) |
56
- | `showMusicVoice` | boolean | false | 是否发送音乐(转语音)。**需要依赖 `koishi-plugin-silk` 和 `koishi-plugin-ffmpeg`** |
57
- | `showMusicVoiceFile` | boolean | true | 音乐语音是否以文件形式发送(关闭则只发链接) |
58
- | `forceDownloadMusicVoice` | boolean | false | 强制下载音乐语音后发送 |
59
- | `musicDownloadTimeout` | number | 120000 | 音乐下载超时(毫秒) |
60
- | `musicTempDir` | string | `./temp_music` | 临时音乐存储目录 |
61
- | `maxMusicSize` | number | 0 | 最大下载音乐大小(MB),0 不限制 |
62
-
63
- ### 网络与 API 设置 (Network & API Settings)
40
+ | `showImageText` | boolean | true | 发送文字内容 |
41
+ | `showCoverImage` | boolean | true | 发送封面图片 |
42
+ | `showMusicCover` | boolean | true | 发送音乐封面 |
43
+ | `showImageFile` | boolean | true | 封面/图片是否以图片形式发送(关闭则只发送链接) |
44
+ | `showVideoFile` | boolean | true | 视频是否以视频形式发送(关闭则只发送链接) |
45
+ | `forceDownloadImage` | boolean | false | 强制下载封面/图片 |
46
+ | `forceDownloadVideo` | boolean | false | 强制下载视频 |
47
+
48
+ ### 音乐语音(需 silk ffmpeg)
64
49
  | 配置项 | 类型 | 默认值 | 说明 |
65
50
  |--------|------|--------|------|
66
- | `timeout` | number | 180000 | API 请求超时时间(毫秒) |
67
- | `videoSendTimeout` | number | 60000 | 消息发送超时时间(毫秒,0 为不限制) |
68
- | `userAgent` | string | `Mozilla/5.0 ...` | User-Agent |
69
- | `proxy` | object | `{ enabled: false, protocol: "http", host: "127.0.0.1", port: 7890, auth: { username: "", password: "" } }` | HTTP/HTTPS 代理。`enabled` 开关(默认关闭),`protocol` 下拉选择 `http` 或 `https` |
70
- | `customHeaders` | array | [] | 自定义请求头,每项含 `name` 和 `value` |
51
+ | `showMusicVoice` | boolean | false | 音乐链接以语音发送 |
52
+ | `showMusicVoiceFile` | boolean | true | 音乐语音是否以文件形式发送(关闭则只发送链接) |
53
+ | `forceDownloadMusicVoice` | boolean | false | 强制下载音乐语音 |
71
54
 
72
- ### API 选择与回退设置 (API Selection & Fallback)
55
+ ### 性能与限制
73
56
  | 配置项 | 类型 | 默认值 | 说明 |
74
57
  |--------|------|--------|------|
75
- | `platformDedicatedFirst` | object | 各平台均为 `false` | 平台专属 API 优先开关,键:`bilibili` 等 |
76
- | `customApis` | array | [] | 自定义内置平台专属 API,含 `platform`, `apiUrl`, `apiKey`, `authHeaderType`, `customHeaderName`, `fieldMapping` |
77
- | `customPlatforms` | array | [] | 完全自定义新平台。每项含:`name`(平台名称)、`exampleUrl`(示例链接)、`keywords`(匹配关键词,逗号分隔)、`apiUrl`(解析API)、`apiKey`、`authHeaderType`、`customHeaderName`、`fieldMapping`、`proxy`(独立代理) |
78
- | `globalFieldMapping` | string | 预设字段映射 JSON | 全局字段映射,支持点号路径 |
58
+ | `maxDescLength` | number | 200 | 简介长度上限 |
59
+ | `maxConcurrent` | number | 3 | 解析最大并发数 |
60
+ | `downloadConcurrency` | number | 3 | 下载线程数 |
61
+ | `mediaDownloadTimeout` | number | 120000 | 统一下载超时 (ms) |
62
+ | `maxMediaSize` | number | 0 | 最大下载文件大小 (MB),0 为不限制 |
63
+ | `downloadEngine` | string | internal | 下载引擎(internal / aria2) |
64
+ | `aria2Host` | string | 127.0.0.1 | aria2 RPC 地址 |
65
+ | `aria2Port` | number | 6800 | aria2 RPC 端口 |
66
+ | `aria2Secret` | string | | aria2 RPC 密钥 |
67
+ | `resumeDownload` | boolean | true | 启用断点续传(仅 aria2) |
68
+
69
+ ### 网络与请求
70
+ | 配置项 | 类型 | 默认值 | 说明 |
71
+ |--------|------|--------|------|
72
+ | `timeout` | number | 180000 | API 超时 (ms) |
73
+ | `videoSendTimeout` | number | 180000 | 发送超时 (ms) |
74
+ | `userAgent` | string | 见预设 | User-Agent |
75
+ | `proxy` | object | ... | HTTP/HTTPS 代理 |
76
+ | `customHeaders` | array | [] | 自定义请求头 |
79
77
 
80
- ### 错误与重试设置 (Error & Retry Settings)
78
+ ### 发送与重试
81
79
  | 配置项 | 类型 | 默认值 | 说明 |
82
80
  |--------|------|--------|------|
83
81
  | `ignoreSendError` | boolean | true | 忽略发送失败 |
84
82
  | `retryTimes` | number | 3 | 重试次数 |
85
- | `retryInterval` | number | 1000 | 重试间隔(毫秒) |
83
+ | `retryInterval` | number | 1000 | 重试间隔 (ms) |
84
+ | `enableForward` | boolean | false | 合并转发(OneBot/Satori) |
86
85
 
87
- ### 发送方式设置 (Send Mode Settings)
86
+ ### 缓存与临时文件
88
87
  | 配置项 | 类型 | 默认值 | 说明 |
89
88
  |--------|------|--------|------|
90
- | `enableForward` | boolean | false | 启用合并转发(支持 OneBot、Satori 平台) |
89
+ | `deduplicationInterval` | number | 180 | 去重间隔 (s) |
90
+ | `cacheTTL` | number | 600 | 缓存时间 (s) |
91
+ | `cacheDir` | string | ./temp_cache | 统一临时目录 |
91
92
 
92
- ### 缓存与去重设置 (Cache & Deduplication Settings)
93
+ ### API 与平台
93
94
  | 配置项 | 类型 | 默认值 | 说明 |
94
95
  |--------|------|--------|------|
95
- | `deduplicationInterval` | number | 180 | 去重间隔(秒) |
96
- | `cacheTTL` | number | 600 | 缓存时间(秒) |
97
-
98
- ### 界面文字设置 (UI Text Settings)
96
+ | `apiKeys` | array | [] | API 密钥列表 |
97
+ | `rotationMode` | string | sequential | 密钥轮换模式(sequential / load_balance) |
98
+ | `platformDedicatedFirst` | object | 全关 | 优先专属 API |
99
+ | `customApis` | array | [] | 覆盖内置平台 API |
100
+ | `customPlatforms` | array | [] | 自定义新平台 |
101
+ | `globalFieldMapping` | string | 预设 | 全局字段映射 JSON |
102
+
103
+ ### 界面文本
99
104
  | 配置项 | 类型 | 默认值 | 说明 |
100
105
  |--------|------|--------|------|
101
- | `waitingTipText` | string | 正在解析视频,请稍候... | 等待提示 |
102
- | `unsupportedPlatformText` | string | 不支持该平台链接 | 不支持平台提示 |
103
- | `invalidLinkText` | string | 无效的视频链接 | 无效链接提示 |
106
+ | `waitingTipText` | string | 正在解析... | 等待提示 |
107
+ | `unsupportedPlatformText` | string | 不支持该平台 | 不支持提示 |
108
+ | `invalidLinkText` | string | 无效链接 | 无效链接提示 |
104
109
  | `parseErrorPrefix` | string | ❌ 解析失败: | 错误前缀 |
105
- | `parseErrorItemFormat` | string | `【${url}】: ${msg}` | 错误项格式 |
110
+ | `parseErrorItemFormat` | string | ... | 错误格式 |
106
111
 
107
112
  ## 支持的变量 (Supported Variables)
108
113
  在 `unifiedMessageFormat` 中可使用以下变量,空行自动隐藏:
@@ -125,13 +130,19 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
125
130
  | `${音乐标题}` | 音乐标题 |
126
131
  | `${音乐作者}` | 音乐作者 |
127
132
 
128
- ## 音乐语音依赖说明 (Music Voice Dependencies)
129
- 若启用 `showMusicVoice`,请确保已安装以下 Koishi 插件:
130
- - `koishi-plugin-silk`:提供 silk 编解码支持
131
- - `koishi-plugin-ffmpeg`:提供音频重采样支持
132
-
133
- 这些依赖已声明为可选依赖
134
- 若未安装,音乐语音将尝试直接发送原始音频,部分平台可能无法播放。
133
+ ## 依赖说明 (Dependencies)
134
+ ### 音乐语音(可选)
135
+ 若启用 `showMusicVoice`,请安装:
136
+ - `koishi-plugin-silk`:silk 编解码
137
+ - `koishi-plugin-ffmpeg`:音频重采样
138
+ ### aria2 下载引擎(可选)
139
+ 若启用 `downloadEngine: 'aria2'`,请安装并启动 aria2 服务,并安装 npm 包 `aria2`:
140
+ - 安装 aria2 服务端:https://github.com/aria2/aria2
141
+ - 安装 npm 客户端:`npm install aria2`
142
+ - 启动 RPC:`aria2c --enable-rpc --rpc-listen-all=true --rpc-allow-origin-all`
143
+ 未满足条件时自动降级为内置下载,不影响正常使用。
144
+ ### downloads 服务(可选)
145
+ 插件会自动检测 Koishi 内置 `downloads` 服务,优先使用其下载文件,失败时回退到内置/aria2 下载。
135
146
 
136
147
  ## 支持的平台 (Supported Platforms)
137
148
  | 平台名称 | 关键词识别 | 解析能力 |
@@ -150,22 +161,19 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
150
161
  | YouTube(油管) | youtube, youtu.be | 视频 |
151
162
  | TikTok(国际版抖音) | tiktok, tiktok.com | 短视频 |
152
163
  | 好看视频 | haokan, haokan.baidu.com | 短视频 |
153
- | 梨视频 | video.li | 短视频 |
154
164
  | 美拍 | meipai, meipai.com | 短视频 |
155
- | 全民直播 | quanmin (quanmin.tv) | 直播 |
156
165
  | Twitter / X | twitter, x.com | 视频、图文 |
157
166
  | Instagram | instagram, instagram.com | 图文、Reels |
158
167
  | 豆包 | doubao (doubao.com/video) | 视频 |
159
- | 豆包对话 | doubao (doubao.com/thread) | 对话分享 |
160
168
  | 皮皮搞笑 | pipigx, h5.pipigx.com | 短视频 |
161
169
  | 皮皮虾 | pipixia, h5.pipix.com | 短视频 |
162
170
  | 最右 | zuiyou, xiaochuankeji.cn | 短视频 |
171
+ | 梨视频 | video.li, pearvideo.com | 短视频 |
172
+ | 全民直播 | quanmin (quanmin.tv) | 直播 |
163
173
  | 绿洲 (Oasis) | oasis.weibo.com | 视频、图文 |
164
174
  | 视频号 (WeChat Channels) | channels.weixin.qq.com, weixin.qq.com/sph/ | 短视频 |
165
175
  | 🔧 自定义平台 | 通过 `customPlatforms` 添加 | 取决于 API |
166
176
 
167
- > 注:部分平台解析能力可能因API限制有所差异。可通过 `platformEnabled` 单独关闭。
168
-
169
177
  ## 项目贡献者 (Contributors)
170
178
 
171
179
  | 贡献者 (Contributor) | 贡献内容 (Contribution) |