mioku-plugin-media 2.0.0 → 2.1.0
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/README.md +10 -1
- package/config.md +6 -1
- package/config.ts +2 -0
- package/index.ts +18 -1
- package/package.json +1 -1
- package/platforms/amagi-client.ts +6 -0
- package/platforms/resolvers/kuaishou.ts +80 -13
- package/platforms/types.ts +27 -1
- package/platforms/url-parser.ts +24 -23
- package/utils/error-handler.ts +33 -1
- package/utils/message.ts +145 -67
package/README.md
CHANGED
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
- 返回封面图、标题、作者、简介和视频文件
|
|
10
10
|
- 可选配置各平台 Cookies 以获取更高质量的视频
|
|
11
11
|
|
|
12
|
+
## Cookies 配置
|
|
13
|
+
|
|
14
|
+
配置 Cookies 可获取更高画质和完整解析能力。可使用内置扫码登录命令自动获取,或手动从浏览器复制。
|
|
15
|
+
|
|
16
|
+
### 手动获取方式
|
|
17
|
+
|
|
18
|
+
安装 Cookie-Editor 浏览器插件并在对应平台界面登录账号,点击 Cookie-Editor 右下方的导出,选择导出为字符串即可。
|
|
19
|
+
|
|
20
|
+
|
|
12
21
|
## 支持平台
|
|
13
22
|
|
|
14
23
|
### 哔哩哔哩
|
|
@@ -39,4 +48,4 @@
|
|
|
39
48
|
},
|
|
40
49
|
"debug": false
|
|
41
50
|
}
|
|
42
|
-
```
|
|
51
|
+
```
|
package/config.md
CHANGED
|
@@ -15,7 +15,7 @@ fields:
|
|
|
15
15
|
- key: base.cookies.kuaishou
|
|
16
16
|
label: 快手 Cookie
|
|
17
17
|
type: textarea
|
|
18
|
-
description: 快手 Cookie
|
|
18
|
+
description: 快手 Cookie,配置后可正常解析作品。留空则无法解析。
|
|
19
19
|
|
|
20
20
|
- key: base.cookies.xiaohongshu
|
|
21
21
|
label: 小红书 Cookie
|
|
@@ -26,4 +26,9 @@ fields:
|
|
|
26
26
|
label: 调试模式
|
|
27
27
|
type: switch
|
|
28
28
|
description: 开启后解析失败时直接发送错误信息,不走 AI 通知
|
|
29
|
+
|
|
30
|
+
- key: base.maxVideoDurationSeconds
|
|
31
|
+
label: 视频解析时长上限
|
|
32
|
+
type: number
|
|
33
|
+
description: 允许解析的视频最大时长(秒),默认 1200(20分钟)。超过限制的视频将被拒绝解析。
|
|
29
34
|
---
|
package/config.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface MediaConfig {
|
|
|
5
5
|
kuaishou: string;
|
|
6
6
|
xiaohongshu: string;
|
|
7
7
|
};
|
|
8
|
+
maxVideoDurationSeconds: number;
|
|
8
9
|
debug: boolean;
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -15,5 +16,6 @@ export const MEDIA_DEFAULTS: MediaConfig = {
|
|
|
15
16
|
kuaishou: "",
|
|
16
17
|
xiaohongshu: "",
|
|
17
18
|
},
|
|
19
|
+
maxVideoDurationSeconds: 20 * 60,
|
|
18
20
|
debug: false,
|
|
19
21
|
};
|
package/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { MEDIA_DEFAULTS } from "./config";
|
|
|
5
5
|
import { createMediaAmagiClient } from "./platforms/amagi-client";
|
|
6
6
|
import { extractMediaUrlFromEvent, resolveShortUrl, isShortUrl } from "./platforms/url-parser";
|
|
7
7
|
import { resolveMedia } from "./platforms/resolvers";
|
|
8
|
-
import { sendMediaResult } from "./utils/message";
|
|
8
|
+
import { sendMediaResult, sendDurationLimitResult } from "./utils/message";
|
|
9
9
|
import { handleMediaError } from "./utils/error-handler";
|
|
10
10
|
import { setMediaRuntimeState, resetMediaRuntimeState } from "./runtime";
|
|
11
11
|
|
|
@@ -102,6 +102,23 @@ export default definePlugin({
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
const result = await resolveMedia(amagiClient, parsed);
|
|
105
|
+
|
|
106
|
+
if (result.duration && config.maxVideoDurationSeconds > 0) {
|
|
107
|
+
if (result.duration > config.maxVideoDurationSeconds) {
|
|
108
|
+
ctx.logger.warn(
|
|
109
|
+
`[media] 视频时长超过限制: ${result.duration}秒 > ${config.maxVideoDurationSeconds}秒`,
|
|
110
|
+
);
|
|
111
|
+
await sendDurationLimitResult(
|
|
112
|
+
ctx,
|
|
113
|
+
event,
|
|
114
|
+
parsed,
|
|
115
|
+
result,
|
|
116
|
+
Math.floor(config.maxVideoDurationSeconds / 60),
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
105
122
|
await sendMediaResult(ctx, event, parsed, result);
|
|
106
123
|
} catch (error) {
|
|
107
124
|
await handleMediaError({
|
package/package.json
CHANGED
|
@@ -27,8 +27,14 @@ export function createMediaAmagiClient(config: MediaConfig): AmagiClient {
|
|
|
27
27
|
bilibili: {
|
|
28
28
|
fetcher: client.bilibili.fetcher,
|
|
29
29
|
},
|
|
30
|
+
douyin: {
|
|
31
|
+
fetcher: client.douyin.fetcher,
|
|
32
|
+
},
|
|
30
33
|
kuaishou: {
|
|
31
34
|
fetcher: client.kuaishou.fetcher,
|
|
32
35
|
},
|
|
36
|
+
xiaohongshu: {
|
|
37
|
+
fetcher: client.xiaohongshu.fetcher,
|
|
38
|
+
},
|
|
33
39
|
};
|
|
34
40
|
}
|
|
@@ -2,17 +2,74 @@ import type { ParsedMediaResult } from "../../types";
|
|
|
2
2
|
import type { AmagiClient, ParsedMediaUrl } from "../types";
|
|
3
3
|
import type { PlatformResolver } from "./types";
|
|
4
4
|
|
|
5
|
+
const KUAISHOU_SHORT_CODE_REGEX = /^([a-zA-Z0-9_-]+)$/;
|
|
6
|
+
|
|
7
|
+
async function resolveKuaishouShortCode(shortCode: string): Promise<string> {
|
|
8
|
+
const url = `https://v.kuaishou.com/${shortCode}`;
|
|
9
|
+
try {
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
12
|
+
const response = await fetch(url, {
|
|
13
|
+
redirect: "follow",
|
|
14
|
+
signal: controller.signal,
|
|
15
|
+
});
|
|
16
|
+
clearTimeout(timeout);
|
|
17
|
+
return response.url || url;
|
|
18
|
+
} catch {
|
|
19
|
+
return url;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function extractPhotoIdFromUrl(url: string): string | null {
|
|
24
|
+
const shortMatch = url.match(/\/short-video\/([a-zA-Z0-9_-]+)/);
|
|
25
|
+
if (shortMatch) return shortMatch[1];
|
|
26
|
+
const photoMatch = url.match(/\/photo\/([a-zA-Z0-9_-]+)/);
|
|
27
|
+
if (photoMatch) return photoMatch[1];
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
5
31
|
export class KuaishouResolver implements PlatformResolver {
|
|
6
|
-
async resolve(
|
|
7
|
-
|
|
32
|
+
async resolve(
|
|
33
|
+
client: AmagiClient,
|
|
34
|
+
parsed: ParsedMediaUrl,
|
|
35
|
+
): Promise<ParsedMediaResult> {
|
|
36
|
+
let photoId = parsed.id;
|
|
8
37
|
|
|
38
|
+
// If id is a short code (just the code like "KfhlEcGV"), resolve it first
|
|
39
|
+
if (
|
|
40
|
+
!parsed.id.startsWith("http") &&
|
|
41
|
+
KUAISHOU_SHORT_CODE_REGEX.test(parsed.id)
|
|
42
|
+
) {
|
|
43
|
+
const resolvedUrl = await resolveKuaishouShortCode(parsed.id);
|
|
44
|
+
const extracted = extractPhotoIdFromUrl(resolvedUrl);
|
|
45
|
+
if (extracted) {
|
|
46
|
+
photoId = extracted;
|
|
47
|
+
}
|
|
48
|
+
} else if (parsed.id.startsWith("http")) {
|
|
49
|
+
// If id is a full URL, extract photoId from URL path
|
|
50
|
+
const extracted = extractPhotoIdFromUrl(parsed.id);
|
|
51
|
+
if (extracted) {
|
|
52
|
+
photoId = extracted;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
9
55
|
const result = await client.kuaishou.fetcher.fetchVideoWork({ photoId });
|
|
56
|
+
|
|
10
57
|
if (!result.success) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
58
|
+
const errorInfo = (result.error as any) || {};
|
|
59
|
+
const amagiError = errorInfo.amagiError as any;
|
|
60
|
+
const errorDesc =
|
|
61
|
+
amagiError?.errorDescription || errorInfo.errorDescription || "";
|
|
62
|
+
const errorCode = result.code as any;
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
errorDesc.includes("ck可能已经失效") ||
|
|
66
|
+
errorDesc.includes("接口返回内容为空") ||
|
|
67
|
+
errorCode === "INVALID_COOKIE" ||
|
|
68
|
+
errorCode === "UNKNOWN_ERROR"
|
|
69
|
+
) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"快手接口返回内容为空,可能是作品不存在、链接有误或需要登录。请确认链接是否正确,或检查作品是否公开。",
|
|
72
|
+
);
|
|
16
73
|
}
|
|
17
74
|
throw new Error(`快手作品解析失败: ${result.message}`);
|
|
18
75
|
}
|
|
@@ -38,10 +95,16 @@ export class KuaishouResolver implements PlatformResolver {
|
|
|
38
95
|
videoUrl = photo.photoUrl;
|
|
39
96
|
} else if (photo.croppedPhotoUrl) {
|
|
40
97
|
videoUrl = photo.croppedPhotoUrl;
|
|
41
|
-
} else if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
videoUrl =
|
|
98
|
+
} else if (
|
|
99
|
+
photo.videoResource?.h264?.adaptationSet?.[0]?.representation?.[0]?.url
|
|
100
|
+
) {
|
|
101
|
+
videoUrl =
|
|
102
|
+
photo.videoResource.h264.adaptationSet[0].representation[0].url;
|
|
103
|
+
} else if (
|
|
104
|
+
photo.videoResource?.hevc?.adaptationSet?.[0]?.representation?.[0]?.url
|
|
105
|
+
) {
|
|
106
|
+
videoUrl =
|
|
107
|
+
photo.videoResource.hevc.adaptationSet[0].representation[0].url;
|
|
45
108
|
} else if (photo.manifest?.adaptationSet?.[0]?.representation?.[0]?.url) {
|
|
46
109
|
videoUrl = photo.manifest.adaptationSet[0].representation[0].url;
|
|
47
110
|
}
|
|
@@ -49,7 +112,11 @@ export class KuaishouResolver implements PlatformResolver {
|
|
|
49
112
|
const images: string[] = [];
|
|
50
113
|
if (!videoUrl && photo.images && Array.isArray(photo.images)) {
|
|
51
114
|
for (const img of photo.images) {
|
|
52
|
-
const url =
|
|
115
|
+
const url =
|
|
116
|
+
img?.url ||
|
|
117
|
+
img?.url_list?.[0] ||
|
|
118
|
+
img?.download_addr?.url_list?.[0] ||
|
|
119
|
+
"";
|
|
53
120
|
if (url) images.push(url);
|
|
54
121
|
}
|
|
55
122
|
}
|
|
@@ -70,4 +137,4 @@ export class KuaishouResolver implements PlatformResolver {
|
|
|
70
137
|
},
|
|
71
138
|
};
|
|
72
139
|
}
|
|
73
|
-
}
|
|
140
|
+
}
|
package/platforms/types.ts
CHANGED
|
@@ -44,9 +44,35 @@ export interface AmagiClient {
|
|
|
44
44
|
typeMode?: "strict" | "loose";
|
|
45
45
|
}): Promise<{
|
|
46
46
|
success: boolean;
|
|
47
|
-
code?: number;
|
|
47
|
+
code?: number | string | undefined;
|
|
48
48
|
data: any;
|
|
49
49
|
message?: string;
|
|
50
|
+
error?: any;
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
douyin: {
|
|
55
|
+
fetcher: {
|
|
56
|
+
parseWork(options: {
|
|
57
|
+
aweme_id: string;
|
|
58
|
+
typeMode?: "strict" | "loose";
|
|
59
|
+
}): Promise<{
|
|
60
|
+
success: boolean;
|
|
61
|
+
data?: any;
|
|
62
|
+
message?: string;
|
|
63
|
+
}>;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
xiaohongshu: {
|
|
67
|
+
fetcher: {
|
|
68
|
+
fetchNoteDetail(options: {
|
|
69
|
+
note_id: string;
|
|
70
|
+
xsec_token: string;
|
|
71
|
+
typeMode?: "strict" | "loose";
|
|
72
|
+
}): Promise<{
|
|
73
|
+
success: boolean;
|
|
74
|
+
data?: any;
|
|
75
|
+
message?: string;
|
|
50
76
|
}>;
|
|
51
77
|
};
|
|
52
78
|
};
|
package/platforms/url-parser.ts
CHANGED
|
@@ -206,7 +206,8 @@ function parseKuaishouUrl(url: string): ParsedMediaUrl | null {
|
|
|
206
206
|
// Handle v.kuaishou.com/xxx short URLs like https://v.kuaishou.com/KfhlEcGV
|
|
207
207
|
const shortCodeMatch = url.match(/:\/\/[^/]+\/([a-zA-Z0-9_-]+)/);
|
|
208
208
|
if (shortCodeMatch) {
|
|
209
|
-
|
|
209
|
+
// Store full URL so isShortUrl can detect it and resolveShortUrl can work
|
|
210
|
+
return { platform: "kuaishou", id: url, subtype: "video" };
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
return null;
|
|
@@ -393,29 +394,29 @@ export function extractMediaUrlFromEvent(event: any): ParsedMediaUrl | null {
|
|
|
393
394
|
|
|
394
395
|
export function resolveShortUrl(url: string): Promise<string> {
|
|
395
396
|
return new Promise((resolve) => {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
controller.abort();
|
|
400
|
-
resolve(url);
|
|
401
|
-
}, 5000);
|
|
402
|
-
|
|
403
|
-
fetch(url, {
|
|
404
|
-
redirect: "follow",
|
|
405
|
-
signal: controller.signal,
|
|
406
|
-
method: "HEAD",
|
|
407
|
-
})
|
|
408
|
-
.then((response) => {
|
|
409
|
-
clearTimeout(timeout);
|
|
410
|
-
resolve(response.url || url);
|
|
411
|
-
})
|
|
412
|
-
.catch(() => {
|
|
413
|
-
clearTimeout(timeout);
|
|
414
|
-
resolve(url);
|
|
415
|
-
});
|
|
416
|
-
} catch {
|
|
397
|
+
const controller = new AbortController();
|
|
398
|
+
const timeout = setTimeout(() => {
|
|
399
|
+
controller.abort();
|
|
417
400
|
resolve(url);
|
|
418
|
-
}
|
|
401
|
+
}, 10000);
|
|
402
|
+
|
|
403
|
+
fetch(url, {
|
|
404
|
+
redirect: "follow",
|
|
405
|
+
signal: controller.signal,
|
|
406
|
+
})
|
|
407
|
+
.then((response) => {
|
|
408
|
+
clearTimeout(timeout);
|
|
409
|
+
const finalUrl = response.url;
|
|
410
|
+
if (finalUrl && finalUrl !== url) {
|
|
411
|
+
resolve(finalUrl);
|
|
412
|
+
} else {
|
|
413
|
+
resolve(url);
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
.catch(() => {
|
|
417
|
+
clearTimeout(timeout);
|
|
418
|
+
resolve(url);
|
|
419
|
+
});
|
|
419
420
|
});
|
|
420
421
|
}
|
|
421
422
|
|
package/utils/error-handler.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { MiokiContext } from "mioki";
|
|
2
2
|
import type { MediaConfig } from "../types";
|
|
3
|
+
import type { ParsedMediaUrl } from "../platforms/types";
|
|
4
|
+
import type { ParsedMediaResult } from "../types";
|
|
5
|
+
import type { SendNodeElement, SendNodeContentElement } from "napcat-sdk";
|
|
3
6
|
|
|
4
7
|
function normalizeErrorMessage(error: unknown): string {
|
|
5
8
|
if (error instanceof Error && error.message) {
|
|
@@ -24,5 +27,34 @@ export async function handleMediaError(options: {
|
|
|
24
27
|
}): Promise<void> {
|
|
25
28
|
const { ctx, error, platform } = options;
|
|
26
29
|
const errorMessage = normalizeErrorMessage(error);
|
|
27
|
-
ctx.logger.error(`[media] ${platform} 解析失败: ${errorMessage}
|
|
30
|
+
ctx.logger.error(`[media] ${platform} 解析失败: ${errorMessage}`);
|
|
31
|
+
|
|
32
|
+
// 尝试获取 bot 发送错误信息给用户
|
|
33
|
+
const selfId = event?.self_id != null ? Number(event.self_id) : undefined;
|
|
34
|
+
const bot =
|
|
35
|
+
selfId != null && typeof ctx?.pickBot === "function"
|
|
36
|
+
? ctx.pickBot(selfId)
|
|
37
|
+
: undefined;
|
|
38
|
+
|
|
39
|
+
if (!bot) return;
|
|
40
|
+
|
|
41
|
+
const nickname = String(
|
|
42
|
+
ctx?.bot?.nickname || event?.sender?.card || event?.sender?.nickname || "媒体解析",
|
|
43
|
+
);
|
|
44
|
+
const userId = String(selfId || ctx?.bot?.bot_id || event?.self_id || 0);
|
|
45
|
+
|
|
46
|
+
// 构建错误提示消息
|
|
47
|
+
const errorText = `【${platform}】解析失败\n${errorMessage}`;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
if (event?.message_type === "group" && event?.group_id != null) {
|
|
51
|
+
await bot.sendGroupMsg(event.group_id, [ctx.segment.text(errorText)]);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (event?.user_id != null) {
|
|
55
|
+
await bot.sendPrivateMsg(event.user_id, [ctx.segment.text(errorText)]);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// ignore send errors
|
|
59
|
+
}
|
|
28
60
|
}
|
package/utils/message.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
SendNodeElement,
|
|
3
|
+
SendNodeContentElement,
|
|
4
|
+
ForwardDisplayOptions,
|
|
5
|
+
} from "napcat-sdk";
|
|
2
6
|
import type { ParsedMediaResult, MediaStats } from "../types";
|
|
3
7
|
import type { ParsedMediaUrl } from "../platforms/types";
|
|
4
8
|
|
|
@@ -37,7 +41,10 @@ function formatCount(count?: number): string {
|
|
|
37
41
|
return String(count);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
export function buildInfoMessage(
|
|
44
|
+
export function buildInfoMessage(
|
|
45
|
+
parsed: ParsedMediaUrl,
|
|
46
|
+
result: ParsedMediaResult,
|
|
47
|
+
): string {
|
|
41
48
|
const platform = PLATFORM_NAMES[parsed.platform] || parsed.platform;
|
|
42
49
|
const lines: string[] = [];
|
|
43
50
|
|
|
@@ -68,24 +75,32 @@ export function buildInfoMessage(parsed: ParsedMediaUrl, result: ParsedMediaResu
|
|
|
68
75
|
return lines.join("\n");
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
function buildSummaryText(
|
|
78
|
+
function buildSummaryText(
|
|
79
|
+
platform: string,
|
|
80
|
+
subtype: string | undefined,
|
|
81
|
+
stats?: MediaStats,
|
|
82
|
+
): string {
|
|
72
83
|
if (!stats) return "";
|
|
73
84
|
|
|
74
85
|
const parts: string[] = [];
|
|
75
86
|
|
|
76
87
|
if (platform === "bilibili") {
|
|
77
88
|
if (subtype === "live") {
|
|
78
|
-
if (stats.views != null && stats.views > 0)
|
|
79
|
-
|
|
89
|
+
if (stats.views != null && stats.views > 0)
|
|
90
|
+
parts.push(`在线${formatCount(stats.views)}`);
|
|
91
|
+
if (stats.comments != null && stats.comments > 0)
|
|
92
|
+
parts.push(`关注${formatCount(stats.comments)}`);
|
|
80
93
|
} else {
|
|
81
94
|
if (stats.likes != null) parts.push(`赞${formatCount(stats.likes)}`);
|
|
82
95
|
if (stats.coins != null) parts.push(`币${formatCount(stats.coins)}`);
|
|
83
|
-
if (stats.favorites != null)
|
|
96
|
+
if (stats.favorites != null)
|
|
97
|
+
parts.push(`藏${formatCount(stats.favorites)}`);
|
|
84
98
|
if (stats.shares != null) parts.push(`转${formatCount(stats.shares)}`);
|
|
85
99
|
}
|
|
86
100
|
} else {
|
|
87
101
|
if (stats.likes != null) parts.push(`赞${formatCount(stats.likes)}`);
|
|
88
|
-
if (stats.favorites != null)
|
|
102
|
+
if (stats.favorites != null)
|
|
103
|
+
parts.push(`藏${formatCount(stats.favorites)}`);
|
|
89
104
|
if (stats.shares != null) parts.push(`转${formatCount(stats.shares)}`);
|
|
90
105
|
if (stats.comments != null) parts.push(`评${formatCount(stats.comments)}`);
|
|
91
106
|
}
|
|
@@ -93,7 +108,10 @@ function buildSummaryText(platform: string, subtype: string | undefined, stats?:
|
|
|
93
108
|
return parts.join(" ");
|
|
94
109
|
}
|
|
95
110
|
|
|
96
|
-
function buildForwardDisplay(
|
|
111
|
+
function buildForwardDisplay(
|
|
112
|
+
parsed: ParsedMediaUrl,
|
|
113
|
+
result: ParsedMediaResult,
|
|
114
|
+
): ForwardDisplayOptions {
|
|
97
115
|
const displayTitle = PLATFORM_DISPLAY_TITLES[parsed.platform] || "媒体解析";
|
|
98
116
|
|
|
99
117
|
let summary = buildSummaryText(parsed.platform, parsed.subtype, result.stats);
|
|
@@ -114,10 +132,7 @@ function buildForwardDisplay(parsed: ParsedMediaUrl, result: ParsedMediaResult):
|
|
|
114
132
|
|
|
115
133
|
return {
|
|
116
134
|
source: displayTitle,
|
|
117
|
-
news: [
|
|
118
|
-
{ text: truncateText(result.title, 26) },
|
|
119
|
-
{ text: result.author },
|
|
120
|
-
],
|
|
135
|
+
news: [{ text: truncateText(result.title, 26) }, { text: result.author }],
|
|
121
136
|
summary,
|
|
122
137
|
};
|
|
123
138
|
}
|
|
@@ -149,6 +164,15 @@ function buildForwardNodes(
|
|
|
149
164
|
): SendNodeElement[] {
|
|
150
165
|
const nodes: SendNodeElement[] = [];
|
|
151
166
|
|
|
167
|
+
// 简介文字优先
|
|
168
|
+
const infoText = buildInfoMessage(parsed, result);
|
|
169
|
+
nodes.push({
|
|
170
|
+
type: "node",
|
|
171
|
+
user_id: userId,
|
|
172
|
+
nickname,
|
|
173
|
+
content: [ctx.segment.text(infoText)],
|
|
174
|
+
} as SendNodeContentElement);
|
|
175
|
+
|
|
152
176
|
if (result.coverUrl) {
|
|
153
177
|
nodes.push({
|
|
154
178
|
type: "node",
|
|
@@ -158,14 +182,6 @@ function buildForwardNodes(
|
|
|
158
182
|
} as SendNodeContentElement);
|
|
159
183
|
}
|
|
160
184
|
|
|
161
|
-
const infoText = buildInfoMessage(parsed, result);
|
|
162
|
-
nodes.push({
|
|
163
|
-
type: "node",
|
|
164
|
-
user_id: userId,
|
|
165
|
-
nickname,
|
|
166
|
-
content: [ctx.segment.text(infoText)],
|
|
167
|
-
} as SendNodeContentElement);
|
|
168
|
-
|
|
169
185
|
// 处理图片集/合辑
|
|
170
186
|
if (result.images && result.images.length > 0) {
|
|
171
187
|
// 如果既有图片又有视频/实况图,发送纯图片
|
|
@@ -254,7 +270,10 @@ export async function sendMediaResult(
|
|
|
254
270
|
}
|
|
255
271
|
|
|
256
272
|
const nickname = String(
|
|
257
|
-
ctx?.bot?.nickname ||
|
|
273
|
+
ctx?.bot?.nickname ||
|
|
274
|
+
event?.sender?.card ||
|
|
275
|
+
event?.sender?.nickname ||
|
|
276
|
+
"媒体解析",
|
|
258
277
|
);
|
|
259
278
|
const userId = String(selfId || ctx?.bot?.bot_id || event?.self_id || 0);
|
|
260
279
|
|
|
@@ -262,53 +281,112 @@ export async function sendMediaResult(
|
|
|
262
281
|
const forwardPayload = toOneBotForwardFormat(nodes);
|
|
263
282
|
const display = buildForwardDisplay(parsed, result);
|
|
264
283
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
284
|
+
if (event?.message_type === "group" && event?.group_id != null) {
|
|
285
|
+
await bot.api("send_group_forward_msg", {
|
|
286
|
+
group_id: event.group_id,
|
|
287
|
+
messages: forwardPayload,
|
|
288
|
+
source: display.source,
|
|
289
|
+
news: display.news,
|
|
290
|
+
summary: display.summary,
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
276
294
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
295
|
+
if (event?.user_id != null) {
|
|
296
|
+
await bot.api("send_private_forward_msg", {
|
|
297
|
+
user_id: event.user_id,
|
|
298
|
+
messages: forwardPayload,
|
|
299
|
+
source: display.source,
|
|
300
|
+
news: display.news,
|
|
301
|
+
summary: display.summary,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function sendDurationLimitResult(
|
|
307
|
+
ctx: any,
|
|
308
|
+
event: any,
|
|
309
|
+
parsed: ParsedMediaUrl,
|
|
310
|
+
result: ParsedMediaResult,
|
|
311
|
+
limitMinutes: number,
|
|
312
|
+
): Promise<void> {
|
|
313
|
+
const selfId = event?.self_id != null ? Number(event.self_id) : undefined;
|
|
314
|
+
const bot =
|
|
315
|
+
selfId != null && typeof ctx?.pickBot === "function"
|
|
316
|
+
? ctx.pickBot(selfId)
|
|
317
|
+
: undefined;
|
|
318
|
+
|
|
319
|
+
if (!bot) {
|
|
320
|
+
await event.reply(
|
|
321
|
+
`【${PLATFORM_NAMES[parsed.platform] || parsed.platform}】视频太大了,发不出来~`,
|
|
322
|
+
);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const nickname = String(
|
|
327
|
+
ctx?.bot?.nickname ||
|
|
328
|
+
event?.sender?.card ||
|
|
329
|
+
event?.sender?.nickname ||
|
|
330
|
+
"媒体解析",
|
|
331
|
+
);
|
|
332
|
+
const userId = String(selfId || ctx?.bot?.bot_id || event?.self_id || 0);
|
|
333
|
+
|
|
334
|
+
const nodes: SendNodeElement[] = [];
|
|
335
|
+
|
|
336
|
+
const platform = PLATFORM_NAMES[parsed.platform] || parsed.platform;
|
|
337
|
+
const mins = Math.floor((result.duration || 0) / 60);
|
|
338
|
+
const secs = (result.duration || 0) % 60;
|
|
339
|
+
const infoText = `【${platform}】${result.title}\n作者:${result.author}\n时长:${mins}分${secs}秒\n\n哎嘿,视频太大了发不出来~请选择更短的视频(不超过 ${limitMinutes} 分钟)`;
|
|
340
|
+
|
|
341
|
+
nodes.push({
|
|
342
|
+
type: "node",
|
|
343
|
+
user_id: userId,
|
|
344
|
+
nickname,
|
|
345
|
+
content: [ctx.segment.text(infoText)],
|
|
346
|
+
} as SendNodeContentElement);
|
|
347
|
+
|
|
348
|
+
if (result.coverUrl) {
|
|
349
|
+
nodes.push({
|
|
350
|
+
type: "node",
|
|
351
|
+
user_id: userId,
|
|
352
|
+
nickname,
|
|
353
|
+
content: [ctx.segment.image(result.coverUrl)],
|
|
354
|
+
} as SendNodeContentElement);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 只发图片,不发视频
|
|
358
|
+
if (result.images && result.images.length > 0) {
|
|
359
|
+
for (const imageUrl of result.images) {
|
|
360
|
+
nodes.push({
|
|
361
|
+
type: "node",
|
|
362
|
+
user_id: userId,
|
|
363
|
+
nickname,
|
|
364
|
+
content: [ctx.segment.image(imageUrl)],
|
|
365
|
+
} as SendNodeContentElement);
|
|
309
366
|
}
|
|
310
367
|
}
|
|
311
368
|
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
369
|
+
const forwardPayload = toOneBotForwardFormat(nodes);
|
|
370
|
+
const displayTitle = PLATFORM_DISPLAY_TITLES[parsed.platform] || "媒体解析";
|
|
371
|
+
|
|
372
|
+
if (event?.message_type === "group" && event?.group_id != null) {
|
|
373
|
+
await bot.api("send_group_forward_msg", {
|
|
374
|
+
group_id: event.group_id,
|
|
375
|
+
messages: forwardPayload,
|
|
376
|
+
source: displayTitle,
|
|
377
|
+
news: [{ text: truncateText(result.title, 26) }, { text: result.author }],
|
|
378
|
+
summary: `视频太长无法发送(${mins}分${secs}秒)`,
|
|
379
|
+
});
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (event?.user_id != null) {
|
|
384
|
+
await bot.api("send_private_forward_msg", {
|
|
385
|
+
user_id: event.user_id,
|
|
386
|
+
messages: forwardPayload,
|
|
387
|
+
source: displayTitle,
|
|
388
|
+
news: [{ text: truncateText(result.title, 26) }, { text: result.author }],
|
|
389
|
+
summary: `视频太长无法发送(${mins}分${secs}秒)`,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|