koishi-plugin-video-parser-all 1.3.5 → 1.3.6
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 +67 -34
- package/lib/index.js +241 -87
- package/package.json +7 -5
- package/readme.md +76 -68
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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,14 +85,19 @@ 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;
|
|
96
|
+
apiKeys?: ({
|
|
97
|
+
key?: string | null | undefined;
|
|
98
|
+
weight?: number | null | undefined;
|
|
99
|
+
} & import("cosmokit").Dict)[] | null | undefined;
|
|
100
|
+
rotationMode?: "sequential" | "load_balance" | null | undefined;
|
|
85
101
|
platformDedicatedFirst?: ({
|
|
86
102
|
bilibili?: boolean | null | undefined;
|
|
87
103
|
douyin?: boolean | null | undefined;
|
|
@@ -100,12 +116,16 @@ export declare const Config: Schema<{
|
|
|
100
116
|
twitter?: boolean | null | undefined;
|
|
101
117
|
instagram?: boolean | null | undefined;
|
|
102
118
|
doubao?: boolean | null | undefined;
|
|
103
|
-
doubao_chat?: boolean | null | undefined;
|
|
104
119
|
oasis?: boolean | null | undefined;
|
|
105
120
|
wechat_channel?: boolean | null | undefined;
|
|
121
|
+
lishi?: boolean | null | undefined;
|
|
122
|
+
quanmin?: boolean | null | undefined;
|
|
123
|
+
pipigx?: boolean | null | undefined;
|
|
124
|
+
pipixia?: boolean | null | undefined;
|
|
125
|
+
zuiyou?: boolean | null | undefined;
|
|
106
126
|
} & import("cosmokit").Dict) | null | undefined;
|
|
107
127
|
customApis?: ({
|
|
108
|
-
platform?: "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "
|
|
128
|
+
platform?: "bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "oasis" | "wechat_channel" | null | undefined;
|
|
109
129
|
apiUrl?: string | null | undefined;
|
|
110
130
|
apiKey?: string | null | undefined;
|
|
111
131
|
authHeaderType?: "Bearer" | "X-API-Key" | "Custom" | null | undefined;
|
|
@@ -162,9 +182,13 @@ export declare const Config: Schema<{
|
|
|
162
182
|
twitter: Schema<boolean, boolean>;
|
|
163
183
|
instagram: Schema<boolean, boolean>;
|
|
164
184
|
doubao: Schema<boolean, boolean>;
|
|
165
|
-
doubao_chat: Schema<boolean, boolean>;
|
|
166
185
|
oasis: Schema<boolean, boolean>;
|
|
167
186
|
wechat_channel: Schema<boolean, boolean>;
|
|
187
|
+
lishi: Schema<boolean, boolean>;
|
|
188
|
+
quanmin: Schema<boolean, boolean>;
|
|
189
|
+
pipigx: Schema<boolean, boolean>;
|
|
190
|
+
pipixia: Schema<boolean, boolean>;
|
|
191
|
+
zuiyou: Schema<boolean, boolean>;
|
|
168
192
|
}>;
|
|
169
193
|
} & import("cosmokit").Dict & {
|
|
170
194
|
unifiedMessageFormat: string;
|
|
@@ -173,24 +197,24 @@ export declare const Config: Schema<{
|
|
|
173
197
|
showCoverImage: boolean;
|
|
174
198
|
showMusicCover: boolean;
|
|
175
199
|
showImageFile: boolean;
|
|
176
|
-
forceDownloadImage: boolean;
|
|
177
|
-
imageDownloadTimeout: number;
|
|
178
|
-
imageTempDir: string;
|
|
179
|
-
maxImageSize: number;
|
|
180
200
|
showVideoFile: boolean;
|
|
201
|
+
forceDownloadImage: boolean;
|
|
181
202
|
forceDownloadVideo: boolean;
|
|
182
|
-
|
|
183
|
-
tempDir: string;
|
|
184
|
-
maxVideoSize: number;
|
|
185
|
-
maxDescLength: number;
|
|
186
|
-
maxConcurrent: number;
|
|
187
|
-
downloadConcurrency: number;
|
|
203
|
+
} & {
|
|
188
204
|
showMusicVoice: boolean;
|
|
189
205
|
showMusicVoiceFile: boolean;
|
|
190
206
|
forceDownloadMusicVoice: boolean;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
207
|
+
} & {
|
|
208
|
+
maxDescLength: number;
|
|
209
|
+
maxConcurrent: number;
|
|
210
|
+
downloadConcurrency: number;
|
|
211
|
+
mediaDownloadTimeout: number;
|
|
212
|
+
maxMediaSize: number;
|
|
213
|
+
downloadEngine: "internal" | "aria2";
|
|
214
|
+
aria2Host: string;
|
|
215
|
+
aria2Port: number;
|
|
216
|
+
aria2Secret: string;
|
|
217
|
+
resumeDownload: boolean;
|
|
194
218
|
} & {
|
|
195
219
|
timeout: number;
|
|
196
220
|
videoSendTimeout: number;
|
|
@@ -216,14 +240,19 @@ export declare const Config: Schema<{
|
|
|
216
240
|
ignoreSendError: boolean;
|
|
217
241
|
retryTimes: number;
|
|
218
242
|
retryInterval: number;
|
|
219
|
-
} & {
|
|
220
243
|
enableForward: boolean;
|
|
221
244
|
} & {
|
|
222
245
|
deduplicationInterval: number;
|
|
223
246
|
cacheTTL: number;
|
|
247
|
+
cacheDir: string;
|
|
224
248
|
} & {
|
|
225
249
|
primaryApiUrl: string;
|
|
226
250
|
backupApiUrl: string;
|
|
251
|
+
apiKeys: Schemastery.ObjectT<{
|
|
252
|
+
key: Schema<string, string>;
|
|
253
|
+
weight: Schema<number, number>;
|
|
254
|
+
}>[];
|
|
255
|
+
rotationMode: "sequential" | "load_balance";
|
|
227
256
|
platformDedicatedFirst: Schemastery.ObjectT<{
|
|
228
257
|
bilibili: Schema<boolean, boolean>;
|
|
229
258
|
douyin: Schema<boolean, boolean>;
|
|
@@ -242,12 +271,16 @@ export declare const Config: Schema<{
|
|
|
242
271
|
twitter: Schema<boolean, boolean>;
|
|
243
272
|
instagram: Schema<boolean, boolean>;
|
|
244
273
|
doubao: Schema<boolean, boolean>;
|
|
245
|
-
doubao_chat: Schema<boolean, boolean>;
|
|
246
274
|
oasis: Schema<boolean, boolean>;
|
|
247
275
|
wechat_channel: Schema<boolean, boolean>;
|
|
276
|
+
lishi: Schema<boolean, boolean>;
|
|
277
|
+
quanmin: Schema<boolean, boolean>;
|
|
278
|
+
pipigx: Schema<boolean, boolean>;
|
|
279
|
+
pipixia: Schema<boolean, boolean>;
|
|
280
|
+
zuiyou: Schema<boolean, boolean>;
|
|
248
281
|
}>;
|
|
249
282
|
customApis: Schemastery.ObjectT<{
|
|
250
|
-
platform: Schema<"bilibili" | "douyin" | "kuaishou" | "xiaohongshu" | "weibo" | "xigua" | "youtube" | "tiktok" | "acfun" | "zhihu" | "weishi" | "huya" | "haokan" | "meipai" | "twitter" | "instagram" | "doubao" | "
|
|
283
|
+
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
284
|
apiUrl: Schema<string, string>;
|
|
252
285
|
apiKey: Schema<string, string>;
|
|
253
286
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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(
|
|
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,22 +154,29 @@ 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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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(),
|
|
172
|
+
apiKeys: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
173
|
+
key: koishi_1.Schema.string().required().description('API Key'),
|
|
174
|
+
weight: koishi_1.Schema.number().min(1).default(1).description('权重(负载均衡模式)'),
|
|
175
|
+
})).default([]).description('多 API 密钥(轮换使用)'),
|
|
176
|
+
rotationMode: koishi_1.Schema.union([
|
|
177
|
+
koishi_1.Schema.const('sequential').description('顺序模式(无效时切换)'),
|
|
178
|
+
koishi_1.Schema.const('load_balance').description('负载均衡模式(轮询)'),
|
|
179
|
+
]).default('sequential').description('密钥轮换模式'),
|
|
164
180
|
platformDedicatedFirst: koishi_1.Schema.object({
|
|
165
181
|
bilibili: koishi_1.Schema.boolean().default(false).description('哔哩哔哩'),
|
|
166
182
|
douyin: koishi_1.Schema.boolean().default(false).description('抖音'),
|
|
@@ -179,9 +195,13 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
179
195
|
twitter: koishi_1.Schema.boolean().default(false).description('Twitter/X'),
|
|
180
196
|
instagram: koishi_1.Schema.boolean().default(false).description('Instagram'),
|
|
181
197
|
doubao: koishi_1.Schema.boolean().default(false).description('豆包'),
|
|
182
|
-
doubao_chat: koishi_1.Schema.boolean().default(false).description('豆包对话'),
|
|
183
198
|
oasis: koishi_1.Schema.boolean().default(false).description('绿洲'),
|
|
184
199
|
wechat_channel: koishi_1.Schema.boolean().default(false).description('视频号'),
|
|
200
|
+
lishi: koishi_1.Schema.boolean().default(false).description('梨视频'),
|
|
201
|
+
quanmin: koishi_1.Schema.boolean().default(false).description('全民直播'),
|
|
202
|
+
pipigx: koishi_1.Schema.boolean().default(false).description('皮皮搞笑'),
|
|
203
|
+
pipixia: koishi_1.Schema.boolean().default(false).description('皮皮虾'),
|
|
204
|
+
zuiyou: koishi_1.Schema.boolean().default(false).description('最右'),
|
|
185
205
|
}).description('优先使用专属 API'),
|
|
186
206
|
customApis: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
187
207
|
platform: koishi_1.Schema.union([
|
|
@@ -202,7 +222,6 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
202
222
|
koishi_1.Schema.const('twitter').description('Twitter/X'),
|
|
203
223
|
koishi_1.Schema.const('instagram').description('Instagram'),
|
|
204
224
|
koishi_1.Schema.const('doubao').description('豆包'),
|
|
205
|
-
koishi_1.Schema.const('doubao_chat').description('豆包对话'),
|
|
206
225
|
koishi_1.Schema.const('oasis').description('绿洲'),
|
|
207
226
|
koishi_1.Schema.const('wechat_channel').description('视频号'),
|
|
208
227
|
]).description('平台'),
|
|
@@ -215,12 +234,12 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
215
234
|
]).default('Bearer').description('认证头类型'),
|
|
216
235
|
customHeaderName: koishi_1.Schema.string().default('X-API-Key').description('自定义头名称'),
|
|
217
236
|
fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON'),
|
|
218
|
-
})).default([]).description('
|
|
237
|
+
})).default([]).description('覆盖内置平台 API'),
|
|
219
238
|
customPlatforms: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
220
239
|
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
|
|
240
|
+
exampleUrl: koishi_1.Schema.string().description('示例链接'),
|
|
241
|
+
keywords: koishi_1.Schema.string().required().description('关键词(逗号分隔)'),
|
|
242
|
+
apiUrl: koishi_1.Schema.string().required().description('解析 API'),
|
|
224
243
|
apiKey: koishi_1.Schema.string().default('').description('API Key'),
|
|
225
244
|
authHeaderType: koishi_1.Schema.union([
|
|
226
245
|
koishi_1.Schema.const('Bearer').description('Bearer'),
|
|
@@ -266,14 +285,14 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
266
285
|
' "music_cover": "data.music.cover",\n' +
|
|
267
286
|
' "music_url": "data.music.url"\n' +
|
|
268
287
|
'}').description('全局字段映射 JSON'),
|
|
269
|
-
}).description('API
|
|
288
|
+
}).description('API 与平台'),
|
|
270
289
|
koishi_1.Schema.object({
|
|
271
290
|
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('等待提示'),
|
|
272
291
|
unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持提示'),
|
|
273
292
|
invalidLinkText: koishi_1.Schema.string().default('无效的视频链接').description('无效链接提示'),
|
|
274
293
|
parseErrorPrefix: koishi_1.Schema.string().default('❌ 解析失败:').description('错误前缀'),
|
|
275
|
-
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('
|
|
276
|
-
}).description('
|
|
294
|
+
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('错误格式'),
|
|
295
|
+
}).description('界面文本'),
|
|
277
296
|
]);
|
|
278
297
|
const logger = new koishi_1.Logger(exports.name);
|
|
279
298
|
let debugEnabled = false;
|
|
@@ -309,10 +328,19 @@ const BUILTIN_LINK_RULES = [
|
|
|
309
328
|
{ pattern: /https?:\/\/x\.com\/\w+\/status\/\d{10,}/gi, type: 'twitter' },
|
|
310
329
|
{ pattern: /https?:\/\/(?:www\.)?instagram\.com\/p\/[0-9a-zA-Z_-]{10,}/gi, type: 'instagram' },
|
|
311
330
|
{ 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
331
|
{ pattern: /https?:\/\/(?:www\.)?oasis\.weibo\.com\/v\/[0-9a-zA-Z_-]+/gi, type: 'oasis' },
|
|
314
332
|
{ pattern: /https?:\/\/channels\.weixin\.qq\.com\/[0-9a-zA-Z_-]+/gi, type: 'wechat_channel' },
|
|
315
333
|
{ pattern: /https?:\/\/weixin\.qq\.com\/sph\/[0-9a-zA-Z_-]+/gi, type: 'wechat_channel' },
|
|
334
|
+
{ pattern: /https?:\/\/(?:www\.)?pearvideo\.com\/video_\d+/gi, type: 'lishi' },
|
|
335
|
+
{ pattern: /https?:\/\/video\.li\/[0-9a-zA-Z_-]{3,}/gi, type: 'lishi' },
|
|
336
|
+
{ pattern: /https?:\/\/(?:www\.)?quanmin\.tv\/\w+/gi, type: 'quanmin' },
|
|
337
|
+
{ pattern: /https?:\/\/(?:www\.)?quanmintv\.cn\/\w+/gi, type: 'quanmin' },
|
|
338
|
+
{ pattern: /https?:\/\/h5\.pipigx\.com\/pp\/post\/\d+/gi, type: 'pipigx' },
|
|
339
|
+
{ pattern: /https?:\/\/(?:www\.)?ippzone\.com\/\w+/gi, type: 'pipigx' },
|
|
340
|
+
{ pattern: /https?:\/\/(?:h5|www)\.pipix\.com\/\w+/gi, type: 'pipixia' },
|
|
341
|
+
{ pattern: /https?:\/\/(?:www\.)?pipixia\.com\/\w+/gi, type: 'pipixia' },
|
|
342
|
+
{ pattern: /https?:\/\/share\.xiaochuankeji\.cn\/hybrid\/share\/post\?pid=\d+/gi, type: 'zuiyou' },
|
|
343
|
+
{ pattern: /https?:\/\/(?:h5|www)\.izuiyou\.com\/\w+/gi, type: 'zuiyou' },
|
|
316
344
|
];
|
|
317
345
|
function buildCustomLinkRules(customPlatforms) {
|
|
318
346
|
if (!Array.isArray(customPlatforms) || customPlatforms.length === 0)
|
|
@@ -604,6 +632,10 @@ function generateFormattedText(p, format) {
|
|
|
604
632
|
'音乐作者': p.music.author || '',
|
|
605
633
|
'音乐封面': p.music.cover || '',
|
|
606
634
|
};
|
|
635
|
+
const varReplacements = Object.entries(vars).map(([key, val]) => ({
|
|
636
|
+
regex: new RegExp(`\\$\\{${key}\\}`, 'g'),
|
|
637
|
+
value: val,
|
|
638
|
+
}));
|
|
607
639
|
const lines = format.split('\n');
|
|
608
640
|
const resultLines = [];
|
|
609
641
|
for (const line of lines) {
|
|
@@ -622,8 +654,8 @@ function generateFormattedText(p, format) {
|
|
|
622
654
|
continue;
|
|
623
655
|
}
|
|
624
656
|
let newLine = line;
|
|
625
|
-
for (const
|
|
626
|
-
newLine = newLine.replace(
|
|
657
|
+
for (const { regex, value } of varReplacements) {
|
|
658
|
+
newLine = newLine.replace(regex, value);
|
|
627
659
|
}
|
|
628
660
|
resultLines.push(newLine);
|
|
629
661
|
}
|
|
@@ -661,6 +693,8 @@ function parseFieldMapping(mappingStr) {
|
|
|
661
693
|
}
|
|
662
694
|
}
|
|
663
695
|
function apply(ctx, config) {
|
|
696
|
+
// @ts-expect-error koishi runtime supports optional service dependencies
|
|
697
|
+
ctx.using(['downloads', 'silk', 'ffmpeg'], { optional: true });
|
|
664
698
|
debugEnabled = config.debug || false;
|
|
665
699
|
debugLog('INFO', 'plugin start');
|
|
666
700
|
const dedupCache = new SimpleLRUCache(1000, config.deduplicationInterval * 1000);
|
|
@@ -674,6 +708,7 @@ function apply(ctx, config) {
|
|
|
674
708
|
parseErrorItemFormat: config.parseErrorItemFormat || '【${url}】: ${msg}',
|
|
675
709
|
};
|
|
676
710
|
const proxyConfig = config.proxy || {};
|
|
711
|
+
const cacheDir = config.cacheDir || './temp_cache';
|
|
677
712
|
const customPlatforms = (config.customPlatforms || []).map((p) => ({
|
|
678
713
|
name: p.name,
|
|
679
714
|
apiUrl: p.apiUrl,
|
|
@@ -684,6 +719,59 @@ function apply(ctx, config) {
|
|
|
684
719
|
proxy: p.proxy || null
|
|
685
720
|
}));
|
|
686
721
|
const downloadLimiter = new ConcurrencyLimiter(config.downloadConcurrency || 3);
|
|
722
|
+
const mediaDownloadTimeout = config.mediaDownloadTimeout ?? 120000;
|
|
723
|
+
const maxMediaSize = config.maxMediaSize ?? 0;
|
|
724
|
+
const downloadEngine = config.downloadEngine || 'internal';
|
|
725
|
+
let aria2 = null;
|
|
726
|
+
if (downloadEngine === 'aria2') {
|
|
727
|
+
try {
|
|
728
|
+
const Aria2 = require('aria2');
|
|
729
|
+
aria2 = new Aria2({
|
|
730
|
+
host: config.aria2Host || '127.0.0.1',
|
|
731
|
+
port: config.aria2Port || 6800,
|
|
732
|
+
secure: false,
|
|
733
|
+
secret: config.aria2Secret || '',
|
|
734
|
+
path: '/jsonrpc'
|
|
735
|
+
});
|
|
736
|
+
aria2.open();
|
|
737
|
+
logger.info('aria2 连接成功');
|
|
738
|
+
}
|
|
739
|
+
catch (e) {
|
|
740
|
+
logger.warn('aria2 连接失败,回退到内置下载');
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const apiKeyList = (config.apiKeys || []).map((k) => ({
|
|
744
|
+
key: k.key,
|
|
745
|
+
weight: k.weight || 1,
|
|
746
|
+
lastUsed: 0
|
|
747
|
+
}));
|
|
748
|
+
let keyIndex = 0;
|
|
749
|
+
function getNextApiKey() {
|
|
750
|
+
if (apiKeyList.length === 0)
|
|
751
|
+
return '';
|
|
752
|
+
if (config.rotationMode === 'load_balance') {
|
|
753
|
+
const totalWeight = apiKeyList.reduce((sum, k) => sum + k.weight, 0);
|
|
754
|
+
let rand = Math.random() * totalWeight;
|
|
755
|
+
for (const k of apiKeyList) {
|
|
756
|
+
rand -= k.weight;
|
|
757
|
+
if (rand <= 0)
|
|
758
|
+
return k.key;
|
|
759
|
+
}
|
|
760
|
+
return apiKeyList[0].key;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
const current = apiKeyList[keyIndex % apiKeyList.length];
|
|
764
|
+
keyIndex++;
|
|
765
|
+
return current.key;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function markApiKeyInvalid(key) {
|
|
769
|
+
if (config.rotationMode === 'sequential') {
|
|
770
|
+
const idx = apiKeyList.findIndex(k => k.key === key);
|
|
771
|
+
if (idx !== -1)
|
|
772
|
+
apiKeyList.splice(idx, 1);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
687
775
|
function getPlatformConfig(type) {
|
|
688
776
|
if (type.startsWith('custom_')) {
|
|
689
777
|
const name = type.slice(7);
|
|
@@ -692,7 +780,7 @@ function apply(ctx, config) {
|
|
|
692
780
|
return {
|
|
693
781
|
apiUrl: custom.apiUrl,
|
|
694
782
|
dedicatedFirst: true,
|
|
695
|
-
apiKey: custom.apiKey,
|
|
783
|
+
apiKey: custom.apiKey || getNextApiKey(),
|
|
696
784
|
authHeaderType: custom.authHeaderType,
|
|
697
785
|
customHeaderName: custom.customHeaderName,
|
|
698
786
|
fieldMapping: custom.fieldMapping,
|
|
@@ -706,7 +794,6 @@ function apply(ctx, config) {
|
|
|
706
794
|
bilibili: 'https://api.bugpk.com/api/bilibili',
|
|
707
795
|
douyin: 'https://api.bugpk.com/api/douyin',
|
|
708
796
|
doubao: 'https://api.bugpk.com/api/dbvideos',
|
|
709
|
-
doubao_chat: 'https://api.bugpk.com/api/dbduihua',
|
|
710
797
|
kuaishou: 'https://api.bugpk.com/api/kuaishou',
|
|
711
798
|
xiaohongshu: 'https://api.bugpk.com/api/xhs',
|
|
712
799
|
jimeng: 'https://api.bugpk.com/api/jimengai',
|
|
@@ -725,11 +812,14 @@ function apply(ctx, config) {
|
|
|
725
812
|
let fieldMapping = undefined;
|
|
726
813
|
if (custom && custom.apiUrl) {
|
|
727
814
|
apiUrl = custom.apiUrl;
|
|
728
|
-
apiKey = custom.apiKey ||
|
|
815
|
+
apiKey = custom.apiKey || getNextApiKey();
|
|
729
816
|
authHeaderType = custom.authHeaderType || 'Bearer';
|
|
730
817
|
customHeaderName = custom.customHeaderName || 'X-API-Key';
|
|
731
818
|
fieldMapping = parseFieldMapping(custom.fieldMapping);
|
|
732
819
|
}
|
|
820
|
+
else {
|
|
821
|
+
apiKey = getNextApiKey();
|
|
822
|
+
}
|
|
733
823
|
const dedicatedFirst = config.platformDedicatedFirst?.[type] ?? false;
|
|
734
824
|
if (!fieldMapping) {
|
|
735
825
|
fieldMapping = parseFieldMapping(config.globalFieldMapping);
|
|
@@ -762,13 +852,77 @@ function apply(ctx, config) {
|
|
|
762
852
|
return cleanUrl(url);
|
|
763
853
|
}
|
|
764
854
|
}
|
|
765
|
-
async function downloadFile(url, timeout, maxSize,
|
|
855
|
+
async function downloadFile(url, timeout, maxSize, filePrefix, fileExts) {
|
|
766
856
|
if (!url)
|
|
767
857
|
throw new Error('链接为空');
|
|
768
|
-
await promises_1.default.mkdir(
|
|
769
|
-
const
|
|
858
|
+
await promises_1.default.mkdir(cacheDir, { recursive: true });
|
|
859
|
+
const extRegexCache = {};
|
|
860
|
+
const ext = fileExts.find(e => {
|
|
861
|
+
const r = extRegexCache[e] || (extRegexCache[e] = new RegExp('\\.' + e + '(\\?|$)', 'i'));
|
|
862
|
+
return r.test(url);
|
|
863
|
+
}) || fileExts[0];
|
|
770
864
|
const fileName = `${filePrefix}_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.${ext}`;
|
|
771
|
-
const filePath = path_1.default.resolve(
|
|
865
|
+
const filePath = path_1.default.resolve(cacheDir, fileName);
|
|
866
|
+
if (ctx.downloads) {
|
|
867
|
+
try {
|
|
868
|
+
const dest = await ctx.downloads.download(url, path_1.default.join(cacheDir, fileName), {
|
|
869
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
|
|
870
|
+
timeout
|
|
871
|
+
});
|
|
872
|
+
const stat = await promises_1.default.stat(dest);
|
|
873
|
+
if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
|
|
874
|
+
await promises_1.default.unlink(dest).catch(() => { });
|
|
875
|
+
throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
876
|
+
}
|
|
877
|
+
return dest;
|
|
878
|
+
}
|
|
879
|
+
catch (e) {
|
|
880
|
+
debugLog('ERROR', `downloads 服务下载失败,回退: ${getErrorMessage(e)}`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (aria2 && config.resumeDownload) {
|
|
884
|
+
try {
|
|
885
|
+
const gid = await aria2.call('aria2.addUri', [url], {
|
|
886
|
+
dir: cacheDir,
|
|
887
|
+
out: fileName,
|
|
888
|
+
split: 4,
|
|
889
|
+
continue: true,
|
|
890
|
+
maxConnectionPerServer: 5,
|
|
891
|
+
timeout: timeout / 1000,
|
|
892
|
+
maxFileNotFound: 5,
|
|
893
|
+
maxTries: 5,
|
|
894
|
+
retryWait: 2,
|
|
895
|
+
header: [`User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36`, `Referer: https://www.baidu.com/`]
|
|
896
|
+
});
|
|
897
|
+
let completed = false;
|
|
898
|
+
const ariaStartTime = Date.now();
|
|
899
|
+
while (!completed) {
|
|
900
|
+
if (Date.now() - ariaStartTime > timeout) {
|
|
901
|
+
await aria2.call('aria2.remove', gid).catch(() => { });
|
|
902
|
+
throw new Error('aria2下载超时');
|
|
903
|
+
}
|
|
904
|
+
const status = await aria2.call('aria2.tellStatus', gid);
|
|
905
|
+
if (status.status === 'complete') {
|
|
906
|
+
completed = true;
|
|
907
|
+
}
|
|
908
|
+
else if (status.status === 'error' || status.status === 'removed') {
|
|
909
|
+
throw new Error('aria2下载失败');
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
await delay(1000);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const stat = await promises_1.default.stat(filePath);
|
|
916
|
+
if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
|
|
917
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
918
|
+
throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
919
|
+
}
|
|
920
|
+
return filePath;
|
|
921
|
+
}
|
|
922
|
+
catch (e) {
|
|
923
|
+
debugLog('ERROR', `aria2下载失败,回退内置下载: ${getErrorMessage(e)}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
772
926
|
const writer = (0, fs_1.createWriteStream)(filePath);
|
|
773
927
|
let response;
|
|
774
928
|
try {
|
|
@@ -803,7 +957,7 @@ function apply(ctx, config) {
|
|
|
803
957
|
throw new Error(`写入文件失败: ${getErrorMessage(e)}`);
|
|
804
958
|
}
|
|
805
959
|
}
|
|
806
|
-
async function sendMedia(session, url, type, forceDownload, showFile
|
|
960
|
+
async function sendMedia(session, url, type, forceDownload, showFile) {
|
|
807
961
|
if (!url)
|
|
808
962
|
return;
|
|
809
963
|
await downloadLimiter.acquire();
|
|
@@ -818,7 +972,7 @@ function apply(ctx, config) {
|
|
|
818
972
|
const sendFunc = type === 'audio' ? koishi_1.h.audio : type === 'video' ? koishi_1.h.video : koishi_1.h.image;
|
|
819
973
|
if (forceDownload) {
|
|
820
974
|
try {
|
|
821
|
-
const localPath = await downloadFile(url,
|
|
975
|
+
const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type]);
|
|
822
976
|
try {
|
|
823
977
|
await sendWithTimeout(session, sendFunc(`file://${localPath}`));
|
|
824
978
|
}
|
|
@@ -847,7 +1001,7 @@ function apply(ctx, config) {
|
|
|
847
1001
|
}
|
|
848
1002
|
catch {
|
|
849
1003
|
try {
|
|
850
|
-
const localPath = await downloadFile(url,
|
|
1004
|
+
const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type]);
|
|
851
1005
|
try {
|
|
852
1006
|
await sendWithTimeout(session, sendFunc(`file://${localPath}`));
|
|
853
1007
|
}
|
|
@@ -957,26 +1111,26 @@ function apply(ctx, config) {
|
|
|
957
1111
|
await delay(300);
|
|
958
1112
|
}
|
|
959
1113
|
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
|
|
1114
|
+
await sendMedia(session, p.cover, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
|
|
961
1115
|
await delay(300);
|
|
962
1116
|
}
|
|
963
1117
|
if (config.showMusicCover && p.music.cover) {
|
|
964
|
-
await sendMedia(session, p.music.cover, 'image', config.forceDownloadImage, config.showImageFile
|
|
1118
|
+
await sendMedia(session, p.music.cover, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
|
|
965
1119
|
await delay(300);
|
|
966
1120
|
}
|
|
967
1121
|
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
|
|
1122
|
+
await sendMedia(session, p.video, 'video', config.forceDownloadVideo, config.showVideoFile).catch(() => { });
|
|
969
1123
|
await delay(500);
|
|
970
1124
|
}
|
|
971
1125
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
972
1126
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
973
1127
|
for (const imgUrl of imageUrls) {
|
|
974
|
-
await sendMedia(session, imgUrl, 'image', config.forceDownloadImage, config.showImageFile
|
|
1128
|
+
await sendMedia(session, imgUrl, 'image', config.forceDownloadImage, config.showImageFile).catch(() => { });
|
|
975
1129
|
await delay(200);
|
|
976
1130
|
}
|
|
977
1131
|
}
|
|
978
1132
|
if (config.showMusicVoice && p.music.url) {
|
|
979
|
-
await sendMedia(session, p.music.url, 'audio', config.forceDownloadMusicVoice, config.showMusicVoiceFile
|
|
1133
|
+
await sendMedia(session, p.music.url, 'audio', config.forceDownloadMusicVoice, config.showMusicVoiceFile).catch(() => { });
|
|
980
1134
|
await delay(300);
|
|
981
1135
|
}
|
|
982
1136
|
}
|
|
@@ -1045,6 +1199,10 @@ function apply(ctx, config) {
|
|
|
1045
1199
|
urlCacheLocal.set(cacheKey, { data: parsed, expire: Date.now() + cacheTTL });
|
|
1046
1200
|
return parsed;
|
|
1047
1201
|
}
|
|
1202
|
+
if (res.data?.code === 403 || res.data?.code === 401) {
|
|
1203
|
+
if (api.apiKey)
|
|
1204
|
+
markApiKeyInvalid(api.apiKey);
|
|
1205
|
+
}
|
|
1048
1206
|
throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
|
|
1049
1207
|
}
|
|
1050
1208
|
catch (error) {
|
|
@@ -1170,19 +1328,16 @@ function apply(ctx, config) {
|
|
|
1170
1328
|
});
|
|
1171
1329
|
const tempCleanupInterval = setInterval(async () => {
|
|
1172
1330
|
try {
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
if (now - stats.mtimeMs > 3600000) {
|
|
1184
|
-
await promises_1.default.unlink(filePath).catch(() => { });
|
|
1185
|
-
}
|
|
1331
|
+
const files = await promises_1.default.readdir(cacheDir);
|
|
1332
|
+
const now = Date.now();
|
|
1333
|
+
for (const file of files) {
|
|
1334
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
|
|
1335
|
+
(file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
|
|
1336
|
+
(file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
|
|
1337
|
+
const filePath = path_1.default.join(cacheDir, file);
|
|
1338
|
+
const stats = await promises_1.default.stat(filePath);
|
|
1339
|
+
if (now - stats.mtimeMs > 3600000) {
|
|
1340
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
1186
1341
|
}
|
|
1187
1342
|
}
|
|
1188
1343
|
}
|
|
@@ -1193,21 +1348,20 @@ function apply(ctx, config) {
|
|
|
1193
1348
|
}, 3600000);
|
|
1194
1349
|
ctx.on('dispose', () => {
|
|
1195
1350
|
clearInterval(tempCleanupInterval);
|
|
1351
|
+
if (aria2)
|
|
1352
|
+
aria2.close();
|
|
1196
1353
|
urlCacheLocal.clear();
|
|
1197
1354
|
dedupCache.clear();
|
|
1198
1355
|
debugLog('INFO', '插件已卸载');
|
|
1199
1356
|
});
|
|
1200
1357
|
process.on('beforeExit', async () => {
|
|
1201
1358
|
try {
|
|
1202
|
-
const
|
|
1203
|
-
for (const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
-
}
|
|
1359
|
+
const files = await promises_1.default.readdir(cacheDir);
|
|
1360
|
+
for (const file of files) {
|
|
1361
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
|
|
1362
|
+
(file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
|
|
1363
|
+
(file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
|
|
1364
|
+
await promises_1.default.unlink(path_1.default.join(cacheDir, file)).catch(() => { });
|
|
1211
1365
|
}
|
|
1212
1366
|
}
|
|
1213
1367
|
}
|
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.
|
|
4
|
+
"version": "1.3.6",
|
|
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
|
-
###
|
|
23
|
+
### 基本设置
|
|
24
24
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
25
25
|
|--------|------|--------|------|
|
|
26
|
-
| `enable` | boolean | true |
|
|
27
|
-
| `botName` | string | 视频解析机器人 |
|
|
28
|
-
| `showWaitingTip` | boolean | true |
|
|
29
|
-
| `debug` | boolean | false |
|
|
30
|
-
| `platformEnabled` | object |
|
|
26
|
+
| `enable` | boolean | true | 启用插件 |
|
|
27
|
+
| `botName` | string | 视频解析机器人 | 合并转发中的昵称 |
|
|
28
|
+
| `showWaitingTip` | boolean | true | 显示等待提示 |
|
|
29
|
+
| `debug` | boolean | false | Debug 日志 |
|
|
30
|
+
| `platformEnabled` | object | 全开 | 各平台开关 |
|
|
31
31
|
|
|
32
|
-
###
|
|
32
|
+
### 消息格式
|
|
33
33
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
34
34
|
|--------|------|--------|------|
|
|
35
|
-
| `unifiedMessageFormat` | string |
|
|
35
|
+
| `unifiedMessageFormat` | string | 见预设 | 文字格式,支持变量,空行自动隐藏 |
|
|
36
36
|
|
|
37
|
-
###
|
|
37
|
+
### 媒体发送
|
|
38
38
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
39
39
|
|--------|------|--------|------|
|
|
40
|
-
| `showImageText` | boolean | true |
|
|
41
|
-
| `showCoverImage` | boolean | true |
|
|
42
|
-
| `showMusicCover` | boolean | true |
|
|
43
|
-
| `showImageFile` | boolean | true |
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
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
|
-
###
|
|
55
|
+
### 性能与限制
|
|
73
56
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
74
57
|
|--------|------|--------|------|
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
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
|
-
###
|
|
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
|
-
###
|
|
86
|
+
### 缓存与临时文件
|
|
88
87
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
89
88
|
|--------|------|--------|------|
|
|
90
|
-
| `
|
|
89
|
+
| `deduplicationInterval` | number | 180 | 去重间隔 (s) |
|
|
90
|
+
| `cacheTTL` | number | 600 | 缓存时间 (s) |
|
|
91
|
+
| `cacheDir` | string | ./temp_cache | 统一临时目录 |
|
|
91
92
|
|
|
92
|
-
###
|
|
93
|
+
### API 与平台
|
|
93
94
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
94
95
|
|--------|------|--------|------|
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
|
|
98
|
-
|
|
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 |
|
|
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
|
-
##
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
- `koishi-plugin-
|
|
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) |
|