koishi-plugin-video-parser-all 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +16 -4
- package/lib/index.js +317 -186
- package/package.json +40 -6
- package/readme.md +60 -55
package/lib/index.d.ts
CHANGED
|
@@ -10,12 +10,18 @@ export declare const Config: Schema<{
|
|
|
10
10
|
} & {
|
|
11
11
|
showImageText?: boolean | null | undefined;
|
|
12
12
|
showCoverImage?: boolean | null | undefined;
|
|
13
|
+
showMusicCover?: boolean | null | undefined;
|
|
14
|
+
showImageFile?: boolean | null | undefined;
|
|
15
|
+
forceDownloadImage?: boolean | null | undefined;
|
|
16
|
+
imageDownloadTimeout?: number | null | undefined;
|
|
17
|
+
imageTempDir?: string | null | undefined;
|
|
18
|
+
maxImageSize?: number | null | undefined;
|
|
13
19
|
showVideoFile?: boolean | null | undefined;
|
|
14
|
-
|
|
20
|
+
forceDownloadVideo?: boolean | null | undefined;
|
|
15
21
|
videoDownloadTimeout?: number | null | undefined;
|
|
16
22
|
tempDir?: string | null | undefined;
|
|
17
23
|
maxVideoSize?: number | null | undefined;
|
|
18
|
-
|
|
24
|
+
maxDescLength?: number | null | undefined;
|
|
19
25
|
maxConcurrent?: number | null | undefined;
|
|
20
26
|
} & {
|
|
21
27
|
timeout?: number | null | undefined;
|
|
@@ -94,12 +100,18 @@ export declare const Config: Schema<{
|
|
|
94
100
|
} & {
|
|
95
101
|
showImageText: boolean;
|
|
96
102
|
showCoverImage: boolean;
|
|
103
|
+
showMusicCover: boolean;
|
|
104
|
+
showImageFile: boolean;
|
|
105
|
+
forceDownloadImage: boolean;
|
|
106
|
+
imageDownloadTimeout: number;
|
|
107
|
+
imageTempDir: string;
|
|
108
|
+
maxImageSize: number;
|
|
97
109
|
showVideoFile: boolean;
|
|
98
|
-
|
|
110
|
+
forceDownloadVideo: boolean;
|
|
99
111
|
videoDownloadTimeout: number;
|
|
100
112
|
tempDir: string;
|
|
101
113
|
maxVideoSize: number;
|
|
102
|
-
|
|
114
|
+
maxDescLength: number;
|
|
103
115
|
maxConcurrent: number;
|
|
104
116
|
} & {
|
|
105
117
|
timeout: number;
|
package/lib/index.js
CHANGED
|
@@ -76,78 +76,84 @@ 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
|
|
79
|
+
unifiedMessageFormat: koishi_1.Schema.string().role('textarea').default('标题:${标题}\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('
|
|
83
|
-
showCoverImage: koishi_1.Schema.boolean().default(true).description('
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
showImageText: koishi_1.Schema.boolean().default(true).description('是否发送文字内容'),
|
|
83
|
+
showCoverImage: koishi_1.Schema.boolean().default(true).description('是否发送封面图片(视频/图集封面)'),
|
|
84
|
+
showMusicCover: koishi_1.Schema.boolean().default(true).description('是否发送音乐封面图片'),
|
|
85
|
+
showImageFile: koishi_1.Schema.boolean().default(true).description('封面/图片是否以文件形式发送(关闭则只发送链接)'),
|
|
86
|
+
forceDownloadImage: koishi_1.Schema.boolean().default(false).description('强制下载封面/图片后发送'),
|
|
87
|
+
imageDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(60000).description('图片下载超时(毫秒)'),
|
|
88
|
+
imageTempDir: koishi_1.Schema.string().default('./temp_images').description('临时封面/图片存储目录'),
|
|
89
|
+
maxImageSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载图片大小(MB),0 为不限制'),
|
|
90
|
+
showVideoFile: koishi_1.Schema.boolean().default(true).description('视频是否以文件形式发送(关闭则只发送链接)'),
|
|
91
|
+
forceDownloadVideo: koishi_1.Schema.boolean().default(false).description('强制下载视频后发送'),
|
|
86
92
|
videoDownloadTimeout: koishi_1.Schema.number().min(0).step(1).default(120000).description('视频下载超时(毫秒)'),
|
|
87
93
|
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('
|
|
94
|
+
maxVideoSize: koishi_1.Schema.number().min(0).step(1).default(0).description('最大下载视频大小(MB),0 为不限制'),
|
|
95
|
+
maxDescLength: koishi_1.Schema.number().min(0).step(1).default(200).description('简介最大长度(字符)'),
|
|
96
|
+
maxConcurrent: koishi_1.Schema.number().min(1).step(1).default(3).description('批量解析最大并发数'),
|
|
91
97
|
}).description('内容显示设置'),
|
|
92
98
|
koishi_1.Schema.object({
|
|
93
99
|
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('
|
|
100
|
+
videoSendTimeout: koishi_1.Schema.number().min(0).step(1).default(60000).description('消息发送超时(毫秒)'),
|
|
101
|
+
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
102
|
proxy: koishi_1.Schema.object({
|
|
97
|
-
enabled: koishi_1.Schema.boolean().default(false).description('
|
|
103
|
+
enabled: koishi_1.Schema.boolean().default(false).description('启用代理'),
|
|
98
104
|
protocol: koishi_1.Schema.union([
|
|
99
105
|
koishi_1.Schema.const('http').description('HTTP'),
|
|
100
106
|
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('
|
|
107
|
+
]).default('http').description('协议'),
|
|
108
|
+
host: koishi_1.Schema.string().default('127.0.0.1').description('地址'),
|
|
109
|
+
port: koishi_1.Schema.number().default(7890).description('端口'),
|
|
104
110
|
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
|
|
111
|
+
username: koishi_1.Schema.string().default('').description('用户名'),
|
|
112
|
+
password: koishi_1.Schema.string().default('').description('密码'),
|
|
113
|
+
}).description('认证'),
|
|
114
|
+
}).description('HTTP/HTTPS 代理'),
|
|
109
115
|
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('
|
|
116
|
+
name: koishi_1.Schema.string().required().description('头名称'),
|
|
117
|
+
value: koishi_1.Schema.string().required().description('头值'),
|
|
118
|
+
})).default([]).description('自定义请求头'),
|
|
119
|
+
}).description('网络设置'),
|
|
114
120
|
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('
|
|
121
|
+
ignoreSendError: koishi_1.Schema.boolean().default(true).description('忽略发送失败'),
|
|
122
|
+
retryTimes: koishi_1.Schema.number().min(0).step(1).default(3).description('重试次数'),
|
|
123
|
+
retryInterval: koishi_1.Schema.number().min(0).step(1).default(1000).description('重试间隔(毫秒)'),
|
|
124
|
+
}).description('错误与重试'),
|
|
119
125
|
koishi_1.Schema.object({
|
|
120
|
-
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot
|
|
121
|
-
}).description('
|
|
126
|
+
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot)'),
|
|
127
|
+
}).description('发送方式'),
|
|
122
128
|
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('
|
|
129
|
+
deduplicationInterval: koishi_1.Schema.number().min(0).step(1).default(180).description('去重间隔(秒)'),
|
|
130
|
+
cacheTTL: koishi_1.Schema.number().min(0).step(1).default(600).description('缓存时间(秒)'),
|
|
131
|
+
}).description('缓存与去重'),
|
|
126
132
|
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
|
|
133
|
+
primaryApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/short_videos').description('主 API'),
|
|
134
|
+
backupApiUrl: koishi_1.Schema.string().default('https://api.bugpk.com/api/svparse').description('备用主 API'),
|
|
129
135
|
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('
|
|
136
|
+
bilibili: koishi_1.Schema.boolean().default(false).description('哔哩哔哩'),
|
|
137
|
+
douyin: koishi_1.Schema.boolean().default(false).description('抖音'),
|
|
138
|
+
kuaishou: koishi_1.Schema.boolean().default(false).description('快手'),
|
|
139
|
+
xiaohongshu: koishi_1.Schema.boolean().default(false).description('小红书'),
|
|
140
|
+
weibo: koishi_1.Schema.boolean().default(false).description('微博'),
|
|
141
|
+
xigua: koishi_1.Schema.boolean().default(false).description('西瓜视频'),
|
|
142
|
+
youtube: koishi_1.Schema.boolean().default(false).description('YouTube'),
|
|
143
|
+
tiktok: koishi_1.Schema.boolean().default(false).description('TikTok'),
|
|
144
|
+
acfun: koishi_1.Schema.boolean().default(false).description('AcFun'),
|
|
145
|
+
zhihu: koishi_1.Schema.boolean().default(false).description('知乎'),
|
|
146
|
+
weishi: koishi_1.Schema.boolean().default(false).description('微视'),
|
|
147
|
+
huya: koishi_1.Schema.boolean().default(false).description('虎牙'),
|
|
148
|
+
haokan: koishi_1.Schema.boolean().default(false).description('好看视频'),
|
|
149
|
+
meipai: koishi_1.Schema.boolean().default(false).description('美拍'),
|
|
150
|
+
twitter: koishi_1.Schema.boolean().default(false).description('Twitter/X'),
|
|
151
|
+
instagram: koishi_1.Schema.boolean().default(false).description('Instagram'),
|
|
152
|
+
doubao: koishi_1.Schema.boolean().default(false).description('豆包'),
|
|
153
|
+
doubao_chat: koishi_1.Schema.boolean().default(false).description('豆包对话'),
|
|
154
|
+
oasis: koishi_1.Schema.boolean().default(false).description('绿洲'),
|
|
155
|
+
wechat_channel: koishi_1.Schema.boolean().default(false).description('视频号'),
|
|
156
|
+
}).description('优先使用专属 API'),
|
|
151
157
|
customApis: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
152
158
|
platform: koishi_1.Schema.union([
|
|
153
159
|
koishi_1.Schema.const('bilibili').description('哔哩哔哩'),
|
|
@@ -170,17 +176,17 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
170
176
|
koishi_1.Schema.const('doubao_chat').description('豆包对话'),
|
|
171
177
|
koishi_1.Schema.const('oasis').description('绿洲'),
|
|
172
178
|
koishi_1.Schema.const('wechat_channel').description('视频号'),
|
|
173
|
-
]).description('
|
|
179
|
+
]).description('平台'),
|
|
174
180
|
apiUrl: koishi_1.Schema.string().description('API 地址'),
|
|
175
|
-
apiKey: koishi_1.Schema.string().description('API Key
|
|
181
|
+
apiKey: koishi_1.Schema.string().description('API Key').default(''),
|
|
176
182
|
authHeaderType: koishi_1.Schema.union([
|
|
177
|
-
koishi_1.Schema.const('Bearer').description('Bearer
|
|
183
|
+
koishi_1.Schema.const('Bearer').description('Bearer'),
|
|
178
184
|
koishi_1.Schema.const('X-API-Key').description('X-API-Key'),
|
|
179
|
-
koishi_1.Schema.const('Custom').description('自定义
|
|
185
|
+
koishi_1.Schema.const('Custom').description('自定义'),
|
|
180
186
|
]).default('Bearer').description('认证头类型'),
|
|
181
|
-
customHeaderName: koishi_1.Schema.string().
|
|
182
|
-
fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON
|
|
183
|
-
})).default([]).description('
|
|
187
|
+
customHeaderName: koishi_1.Schema.string().default('X-API-Key').description('自定义头名称'),
|
|
188
|
+
fieldMapping: koishi_1.Schema.string().role('textarea').default('{}').description('字段映射 JSON'),
|
|
189
|
+
})).default([]).description('自定义专属 API'),
|
|
184
190
|
globalFieldMapping: koishi_1.Schema.string().role('textarea').default('{\n' +
|
|
185
191
|
' "title": "data.title",\n' +
|
|
186
192
|
' "desc": "data.description",\n' +
|
|
@@ -203,15 +209,15 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
203
209
|
' "music_author": "data.music.author",\n' +
|
|
204
210
|
' "music_cover": "data.music.cover",\n' +
|
|
205
211
|
' "music_url": "data.music.url"\n' +
|
|
206
|
-
'}').description('全局字段映射 JSON
|
|
207
|
-
}).description('API
|
|
212
|
+
'}').description('全局字段映射 JSON'),
|
|
213
|
+
}).description('API 选择'),
|
|
208
214
|
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('
|
|
215
|
+
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('等待提示'),
|
|
216
|
+
unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持提示'),
|
|
217
|
+
invalidLinkText: koishi_1.Schema.string().default('无效的视频链接').description('无效链接提示'),
|
|
218
|
+
parseErrorPrefix: koishi_1.Schema.string().default('❌ 解析失败:').description('错误前缀'),
|
|
219
|
+
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('错误项格式'),
|
|
220
|
+
}).description('界面文字'),
|
|
215
221
|
]);
|
|
216
222
|
const logger = new koishi_1.Logger(exports.name);
|
|
217
223
|
let debugEnabled = false;
|
|
@@ -523,7 +529,6 @@ function generateFormattedText(p, format) {
|
|
|
523
529
|
'发布时间': p.publishTime ? formatPublishTime(p.publishTime) : '',
|
|
524
530
|
'图片数量': String(imageCount),
|
|
525
531
|
'作者ID': p.uid,
|
|
526
|
-
'封面': p.cover,
|
|
527
532
|
'视频链接': p.video,
|
|
528
533
|
'音乐标题': p.music.title || '',
|
|
529
534
|
'音乐作者': p.music.author || '',
|
|
@@ -724,113 +729,125 @@ function apply(ctx, config) {
|
|
|
724
729
|
throw new Error(`写入视频文件失败: ${getErrorMessage(e)}`);
|
|
725
730
|
}
|
|
726
731
|
}
|
|
727
|
-
async function
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
732
|
+
async function downloadImageFile(imageUrl) {
|
|
733
|
+
if (!imageUrl)
|
|
734
|
+
throw new Error('图片链接为空');
|
|
735
|
+
const imgTempDir = config.imageTempDir || './temp_images';
|
|
736
|
+
await promises_1.default.mkdir(imgTempDir, { recursive: true });
|
|
737
|
+
const ext = imageUrl.match(/\.(png|jpg|jpeg|gif|webp)(\?|$)/i)?.[1] || 'jpg';
|
|
738
|
+
const fileName = `img_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.${ext}`;
|
|
739
|
+
const filePath = path_1.default.resolve(imgTempDir, fileName);
|
|
740
|
+
const writer = (0, fs_1.createWriteStream)(filePath);
|
|
741
|
+
let response;
|
|
742
|
+
try {
|
|
743
|
+
response = await http({
|
|
744
|
+
method: 'GET',
|
|
745
|
+
url: imageUrl,
|
|
746
|
+
responseType: 'stream',
|
|
747
|
+
timeout: config.imageDownloadTimeout || 60000,
|
|
748
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.baidu.com/' },
|
|
749
|
+
maxRedirects: 5,
|
|
750
|
+
validateStatus: (status) => status >= 200 && status < 300,
|
|
751
|
+
});
|
|
742
752
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (dedicatedUrl)
|
|
748
|
-
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
753
|
+
catch (e) {
|
|
754
|
+
writer.destroy();
|
|
755
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
756
|
+
throw new Error(`下载图片失败: ${getErrorMessage(e)}`);
|
|
749
757
|
}
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
758
|
+
const maxSizeBytes = (config.maxImageSize ?? 0) * 1024 * 1024;
|
|
759
|
+
const contentLength = Number(response.headers['content-length'] || 0);
|
|
760
|
+
if (maxSizeBytes > 0 && contentLength > maxSizeBytes) {
|
|
761
|
+
writer.destroy();
|
|
762
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
763
|
+
throw new Error(`图片文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${config.maxImageSize}MB)`);
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
await (0, promises_2.pipeline)(response.data, writer);
|
|
767
|
+
return filePath;
|
|
768
|
+
}
|
|
769
|
+
catch (e) {
|
|
770
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
771
|
+
throw new Error(`写入图片文件失败: ${getErrorMessage(e)}`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async function sendImage(session, imageUrl) {
|
|
775
|
+
if (!config.showCoverImage)
|
|
776
|
+
return;
|
|
777
|
+
const sendLink = async () => { await sendWithTimeout(session, `图片链接:${imageUrl}`).catch(() => { }); };
|
|
778
|
+
if (config.forceDownloadImage) {
|
|
779
|
+
try {
|
|
780
|
+
const localPath = await downloadImageFile(imageUrl);
|
|
781
|
+
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
catch (e) {
|
|
785
|
+
debugLog('ERROR', '强制下载图片失败,尝试URL发送:', getErrorMessage(e));
|
|
754
786
|
try {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
'Referer': 'https://www.baidu.com/',
|
|
758
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
759
|
-
};
|
|
760
|
-
for (const h of customHeaders) {
|
|
761
|
-
if (h.name && h.value)
|
|
762
|
-
headers[h.name] = h.value;
|
|
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}`);
|
|
787
|
+
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
788
|
+
return;
|
|
775
789
|
}
|
|
776
|
-
catch
|
|
777
|
-
|
|
778
|
-
debugLog('ERROR', `${api.label} attempt ${attempt + 1} failed: ${lastError.message}`);
|
|
779
|
-
if (attempt < config.retryTimes)
|
|
780
|
-
await delay(config.retryInterval);
|
|
790
|
+
catch {
|
|
791
|
+
await sendLink();
|
|
781
792
|
}
|
|
782
793
|
}
|
|
783
|
-
|
|
794
|
+
return;
|
|
784
795
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
796
|
+
if (!config.showImageFile) {
|
|
797
|
+
await sendLink();
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
791
804
|
try {
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
return { success: true, data: info };
|
|
795
|
-
debugLog('WARN', `解析成功但无内容: ${candidate}`);
|
|
805
|
+
const localPath = await downloadImageFile(imageUrl);
|
|
806
|
+
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
796
807
|
}
|
|
797
|
-
catch
|
|
798
|
-
|
|
808
|
+
catch {
|
|
809
|
+
await sendLink();
|
|
799
810
|
}
|
|
800
811
|
}
|
|
801
|
-
return { success: false, msg: texts.unsupportedPlatformText };
|
|
802
812
|
}
|
|
803
|
-
async function
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
return { success: true, data: { text, parsed: result.data } };
|
|
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++) {
|
|
813
|
+
async function sendMusicCover(session, imageUrl) {
|
|
814
|
+
if (!config.showMusicCover)
|
|
815
|
+
return;
|
|
816
|
+
const sendLink = async () => { await sendWithTimeout(session, `图片链接:${imageUrl}`).catch(() => { }); };
|
|
817
|
+
if (config.forceDownloadImage) {
|
|
814
818
|
try {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
+
const localPath = await downloadImageFile(imageUrl);
|
|
820
|
+
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
catch (e) {
|
|
824
|
+
debugLog('ERROR', '强制下载音乐封面失败,尝试URL发送:', getErrorMessage(e));
|
|
825
|
+
try {
|
|
826
|
+
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
827
|
+
return;
|
|
819
828
|
}
|
|
820
|
-
|
|
821
|
-
|
|
829
|
+
catch {
|
|
830
|
+
await sendLink();
|
|
822
831
|
}
|
|
823
832
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (!config.showImageFile) {
|
|
836
|
+
await sendLink();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
await sendWithTimeout(session, koishi_1.h.image(imageUrl));
|
|
841
|
+
}
|
|
842
|
+
catch {
|
|
843
|
+
try {
|
|
844
|
+
const localPath = await downloadImageFile(imageUrl);
|
|
845
|
+
await sendWithTimeout(session, koishi_1.h.image(`file://${localPath}`));
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
await sendLink();
|
|
831
849
|
}
|
|
832
850
|
}
|
|
833
|
-
return null;
|
|
834
851
|
}
|
|
835
852
|
async function sendVideoFile(session, videoUrl) {
|
|
836
853
|
if (!videoUrl)
|
|
@@ -845,7 +862,7 @@ function apply(ctx, config) {
|
|
|
845
862
|
return;
|
|
846
863
|
}
|
|
847
864
|
catch (e) {
|
|
848
|
-
debugLog('ERROR', '
|
|
865
|
+
debugLog('ERROR', '强制下载视频失败,尝试URL发送:', getErrorMessage(e));
|
|
849
866
|
try {
|
|
850
867
|
await sendWithTimeout(session, koishi_1.h.video(videoUrl));
|
|
851
868
|
return;
|
|
@@ -917,8 +934,12 @@ function apply(ctx, config) {
|
|
|
917
934
|
const text = item.text;
|
|
918
935
|
if (text && config.showImageText)
|
|
919
936
|
forwardMessages.push(buildForwardNode(session, text, botName));
|
|
920
|
-
if (
|
|
937
|
+
if (p.cover && config.showCoverImage && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
921
938
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.cover), botName));
|
|
939
|
+
}
|
|
940
|
+
if (config.showMusicCover && p.music.cover) {
|
|
941
|
+
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.music.cover), botName));
|
|
942
|
+
}
|
|
922
943
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
923
944
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
924
945
|
for (const imgUrl of imageUrls)
|
|
@@ -948,21 +969,22 @@ function apply(ctx, config) {
|
|
|
948
969
|
await sendWithTimeout(session, text);
|
|
949
970
|
await delay(300);
|
|
950
971
|
}
|
|
951
|
-
if (
|
|
952
|
-
await
|
|
972
|
+
if (p.cover && config.showCoverImage && p.type !== 'live_photo' && !(p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
973
|
+
await sendImage(session, p.cover).catch(() => { });
|
|
974
|
+
await delay(300);
|
|
975
|
+
}
|
|
976
|
+
if (config.showMusicCover && p.music.cover) {
|
|
977
|
+
await sendMusicCover(session, p.music.cover).catch(() => { });
|
|
953
978
|
await delay(300);
|
|
954
979
|
}
|
|
955
980
|
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}`);
|
|
981
|
+
await sendVideoFile(session, p.video);
|
|
960
982
|
await delay(500);
|
|
961
983
|
}
|
|
962
984
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
963
985
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
964
986
|
for (const imgUrl of imageUrls) {
|
|
965
|
-
await
|
|
987
|
+
await sendImage(session, imgUrl).catch(() => { });
|
|
966
988
|
await delay(200);
|
|
967
989
|
}
|
|
968
990
|
}
|
|
@@ -970,6 +992,114 @@ function apply(ctx, config) {
|
|
|
970
992
|
}
|
|
971
993
|
debugLog('INFO', '处理完成');
|
|
972
994
|
}
|
|
995
|
+
async function fetchApi(url, type, fieldMapping) {
|
|
996
|
+
const cacheKey = url;
|
|
997
|
+
const cached = urlCacheLocal.get(cacheKey);
|
|
998
|
+
if (cached && cached.expire > Date.now())
|
|
999
|
+
return cached.data;
|
|
1000
|
+
const { apiUrl: dedicatedUrl, dedicatedFirst, apiKey, authHeaderType, customHeaderName } = getPlatformConfig(type);
|
|
1001
|
+
const primaryApi = config.primaryApiUrl || 'https://api.bugpk.com/api/short_videos';
|
|
1002
|
+
const backupApi = config.backupApiUrl || 'https://api.bugpk.com/api/svparse';
|
|
1003
|
+
const backupAllowed = backupSupportedPlatforms.has(type);
|
|
1004
|
+
const apiList = [];
|
|
1005
|
+
if (dedicatedFirst && dedicatedUrl) {
|
|
1006
|
+
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
1007
|
+
apiList.push({ url: primaryApi, label: '默认主API', fieldMapping });
|
|
1008
|
+
if (backupAllowed)
|
|
1009
|
+
apiList.push({ url: backupApi, label: '备用主API', fieldMapping });
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
apiList.push({ url: primaryApi, label: '默认主API', fieldMapping });
|
|
1013
|
+
if (backupAllowed)
|
|
1014
|
+
apiList.push({ url: backupApi, label: '备用主API', fieldMapping });
|
|
1015
|
+
if (dedicatedUrl)
|
|
1016
|
+
apiList.push({ url: dedicatedUrl, label: `专属API(${type})`, apiKey, authHeaderType, customHeaderName, fieldMapping });
|
|
1017
|
+
}
|
|
1018
|
+
const customHeaders = config.customHeaders || [];
|
|
1019
|
+
let lastError = null;
|
|
1020
|
+
for (const api of apiList) {
|
|
1021
|
+
for (let attempt = 0; attempt <= config.retryTimes; attempt++) {
|
|
1022
|
+
try {
|
|
1023
|
+
const headers = {
|
|
1024
|
+
'User-Agent': config.userAgent,
|
|
1025
|
+
'Referer': 'https://www.baidu.com/',
|
|
1026
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1027
|
+
};
|
|
1028
|
+
for (const h of customHeaders) {
|
|
1029
|
+
if (h.name && h.value)
|
|
1030
|
+
headers[h.name] = h.value;
|
|
1031
|
+
}
|
|
1032
|
+
if (api.apiKey) {
|
|
1033
|
+
const authHeaders = buildAuthHeaders(api.apiKey, api.authHeaderType || 'Bearer', api.customHeaderName || 'X-API-Key');
|
|
1034
|
+
Object.assign(headers, authHeaders);
|
|
1035
|
+
}
|
|
1036
|
+
const res = await http.get(api.url, { params: { url }, timeout: config.timeout, headers });
|
|
1037
|
+
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
1038
|
+
const parsed = parseApiResponse(res.data, config.maxDescLength, api.fieldMapping);
|
|
1039
|
+
urlCacheLocal.set(cacheKey, { data: parsed, expire: Date.now() + cacheTTL });
|
|
1040
|
+
return parsed;
|
|
1041
|
+
}
|
|
1042
|
+
throw new Error(res.data?.msg || `API返回错误码: ${res.data?.code}`);
|
|
1043
|
+
}
|
|
1044
|
+
catch (error) {
|
|
1045
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1046
|
+
debugLog('ERROR', `${api.label} attempt ${attempt + 1} failed: ${lastError.message}`);
|
|
1047
|
+
if (attempt < config.retryTimes)
|
|
1048
|
+
await delay(config.retryInterval);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
debugLog('WARN', `${api.label} all retries failed`);
|
|
1052
|
+
}
|
|
1053
|
+
throw lastError || new Error('所有API请求全部失败');
|
|
1054
|
+
}
|
|
1055
|
+
async function parseUrl(url, type, fieldMapping) {
|
|
1056
|
+
const realUrl = await resolveShortUrl(url);
|
|
1057
|
+
const candidates = [...new Set([realUrl, url])];
|
|
1058
|
+
for (const candidate of candidates) {
|
|
1059
|
+
try {
|
|
1060
|
+
const info = await fetchApi(candidate, type, fieldMapping);
|
|
1061
|
+
if (info.video || info.images.length > 0 || info.live_photo.length > 0)
|
|
1062
|
+
return { success: true, data: info };
|
|
1063
|
+
debugLog('WARN', `解析成功但无内容: ${candidate}`);
|
|
1064
|
+
}
|
|
1065
|
+
catch (error) {
|
|
1066
|
+
debugLog('ERROR', `候选链接失败: ${candidate}`, getErrorMessage(error));
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return { success: false, msg: texts.unsupportedPlatformText };
|
|
1070
|
+
}
|
|
1071
|
+
async function processSingleUrl(url, type, fieldMapping) {
|
|
1072
|
+
const result = await parseUrl(url, type, fieldMapping);
|
|
1073
|
+
if (!result.success)
|
|
1074
|
+
return { success: false, msg: result.msg, url };
|
|
1075
|
+
const text = generateFormattedText(result.data, config.unifiedMessageFormat);
|
|
1076
|
+
return { success: true, data: { text, parsed: result.data } };
|
|
1077
|
+
}
|
|
1078
|
+
async function sendWithTimeout(session, content, customRetries) {
|
|
1079
|
+
const maxRetries = customRetries ?? config.retryTimes ?? 3;
|
|
1080
|
+
const retryDelay = config.retryInterval || 1000;
|
|
1081
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1082
|
+
try {
|
|
1083
|
+
let sendPromise = session.send(content);
|
|
1084
|
+
if (config.videoSendTimeout > 0) {
|
|
1085
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('发送超时')), config.videoSendTimeout));
|
|
1086
|
+
return await Promise.race([sendPromise, timeoutPromise]);
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
return await sendPromise;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
catch (err) {
|
|
1093
|
+
const errMsg = getErrorMessage(err);
|
|
1094
|
+
debugLog('ERROR', `发送失败尝试 ${attempt + 1}: ${errMsg}`);
|
|
1095
|
+
if (attempt < maxRetries)
|
|
1096
|
+
await delay(retryDelay);
|
|
1097
|
+
else if (!config.ignoreSendError)
|
|
1098
|
+
throw err;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
973
1103
|
ctx.on('message', async (session) => {
|
|
974
1104
|
if (!config.enable)
|
|
975
1105
|
return;
|
|
@@ -1013,22 +1143,20 @@ function apply(ctx, config) {
|
|
|
1013
1143
|
});
|
|
1014
1144
|
const tempCleanupInterval = setInterval(async () => {
|
|
1015
1145
|
try {
|
|
1016
|
-
const
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1146
|
+
const dirs = [config.tempDir || './temp_videos', config.imageTempDir || './temp_images'];
|
|
1147
|
+
for (const dir of dirs) {
|
|
1148
|
+
const files = await promises_1.default.readdir(dir);
|
|
1149
|
+
const now = Date.now();
|
|
1150
|
+
for (const file of files) {
|
|
1151
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) || (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i))) {
|
|
1152
|
+
const filePath = path_1.default.join(dir, file);
|
|
1153
|
+
const stats = await promises_1.default.stat(filePath);
|
|
1154
|
+
if (now - stats.mtimeMs > 3600000) {
|
|
1155
|
+
await promises_1.default.unlink(filePath).catch(() => { });
|
|
1156
|
+
}
|
|
1027
1157
|
}
|
|
1028
1158
|
}
|
|
1029
1159
|
}
|
|
1030
|
-
if (deleted)
|
|
1031
|
-
debugLog('INFO', `清理了 ${deleted} 个过期临时视频文件`);
|
|
1032
1160
|
}
|
|
1033
1161
|
catch (e) {
|
|
1034
1162
|
debugLog('WARN', '清理临时文件失败:', e);
|
|
@@ -1042,11 +1170,14 @@ function apply(ctx, config) {
|
|
|
1042
1170
|
});
|
|
1043
1171
|
process.on('beforeExit', async () => {
|
|
1044
1172
|
try {
|
|
1045
|
-
const
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1173
|
+
const dirs = [config.tempDir || './temp_videos', config.imageTempDir || './temp_images'];
|
|
1174
|
+
for (const dir of dirs) {
|
|
1175
|
+
const files = await promises_1.default.readdir(dir);
|
|
1176
|
+
for (const file of files) {
|
|
1177
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) || (file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i))) {
|
|
1178
|
+
await promises_1.default.unlink(path_1.default.join(dir, file)).catch(() => { });
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1050
1181
|
}
|
|
1051
1182
|
}
|
|
1052
1183
|
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.4",
|
|
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
|
|
34
|
+
| `unifiedMessageFormat` | string | `标题:${标题}\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
|
+
| `showMusicCover` | boolean | true | 是否发送音乐封面图片 |
|
|
42
|
+
| `showImageFile` | boolean | true | 封面/图片是否以文件形式发送(关闭则只发链接) |
|
|
43
|
+
| `forceDownloadImage` | boolean | false | 强制下载封面/图片后发送 |
|
|
44
|
+
| `imageDownloadTimeout` | number | 60000 | 图片下载超时(毫秒) |
|
|
45
|
+
| `imageTempDir` | string | `./temp_images` | 临时封面/图片存储目录 |
|
|
46
|
+
| `maxImageSize` | number | 0 | 最大下载图片大小(MB),0 不限制 |
|
|
47
|
+
| `showVideoFile` | boolean | true | 视频是否以文件形式发送(关闭则只发链接) |
|
|
48
|
+
| `forceDownloadVideo` | boolean | false | 强制下载视频后发送 |
|
|
43
49
|
| `videoDownloadTimeout` | number | 120000 | 视频下载超时(毫秒) |
|
|
44
50
|
| `tempDir` | string | `./temp_videos` | 临时视频存储目录 |
|
|
45
|
-
| `maxVideoSize` | number | 0 | 最大下载视频大小(MB),0
|
|
46
|
-
| `
|
|
47
|
-
| `maxConcurrent` | number | 3 |
|
|
51
|
+
| `maxVideoSize` | number | 0 | 最大下载视频大小(MB),0 不限制 |
|
|
52
|
+
| `maxDescLength` | number | 200 | 简介最大长度(字符) |
|
|
53
|
+
| `maxConcurrent` | number | 3 | 批量解析最大并发数 |
|
|
48
54
|
|
|
49
55
|
### 网络与 API 设置 (Network & API Settings)
|
|
50
56
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
51
57
|
|--------|------|--------|------|
|
|
52
58
|
| `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 | [] |
|
|
59
|
+
| `videoSendTimeout` | number | 60000 | 消息发送超时时间(毫秒,0 为不限制) |
|
|
60
|
+
| `userAgent` | string | `Mozilla/5.0 ...` | User-Agent |
|
|
61
|
+
| `proxy` | object | `{ enabled: false, protocol: "http", host: "127.0.0.1", port: 7890, auth: { username: "", password: "" } }` | HTTP/HTTPS 代理。`enabled` 开关(默认关闭),`protocol` 下拉选择 `http` 或 `https` |
|
|
62
|
+
| `customHeaders` | array | [] | 自定义请求头,每项含 `name` 和 `value` |
|
|
57
63
|
|
|
58
64
|
### API 选择与回退设置 (API Selection & Fallback)
|
|
59
65
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
60
66
|
|--------|------|--------|------|
|
|
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 |
|
|
67
|
+
| `primaryApiUrl` | string | `https://api.bugpk.com/api/short_videos` | 主 API 地址 |
|
|
68
|
+
| `backupApiUrl` | string | `https://api.bugpk.com/api/svparse` | 备用主 API,仅支持部分平台 |
|
|
69
|
+
| `platformDedicatedFirst` | object | 各平台均为 `false` | 平台专属 API 优先开关,键:`bilibili` 等 |
|
|
70
|
+
| `customApis` | array | [] | 自定义平台专属 API,含 `platform`, `apiUrl`, `apiKey`, `authHeaderType`, `customHeaderName`, `fieldMapping` |
|
|
71
|
+
| `globalFieldMapping` | string | 预设字段映射 JSON | 全局字段映射,支持点号路径 |
|
|
66
72
|
|
|
67
73
|
### 错误与重试设置 (Error & Retry Settings)
|
|
68
74
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
69
75
|
|--------|------|--------|------|
|
|
70
|
-
| `ignoreSendError` | boolean | true |
|
|
71
|
-
| `retryTimes` | number | 3 |
|
|
72
|
-
| `retryInterval` | number | 1000 |
|
|
76
|
+
| `ignoreSendError` | boolean | true | 忽略发送失败 |
|
|
77
|
+
| `retryTimes` | number | 3 | 重试次数 |
|
|
78
|
+
| `retryInterval` | number | 1000 | 重试间隔(毫秒) |
|
|
73
79
|
|
|
74
80
|
### 发送方式设置 (Send Mode Settings)
|
|
75
81
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
76
82
|
|--------|------|--------|------|
|
|
77
|
-
| `enableForward` | boolean | false |
|
|
83
|
+
| `enableForward` | boolean | false | 启用合并转发(仅 OneBot 平台) |
|
|
78
84
|
|
|
79
85
|
### 缓存与去重设置 (Cache & Deduplication Settings)
|
|
80
86
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
81
87
|
|--------|------|--------|------|
|
|
82
|
-
| `deduplicationInterval` | number | 180 |
|
|
83
|
-
| `cacheTTL` | number | 600 |
|
|
88
|
+
| `deduplicationInterval` | number | 180 | 去重间隔(秒) |
|
|
89
|
+
| `cacheTTL` | number | 600 | 缓存时间(秒) |
|
|
84
90
|
|
|
85
91
|
### 界面文字设置 (UI Text Settings)
|
|
86
92
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
87
93
|
|--------|------|--------|------|
|
|
88
|
-
| `waitingTipText` | string | 正在解析视频,请稍候... |
|
|
89
|
-
| `unsupportedPlatformText` | string | 不支持该平台链接 |
|
|
90
|
-
| `invalidLinkText` | string | 无效的视频链接 |
|
|
91
|
-
| `parseErrorPrefix` | string | ❌ 解析失败: |
|
|
92
|
-
| `parseErrorItemFormat` | string | `【${url}】: ${msg}` |
|
|
94
|
+
| `waitingTipText` | string | 正在解析视频,请稍候... | 等待提示 |
|
|
95
|
+
| `unsupportedPlatformText` | string | 不支持该平台链接 | 不支持平台提示 |
|
|
96
|
+
| `invalidLinkText` | string | 无效的视频链接 | 无效链接提示 |
|
|
97
|
+
| `parseErrorPrefix` | string | ❌ 解析失败: | 错误前缀 |
|
|
98
|
+
| `parseErrorItemFormat` | string | `【${url}】: ${msg}` | 错误项格式 |
|
|
93
99
|
|
|
94
100
|
## 支持的变量 (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
|
-
> 注:部分变量可能因平台API返回数据不同而显示为空,某行所有变量为空(或为"0")时该行会自动隐藏。
|
|
101
|
+
在 `unifiedMessageFormat` 中可使用以下变量,空行自动隐藏:
|
|
102
|
+
|
|
103
|
+
| 变量名 | 说明 |
|
|
104
|
+
|--------|------|
|
|
105
|
+
| `${标题}` | 视频/图集标题 |
|
|
106
|
+
| `${作者}` | 作者名称 |
|
|
107
|
+
| `${简介}` | 内容简介 |
|
|
108
|
+
| `${视频时长}` | 视频时长(时:分:秒) |
|
|
109
|
+
| `${点赞数}` | 点赞数量 |
|
|
110
|
+
| `${收藏数}` | 收藏数量 |
|
|
111
|
+
| `${转发数}` | 转发/分享数量 |
|
|
112
|
+
| `${播放数}` | 播放量 |
|
|
113
|
+
| `${评论数}` | 评论数量 |
|
|
114
|
+
| `${发布时间}` | 发布时间(格式化) |
|
|
115
|
+
| `${图片数量}` | 图集/实况图片数量 |
|
|
116
|
+
| `${作者ID}` | 作者唯一标识ID |
|
|
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 | 图文、视频 |
|
|
@@ -146,7 +151,7 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
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
|
|
|
@@ -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!
|