koishi-plugin-video-parser-all 1.3.1 → 1.3.3
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 +14 -4
- package/lib/index.js +276 -192
- package/package.json +40 -6
- package/readme.md +66 -61
package/lib/index.d.ts
CHANGED
|
@@ -10,12 +10,17 @@ export declare const Config: Schema<{
|
|
|
10
10
|
} & {
|
|
11
11
|
showImageText?: boolean | null | undefined;
|
|
12
12
|
showCoverImage?: boolean | null | undefined;
|
|
13
|
+
showImageFile?: boolean | null | undefined;
|
|
14
|
+
forceDownloadImage?: boolean | null | undefined;
|
|
15
|
+
imageDownloadTimeout?: number | null | undefined;
|
|
16
|
+
imageTempDir?: string | null | undefined;
|
|
17
|
+
maxImageSize?: number | null | undefined;
|
|
13
18
|
showVideoFile?: boolean | null | undefined;
|
|
14
|
-
|
|
19
|
+
forceDownloadVideo?: boolean | null | undefined;
|
|
15
20
|
videoDownloadTimeout?: number | null | undefined;
|
|
16
21
|
tempDir?: string | null | undefined;
|
|
17
22
|
maxVideoSize?: number | null | undefined;
|
|
18
|
-
|
|
23
|
+
maxDescLength?: number | null | undefined;
|
|
19
24
|
maxConcurrent?: number | null | undefined;
|
|
20
25
|
} & {
|
|
21
26
|
timeout?: number | null | undefined;
|
|
@@ -94,12 +99,17 @@ export declare const Config: Schema<{
|
|
|
94
99
|
} & {
|
|
95
100
|
showImageText: boolean;
|
|
96
101
|
showCoverImage: boolean;
|
|
102
|
+
showImageFile: boolean;
|
|
103
|
+
forceDownloadImage: boolean;
|
|
104
|
+
imageDownloadTimeout: number;
|
|
105
|
+
imageTempDir: string;
|
|
106
|
+
maxImageSize: number;
|
|
97
107
|
showVideoFile: boolean;
|
|
98
|
-
|
|
108
|
+
forceDownloadVideo: boolean;
|
|
99
109
|
videoDownloadTimeout: number;
|
|
100
110
|
tempDir: string;
|
|
101
111
|
maxVideoSize: number;
|
|
102
|
-
|
|
112
|
+
maxDescLength: number;
|
|
103
113
|
maxConcurrent: number;
|
|
104
114
|
} & {
|
|
105
115
|
timeout: number;
|
package/lib/index.js
CHANGED
|
@@ -76,78 +76,83 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
76
76
|
debug: koishi_1.Schema.boolean().default(false).description('开启调试模式,在控制台输出详细日志'),
|
|
77
77
|
}).description('基础设置'),
|
|
78
78
|
koishi_1.Schema.object({
|
|
79
|
-
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}').description('
|
|
79
|
+
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\n作者:${作者}\n简介:${简介}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n音乐封面:${音乐封面}\n音乐链接:${音乐链接}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}').description('文字消息格式,支持变量。某行所有变量为空时自动隐藏。封面及媒体文件由独立开关控制,默认不包含在文字中'),
|
|
80
80
|
}).description('消息格式设置'),
|
|
81
81
|
koishi_1.Schema.object({
|
|
82
|
-
showImageText: koishi_1.Schema.boolean().default(true).description('
|
|
82
|
+
showImageText: koishi_1.Schema.boolean().default(true).description('是否发送文字内容'),
|
|
83
83
|
showCoverImage: koishi_1.Schema.boolean().default(true).description('是否发送封面图片'),
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
showImageFile: koishi_1.Schema.boolean().default(true).description('封面/图片是否以文件形式发送(关闭则只发送链接)'),
|
|
85
|
+
forceDownloadImage: koishi_1.Schema.boolean().default(false).description('强制下载封面/图片后发送'),
|
|
86
|
+
imageDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(60000).description('图片下载超时(毫秒)'),
|
|
87
|
+
imageTempDir: koishi_1.Schema.string().default('./temp_images').description('临时封面/图片存储目录'),
|
|
88
|
+
maxImageSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载图片大小(MB),0 为不限制'),
|
|
89
|
+
showVideoFile: koishi_1.Schema.boolean().default(true).description('视频是否以文件形式发送(关闭则只发送链接)'),
|
|
90
|
+
forceDownloadVideo: koishi_1.Schema.boolean().default(false).description('强制下载视频后发送'),
|
|
86
91
|
videoDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('视频下载超时(毫秒)'),
|
|
87
92
|
tempDir: koishi_1.Schema.string().default('./temp_videos').description('临时视频存储目录'),
|
|
88
|
-
maxVideoSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载视频大小(MB),0
|
|
89
|
-
|
|
90
|
-
maxConcurrent: koishi_1.Schema.number().min(1).step(1).default(3).description('
|
|
93
|
+
maxVideoSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载视频大小(MB),0 为不限制'),
|
|
94
|
+
maxDescLength: koishi_1.Schema.number().min(0).step(1).default(200).description('简介最大长度(字符)'),
|
|
95
|
+
maxConcurrent: koishi_1.Schema.number().min(1).step(1).default(3).description('批量解析最大并发数'),
|
|
91
96
|
}).description('内容显示设置'),
|
|
92
97
|
koishi_1.Schema.object({
|
|
93
98
|
timeout: koishi_1.Schema.number().min(0).step(1).default(180000).description('API 请求超时(毫秒)'),
|
|
94
|
-
videoSendTimeout: koishi_1.Schema.number().min(0).step(1).default(60000).description('
|
|
95
|
-
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('
|
|
99
|
+
videoSendTimeout: koishi_1.Schema.number().min(0).step(1).default(60000).description('消息发送超时(毫秒)'),
|
|
100
|
+
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'),
|
|
96
101
|
proxy: koishi_1.Schema.object({
|
|
97
|
-
enabled: koishi_1.Schema.boolean().default(false).description('
|
|
102
|
+
enabled: koishi_1.Schema.boolean().default(false).description('启用代理'),
|
|
98
103
|
protocol: koishi_1.Schema.union([
|
|
99
104
|
koishi_1.Schema.const('http').description('HTTP'),
|
|
100
105
|
koishi_1.Schema.const('https').description('HTTPS'),
|
|
101
|
-
]).default('http').description('
|
|
102
|
-
host: koishi_1.Schema.string().default('127.0.0.1').description('
|
|
103
|
-
port: koishi_1.Schema.number().default(7890).description('
|
|
106
|
+
]).default('http').description('协议'),
|
|
107
|
+
host: koishi_1.Schema.string().default('127.0.0.1').description('地址'),
|
|
108
|
+
port: koishi_1.Schema.number().default(7890).description('端口'),
|
|
104
109
|
auth: koishi_1.Schema.object({
|
|
105
|
-
username: koishi_1.Schema.string().default('').description('
|
|
106
|
-
password: koishi_1.Schema.string().default('').description('
|
|
107
|
-
}).description('
|
|
108
|
-
}).description('HTTP/HTTPS
|
|
110
|
+
username: koishi_1.Schema.string().default('').description('用户名'),
|
|
111
|
+
password: koishi_1.Schema.string().default('').description('密码'),
|
|
112
|
+
}).description('认证'),
|
|
113
|
+
}).description('HTTP/HTTPS 代理'),
|
|
109
114
|
customHeaders: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
110
|
-
name: koishi_1.Schema.string().required().description('
|
|
111
|
-
value: koishi_1.Schema.string().required().description('
|
|
112
|
-
})).default([]).description('
|
|
113
|
-
}).description('
|
|
115
|
+
name: koishi_1.Schema.string().required().description('头名称'),
|
|
116
|
+
value: koishi_1.Schema.string().required().description('头值'),
|
|
117
|
+
})).default([]).description('自定义请求头'),
|
|
118
|
+
}).description('网络设置'),
|
|
114
119
|
koishi_1.Schema.object({
|
|
115
|
-
ignoreSendError: koishi_1.Schema.boolean().default(true).description('
|
|
116
|
-
retryTimes: koishi_1.Schema.number().min(0).step(1).default(3).description('
|
|
117
|
-
retryInterval: koishi_1.Schema.number().min(0).step(1).default(1000).description('
|
|
118
|
-
}).description('
|
|
120
|
+
ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略发送失败'),
|
|
121
|
+
retryTimes: koishi_1.Schema.number().min(0).step(1).default(3).description('重试次数'),
|
|
122
|
+
retryInterval: koishi_1.Schema.number().min(0).step(1).default(1000).description('重试间隔(毫秒)'),
|
|
123
|
+
}).description('错误与重试'),
|
|
119
124
|
koishi_1.Schema.object({
|
|
120
|
-
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot
|
|
121
|
-
}).description('
|
|
125
|
+
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot)'),
|
|
126
|
+
}).description('发送方式'),
|
|
122
127
|
koishi_1.Schema.object({
|
|
123
|
-
deduplicationInterval: koishi_1.Schema.number().min(0).step(1).default(180).description('
|
|
124
|
-
cacheTTL: koishi_1.Schema.number().min(0).step(1).default(600).description('
|
|
125
|
-
}).description('
|
|
128
|
+
deduplicationInterval: koishi_1.Schema.number().min(0).step(1).default(180).description('去重间隔(秒)'),
|
|
129
|
+
cacheTTL: koishi_1.Schema.number().min(0).step(1).default(600).description('缓存时间(秒)'),
|
|
130
|
+
}).description('缓存与去重'),
|
|
126
131
|
koishi_1.Schema.object({
|
|
127
|
-
primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('主 API
|
|
128
|
-
backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').description('备用主 API
|
|
132
|
+
primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('主 API'),
|
|
133
|
+
backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').description('备用主 API'),
|
|
129
134
|
platformDedicatedFirst: koishi_1.Schema.object({
|
|
130
|
-
bilibili: koishi_1.Schema.boolean().default(false).description('哔哩哔哩
|
|
131
|
-
douyin: koishi_1.Schema.boolean().default(false).description('抖音
|
|
132
|
-
kuaishou: koishi_1.Schema.boolean().default(false).description('快手
|
|
133
|
-
xiaohongshu: koishi_1.Schema.boolean().default(false).description('小红书
|
|
134
|
-
weibo: koishi_1.Schema.boolean().default(false).description('微博
|
|
135
|
-
xigua: koishi_1.Schema.boolean().default(false).description('西瓜视频
|
|
136
|
-
youtube: koishi_1.Schema.boolean().default(false).description('YouTube
|
|
137
|
-
tiktok: koishi_1.Schema.boolean().default(false).description('TikTok
|
|
138
|
-
acfun: koishi_1.Schema.boolean().default(false).description('AcFun
|
|
139
|
-
zhihu: koishi_1.Schema.boolean().default(false).description('知乎
|
|
140
|
-
weishi: koishi_1.Schema.boolean().default(false).description('微视
|
|
141
|
-
huya: koishi_1.Schema.boolean().default(false).description('虎牙
|
|
142
|
-
haokan: koishi_1.Schema.boolean().default(false).description('好看视频
|
|
143
|
-
meipai: koishi_1.Schema.boolean().default(false).description('美拍
|
|
144
|
-
twitter: koishi_1.Schema.boolean().default(false).description('Twitter/X
|
|
145
|
-
instagram: koishi_1.Schema.boolean().default(false).description('Instagram
|
|
146
|
-
doubao: koishi_1.Schema.boolean().default(false).description('豆包
|
|
147
|
-
doubao_chat: koishi_1.Schema.boolean().default(false).description('
|
|
148
|
-
oasis: koishi_1.Schema.boolean().default(false).description('绿洲
|
|
149
|
-
wechat_channel: koishi_1.Schema.boolean().default(false).description('视频号
|
|
150
|
-
}).description('
|
|
135
|
+
bilibili: koishi_1.Schema.boolean().default(false).description('哔哩哔哩'),
|
|
136
|
+
douyin: koishi_1.Schema.boolean().default(false).description('抖音'),
|
|
137
|
+
kuaishou: koishi_1.Schema.boolean().default(false).description('快手'),
|
|
138
|
+
xiaohongshu: koishi_1.Schema.boolean().default(false).description('小红书'),
|
|
139
|
+
weibo: koishi_1.Schema.boolean().default(false).description('微博'),
|
|
140
|
+
xigua: koishi_1.Schema.boolean().default(false).description('西瓜视频'),
|
|
141
|
+
youtube: koishi_1.Schema.boolean().default(false).description('YouTube'),
|
|
142
|
+
tiktok: koishi_1.Schema.boolean().default(false).description('TikTok'),
|
|
143
|
+
acfun: koishi_1.Schema.boolean().default(false).description('AcFun'),
|
|
144
|
+
zhihu: koishi_1.Schema.boolean().default(false).description('知乎'),
|
|
145
|
+
weishi: koishi_1.Schema.boolean().default(false).description('微视'),
|
|
146
|
+
huya: koishi_1.Schema.boolean().default(false).description('虎牙'),
|
|
147
|
+
haokan: koishi_1.Schema.boolean().default(false).description('好看视频'),
|
|
148
|
+
meipai: koishi_1.Schema.boolean().default(false).description('美拍'),
|
|
149
|
+
twitter: koishi_1.Schema.boolean().default(false).description('Twitter/X'),
|
|
150
|
+
instagram: koishi_1.Schema.boolean().default(false).description('Instagram'),
|
|
151
|
+
doubao: koishi_1.Schema.boolean().default(false).description('豆包'),
|
|
152
|
+
doubao_chat: koishi_1.Schema.boolean().default(false).description('豆包对话'),
|
|
153
|
+
oasis: koishi_1.Schema.boolean().default(false).description('绿洲'),
|
|
154
|
+
wechat_channel: koishi_1.Schema.boolean().default(false).description('视频号'),
|
|
155
|
+
}).description('优先使用专属 API'),
|
|
151
156
|
customApis: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
152
157
|
platform: koishi_1.Schema.union([
|
|
153
158
|
koishi_1.Schema.const('bilibili').description('哔哩哔哩'),
|
|
@@ -167,20 +172,20 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
167
172
|
koishi_1.Schema.const('twitter').description('Twitter/X'),
|
|
168
173
|
koishi_1.Schema.const('instagram').description('Instagram'),
|
|
169
174
|
koishi_1.Schema.const('doubao').description('豆包'),
|
|
170
|
-
koishi_1.Schema.const('doubao_chat').description('
|
|
175
|
+
koishi_1.Schema.const('doubao_chat').description('豆包对话'),
|
|
171
176
|
koishi_1.Schema.const('oasis').description('绿洲'),
|
|
172
177
|
koishi_1.Schema.const('wechat_channel').description('视频号'),
|
|
173
|
-
]).description('
|
|
178
|
+
]).description('平台'),
|
|
174
179
|
apiUrl: koishi_1.Schema.string().description('API 地址'),
|
|
175
|
-
apiKey: koishi_1.Schema.string().description('API Key
|
|
180
|
+
apiKey: koishi_1.Schema.string().description('API Key').default(''),
|
|
176
181
|
authHeaderType: koishi_1.Schema.union([
|
|
177
|
-
koishi_1.Schema.const('Bearer').description('Bearer
|
|
182
|
+
koishi_1.Schema.const('Bearer').description('Bearer'),
|
|
178
183
|
koishi_1.Schema.const('X-API-Key').description('X-API-Key'),
|
|
179
|
-
koishi_1.Schema.const('Custom').description('自定义
|
|
184
|
+
koishi_1.Schema.const('Custom').description('自定义'),
|
|
180
185
|
]).default('Bearer').description('认证头类型'),
|
|
181
|
-
customHeaderName: koishi_1.Schema.string().
|
|
182
|
-
fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON
|
|
183
|
-
})).default([]).description('
|
|
186
|
+
customHeaderName: koishi_1.Schema.string().default('X-API-Key').description('自定义头名称'),
|
|
187
|
+
fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON'),
|
|
188
|
+
})).default([]).description('自定义专属 API'),
|
|
184
189
|
globalFieldMapping: koishi_1.Schema.string().role('textarea').default('{\n' +
|
|
185
190
|
' "title": "data.title",\n' +
|
|
186
191
|
' "desc": "data.description",\n' +
|
|
@@ -203,15 +208,15 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
203
208
|
' "music_author": "data.music.author",\n' +
|
|
204
209
|
' "music_cover": "data.music.cover",\n' +
|
|
205
210
|
' "music_url": "data.music.url"\n' +
|
|
206
|
-
'}').description('全局字段映射 JSON
|
|
207
|
-
}).description('API
|
|
211
|
+
'}').description('全局字段映射 JSON'),
|
|
212
|
+
}).description('API 选择'),
|
|
208
213
|
koishi_1.Schema.object({
|
|
209
|
-
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('
|
|
210
|
-
unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('
|
|
211
|
-
invalidLinkText: koishi_1.Schema.string().default('无效的视频链接').description('
|
|
212
|
-
parseErrorPrefix: koishi_1.Schema.string().default('❌ 解析失败:').description('
|
|
213
|
-
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('
|
|
214
|
-
}).description('
|
|
214
|
+
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('等待提示'),
|
|
215
|
+
unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持提示'),
|
|
216
|
+
invalidLinkText: koishi_1.Schema.string().default('无效的视频链接').description('无效链接提示'),
|
|
217
|
+
parseErrorPrefix: koishi_1.Schema.string().default('❌ 解析失败:').description('错误前缀'),
|
|
218
|
+
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('错误项格式'),
|
|
219
|
+
}).description('界面文字'),
|
|
215
220
|
]);
|
|
216
221
|
const logger = new koishi_1.Logger(exports.name);
|
|
217
222
|
let debugEnabled = false;
|
|
@@ -523,7 +528,6 @@ function generateFormattedText(p, format) {
|
|
|
523
528
|
'发布时间': p.publishTime ? formatPublishTime(p.publishTime) : '',
|
|
524
529
|
'图片数量': String(imageCount),
|
|
525
530
|
'作者ID': p.uid,
|
|
526
|
-
'封面': p.cover,
|
|
527
531
|
'视频链接': p.video,
|
|
528
532
|
'音乐标题': p.music.title || '',
|
|
529
533
|
'音乐作者': p.music.author || '',
|
|
@@ -724,113 +728,86 @@ function apply(ctx, config) {
|
|
|
724
728
|
throw new Error(`写入视频文件失败: ${getErrorMessage(e)}`);
|
|
725
729
|
}
|
|
726
730
|
}
|
|
727
|
-
async function
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
731
|
+
async function downloadImageFile(imageUrl) {
|
|
732
|
+
if (!imageUrl)
|
|
733
|
+
throw new Error('图片链接为空');
|
|
734
|
+
const imgTempDir = config.imageTempDir || './temp_images';
|
|
735
|
+
await promises_1.default.mkdir(imgTempDir, { recursive: true });
|
|
736
|
+
const ext = imageUrl.match(/\.(png|jpg|jpeg|gif|webp)(\?|$)/i)?.[1] || 'jpg';
|
|
737
|
+
const fileName = `img_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.${ext}`;
|
|
738
|
+
const filePath = path_1.default.resolve(imgTempDir, fileName);
|
|
739
|
+
const writer = (0, fs_1.createWriteStream)(filePath);
|
|
740
|
+
let response;
|
|
741
|
+
try {
|
|
742
|
+
response = await http({
|
|
743
|
+
method: 'GET',
|
|
744
|
+
url: imageUrl,
|
|
745
|
+
responseType: 'stream',
|
|
746
|
+
timeout: config.imageDownloadTimeout || 60000,
|
|
747
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.baidu.com/' },
|
|
748
|
+
maxRedirects: 5,
|
|
749
|
+
validateStatus: (status) => status >= 200 && status < 300,
|
|
750
|
+
});
|
|
742
751
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (dedicatedUrl)
|
|
748
|
-
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
752
|
+
catch (e) {
|
|
753
|
+
writer.destroy();
|
|
754
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
755
|
+
throw new Error(`下载图片失败: ${getErrorMessage(e)}`);
|
|
749
756
|
}
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
if (api.apiKey) {
|
|
765
|
-
const authHeaders = buildAuthHeaders(api.apiKey, api.authHeaderType || 'Bearer', api.customHeaderName || 'X-API-Key');
|
|
766
|
-
Object.assign(headers, authHeaders);
|
|
767
|
-
}
|
|
768
|
-
const res = await http.get(api.url, { params: { url }, timeout: config.timeout, headers });
|
|
769
|
-
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
770
|
-
const parsed = parseApiResponse(res.data, config.maxDescLength, api.fieldMapping);
|
|
771
|
-
urlCacheLocal.set(cacheKey, { data: parsed, expire: Date.now() + cacheTTL });
|
|
772
|
-
return parsed;
|
|
773
|
-
}
|
|
774
|
-
throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
|
|
775
|
-
}
|
|
776
|
-
catch (error) {
|
|
777
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
778
|
-
debugLog('ERROR', `${api.label} attempt ${attempt + 1} failed: ${lastError.message}`);
|
|
779
|
-
if (attempt < config.retryTimes)
|
|
780
|
-
await delay(config.retryInterval);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
debugLog('WARN', `${api.label} all retries failed`);
|
|
757
|
+
const maxSizeBytes = (config.maxImageSize ?? 0) * 1024 * 1024;
|
|
758
|
+
const contentLength = Number(response.headers['content-length'] || 0);
|
|
759
|
+
if (maxSizeBytes > 0 && contentLength > maxSizeBytes) {
|
|
760
|
+
writer.destroy();
|
|
761
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
762
|
+
throw new Error(`图片文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${config.maxImageSize}MB)`);
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
await (0, promises_2.pipeline)(response.data, writer);
|
|
766
|
+
return filePath;
|
|
767
|
+
}
|
|
768
|
+
catch (e) {
|
|
769
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
770
|
+
throw new Error(`写入图片文件失败: ${getErrorMessage(e)}`);
|
|
784
771
|
}
|
|
785
|
-
throw lastError || new Error('所有API请求全部失败');
|
|
786
772
|
}
|
|
787
|
-
async function
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
773
|
+
async function sendImage(session, imageUrl) {
|
|
774
|
+
if (!config.showCoverImage)
|
|
775
|
+
return;
|
|
776
|
+
const sendLink = async () => { await sendWithTimeout(session, `图片链接:${imageUrl}`).catch(() => { }); };
|
|
777
|
+
if (config.forceDownloadImage) {
|
|
791
778
|
try {
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
debugLog('WARN', `解析成功但无内容: ${candidate}`);
|
|
779
|
+
const localPath = await downloadImageFile(imageUrl);
|
|
780
|
+
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
781
|
+
return;
|
|
796
782
|
}
|
|
797
|
-
catch (
|
|
798
|
-
debugLog('ERROR',
|
|
783
|
+
catch (e) {
|
|
784
|
+
debugLog('ERROR', '强制下载图片失败,尝试URL发送:', getErrorMessage(e));
|
|
785
|
+
try {
|
|
786
|
+
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
await sendLink();
|
|
791
|
+
}
|
|
799
792
|
}
|
|
793
|
+
return;
|
|
800
794
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
}
|
|
810
|
-
async function sendWithTimeout(session, content, customRetries) {
|
|
811
|
-
const maxRetries = customRetries ?? config.retryTimes ?? 3;
|
|
812
|
-
const retryDelay = config.retryInterval || 1000;
|
|
813
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
795
|
+
if (!config.showImageFile) {
|
|
796
|
+
await sendLink();
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
814
803
|
try {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout));
|
|
818
|
-
return await Promise.race([sendPromise, timeoutPromise]);
|
|
819
|
-
}
|
|
820
|
-
else {
|
|
821
|
-
return await sendPromise;
|
|
822
|
-
}
|
|
804
|
+
const localPath = await downloadImageFile(imageUrl);
|
|
805
|
+
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
823
806
|
}
|
|
824
|
-
catch
|
|
825
|
-
|
|
826
|
-
debugLog('ERROR', `发送失败尝试 ${attempt + 1}: ${errMsg}`);
|
|
827
|
-
if (attempt < maxRetries)
|
|
828
|
-
await delay(retryDelay);
|
|
829
|
-
else if (!config.ignoreSendError)
|
|
830
|
-
throw err;
|
|
807
|
+
catch {
|
|
808
|
+
await sendLink();
|
|
831
809
|
}
|
|
832
810
|
}
|
|
833
|
-
return null;
|
|
834
811
|
}
|
|
835
812
|
async function sendVideoFile(session, videoUrl) {
|
|
836
813
|
if (!videoUrl)
|
|
@@ -845,7 +822,7 @@ function apply(ctx, config) {
|
|
|
845
822
|
return;
|
|
846
823
|
}
|
|
847
824
|
catch (e) {
|
|
848
|
-
debugLog('ERROR', '
|
|
825
|
+
debugLog('ERROR', '强制下载视频失败,尝试URL发送:', getErrorMessage(e));
|
|
849
826
|
try {
|
|
850
827
|
await sendWithTimeout(session, koishi_1.h.video(videoUrl));
|
|
851
828
|
return;
|
|
@@ -917,8 +894,9 @@ function apply(ctx, config) {
|
|
|
917
894
|
const text = item.text;
|
|
918
895
|
if (text && config.showImageText)
|
|
919
896
|
forwardMessages.push(buildForwardNode(session, text, botName));
|
|
920
|
-
if (
|
|
897
|
+
if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
921
898
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.cover), botName));
|
|
899
|
+
}
|
|
922
900
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
923
901
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
924
902
|
for (const imgUrl of imageUrls)
|
|
@@ -948,21 +926,18 @@ function apply(ctx, config) {
|
|
|
948
926
|
await sendWithTimeout(session, text);
|
|
949
927
|
await delay(300);
|
|
950
928
|
}
|
|
951
|
-
if (
|
|
952
|
-
await
|
|
929
|
+
if (p.cover && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
930
|
+
await sendImage(session, p.cover).catch(() => { });
|
|
953
931
|
await delay(300);
|
|
954
932
|
}
|
|
955
933
|
if (p.video && (p.type === 'video' || (p.type === 'live' && !p.live_photo?.length && !p.images?.length))) {
|
|
956
|
-
|
|
957
|
-
await sendVideoFile(session, p.video);
|
|
958
|
-
else
|
|
959
|
-
await sendWithTimeout(session, `视频链接:${p.video}`);
|
|
934
|
+
await sendVideoFile(session, p.video);
|
|
960
935
|
await delay(500);
|
|
961
936
|
}
|
|
962
937
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
963
938
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
964
939
|
for (const imgUrl of imageUrls) {
|
|
965
|
-
await
|
|
940
|
+
await sendImage(session, imgUrl).catch(() => { });
|
|
966
941
|
await delay(200);
|
|
967
942
|
}
|
|
968
943
|
}
|
|
@@ -970,6 +945,114 @@ function apply(ctx, config) {
|
|
|
970
945
|
}
|
|
971
946
|
debugLog('INFO', '处理完成');
|
|
972
947
|
}
|
|
948
|
+
async function fetchApi(url, type, fieldMapping) {
|
|
949
|
+
const cacheKey = url;
|
|
950
|
+
const cached = urlCacheLocal.get(cacheKey);
|
|
951
|
+
if (cached && cached.expire > Date.now())
|
|
952
|
+
return cached.data;
|
|
953
|
+
const { apiUrl: dedicatedUrl, dedicatedFirst, apiKey, authHeaderType, customHeaderName } = getPlatformConfig(type);
|
|
954
|
+
const primaryApi = config.primaryApiUrl || 'https://api.bugpk.com/api/short_videos';
|
|
955
|
+
const backupApi = config.backupApiUrl || 'https://api.bugpk.com/api/svparse';
|
|
956
|
+
const backupAllowed = backupSupportedPlatforms.has(type);
|
|
957
|
+
const apiList = [];
|
|
958
|
+
if (dedicatedFirst && dedicatedUrl) {
|
|
959
|
+
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
960
|
+
apiList.push({ url: primaryApi, label: '默认主API', fieldMapping });
|
|
961
|
+
if (backupAllowed)
|
|
962
|
+
apiList.push({ url: backupApi, label: '备用主API', fieldMapping });
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
apiList.push({ url: primaryApi, label: '默认主API', fieldMapping });
|
|
966
|
+
if (backupAllowed)
|
|
967
|
+
apiList.push({ url: backupApi, label: '备用主API', fieldMapping });
|
|
968
|
+
if (dedicatedUrl)
|
|
969
|
+
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
970
|
+
}
|
|
971
|
+
const customHeaders = config.customHeaders || [];
|
|
972
|
+
let lastError = null;
|
|
973
|
+
for (const api of apiList) {
|
|
974
|
+
for (let attempt = 0; attempt <= config.retryTimes; attempt++) {
|
|
975
|
+
try {
|
|
976
|
+
const headers = {
|
|
977
|
+
'User-Agent': config.userAgent,
|
|
978
|
+
'Referer': 'https://www.baidu.com/',
|
|
979
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
980
|
+
};
|
|
981
|
+
for (const h of customHeaders) {
|
|
982
|
+
if (h.name && h.value)
|
|
983
|
+
headers[h.name] = h.value;
|
|
984
|
+
}
|
|
985
|
+
if (api.apiKey) {
|
|
986
|
+
const authHeaders = buildAuthHeaders(api.apiKey, api.authHeaderType || 'Bearer', api.customHeaderName || 'X-API-Key');
|
|
987
|
+
Object.assign(headers, authHeaders);
|
|
988
|
+
}
|
|
989
|
+
const res = await http.get(api.url, { params: { url }, timeout: config.timeout, headers });
|
|
990
|
+
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
991
|
+
const parsed = parseApiResponse(res.data, config.maxDescLength, api.fieldMapping);
|
|
992
|
+
urlCacheLocal.set(cacheKey, { data: parsed, expire: Date.now() + cacheTTL });
|
|
993
|
+
return parsed;
|
|
994
|
+
}
|
|
995
|
+
throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
|
|
996
|
+
}
|
|
997
|
+
catch (error) {
|
|
998
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
999
|
+
debugLog('ERROR', `${api.label} attempt ${attempt + 1} failed: ${lastError.message}`);
|
|
1000
|
+
if (attempt < config.retryTimes)
|
|
1001
|
+
await delay(config.retryInterval);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
debugLog('WARN', `${api.label} all retries failed`);
|
|
1005
|
+
}
|
|
1006
|
+
throw lastError || new Error('所有API请求全部失败');
|
|
1007
|
+
}
|
|
1008
|
+
async function parseUrl(url, type, fieldMapping) {
|
|
1009
|
+
const realUrl = await resolveShortUrl(url);
|
|
1010
|
+
const candidates = [...new Set([realUrl, url])];
|
|
1011
|
+
for (const candidate of candidates) {
|
|
1012
|
+
try {
|
|
1013
|
+
const info = await fetchApi(candidate, type, fieldMapping);
|
|
1014
|
+
if (info.video || info.images.length > 0 || info.live_photo.length > 0)
|
|
1015
|
+
return { success: true, data: info };
|
|
1016
|
+
debugLog('WARN', `解析成功但无内容: ${candidate}`);
|
|
1017
|
+
}
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
debugLog('ERROR', `候选链接失败: ${candidate}`, getErrorMessage(error));
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return { success: false, msg: texts.unsupportedPlatformText };
|
|
1023
|
+
}
|
|
1024
|
+
async function processSingleUrl(url, type, fieldMapping) {
|
|
1025
|
+
const result = await parseUrl(url, type, fieldMapping);
|
|
1026
|
+
if (!result.success)
|
|
1027
|
+
return { success: false, msg: result.msg, url };
|
|
1028
|
+
const text = generateFormattedText(result.data, config.unifiedMessageFormat);
|
|
1029
|
+
return { success: true, data: { text, parsed: result.data } };
|
|
1030
|
+
}
|
|
1031
|
+
async function sendWithTimeout(session, content, customRetries) {
|
|
1032
|
+
const maxRetries = customRetries ?? config.retryTimes ?? 3;
|
|
1033
|
+
const retryDelay = config.retryInterval || 1000;
|
|
1034
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1035
|
+
try {
|
|
1036
|
+
let sendPromise = session.send(content);
|
|
1037
|
+
if (config.videoSendTimeout > 0) {
|
|
1038
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout));
|
|
1039
|
+
return await Promise.race([sendPromise, timeoutPromise]);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
return await sendPromise;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
catch (err) {
|
|
1046
|
+
const errMsg = getErrorMessage(err);
|
|
1047
|
+
debugLog('ERROR', `发送失败尝试 ${attempt + 1}: ${errMsg}`);
|
|
1048
|
+
if (attempt < maxRetries)
|
|
1049
|
+
await delay(retryDelay);
|
|
1050
|
+
else if (!config.ignoreSendError)
|
|
1051
|
+
throw err;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
973
1056
|
ctx.on('message', async (session) => {
|
|
974
1057
|
if (!config.enable)
|
|
975
1058
|
return;
|
|
@@ -1013,22 +1096,20 @@ function apply(ctx, config) {
|
|
|
1013
1096
|
});
|
|
1014
1097
|
const tempCleanupInterval = setInterval(async () => {
|
|
1015
1098
|
try {
|
|
1016
|
-
const
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1099
|
+
const dirs = [config.tempDir || './temp_videos', config.imageTempDir || './temp_images'];
|
|
1100
|
+
for (const dir of dirs) {
|
|
1101
|
+
const files = await promises_1.default.readdir(dir);
|
|
1102
|
+
const now = Date.now();
|
|
1103
|
+
for (const file of files) {
|
|
1104
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) || (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i))) {
|
|
1105
|
+
const filePath = path_1.default.join(dir, file);
|
|
1106
|
+
const stats = await promises_1.default.stat(filePath);
|
|
1107
|
+
if (now - stats.mtimeMs > 3600000) {
|
|
1108
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
1109
|
+
}
|
|
1027
1110
|
}
|
|
1028
1111
|
}
|
|
1029
1112
|
}
|
|
1030
|
-
if (deleted)
|
|
1031
|
-
debugLog('INFO', `清理了 ${deleted} 个过期临时视频文件`);
|
|
1032
1113
|
}
|
|
1033
1114
|
catch (e) {
|
|
1034
1115
|
debugLog('WARN', '清理临时文件失败:', e);
|
|
@@ -1042,11 +1123,14 @@ function apply(ctx, config) {
|
|
|
1042
1123
|
});
|
|
1043
1124
|
process.on('beforeExit', async () => {
|
|
1044
1125
|
try {
|
|
1045
|
-
const
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1126
|
+
const dirs = [config.tempDir || './temp_videos', config.imageTempDir || './temp_images'];
|
|
1127
|
+
for (const dir of dirs) {
|
|
1128
|
+
const files = await promises_1.default.readdir(dir);
|
|
1129
|
+
for (const file of files) {
|
|
1130
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) || (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i))) {
|
|
1131
|
+
await promises_1.default.unlink(path_1.default.join(dir, file)).catch(() => { });
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1050
1134
|
}
|
|
1051
1135
|
}
|
|
1052
1136
|
catch { }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-video-parser-all",
|
|
3
|
-
"description": "Koishi
|
|
4
|
-
"version": "1.3.
|
|
3
|
+
"description": "Koishi 全平台视频/图集解析插件,支持抖音/快手/B站/微博/小红书/剪映/YouTube/TikTok等20+平台",
|
|
4
|
+
"version": "1.3.3",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -45,10 +45,45 @@
|
|
|
45
45
|
"twitter",
|
|
46
46
|
"instagram",
|
|
47
47
|
"doubao",
|
|
48
|
+
"doubao_chat",
|
|
48
49
|
"jimeng",
|
|
50
|
+
"oasis",
|
|
51
|
+
"wechat_channel",
|
|
49
52
|
"debug",
|
|
50
53
|
"unified-api",
|
|
51
|
-
"
|
|
54
|
+
"proxy",
|
|
55
|
+
"field-mapping",
|
|
56
|
+
"image-download",
|
|
57
|
+
"视频解析",
|
|
58
|
+
"多平台",
|
|
59
|
+
"图集解析",
|
|
60
|
+
"去水印",
|
|
61
|
+
"哔哩哔哩",
|
|
62
|
+
"抖音",
|
|
63
|
+
"快手",
|
|
64
|
+
"微博",
|
|
65
|
+
"头条",
|
|
66
|
+
"西瓜视频",
|
|
67
|
+
"小红书",
|
|
68
|
+
"剪映",
|
|
69
|
+
"A站",
|
|
70
|
+
"知乎",
|
|
71
|
+
"微视",
|
|
72
|
+
"虎牙",
|
|
73
|
+
"油管",
|
|
74
|
+
"国际版抖音",
|
|
75
|
+
"好看视频",
|
|
76
|
+
"美拍",
|
|
77
|
+
"全民直播",
|
|
78
|
+
"推特",
|
|
79
|
+
"照片墙",
|
|
80
|
+
"豆包",
|
|
81
|
+
"即梦",
|
|
82
|
+
"绿洲",
|
|
83
|
+
"视频号",
|
|
84
|
+
"皮皮搞笑",
|
|
85
|
+
"皮皮虾",
|
|
86
|
+
"最右"
|
|
52
87
|
],
|
|
53
88
|
"devDependencies": {
|
|
54
89
|
"@koishijs/client": "^5.30.4",
|
|
@@ -58,8 +93,7 @@
|
|
|
58
93
|
},
|
|
59
94
|
"dependencies": {
|
|
60
95
|
"axios": "^1.16.1",
|
|
61
|
-
"fast-xml-parser": "^4.5.6"
|
|
62
|
-
"stream": "^0.0.3"
|
|
96
|
+
"fast-xml-parser": "^4.5.6"
|
|
63
97
|
},
|
|
64
98
|
"peerDependencies": {
|
|
65
99
|
"@koishijs/plugin-console": "^5.30.4",
|
|
@@ -76,4 +110,4 @@
|
|
|
76
110
|
"engines": {
|
|
77
111
|
"node": ">=16.0.0"
|
|
78
112
|
}
|
|
79
|
-
}
|
|
113
|
+
}
|
package/readme.md
CHANGED
|
@@ -31,95 +31,100 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
31
31
|
### 统一消息格式 (Unified Message Format)
|
|
32
32
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
33
33
|
|--------|------|--------|------|
|
|
34
|
-
| `unifiedMessageFormat` | string | `标题:${标题}\n作者:${作者}\n简介:${简介}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}` |
|
|
34
|
+
| `unifiedMessageFormat` | string | `标题:${标题}\n作者:${作者}\n简介:${简介}\n音乐标题:${音乐标题}\n音乐作者:${音乐作者}\n音乐封面:${音乐封面}\n音乐链接:${音乐链接}\n点赞:${点赞数}\n收藏:${收藏数}\n转发:${转发数}\n播放:${播放数}\n评论:${评论数}\n图片数量:${图片数量}` | 文字消息格式,支持变量替换。空行自动隐藏。封面及媒体由独立开关控制,默认不包含在文字中 |
|
|
35
35
|
|
|
36
36
|
### 内容显示设置 (Content Display Settings)
|
|
37
37
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
38
38
|
|--------|------|--------|------|
|
|
39
|
-
| `showImageText` | boolean | true |
|
|
40
|
-
| `showCoverImage` | boolean | true |
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
39
|
+
| `showImageText` | boolean | true | 是否发送文字内容 |
|
|
40
|
+
| `showCoverImage` | boolean | true | 是否发送封面图片 |
|
|
41
|
+
| `showImageFile` | boolean | true | 封面/图片是否以文件形式发送(关闭则只发链接) |
|
|
42
|
+
| `forceDownloadImage` | boolean | false | 强制下载封面/图片后发送 |
|
|
43
|
+
| `imageDownloadTimeout` | number | 60000 | 图片下载超时(毫秒) |
|
|
44
|
+
| `imageTempDir` | string | `./temp_images` | 临时封面/图片存储目录 |
|
|
45
|
+
| `maxImageSize` | number | 0 | 最大下载图片大小(MB),0 不限制 |
|
|
46
|
+
| `showVideoFile` | boolean | true | 视频是否以文件形式发送(关闭则只发链接) |
|
|
47
|
+
| `forceDownloadVideo` | boolean | false | 强制下载视频后发送 |
|
|
43
48
|
| `videoDownloadTimeout` | number | 120000 | 视频下载超时(毫秒) |
|
|
44
49
|
| `tempDir` | string | `./temp_videos` | 临时视频存储目录 |
|
|
45
|
-
| `maxVideoSize` | number | 0 | 最大下载视频大小(MB),0
|
|
46
|
-
| `
|
|
47
|
-
| `maxConcurrent` | number | 3 |
|
|
50
|
+
| `maxVideoSize` | number | 0 | 最大下载视频大小(MB),0 不限制 |
|
|
51
|
+
| `maxDescLength` | number | 200 | 简介最大长度(字符) |
|
|
52
|
+
| `maxConcurrent` | number | 3 | 批量解析最大并发数 |
|
|
48
53
|
|
|
49
54
|
### 网络与 API 设置 (Network & API Settings)
|
|
50
55
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
51
56
|
|--------|------|--------|------|
|
|
52
57
|
| `timeout` | number | 180000 | API 请求超时时间(毫秒) |
|
|
53
|
-
| `videoSendTimeout` | number | 60000 |
|
|
54
|
-
| `userAgent` | string | `Mozilla/5.0
|
|
55
|
-
| `proxy` | object | `{ enabled: false, protocol: "http", host: "127.0.0.1", port: 7890, auth: { username: "", password: "" } }` | HTTP/HTTPS
|
|
56
|
-
| `customHeaders` | array | [] |
|
|
58
|
+
| `videoSendTimeout` | number | 60000 | 消息发送超时时间(毫秒,0 为不限制) |
|
|
59
|
+
| `userAgent` | string | `Mozilla/5.0 ...` | User-Agent |
|
|
60
|
+
| `proxy` | object | `{ enabled: false, protocol: "http", host: "127.0.0.1", port: 7890, auth: { username: "", password: "" } }` | HTTP/HTTPS 代理。`enabled` 开关(默认关闭),`protocol` 下拉选择 `http` 或 `https` |
|
|
61
|
+
| `customHeaders` | array | [] | 自定义请求头,每项含 `name` 和 `value` |
|
|
57
62
|
|
|
58
63
|
### API 选择与回退设置 (API Selection & Fallback)
|
|
59
64
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
60
65
|
|--------|------|--------|------|
|
|
61
|
-
| `primaryApiUrl` | string | `https://api.bugpk.com/api/short_videos` | 主 API
|
|
62
|
-
| `backupApiUrl` | string | `https://api.bugpk.com/api/svparse` | 备用主 API
|
|
63
|
-
| `platformDedicatedFirst` | object | 各平台均为 `false` |
|
|
64
|
-
| `customApis` | array | [] | 自定义平台专属 API
|
|
65
|
-
| `globalFieldMapping` | string |
|
|
66
|
+
| `primaryApiUrl` | string | `https://api.bugpk.com/api/short_videos` | 主 API 地址 |
|
|
67
|
+
| `backupApiUrl` | string | `https://api.bugpk.com/api/svparse` | 备用主 API,仅支持部分平台 |
|
|
68
|
+
| `platformDedicatedFirst` | object | 各平台均为 `false` | 平台专属 API 优先开关,键:`bilibili` 等 |
|
|
69
|
+
| `customApis` | array | [] | 自定义平台专属 API,含 `platform`, `apiUrl`, `apiKey`, `authHeaderType`, `customHeaderName`, `fieldMapping` |
|
|
70
|
+
| `globalFieldMapping` | string | 预设字段映射 JSON | 全局字段映射,支持点号路径 |
|
|
66
71
|
|
|
67
72
|
### 错误与重试设置 (Error & Retry Settings)
|
|
68
73
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
69
74
|
|--------|------|--------|------|
|
|
70
|
-
| `ignoreSendError` | boolean | true |
|
|
71
|
-
| `retryTimes` | number | 3 |
|
|
72
|
-
| `retryInterval` | number | 1000 |
|
|
75
|
+
| `ignoreSendError` | boolean | true | 忽略发送失败 |
|
|
76
|
+
| `retryTimes` | number | 3 | 重试次数 |
|
|
77
|
+
| `retryInterval` | number | 1000 | 重试间隔(毫秒) |
|
|
73
78
|
|
|
74
79
|
### 发送方式设置 (Send Mode Settings)
|
|
75
80
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
76
81
|
|--------|------|--------|------|
|
|
77
|
-
| `enableForward` | boolean | false |
|
|
82
|
+
| `enableForward` | boolean | false | 启用合并转发(仅 OneBot 平台) |
|
|
78
83
|
|
|
79
84
|
### 缓存与去重设置 (Cache & Deduplication Settings)
|
|
80
85
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
81
86
|
|--------|------|--------|------|
|
|
82
|
-
| `deduplicationInterval` | number | 180 |
|
|
83
|
-
| `cacheTTL` | number | 600 |
|
|
87
|
+
| `deduplicationInterval` | number | 180 | 去重间隔(秒) |
|
|
88
|
+
| `cacheTTL` | number | 600 | 缓存时间(秒) |
|
|
84
89
|
|
|
85
90
|
### 界面文字设置 (UI Text Settings)
|
|
86
91
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
87
92
|
|--------|------|--------|------|
|
|
88
|
-
| `waitingTipText` | string | 正在解析视频,请稍候... |
|
|
89
|
-
| `unsupportedPlatformText` | string | 不支持该平台链接 |
|
|
90
|
-
| `invalidLinkText` | string | 无效的视频链接 |
|
|
91
|
-
| `parseErrorPrefix` | string | ❌ 解析失败: |
|
|
92
|
-
| `parseErrorItemFormat` | string | `【${url}】: ${msg}` |
|
|
93
|
+
| `waitingTipText` | string | 正在解析视频,请稍候... | 等待提示 |
|
|
94
|
+
| `unsupportedPlatformText` | string | 不支持该平台链接 | 不支持平台提示 |
|
|
95
|
+
| `invalidLinkText` | string | 无效的视频链接 | 无效链接提示 |
|
|
96
|
+
| `parseErrorPrefix` | string | ❌ 解析失败: | 错误前缀 |
|
|
97
|
+
| `parseErrorItemFormat` | string | `【${url}】: ${msg}` | 错误项格式 |
|
|
93
98
|
|
|
94
99
|
## 支持的变量 (Supported Variables)
|
|
95
|
-
在 `unifiedMessageFormat`
|
|
96
|
-
|
|
97
|
-
| 变量名 | 说明 |
|
|
98
|
-
|
|
99
|
-
| `${标题}` | 视频/图集标题 |
|
|
100
|
-
| `${作者}` |
|
|
101
|
-
| `${简介}` |
|
|
102
|
-
| `${视频时长}` | 视频时长(时:分:秒) |
|
|
103
|
-
| `${点赞数}` | 点赞数量 |
|
|
104
|
-
| `${收藏数}` | 收藏数量 |
|
|
105
|
-
| `${转发数}` | 转发/分享数量 |
|
|
106
|
-
| `${播放数}` | 播放量 |
|
|
107
|
-
| `${评论数}` | 评论数量 |
|
|
108
|
-
| `${发布时间}` | 发布时间(格式化) |
|
|
109
|
-
| `${图片数量}` | 图集/实况图片数量 |
|
|
110
|
-
| `${作者ID}` | 作者唯一标识ID |
|
|
111
|
-
| `${视频链接}` | 视频原始链接 |
|
|
112
|
-
| `${音乐标题}` | 音乐标题 |
|
|
113
|
-
| `${音乐作者}` | 音乐作者 |
|
|
114
|
-
| `${音乐封面}` | 音乐封面图片地址 |
|
|
115
|
-
| `${音乐链接}` | 音乐原始链接 |
|
|
116
|
-
|
|
117
|
-
>
|
|
100
|
+
在 `unifiedMessageFormat` 中可使用以下变量,空行自动隐藏:
|
|
101
|
+
|
|
102
|
+
| 变量名 | 说明 |
|
|
103
|
+
|--------|------|
|
|
104
|
+
| `${标题}` | 视频/图集标题 |
|
|
105
|
+
| `${作者}` | 作者名称 |
|
|
106
|
+
| `${简介}` | 内容简介 |
|
|
107
|
+
| `${视频时长}` | 视频时长(时:分:秒) |
|
|
108
|
+
| `${点赞数}` | 点赞数量 |
|
|
109
|
+
| `${收藏数}` | 收藏数量 |
|
|
110
|
+
| `${转发数}` | 转发/分享数量 |
|
|
111
|
+
| `${播放数}` | 播放量 |
|
|
112
|
+
| `${评论数}` | 评论数量 |
|
|
113
|
+
| `${发布时间}` | 发布时间(格式化) |
|
|
114
|
+
| `${图片数量}` | 图集/实况图片数量 |
|
|
115
|
+
| `${作者ID}` | 作者唯一标识ID |
|
|
116
|
+
| `${视频链接}` | 视频原始链接 |
|
|
117
|
+
| `${音乐标题}` | 音乐标题 |
|
|
118
|
+
| `${音乐作者}` | 音乐作者 |
|
|
119
|
+
| `${音乐封面}` | 音乐封面图片地址 |
|
|
120
|
+
| `${音乐链接}` | 音乐原始链接 |
|
|
121
|
+
|
|
122
|
+
> 注:封面图片由独立开关控制,不会出现在文字消息中。
|
|
118
123
|
|
|
119
124
|
## 支持的平台 (Supported Platforms)
|
|
120
125
|
| 平台名称 | 关键词识别 | 解析能力 |
|
|
121
126
|
|----------|------------|----------|
|
|
122
|
-
| 哔哩哔哩 (B站) | bilibili, b23.tv, bilibili.com |
|
|
127
|
+
| 哔哩哔哩 (B站) | bilibili, b23.tv, bilibili.com | 视频 |
|
|
123
128
|
| 抖音 | douyin, v.douyin.com | 短视频、图集、实况 |
|
|
124
129
|
| 快手 | kuaishou, v.kuaishou.com | 短视频、图集 |
|
|
125
130
|
| 小红书 | xiaohongshu, xhslink.com | 图文、视频 |
|
|
@@ -139,24 +144,24 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
139
144
|
| Twitter / X | twitter, x.com | 视频、图文 |
|
|
140
145
|
| Instagram | instagram, instagram.com | 图文、Reels |
|
|
141
146
|
| 豆包 | doubao (doubao.com/video) | 视频 |
|
|
142
|
-
|
|
|
147
|
+
| 豆包对话 | doubao (doubao.com/thread) | 对话分享 |
|
|
143
148
|
| 皮皮搞笑 | pipigx, h5.pipigx.com | 短视频 |
|
|
144
149
|
| 皮皮虾 | pipixia, h5.pipix.com | 短视频 |
|
|
145
150
|
| 最右 | zuiyou, xiaochuankeji.cn | 短视频 |
|
|
146
151
|
| 绿洲 (Oasis) | oasis.weibo.com | 视频、图文 |
|
|
147
152
|
| 视频号 (WeChat Channels) | channels.weixin.qq.com, weixin.qq.com/sph/ | 短视频 |
|
|
148
153
|
|
|
149
|
-
> 注:部分平台解析能力可能因API
|
|
154
|
+
> 注:部分平台解析能力可能因API限制有所差异。
|
|
150
155
|
|
|
151
156
|
## 项目贡献者 (Contributors)
|
|
152
157
|
|
|
153
158
|
| 贡献者 (Contributor) | 贡献内容 (Contribution) |
|
|
154
159
|
|----------------------|-------------------------|
|
|
155
|
-
| Minecraft-1314 | 插件完整开发
|
|
156
|
-
| ShiraiKuroko003 |
|
|
157
|
-
| cyavb |
|
|
158
|
-
| Keep785 |
|
|
159
|
-
| dzt2008 + Apricityx |
|
|
160
|
+
| Minecraft-1314 | 插件完整开发 |
|
|
161
|
+
| ShiraiKuroko003 | 修复消息格式问题 |
|
|
162
|
+
| cyavb | 自定义API KEY认证 |
|
|
163
|
+
| Keep785 | 无法关闭发送封面 |
|
|
164
|
+
| dzt2008 + Apricityx | 误解析修复 |
|
|
160
165
|
| JH-Ahua | BugPk-Api 支持 |
|
|
161
166
|
| shangxue | 灵感来源 |
|
|
162
167
|
|
|
@@ -170,6 +175,6 @@ This project is licensed under the MIT License, see the [LICENSE](LICENSE) file
|
|
|
170
175
|
|
|
171
176
|
## 支持我们 (Support Us)
|
|
172
177
|
|
|
173
|
-
如果这个项目对您有帮助,欢迎点亮右上角的 Star ⭐
|
|
178
|
+
如果这个项目对您有帮助,欢迎点亮右上角的 Star ⭐ 支持我们!
|
|
174
179
|
|
|
175
|
-
If this project is helpful to you, please feel free to star it in the upper right corner ⭐ to support us
|
|
180
|
+
If this project is helpful to you, please feel free to star it in the upper right corner ⭐ to support us!
|