mioku-plugin-music 2.0.0 → 2.2.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 +2 -2
- package/package.json +18 -4
- package/render/search-list.ts +1 -1
- package/runtime-core/service.ts +28 -37
- package/skills.ts +19 -6
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-music",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "音乐插件:点歌、搜索音乐、听歌语音发送",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,20 @@
|
|
|
17
17
|
"ai",
|
|
18
18
|
"screenshot"
|
|
19
19
|
],
|
|
20
|
+
"accessHooks": [
|
|
21
|
+
{
|
|
22
|
+
"id": "点歌",
|
|
23
|
+
"match": "/^\\/?点歌\\s+/"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "听",
|
|
27
|
+
"match": "/^听\\d+$/"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "原曲",
|
|
31
|
+
"match": "/^原曲\\d+$/"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
20
34
|
"help": {
|
|
21
35
|
"title": "音乐",
|
|
22
36
|
"description": "点歌并发送语音,支持搜索结果图片列表",
|
|
@@ -34,9 +48,9 @@
|
|
|
34
48
|
"role": "member"
|
|
35
49
|
},
|
|
36
50
|
{
|
|
37
|
-
"cmd": "
|
|
38
|
-
"desc": "
|
|
39
|
-
"usage": "
|
|
51
|
+
"cmd": "原曲 <编号>",
|
|
52
|
+
"desc": "发送上次搜索列表中的指定歌曲原曲文件",
|
|
53
|
+
"usage": "原曲1",
|
|
40
54
|
"role": "member"
|
|
41
55
|
}
|
|
42
56
|
]
|
package/render/search-list.ts
CHANGED
|
@@ -66,7 +66,7 @@ export function renderMusicSearchListHtml(search: MusicSearchResult): string {
|
|
|
66
66
|
<div class="grid gap-3">${items}</div>
|
|
67
67
|
<div class="mt-5 flex items-center justify-between text-[16px] text-slate-600 dark:text-slate-300">
|
|
68
68
|
<div>音源:${escapeHtml(providerLabel)}</div>
|
|
69
|
-
<div>发送「听1
|
|
69
|
+
<div>发送「听1」或「原曲1」试试看吧~</div>
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
72
72
|
`;
|
package/runtime-core/service.ts
CHANGED
|
@@ -42,25 +42,25 @@ function parseListenIndex(text: string): number | null {
|
|
|
42
42
|
return idx;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function
|
|
46
|
-
const match = text.match(
|
|
45
|
+
function parseOriginalIndex(text: string): number | null {
|
|
46
|
+
const match = text.match(/^\/?原曲\s*(\d{1,2})$/);
|
|
47
47
|
if (!match) {
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
50
|
+
const idx = Number(match[1]);
|
|
51
|
+
if (!Number.isFinite(idx) || idx <= 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return idx;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
function
|
|
55
|
-
const match = text.match(
|
|
57
|
+
function parseSearchKeyword(text: string): string | null {
|
|
58
|
+
const match = text.match(/^\/?点歌\s*(.+)$/);
|
|
56
59
|
if (!match) {
|
|
57
60
|
return null;
|
|
58
61
|
}
|
|
59
62
|
const value = String(match[1] || "").trim();
|
|
60
|
-
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
return value;
|
|
63
|
+
return value ? value : null;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export class MusicPluginRuntime {
|
|
@@ -124,14 +124,14 @@ export class MusicPluginRuntime {
|
|
|
124
124
|
const listenIndex = parseListenIndex(text);
|
|
125
125
|
if (listenIndex != null) {
|
|
126
126
|
await this.tryReactToCommandMessage(ctx, event);
|
|
127
|
-
await this.sendByIndex(ctx, event, listenIndex);
|
|
127
|
+
await this.sendByIndex(ctx, event, listenIndex, false);
|
|
128
128
|
return true;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
131
|
+
const originalIndex = parseOriginalIndex(text);
|
|
132
|
+
if (originalIndex != null) {
|
|
133
133
|
await this.tryReactToCommandMessage(ctx, event);
|
|
134
|
-
await this.
|
|
134
|
+
await this.sendByIndex(ctx, event, originalIndex, true);
|
|
135
135
|
return true;
|
|
136
136
|
}
|
|
137
137
|
|
|
@@ -176,6 +176,7 @@ export class MusicPluginRuntime {
|
|
|
176
176
|
event: any,
|
|
177
177
|
songId: string,
|
|
178
178
|
fileName?: string,
|
|
179
|
+
options?: { forceFile?: boolean },
|
|
179
180
|
): Promise<void> {
|
|
180
181
|
const session = this.getOrCreateSession(event);
|
|
181
182
|
const provider = this.createProvider(
|
|
@@ -196,7 +197,8 @@ export class MusicPluginRuntime {
|
|
|
196
197
|
throw new Error("当前未获取到高质量 HLS 音频,请检查 media user token");
|
|
197
198
|
}
|
|
198
199
|
const isPrivate = event?.message_type !== "group";
|
|
199
|
-
|
|
200
|
+
const sendAsFile = isPrivate || options?.forceFile === true;
|
|
201
|
+
if (sendAsFile) {
|
|
200
202
|
const finalName = fileName || "song";
|
|
201
203
|
await sendFileMessage(ctx, event, result.filePath, finalName);
|
|
202
204
|
} else {
|
|
@@ -219,7 +221,12 @@ export class MusicPluginRuntime {
|
|
|
219
221
|
});
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
async sendSongByQuery(
|
|
224
|
+
async sendSongByQuery(
|
|
225
|
+
ctx: any,
|
|
226
|
+
event: any,
|
|
227
|
+
query: string,
|
|
228
|
+
options?: { forceFile?: boolean },
|
|
229
|
+
): Promise<void> {
|
|
223
230
|
const result = await this.searchSongs(event, query);
|
|
224
231
|
const first = result.tracks[0];
|
|
225
232
|
if (!first) {
|
|
@@ -233,7 +240,7 @@ export class MusicPluginRuntime {
|
|
|
233
240
|
return;
|
|
234
241
|
}
|
|
235
242
|
|
|
236
|
-
await this.sendSongById(ctx, event, first.id, first.title);
|
|
243
|
+
await this.sendSongById(ctx, event, first.id, first.title, options);
|
|
237
244
|
}
|
|
238
245
|
|
|
239
246
|
private async searchAndSendList(
|
|
@@ -291,6 +298,7 @@ export class MusicPluginRuntime {
|
|
|
291
298
|
ctx: any,
|
|
292
299
|
event: any,
|
|
293
300
|
index: number,
|
|
301
|
+
forceFile: boolean,
|
|
294
302
|
): Promise<void> {
|
|
295
303
|
const session = this.getOrCreateSession(event);
|
|
296
304
|
const tracks = session.lastSearch?.tracks || [];
|
|
@@ -307,32 +315,15 @@ export class MusicPluginRuntime {
|
|
|
307
315
|
}
|
|
308
316
|
|
|
309
317
|
try {
|
|
310
|
-
await this.sendSongById(ctx, event, target.id, target.title
|
|
311
|
-
|
|
312
|
-
await notifyFallback({
|
|
313
|
-
ctx,
|
|
314
|
-
event,
|
|
315
|
-
aiService: this.deps.aiService,
|
|
316
|
-
instruction: `用户选择听第${index}首时下载失败。错误:${String(error)}。请简短道歉并建议重试。`,
|
|
317
|
-
fallbackMessage: "播放失败,请稍后重试",
|
|
318
|
-
error,
|
|
318
|
+
await this.sendSongById(ctx, event, target.id, target.title, {
|
|
319
|
+
forceFile,
|
|
319
320
|
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private async sendByKeyword(
|
|
324
|
-
ctx: any,
|
|
325
|
-
event: any,
|
|
326
|
-
keyword: string,
|
|
327
|
-
): Promise<void> {
|
|
328
|
-
try {
|
|
329
|
-
await this.sendSongByQuery(ctx, event, keyword);
|
|
330
321
|
} catch (error) {
|
|
331
322
|
await notifyFallback({
|
|
332
323
|
ctx,
|
|
333
324
|
event,
|
|
334
325
|
aiService: this.deps.aiService,
|
|
335
|
-
instruction:
|
|
326
|
+
instruction: `用户选择听第${index}首时下载失败。错误:${String(error)}。请简短道歉并建议重试。`,
|
|
336
327
|
fallbackMessage: "播放失败,请稍后重试",
|
|
337
328
|
error,
|
|
338
329
|
});
|
package/skills.ts
CHANGED
|
@@ -60,7 +60,8 @@ const musicSkills: AISkill[] = [
|
|
|
60
60
|
} as AITool,
|
|
61
61
|
{
|
|
62
62
|
name: "send_music_song",
|
|
63
|
-
description:
|
|
63
|
+
description:
|
|
64
|
+
"发送歌曲(通过 song_id 或 query)。默认发语音;传 as_file=true 时直接发源文件",
|
|
64
65
|
parameters: {
|
|
65
66
|
type: "object",
|
|
66
67
|
properties: {
|
|
@@ -72,6 +73,11 @@ const musicSkills: AISkill[] = [
|
|
|
72
73
|
type: "string",
|
|
73
74
|
description: "关键词,内部会搜索后发送第一首",
|
|
74
75
|
},
|
|
76
|
+
as_file: {
|
|
77
|
+
type: "boolean",
|
|
78
|
+
description: "是否直接发送源文件",
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
75
81
|
},
|
|
76
82
|
required: [],
|
|
77
83
|
},
|
|
@@ -86,20 +92,27 @@ const musicSkills: AISkill[] = [
|
|
|
86
92
|
|
|
87
93
|
const songId = String(args?.song_id || "").trim();
|
|
88
94
|
const query = String(args?.query || "").trim();
|
|
95
|
+
const asFile = args?.as_file === true;
|
|
89
96
|
if (!songId && !query) {
|
|
90
97
|
return "必须提供 song_id 或 query";
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
ctx.logger.info(
|
|
100
|
+
ctx.logger.info(
|
|
101
|
+
`[music] send_music_song called: songId=${songId}, query=${query}, asFile=${asFile}`,
|
|
102
|
+
);
|
|
94
103
|
|
|
95
104
|
try {
|
|
96
105
|
if (songId) {
|
|
97
|
-
await runtime.sendSongById(ctx, event, songId
|
|
98
|
-
|
|
106
|
+
await runtime.sendSongById(ctx, event, songId, undefined, {
|
|
107
|
+
forceFile: asFile,
|
|
108
|
+
});
|
|
109
|
+
return asFile ? "已发送歌曲源文件" : "已发送歌曲";
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
await runtime.sendSongByQuery(ctx, event, query
|
|
102
|
-
|
|
112
|
+
await runtime.sendSongByQuery(ctx, event, query, {
|
|
113
|
+
forceFile: asFile,
|
|
114
|
+
});
|
|
115
|
+
return asFile ? "已按关键词发送歌曲源文件" : "已按关键词发送歌曲";
|
|
103
116
|
} catch (error) {
|
|
104
117
|
ctx.logger.error(`[music] send_music_song failed: ${error}`);
|
|
105
118
|
await runtime.notifyFailure(
|