koishi-plugin-video-parser-all 1.5.1 → 1.5.2
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 +2 -0
- package/lib/index.js +83 -52
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export declare const Config: Schema<{
|
|
|
45
45
|
authorAvatarText?: string | null | undefined;
|
|
46
46
|
showMusicCover?: boolean | null | undefined;
|
|
47
47
|
showVideoFile?: boolean | null | undefined;
|
|
48
|
+
sendLiveMessage?: boolean | null | undefined;
|
|
48
49
|
forceDownloadCover?: boolean | null | undefined;
|
|
49
50
|
forceDownloadImageNew?: boolean | null | undefined;
|
|
50
51
|
forceDownloadAuthorAvatar?: boolean | null | undefined;
|
|
@@ -197,6 +198,7 @@ export declare const Config: Schema<{
|
|
|
197
198
|
authorAvatarText: string;
|
|
198
199
|
showMusicCover: boolean;
|
|
199
200
|
showVideoFile: boolean;
|
|
201
|
+
sendLiveMessage: boolean;
|
|
200
202
|
forceDownloadCover: boolean;
|
|
201
203
|
forceDownloadImageNew: boolean;
|
|
202
204
|
forceDownloadAuthorAvatar: boolean;
|
package/lib/index.js
CHANGED
|
@@ -72,7 +72,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
72
72
|
koishi_1.Schema.object({
|
|
73
73
|
enable: koishi_1.Schema.boolean().default(true).description('是否启用视频解析插件'),
|
|
74
74
|
botName: koishi_1.Schema.string().default('视频解析机器人').description('合并转发中显示的昵称'),
|
|
75
|
-
showWaitingTip: koishi_1.Schema.boolean().default(true).description('
|
|
75
|
+
showWaitingTip: koishi_1.Schema.boolean().default(true).description('显示等待提示'),
|
|
76
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('哔哩哔哩'),
|
|
@@ -116,6 +116,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
116
116
|
authorAvatarText: koishi_1.Schema.string().default('作者头像:').description('作者头像前显示的文字'),
|
|
117
117
|
showMusicCover: koishi_1.Schema.boolean().default(true).description('发送音乐封面图片'),
|
|
118
118
|
showVideoFile: koishi_1.Schema.boolean().default(true).description('视频是否以视频形式发送(关闭则只发送链接)'),
|
|
119
|
+
sendLiveMessage: koishi_1.Schema.boolean().default(true).description('直播作品发送文字消息(不发送视频)'),
|
|
119
120
|
forceDownloadCover: koishi_1.Schema.boolean().default(false).description('强制下载封面'),
|
|
120
121
|
forceDownloadImageNew: koishi_1.Schema.boolean().default(false).description('强制下载图片'),
|
|
121
122
|
forceDownloadAuthorAvatar: koishi_1.Schema.boolean().default(false).description('强制下载作者头像'),
|
|
@@ -135,7 +136,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
135
136
|
downloadEngine: koishi_1.Schema.union([
|
|
136
137
|
koishi_1.Schema.const('internal').description('内置下载'),
|
|
137
138
|
koishi_1.Schema.const('aria2').description('aria2 下载(需 koishi-plugin-aria2-plus)'),
|
|
138
|
-
koishi_1.Schema.const('downloads').description('downloads
|
|
139
|
+
koishi_1.Schema.const('downloads').description('downloads 服务下载'),
|
|
139
140
|
]).default('internal').description('下载引擎'),
|
|
140
141
|
}).description('性能与限制'),
|
|
141
142
|
koishi_1.Schema.object({
|
|
@@ -437,8 +438,9 @@ function cleanUrl(url) {
|
|
|
437
438
|
try {
|
|
438
439
|
url = url.replace(/&/g, '&');
|
|
439
440
|
const urlObj = new URL(url);
|
|
440
|
-
if (urlObj.protocol
|
|
441
|
-
|
|
441
|
+
if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') {
|
|
442
|
+
throw new Error('非法的协议');
|
|
443
|
+
}
|
|
442
444
|
if (urlObj.hostname.includes('douyin.com') || urlObj.hostname.includes('v.douyin.com')) {
|
|
443
445
|
['source', 'share_type', 'share_token', 'timestamp', 'from', 'isappinstalled'].forEach(p => urlObj.searchParams.delete(p));
|
|
444
446
|
return urlObj.origin + urlObj.pathname;
|
|
@@ -838,23 +840,32 @@ function apply(ctx, config) {
|
|
|
838
840
|
}) || fileExts[0];
|
|
839
841
|
const fileName = `${filePrefix}_${Date.now()}_${(0, crypto_1.randomBytes)(4).toString('hex')}.${ext}`;
|
|
840
842
|
const filePath = path_1.default.resolve(cacheDir, fileName);
|
|
843
|
+
const startTime = Date.now();
|
|
841
844
|
if (downloadEngine === 'downloads' && downloadsService) {
|
|
842
|
-
logger.info(
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
845
|
+
logger.info(`[下载] 开始 (downloads): ${url}`);
|
|
846
|
+
try {
|
|
847
|
+
const dest = await downloadsService.download(url, path_1.default.join(cacheDir, fileName), {
|
|
848
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
|
|
849
|
+
timeout
|
|
850
|
+
});
|
|
851
|
+
const stat = await promises_1.default.stat(dest);
|
|
852
|
+
const sizeMB = (stat.size / (1024 * 1024)).toFixed(2);
|
|
853
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
854
|
+
logger.info(`[下载] 完成 (downloads): ${dest} (${sizeMB} MB, 耗时 ${elapsed}s)`);
|
|
855
|
+
if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
|
|
856
|
+
await promises_1.default.unlink(dest).catch(() => { });
|
|
857
|
+
throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
858
|
+
}
|
|
859
|
+
return dest;
|
|
860
|
+
}
|
|
861
|
+
catch (e) {
|
|
862
|
+
debugLog('ERROR', 'downloads 下载失败,回退内置下载:', e);
|
|
863
|
+
throw e;
|
|
853
864
|
}
|
|
854
|
-
return dest;
|
|
855
865
|
}
|
|
856
866
|
if (downloadEngine === 'aria2' && aria2Service) {
|
|
857
867
|
try {
|
|
868
|
+
logger.info(`[下载] 开始 (aria2): ${url}`);
|
|
858
869
|
const gid = await aria2Service.addUri([url], {
|
|
859
870
|
dir: cacheDir,
|
|
860
871
|
out: fileName,
|
|
@@ -868,10 +879,10 @@ function apply(ctx, config) {
|
|
|
868
879
|
header: [`User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36`, `Referer: https://www.baidu.com/`]
|
|
869
880
|
});
|
|
870
881
|
let completed = false;
|
|
871
|
-
const
|
|
882
|
+
const ariaStart = Date.now();
|
|
872
883
|
let lastProgressTime = 0;
|
|
873
884
|
while (!completed) {
|
|
874
|
-
if (Date.now() -
|
|
885
|
+
if (Date.now() - ariaStart > timeout) {
|
|
875
886
|
await aria2Service.remove(gid).catch(() => { });
|
|
876
887
|
throw new Error('aria2下载超时');
|
|
877
888
|
}
|
|
@@ -897,6 +908,9 @@ function apply(ctx, config) {
|
|
|
897
908
|
}
|
|
898
909
|
}
|
|
899
910
|
const stat = await promises_1.default.stat(filePath);
|
|
911
|
+
const sizeMB = (stat.size / (1024 * 1024)).toFixed(2);
|
|
912
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
913
|
+
logger.info(`[下载] 完成 (aria2): ${filePath} (${sizeMB} MB, 耗时 ${elapsed}s)`);
|
|
900
914
|
if (maxSize > 0 && stat.size > maxSize * 1024 * 1024) {
|
|
901
915
|
await promises_1.default.unlink(filePath).catch(() => { });
|
|
902
916
|
throw new Error(`文件过大(${Math.round(stat.size / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
@@ -907,6 +921,10 @@ function apply(ctx, config) {
|
|
|
907
921
|
debugLog('ERROR', `aria2下载失败,回退内置下载: ${getErrorMessage(e)}`);
|
|
908
922
|
}
|
|
909
923
|
}
|
|
924
|
+
const urlObj = new URL(url);
|
|
925
|
+
if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') {
|
|
926
|
+
throw new Error('不支持的下载协议:' + urlObj.protocol);
|
|
927
|
+
}
|
|
910
928
|
const writer = (0, fs_1.createWriteStream)(filePath);
|
|
911
929
|
let response;
|
|
912
930
|
try {
|
|
@@ -932,6 +950,8 @@ function apply(ctx, config) {
|
|
|
932
950
|
await promises_1.default.unlink(filePath).catch(() => { });
|
|
933
951
|
throw new Error(`文件过大(${Math.round(contentLength / 1024 / 1024)}MB),超过限制(${maxSize}MB)`);
|
|
934
952
|
}
|
|
953
|
+
const sizeStr = contentLength > 0 ? `${(contentLength / (1024 * 1024)).toFixed(2)}MB` : '未知大小';
|
|
954
|
+
logger.info(`[下载] 开始 (内置): ${url} (${sizeStr})`);
|
|
935
955
|
let downloaded = 0;
|
|
936
956
|
let lastProgressTime = 0;
|
|
937
957
|
response.data.on('data', (chunk) => {
|
|
@@ -940,22 +960,27 @@ function apply(ctx, config) {
|
|
|
940
960
|
lastProgressTime = Date.now();
|
|
941
961
|
if (contentLength > 0) {
|
|
942
962
|
const percent = Math.round((downloaded / contentLength) * 100);
|
|
943
|
-
|
|
963
|
+
const mb = (downloaded / (1024 * 1024)).toFixed(2);
|
|
964
|
+
const totalMB = (contentLength / (1024 * 1024)).toFixed(2);
|
|
965
|
+
onProgress(percent, `${mb}/${totalMB}MB`);
|
|
944
966
|
}
|
|
945
967
|
else {
|
|
946
|
-
|
|
968
|
+
const mb = (downloaded / (1024 * 1024)).toFixed(2);
|
|
969
|
+
onProgress(-1, `已下载 ${mb} MB`);
|
|
947
970
|
}
|
|
948
971
|
}
|
|
949
972
|
});
|
|
950
973
|
try {
|
|
951
974
|
await (0, promises_2.pipeline)(response.data, writer);
|
|
975
|
+
const stat = await promises_1.default.stat(filePath);
|
|
976
|
+
const sizeMB = (stat.size / (1024 * 1024)).toFixed(2);
|
|
977
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
978
|
+
logger.info(`[下载] 完成 (内置): ${filePath} (${sizeMB} MB, 耗时 ${elapsed}s)`);
|
|
952
979
|
if (onProgress) {
|
|
953
|
-
if (contentLength > 0)
|
|
954
|
-
onProgress(100,
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
onProgress(-1, formatSpeed(downloaded / ((Date.now() - lastProgressTime) / 1000)));
|
|
958
|
-
}
|
|
980
|
+
if (contentLength > 0)
|
|
981
|
+
onProgress(100, `${sizeMB}MB`);
|
|
982
|
+
else
|
|
983
|
+
onProgress(-1, `完成,共 ${sizeMB} MB`);
|
|
959
984
|
}
|
|
960
985
|
return filePath;
|
|
961
986
|
}
|
|
@@ -989,15 +1014,15 @@ function apply(ctx, config) {
|
|
|
989
1014
|
};
|
|
990
1015
|
const prefixMap = { image: 'img', video: 'video', audio: 'music' };
|
|
991
1016
|
const sendFunc = type === 'audio' ? koishi_1.h.audio : type === 'video' ? koishi_1.h.video : koishi_1.h.image;
|
|
992
|
-
const onProgress =
|
|
1017
|
+
const onProgress = (percent, info) => {
|
|
993
1018
|
if (percent === -1) {
|
|
994
|
-
logger.info(
|
|
1019
|
+
logger.info(`[下载] ${info}`);
|
|
995
1020
|
}
|
|
996
1021
|
else {
|
|
997
|
-
const text =
|
|
1022
|
+
const text = `[下载] ${percent}%` + (info ? ` (${info})` : '');
|
|
998
1023
|
logger.info(text);
|
|
999
1024
|
}
|
|
1000
|
-
}
|
|
1025
|
+
};
|
|
1001
1026
|
if (forceDownload) {
|
|
1002
1027
|
try {
|
|
1003
1028
|
const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type], onProgress);
|
|
@@ -1029,7 +1054,7 @@ function apply(ctx, config) {
|
|
|
1029
1054
|
}
|
|
1030
1055
|
catch {
|
|
1031
1056
|
try {
|
|
1032
|
-
const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type]);
|
|
1057
|
+
const localPath = await downloadFile(url, mediaDownloadTimeout, maxMediaSize, prefixMap[type], extMap[type], onProgress);
|
|
1033
1058
|
try {
|
|
1034
1059
|
await sendWithTimeout(session, sendFunc(`file://${localPath}`));
|
|
1035
1060
|
}
|
|
@@ -1114,7 +1139,7 @@ function apply(ctx, config) {
|
|
|
1114
1139
|
if (config.showAuthorAvatar && p.avatar) {
|
|
1115
1140
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.avatar), botName));
|
|
1116
1141
|
}
|
|
1117
|
-
if (p.cover && config.showCoverImage && p.type !== 'live_photo' && p.type !== 'image' &&
|
|
1142
|
+
if (p.cover && config.showCoverImage && p.type !== 'live_photo' && p.type !== 'image' && p.type !== 'live') {
|
|
1118
1143
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.cover), botName));
|
|
1119
1144
|
}
|
|
1120
1145
|
if (config.showMusicCover && p.music.cover) {
|
|
@@ -1125,7 +1150,7 @@ function apply(ctx, config) {
|
|
|
1125
1150
|
for (const imgUrl of imageUrls)
|
|
1126
1151
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
|
|
1127
1152
|
}
|
|
1128
|
-
if (p.video)
|
|
1153
|
+
if (p.video && p.type !== 'live')
|
|
1129
1154
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.video(p.video), botName));
|
|
1130
1155
|
if (config.showMusicVoice && p.music.url) {
|
|
1131
1156
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.audio(p.music.url), botName));
|
|
@@ -1160,7 +1185,7 @@ function apply(ctx, config) {
|
|
|
1160
1185
|
await sendMedia(session, p.avatar, 'image', config.forceDownloadAuthorAvatar, config.showAuthorAvatarFile).catch(() => { });
|
|
1161
1186
|
await delay(300);
|
|
1162
1187
|
}
|
|
1163
|
-
if (p.cover && config.showCoverImage && p.type !== 'live_photo' && p.type !== 'image' &&
|
|
1188
|
+
if (p.cover && config.showCoverImage && p.type !== 'live_photo' && p.type !== 'image' && p.type !== 'live') {
|
|
1164
1189
|
await sendMedia(session, p.cover, 'image', config.forceDownloadCover, config.showCoverFile).catch(() => { });
|
|
1165
1190
|
await delay(300);
|
|
1166
1191
|
}
|
|
@@ -1168,17 +1193,21 @@ function apply(ctx, config) {
|
|
|
1168
1193
|
await sendMedia(session, p.music.cover, 'image', false, true).catch(() => { });
|
|
1169
1194
|
await delay(300);
|
|
1170
1195
|
}
|
|
1171
|
-
if (p.video &&
|
|
1196
|
+
if (p.video && p.type !== 'live') {
|
|
1172
1197
|
await sendMedia(session, p.video, 'video', config.forceDownloadVideo, config.showVideoFile).catch(() => { });
|
|
1173
1198
|
await delay(500);
|
|
1174
1199
|
}
|
|
1175
1200
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
1176
1201
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
1177
|
-
for (
|
|
1178
|
-
|
|
1179
|
-
await
|
|
1202
|
+
for (let i = 0; i < imageUrls.length; i++) {
|
|
1203
|
+
logger.info(`[发送] 图片 ${i + 1}/${imageUrls.length}`);
|
|
1204
|
+
await sendMedia(session, imageUrls[i], 'image', config.forceDownloadImageNew, config.showImageFileNew).catch(() => { });
|
|
1205
|
+
await delay(1000);
|
|
1180
1206
|
}
|
|
1181
1207
|
}
|
|
1208
|
+
if (p.type === 'live' && config.sendLiveMessage) {
|
|
1209
|
+
await sendWithTimeout(session, '直播进行中,无法发送视频流。').catch(() => { });
|
|
1210
|
+
}
|
|
1182
1211
|
if (config.showMusicVoice && p.music.url) {
|
|
1183
1212
|
await sendMedia(session, p.music.url, 'audio', config.forceDownloadMusicVoice, config.showMusicVoiceFile).catch(() => { });
|
|
1184
1213
|
await delay(300);
|
|
@@ -1413,21 +1442,23 @@ function apply(ctx, config) {
|
|
|
1413
1442
|
dedupCache.clear();
|
|
1414
1443
|
debugLog('INFO', '插件已卸载');
|
|
1415
1444
|
});
|
|
1416
|
-
process.
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
(file.startsWith('
|
|
1422
|
-
|
|
1423
|
-
|
|
1445
|
+
if (!process.listenerCount('beforeExit')) {
|
|
1446
|
+
process.once('beforeExit', async () => {
|
|
1447
|
+
try {
|
|
1448
|
+
const files = await promises_1.default.readdir(cacheDir);
|
|
1449
|
+
for (const file of files) {
|
|
1450
|
+
if ((file.startsWith('video_') && file.endsWith('.mp4')) ||
|
|
1451
|
+
(file.startsWith('img_') && file.match(/\.(png|jpg|jpeg|gif|webp)$/i)) ||
|
|
1452
|
+
(file.startsWith('music_') && file.match(/\.(mp3|wav|ogg|flac|aac|m4a)$/i))) {
|
|
1453
|
+
await promises_1.default.unlink(path_1.default.join(cacheDir, file)).catch(() => { });
|
|
1454
|
+
}
|
|
1424
1455
|
}
|
|
1425
1456
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1457
|
+
catch (e) {
|
|
1458
|
+
if (e?.code !== 'ENOENT')
|
|
1459
|
+
debugLog('WARN', '退出清理临时文件失败:', e);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1432
1463
|
debugLog('INFO', '插件初始化完成');
|
|
1433
1464
|
}
|
package/package.json
CHANGED