koishi-plugin-video-parser-all 1.5.0 → 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 +89 -55
- package/package.json +1 -1
- package/readme.md +3 -3
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('强制下载作者头像'),
|
|
@@ -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
|
}
|
|
@@ -1108,12 +1133,13 @@ function apply(ctx, config) {
|
|
|
1108
1133
|
const text = item.text;
|
|
1109
1134
|
if (text && config.showImageText)
|
|
1110
1135
|
forwardMessages.push(buildForwardNode(session, text, botName));
|
|
1136
|
+
if (config.showAuthorAvatar && p.avatar && config.showAuthorAvatarText) {
|
|
1137
|
+
forwardMessages.push(buildForwardNode(session, config.authorAvatarText || '作者头像:', botName));
|
|
1138
|
+
}
|
|
1111
1139
|
if (config.showAuthorAvatar && p.avatar) {
|
|
1112
|
-
if (config.showAuthorAvatarText)
|
|
1113
|
-
forwardMessages.push(buildForwardNode(session, config.authorAvatarText || '作者头像:', botName));
|
|
1114
1140
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.avatar), botName));
|
|
1115
1141
|
}
|
|
1116
|
-
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') {
|
|
1117
1143
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(p.cover), botName));
|
|
1118
1144
|
}
|
|
1119
1145
|
if (config.showMusicCover && p.music.cover) {
|
|
@@ -1124,7 +1150,7 @@ function apply(ctx, config) {
|
|
|
1124
1150
|
for (const imgUrl of imageUrls)
|
|
1125
1151
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.image(imgUrl), botName));
|
|
1126
1152
|
}
|
|
1127
|
-
if (p.video)
|
|
1153
|
+
if (p.video && p.type !== 'live')
|
|
1128
1154
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.video(p.video), botName));
|
|
1129
1155
|
if (config.showMusicVoice && p.music.url) {
|
|
1130
1156
|
forwardMessages.push(buildForwardNode(session, koishi_1.h.audio(p.music.url), botName));
|
|
@@ -1151,13 +1177,15 @@ function apply(ctx, config) {
|
|
|
1151
1177
|
await sendWithTimeout(session, text);
|
|
1152
1178
|
await delay(300);
|
|
1153
1179
|
}
|
|
1180
|
+
if (config.showAuthorAvatar && p.avatar && config.showAuthorAvatarText) {
|
|
1181
|
+
await sendWithTimeout(session, config.authorAvatarText || '作者头像:');
|
|
1182
|
+
await delay(300);
|
|
1183
|
+
}
|
|
1154
1184
|
if (config.showAuthorAvatar && p.avatar) {
|
|
1155
|
-
if (config.showAuthorAvatarText)
|
|
1156
|
-
await sendWithTimeout(session, config.authorAvatarText || '作者头像:');
|
|
1157
1185
|
await sendMedia(session, p.avatar, 'image', config.forceDownloadAuthorAvatar, config.showAuthorAvatarFile).catch(() => { });
|
|
1158
1186
|
await delay(300);
|
|
1159
1187
|
}
|
|
1160
|
-
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') {
|
|
1161
1189
|
await sendMedia(session, p.cover, 'image', config.forceDownloadCover, config.showCoverFile).catch(() => { });
|
|
1162
1190
|
await delay(300);
|
|
1163
1191
|
}
|
|
@@ -1165,17 +1193,21 @@ function apply(ctx, config) {
|
|
|
1165
1193
|
await sendMedia(session, p.music.cover, 'image', false, true).catch(() => { });
|
|
1166
1194
|
await delay(300);
|
|
1167
1195
|
}
|
|
1168
|
-
if (p.video &&
|
|
1196
|
+
if (p.video && p.type !== 'live') {
|
|
1169
1197
|
await sendMedia(session, p.video, 'video', config.forceDownloadVideo, config.showVideoFile).catch(() => { });
|
|
1170
1198
|
await delay(500);
|
|
1171
1199
|
}
|
|
1172
1200
|
if (p.type === 'image' || p.type === 'live_photo' || (p.type === 'live' && (p.live_photo?.length || p.images?.length))) {
|
|
1173
1201
|
const imageUrls = p.images?.length ? p.images : (p.live_photo?.map(lp => lp.image) ?? []);
|
|
1174
|
-
for (
|
|
1175
|
-
|
|
1176
|
-
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);
|
|
1177
1206
|
}
|
|
1178
1207
|
}
|
|
1208
|
+
if (p.type === 'live' && config.sendLiveMessage) {
|
|
1209
|
+
await sendWithTimeout(session, '直播进行中,无法发送视频流。').catch(() => { });
|
|
1210
|
+
}
|
|
1179
1211
|
if (config.showMusicVoice && p.music.url) {
|
|
1180
1212
|
await sendMedia(session, p.music.url, 'audio', config.forceDownloadMusicVoice, config.showMusicVoiceFile).catch(() => { });
|
|
1181
1213
|
await delay(300);
|
|
@@ -1410,21 +1442,23 @@ function apply(ctx, config) {
|
|
|
1410
1442
|
dedupCache.clear();
|
|
1411
1443
|
debugLog('INFO', '插件已卸载');
|
|
1412
1444
|
});
|
|
1413
|
-
process.
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
(file.startsWith('
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|
+
}
|
|
1421
1455
|
}
|
|
1422
1456
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1457
|
+
catch (e) {
|
|
1458
|
+
if (e?.code !== 'ENOENT')
|
|
1459
|
+
debugLog('WARN', '退出清理临时文件失败:', e);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1429
1463
|
debugLog('INFO', '插件初始化完成');
|
|
1430
1464
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -179,7 +179,7 @@ If you use `downloadEngine: 'downloads'`, please install the optional dependency
|
|
|
179
179
|
| 皮皮搞笑 Pipigx | `h5.pipigx.com/pp/post/`, `ippzone.com` | 短视频 (Short video) |
|
|
180
180
|
| 皮皮虾 Pipixia | `pipix.com`, `pipixia.com` | 短视频 (Short video) |
|
|
181
181
|
| 最右 Zuiyou | `share.xiaochuankeji.cn/hybrid/share/post`, `izuiyou.com` | 短视频 (Short video) |
|
|
182
|
-
| 自定义平台 Custom | 通过 `customPlatforms` 配置添加 (Add via `customPlatforms`) | 取决于提供的 API (Depends on API) |
|
|
182
|
+
| 🔧 自定义平台 Custom | 通过 `customPlatforms` 配置添加 (Add via `customPlatforms`) | 取决于提供的 API (Depends on API) |
|
|
183
183
|
|
|
184
184
|
## 项目贡献者 (Contributors)
|
|
185
185
|
|
|
@@ -188,9 +188,9 @@ If you use `downloadEngine: 'downloads'`, please install the optional dependency
|
|
|
188
188
|
| Minecraft-1314 | 插件完整开发 (Complete plugin development) |
|
|
189
189
|
| ShiraiKuroko003 | 修复消息格式设置问题并且PR-1.2.5版本已修复 (Fixed message format issue, PR-1.2.5) |
|
|
190
190
|
| cyavb | 提交功能建议-给自定义API添加KEY认证-已修复 (Suggested custom API key auth - fixed) |
|
|
191
|
-
| Keep785 | 提交Bug-无法正常关闭发送封面-已修复<br>提交Bug-解析问题-已修复 (Reported
|
|
191
|
+
| Keep785 | 提交Bug-无法正常关闭发送封面-已修复<br>提交Bug-解析问题-已修复 (Reported bug - cannot disable cover sending - fixed<br>Reported bug - parsing issue - fixed) |
|
|
192
192
|
| dzt2008 + Apricityx | 提交Bug-会对非支持视频平台URL进行误解析-已修复 (Reported incorrect parsing of unsupported URLs - fixed) |
|
|
193
|
-
| linyves | 提交Bug-小红书图集重复发送封面-已修复<br>提交Bug-话题显示异常 #**[话题]#-已修复<br>提交建议-Live Photo 全部按普通图片处理-已采纳<br>提交Bug-解析后会把作者头像一起发送-已修复 |
|
|
193
|
+
| linyves | 提交Bug-小红书图集重复发送封面-已修复<br>提交Bug-话题显示异常 #**[话题]#-已修复<br>提交建议-Live Photo 全部按普通图片处理-已采纳<br>提交Bug-解析后会把作者头像一起发送-已修复 (Reported bug - duplicate cover for Xiaohongshu image posts - fixed<br>Reported bug - abnormal topic display #**[topic]# - fixed<br>Suggestion - treat Live Photos as normal images - adopted<br>Reported bug - author avatar sent together after parsing - fixed) |
|
|
194
194
|
| JH-Ahua | BugPk-Api 支持 (BugPk-Api support) |
|
|
195
195
|
| shangxue | 灵感来源 (Inspiration) |
|
|
196
196
|
|