mioku-plugin-media 1.0.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/LICENSE +21 -0
- package/README.md +42 -0
- package/config.md +29 -0
- package/config.ts +19 -0
- package/index.ts +124 -0
- package/package.json +33 -0
- package/platforms/amagi-client.ts +24 -0
- package/platforms/resolvers/bilibili.ts +95 -0
- package/platforms/resolvers/douyin.ts +80 -0
- package/platforms/resolvers/index.ts +27 -0
- package/platforms/resolvers/kuaishou.ts +58 -0
- package/platforms/resolvers/types.ts +6 -0
- package/platforms/resolvers/xiaohongshu.ts +92 -0
- package/platforms/types.ts +8 -0
- package/platforms/url-parser.ts +403 -0
- package/runtime.ts +22 -0
- package/skills.ts +79 -0
- package/types.ts +28 -0
- package/utils/error-handler.ts +67 -0
- package/utils/message.ts +244 -0
package/utils/message.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { SendNodeElement, SendNodeContentElement, ForwardDisplayOptions } from "napcat-sdk";
|
|
2
|
+
import type { ParsedMediaResult, MediaStats } from "../types";
|
|
3
|
+
import type { ParsedMediaUrl } from "../platforms/types";
|
|
4
|
+
|
|
5
|
+
const PLATFORM_NAMES: Record<string, string> = {
|
|
6
|
+
bilibili: "哔哩哔哩",
|
|
7
|
+
douyin: "抖音",
|
|
8
|
+
kuaishou: "快手",
|
|
9
|
+
xiaohongshu: "小红书",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const PLATFORM_DISPLAY_TITLES: Record<string, string> = {
|
|
13
|
+
bilibili: "bilibili视频解析",
|
|
14
|
+
douyin: "抖音视频解析",
|
|
15
|
+
kuaishou: "快手视频解析",
|
|
16
|
+
xiaohongshu: "小红书笔记解析",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function formatDuration(seconds?: number): string {
|
|
20
|
+
if (!seconds || seconds <= 0) return "";
|
|
21
|
+
const m = Math.floor(seconds / 60);
|
|
22
|
+
const s = seconds % 60;
|
|
23
|
+
return m > 0 ? `${m}分${s}秒` : `${s}秒`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function truncateText(text: string, maxLen: number): string {
|
|
27
|
+
if (!text) return "";
|
|
28
|
+
const cleaned = text.replace(/\n/g, " ").trim();
|
|
29
|
+
if (cleaned.length <= maxLen) return cleaned;
|
|
30
|
+
return cleaned.slice(0, maxLen) + "...";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatCount(count?: number): string {
|
|
34
|
+
if (count == null) return "0";
|
|
35
|
+
if (count >= 100000000) return `${(count / 100000000).toFixed(1)}亿`;
|
|
36
|
+
if (count >= 10000) return `${(count / 10000).toFixed(1)}w`;
|
|
37
|
+
return String(count);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function buildInfoMessage(parsed: ParsedMediaUrl, result: ParsedMediaResult): string {
|
|
41
|
+
const platform = PLATFORM_NAMES[parsed.platform] || parsed.platform;
|
|
42
|
+
const lines: string[] = [];
|
|
43
|
+
|
|
44
|
+
lines.push(`【${platform}】${result.title}`);
|
|
45
|
+
lines.push(`作者:${result.author}`);
|
|
46
|
+
|
|
47
|
+
if (result.duration && result.duration > 0) {
|
|
48
|
+
lines.push(`时长:${formatDuration(result.duration)}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const desc = truncateText(result.description, 200);
|
|
52
|
+
if (desc) {
|
|
53
|
+
lines.push(`简介:${desc}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildSummaryText(platform: string, stats?: MediaStats): string {
|
|
60
|
+
if (!stats) return "";
|
|
61
|
+
|
|
62
|
+
const parts: string[] = [];
|
|
63
|
+
|
|
64
|
+
if (platform === "bilibili") {
|
|
65
|
+
if (stats.likes != null) parts.push(`赞${formatCount(stats.likes)}`);
|
|
66
|
+
if (stats.coins != null) parts.push(`币${formatCount(stats.coins)}`);
|
|
67
|
+
if (stats.favorites != null) parts.push(`藏${formatCount(stats.favorites)}`);
|
|
68
|
+
if (stats.shares != null) parts.push(`转${formatCount(stats.shares)}`);
|
|
69
|
+
} else {
|
|
70
|
+
if (stats.likes != null) parts.push(`赞${formatCount(stats.likes)}`);
|
|
71
|
+
if (stats.favorites != null) parts.push(`藏${formatCount(stats.favorites)}`);
|
|
72
|
+
if (stats.shares != null) parts.push(`转${formatCount(stats.shares)}`);
|
|
73
|
+
if (stats.comments != null) parts.push(`评${formatCount(stats.comments)}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return parts.join(" ");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildForwardDisplay(parsed: ParsedMediaUrl, result: ParsedMediaResult): ForwardDisplayOptions {
|
|
80
|
+
const displayTitle = PLATFORM_DISPLAY_TITLES[parsed.platform] || "媒体解析";
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
source: displayTitle,
|
|
84
|
+
news: [
|
|
85
|
+
{ text: truncateText(result.title, 26) },
|
|
86
|
+
{ text: result.author },
|
|
87
|
+
],
|
|
88
|
+
summary: buildSummaryText(parsed.platform, result.stats),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeNodeContent(content: any[]): any[] {
|
|
93
|
+
return content.map((element: any) => {
|
|
94
|
+
if (
|
|
95
|
+
element &&
|
|
96
|
+
typeof element === "object" &&
|
|
97
|
+
"type" in element &&
|
|
98
|
+
"data" in element
|
|
99
|
+
) {
|
|
100
|
+
return element;
|
|
101
|
+
}
|
|
102
|
+
if (element && typeof element === "object" && "type" in element) {
|
|
103
|
+
const { type, ...data } = element;
|
|
104
|
+
return { type, data };
|
|
105
|
+
}
|
|
106
|
+
return element;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildForwardNodes(
|
|
111
|
+
ctx: any,
|
|
112
|
+
userId: string,
|
|
113
|
+
nickname: string,
|
|
114
|
+
parsed: ParsedMediaUrl,
|
|
115
|
+
result: ParsedMediaResult,
|
|
116
|
+
): SendNodeElement[] {
|
|
117
|
+
const nodes: SendNodeElement[] = [];
|
|
118
|
+
|
|
119
|
+
if (result.coverUrl) {
|
|
120
|
+
nodes.push({
|
|
121
|
+
type: "node",
|
|
122
|
+
user_id: userId,
|
|
123
|
+
nickname,
|
|
124
|
+
content: [ctx.segment.image(result.coverUrl)],
|
|
125
|
+
} as SendNodeContentElement);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const infoText = buildInfoMessage(parsed, result);
|
|
129
|
+
nodes.push({
|
|
130
|
+
type: "node",
|
|
131
|
+
user_id: userId,
|
|
132
|
+
nickname,
|
|
133
|
+
content: [ctx.segment.text(infoText)],
|
|
134
|
+
} as SendNodeContentElement);
|
|
135
|
+
|
|
136
|
+
if (result.videoUrl) {
|
|
137
|
+
nodes.push({
|
|
138
|
+
type: "node",
|
|
139
|
+
user_id: userId,
|
|
140
|
+
nickname,
|
|
141
|
+
content: [(ctx.segment as any).video(result.videoUrl)],
|
|
142
|
+
} as SendNodeContentElement);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return nodes;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function toOneBotForwardFormat(nodes: SendNodeElement[]): any[] {
|
|
149
|
+
return nodes.map((node) => {
|
|
150
|
+
if (node.type !== "node") return node;
|
|
151
|
+
|
|
152
|
+
if ("id" in node && node.id) {
|
|
153
|
+
return {
|
|
154
|
+
type: "node",
|
|
155
|
+
data: {
|
|
156
|
+
user_id: (node as any).user_id,
|
|
157
|
+
nickname: (node as any).nickname,
|
|
158
|
+
id: node.id,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const contentNode = node as SendNodeContentElement;
|
|
164
|
+
const content = Array.isArray(contentNode.content)
|
|
165
|
+
? normalizeNodeContent(contentNode.content)
|
|
166
|
+
: [];
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
type: "node",
|
|
170
|
+
data: {
|
|
171
|
+
user_id: contentNode.user_id,
|
|
172
|
+
nickname: contentNode.nickname,
|
|
173
|
+
content,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function sendMediaResult(
|
|
180
|
+
ctx: any,
|
|
181
|
+
event: any,
|
|
182
|
+
parsed: ParsedMediaUrl,
|
|
183
|
+
result: ParsedMediaResult,
|
|
184
|
+
): Promise<void> {
|
|
185
|
+
const selfId = event?.self_id != null ? Number(event.self_id) : undefined;
|
|
186
|
+
const bot =
|
|
187
|
+
selfId != null && typeof ctx?.pickBot === "function"
|
|
188
|
+
? ctx.pickBot(selfId)
|
|
189
|
+
: undefined;
|
|
190
|
+
|
|
191
|
+
if (!bot) {
|
|
192
|
+
await event.reply(buildInfoMessage(parsed, result));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const nickname = String(
|
|
197
|
+
ctx?.bot?.nickname || event?.sender?.card || event?.sender?.nickname || "媒体解析",
|
|
198
|
+
);
|
|
199
|
+
const userId = String(selfId || ctx?.bot?.bot_id || event?.self_id || 0);
|
|
200
|
+
|
|
201
|
+
const nodes = buildForwardNodes(ctx, userId, nickname, parsed, result);
|
|
202
|
+
const forwardPayload = toOneBotForwardFormat(nodes);
|
|
203
|
+
const display = buildForwardDisplay(parsed, result);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
if (event?.message_type === "group" && event?.group_id != null) {
|
|
207
|
+
await bot.api("send_group_forward_msg", {
|
|
208
|
+
group_id: event.group_id,
|
|
209
|
+
messages: forwardPayload,
|
|
210
|
+
source: display.source,
|
|
211
|
+
news: display.news,
|
|
212
|
+
summary: display.summary,
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (event?.user_id != null) {
|
|
218
|
+
await bot.api("send_private_forward_msg", {
|
|
219
|
+
user_id: event.user_id,
|
|
220
|
+
messages: forwardPayload,
|
|
221
|
+
source: display.source,
|
|
222
|
+
news: display.news,
|
|
223
|
+
summary: display.summary,
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
} catch (primaryError) {
|
|
228
|
+
try {
|
|
229
|
+
if (event?.message_type === "group" && event?.group_id != null) {
|
|
230
|
+
await bot.sendGroupMsg(event.group_id, nodes);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (event?.user_id != null) {
|
|
234
|
+
await bot.sendPrivateMsg(event.user_id, nodes);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
// fallback to text reply
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const infoText = buildInfoMessage(parsed, result);
|
|
243
|
+
await event.reply(infoText);
|
|
244
|
+
}
|