mioku-plugin-music 2.4.3 → 3.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/index.ts +16 -24
- package/package.json +4 -6
- package/render/search-list.ts +5 -5
- package/runtime-core/message.ts +7 -23
- package/runtime-core/service.ts +4 -10
- package/skills/music.ts +129 -0
- package/runtime.ts +0 -24
- package/skills.ts +0 -132
package/index.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { definePlugin } from "
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import type { AppleMusicServiceApi } from "mioku-service-applemusic";
|
|
6
|
-
import type { NeteaseServiceApi } from "mioku-service-netease";
|
|
7
|
-
import { resetMusicRuntimeState, setMusicRuntimeState } from "./runtime";
|
|
1
|
+
import { definePlugin } from "mioku";
|
|
2
|
+
import { AppleMusicService } from "mioku-service-applemusic";
|
|
3
|
+
import { NeteaseService } from "mioku-service-netease";
|
|
4
|
+
import { getService, Services } from "mioku";
|
|
8
5
|
import { MusicPluginRuntime } from "./runtime-core/service";
|
|
9
6
|
import { MUSIC_DEFAULTS } from "./config";
|
|
10
7
|
import type { MusicBaseConfig } from "./types";
|
|
8
|
+
import { createMusicSkills } from "./skills/music";
|
|
11
9
|
|
|
12
10
|
function cloneConfig<T>(value: T): T {
|
|
13
11
|
return JSON.parse(JSON.stringify(value)) as T;
|
|
@@ -18,17 +16,11 @@ export default definePlugin({
|
|
|
18
16
|
version: "1.0.0",
|
|
19
17
|
description: "点歌与听歌插件",
|
|
20
18
|
async setup(ctx) {
|
|
21
|
-
const configService = ctx.
|
|
22
|
-
const aiService = ctx.
|
|
23
|
-
const screenshotService = ctx.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const applemusicService = ctx.services?.applemusic as
|
|
27
|
-
| AppleMusicServiceApi
|
|
28
|
-
| undefined;
|
|
29
|
-
const neteaseService = ctx.services?.netease as
|
|
30
|
-
| NeteaseServiceApi
|
|
31
|
-
| undefined;
|
|
19
|
+
const configService = getService(ctx, Services.Config);
|
|
20
|
+
const aiService = getService(ctx, Services.AI);
|
|
21
|
+
const screenshotService = getService(ctx, Services.Screenshot);
|
|
22
|
+
const applemusicService = getService(ctx, AppleMusicService);
|
|
23
|
+
const neteaseService = getService(ctx, NeteaseService);
|
|
32
24
|
let baseConfig = cloneConfig(MUSIC_DEFAULTS);
|
|
33
25
|
|
|
34
26
|
if (configService) {
|
|
@@ -50,7 +42,9 @@ export default definePlugin({
|
|
|
50
42
|
});
|
|
51
43
|
runtime.updateConfig(baseConfig);
|
|
52
44
|
|
|
53
|
-
|
|
45
|
+
if (aiService) {
|
|
46
|
+
for (const skill of createMusicSkills(runtime)) aiService.registerSkill(skill);
|
|
47
|
+
}
|
|
54
48
|
|
|
55
49
|
const disposers: Array<() => void> = [];
|
|
56
50
|
if (configService) {
|
|
@@ -62,15 +56,13 @@ export default definePlugin({
|
|
|
62
56
|
);
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
ctx.handle("message", async (event
|
|
59
|
+
ctx.handle("message", async (event) => {
|
|
66
60
|
await runtime.handleMessage(ctx, event);
|
|
67
61
|
});
|
|
68
62
|
|
|
69
63
|
return () => {
|
|
70
|
-
for (const dispose of disposers)
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
resetMusicRuntimeState();
|
|
64
|
+
for (const dispose of disposers) dispose();
|
|
65
|
+
if (aiService) aiService.removeSkill("music");
|
|
74
66
|
};
|
|
75
67
|
},
|
|
76
68
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-music",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "音乐插件:点歌、搜索音乐、听歌语音发送",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -57,14 +57,12 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"mioku": "^0.
|
|
61
|
-
"
|
|
62
|
-
"mioku-service-
|
|
63
|
-
"mioku-service-netease": "^1.0.0"
|
|
60
|
+
"mioku": "^1.0.0",
|
|
61
|
+
"mioku-service-applemusic": "^3.0.0",
|
|
62
|
+
"mioku-service-netease": "^2.0.0"
|
|
64
63
|
},
|
|
65
64
|
"devDependencies": {
|
|
66
65
|
"mioku": "workspace:*",
|
|
67
|
-
"mioki": "^0.16.0",
|
|
68
66
|
"mioku-service-applemusic": "workspace:*",
|
|
69
67
|
"mioku-service-netease": "workspace:*"
|
|
70
68
|
}
|
package/render/search-list.ts
CHANGED
|
@@ -4,11 +4,11 @@ import { getMusicProviderLabel } from "../providers/provider-labels";
|
|
|
4
4
|
function escapeHtml(value: string): string {
|
|
5
5
|
const source = String(value || "");
|
|
6
6
|
return source
|
|
7
|
-
.replace(/&/g, "&")
|
|
8
|
-
.replace(/</g, "<")
|
|
9
|
-
.replace(/>/g, ">")
|
|
10
|
-
.replace(/"/g, """)
|
|
11
|
-
.replace(/'/g, "'");
|
|
7
|
+
.replace(/&/g, "&);")
|
|
8
|
+
.replace(/</g, "<);")
|
|
9
|
+
.replace(/>/g, ">);")
|
|
10
|
+
.replace(/"/g, "");")
|
|
11
|
+
.replace(/'/g, "');");
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function renderMusicSearchListHtml(search: MusicSearchResult): string {
|
package/runtime-core/message.ts
CHANGED
|
@@ -25,16 +25,12 @@ function getBotAndTarget(ctx: any, event: any): {
|
|
|
25
25
|
groupId?: number;
|
|
26
26
|
userId?: number;
|
|
27
27
|
} {
|
|
28
|
-
const
|
|
29
|
-
const bot =
|
|
30
|
-
selfId != null && typeof ctx?.pickBot === "function"
|
|
31
|
-
? ctx.pickBot(selfId)
|
|
32
|
-
: undefined;
|
|
28
|
+
const bot = event?.bot;
|
|
33
29
|
|
|
34
30
|
return {
|
|
35
31
|
bot,
|
|
36
|
-
groupId: event?.message_type === "group" ?
|
|
37
|
-
userId: event?.message_type !== "group" ?
|
|
32
|
+
groupId: event?.message_type === "group" ? event.group_id : undefined,
|
|
33
|
+
userId: event?.message_type !== "group" ? event?.user_id : undefined,
|
|
38
34
|
};
|
|
39
35
|
}
|
|
40
36
|
|
|
@@ -46,7 +42,7 @@ export async function sendTextMessage(
|
|
|
46
42
|
const { bot, groupId, userId } = getBotAndTarget(ctx, event);
|
|
47
43
|
const payload: any[] = [];
|
|
48
44
|
|
|
49
|
-
payload.push(ctx
|
|
45
|
+
payload.push(ctx.segment.text(text));
|
|
50
46
|
|
|
51
47
|
if (bot && groupId != null) {
|
|
52
48
|
await bot.sendGroupMsg(groupId, payload);
|
|
@@ -71,11 +67,7 @@ export async function sendImageMessage(
|
|
|
71
67
|
const { bot, groupId, userId } = getBotAndTarget(ctx, event);
|
|
72
68
|
const sendPayload = async (source: string) => {
|
|
73
69
|
const payload: any[] = [];
|
|
74
|
-
payload.push(
|
|
75
|
-
ctx?.segment?.image
|
|
76
|
-
? ctx.segment.image(normalizeFileSource(source))
|
|
77
|
-
: { type: "image", file: normalizeFileSource(source) },
|
|
78
|
-
);
|
|
70
|
+
payload.push(ctx.segment.image(normalizeFileSource(source)));
|
|
79
71
|
|
|
80
72
|
if (bot && groupId != null) {
|
|
81
73
|
await bot.sendGroupMsg(groupId, payload);
|
|
@@ -112,11 +104,7 @@ export async function sendFileMessage(
|
|
|
112
104
|
const { bot, groupId, userId } = getBotAndTarget(ctx, event);
|
|
113
105
|
const sendPayload = async (source: string) => {
|
|
114
106
|
const payload: any[] = [];
|
|
115
|
-
payload.push(
|
|
116
|
-
ctx?.segment?.file
|
|
117
|
-
? ctx.segment.file(normalizeFileSource(source), { name: fileName })
|
|
118
|
-
: { type: "file", file: normalizeFileSource(source), name: fileName },
|
|
119
|
-
);
|
|
107
|
+
payload.push(ctx.segment.file(normalizeFileSource(source), { name: fileName }));
|
|
120
108
|
|
|
121
109
|
if (bot && groupId != null) {
|
|
122
110
|
await bot.sendGroupMsg(groupId, payload);
|
|
@@ -184,11 +172,7 @@ export async function sendRecordMessage(
|
|
|
184
172
|
const { bot, groupId, userId } = getBotAndTarget(ctx, event);
|
|
185
173
|
const sendPayload = async (source: string) => {
|
|
186
174
|
const payload: any[] = [];
|
|
187
|
-
payload.push(
|
|
188
|
-
ctx?.segment?.record
|
|
189
|
-
? ctx.segment.record(normalizeFileSource(source))
|
|
190
|
-
: { type: "record", file: normalizeFileSource(source) },
|
|
191
|
-
);
|
|
175
|
+
payload.push(ctx.segment.record(normalizeFileSource(source)));
|
|
192
176
|
|
|
193
177
|
if (bot && groupId != null) {
|
|
194
178
|
await bot.sendGroupMsg(groupId, payload);
|
package/runtime-core/service.ts
CHANGED
|
@@ -414,19 +414,13 @@ export class MusicPluginRuntime {
|
|
|
414
414
|
return;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
const
|
|
418
|
-
if (!
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const bot =
|
|
423
|
-
typeof ctx?.pickBot === "function" ? ctx.pickBot(selfId) : undefined;
|
|
424
|
-
if (!bot || typeof bot.api !== "function") {
|
|
425
|
-
return;
|
|
417
|
+
const bot = event?.bot;
|
|
418
|
+
if (!bot || bot.adapter !== "onebotv11") {
|
|
419
|
+
return; // set_msg_emoji_like 是 onebot 专属特殊 API,icqq 不支持
|
|
426
420
|
}
|
|
427
421
|
|
|
428
422
|
try {
|
|
429
|
-
await bot.
|
|
423
|
+
await bot.sendApi("set_msg_emoji_like", {
|
|
430
424
|
message_id: messageId,
|
|
431
425
|
emoji_id: MusicPluginRuntime.COMMAND_REACTION_FACE_ID,
|
|
432
426
|
set: true,
|
package/skills/music.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { AISkill, AITool } from "mioku";
|
|
2
|
+
import type { MusicPluginRuntime } from "../runtime-core/service";
|
|
3
|
+
|
|
4
|
+
export function createMusicSkills(runtime: MusicPluginRuntime): AISkill[] {
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
name: "music",
|
|
8
|
+
description:
|
|
9
|
+
"搜索音乐/歌曲/专辑/歌手,并可发送歌曲,注意,当用户想让你唱歌时,通常使用该技能而不是发送语音",
|
|
10
|
+
permission: "member",
|
|
11
|
+
tools: [
|
|
12
|
+
{
|
|
13
|
+
name: "search_music",
|
|
14
|
+
description: "按关键词搜索歌曲",
|
|
15
|
+
parameters: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
query: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "搜索关键词,可是歌曲名、歌手或专辑名",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ["query"],
|
|
24
|
+
},
|
|
25
|
+
handler: async (args: any, runtimeCtx?: any) => {
|
|
26
|
+
const ctx = runtimeCtx?.ctx;
|
|
27
|
+
const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
|
|
28
|
+
if (!event) {
|
|
29
|
+
return "music 插件尚未初始化";
|
|
30
|
+
}
|
|
31
|
+
const query = String(args?.query || "").trim();
|
|
32
|
+
if (!query) {
|
|
33
|
+
return "query 不能为空";
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const result = await runtime.searchSongs(event, query);
|
|
37
|
+
if (!result.tracks.length) {
|
|
38
|
+
return `没有找到「${query}」的歌曲`;
|
|
39
|
+
}
|
|
40
|
+
const list = result.tracks
|
|
41
|
+
.slice(0, 10)
|
|
42
|
+
.map(
|
|
43
|
+
(item, index) =>
|
|
44
|
+
`${index + 1}. ${item.title} - ${item.artist} (${item.album}) [${item.id}]`,
|
|
45
|
+
)
|
|
46
|
+
.join("\n");
|
|
47
|
+
return `已找到 ${result.tracks.length} 首:\n${list}`;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (ctx) {
|
|
50
|
+
await runtime.notifyFailure(
|
|
51
|
+
ctx,
|
|
52
|
+
event,
|
|
53
|
+
`AI 发起音乐搜索失败。错误:${String(error)}。请简短告知稍后再试。`,
|
|
54
|
+
`搜索失败:${String(error)}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return `搜索失败:${String(error)}`;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
} as AITool,
|
|
61
|
+
{
|
|
62
|
+
name: "send_music_song",
|
|
63
|
+
description:
|
|
64
|
+
"发送歌曲(通过 song_id 或 query)。默认发语音;传 as_file=true 时直接发源文件",
|
|
65
|
+
parameters: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
song_id: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "歌曲 ID,优先使用",
|
|
71
|
+
},
|
|
72
|
+
query: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "关键词,内部会搜索后发送第一首",
|
|
75
|
+
},
|
|
76
|
+
as_file: {
|
|
77
|
+
type: "boolean",
|
|
78
|
+
description: "是否直接发送源文件",
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: [],
|
|
83
|
+
},
|
|
84
|
+
handler: async (args: any, runtimeCtx?: any) => {
|
|
85
|
+
const ctx = runtimeCtx?.ctx;
|
|
86
|
+
const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
|
|
87
|
+
if (!ctx || !event) {
|
|
88
|
+
return "当前上下文不支持发送歌曲";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const songId = String(args?.song_id || "").trim();
|
|
92
|
+
const query = String(args?.query || "").trim();
|
|
93
|
+
const asFile = args?.as_file === true;
|
|
94
|
+
if (!songId && !query) {
|
|
95
|
+
return "必须提供 song_id 或 query";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
ctx.logger.info(
|
|
99
|
+
`[music] send_music_song called: songId=${songId}, query=${query}, asFile=${asFile}`,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
if (songId) {
|
|
104
|
+
await runtime.sendSongById(ctx, event, songId, undefined, {
|
|
105
|
+
forceFile: asFile,
|
|
106
|
+
});
|
|
107
|
+
return asFile ? "已发送歌曲源文件" : "已发送歌曲";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await runtime.sendSongByQuery(ctx, event, query, {
|
|
111
|
+
forceFile: asFile,
|
|
112
|
+
});
|
|
113
|
+
return asFile ? "已按关键词发送歌曲源文件" : "已按关键词发送歌曲";
|
|
114
|
+
} catch (error) {
|
|
115
|
+
ctx.logger.error(`[music] send_music_song failed: ${error}`);
|
|
116
|
+
await runtime.notifyFailure(
|
|
117
|
+
ctx,
|
|
118
|
+
event,
|
|
119
|
+
`AI 发送歌曲失败。错误:${String(error)}。请简短提示稍后重试。`,
|
|
120
|
+
`发送失败:${String(error)}`,
|
|
121
|
+
);
|
|
122
|
+
return `发送失败:${String(error)}`;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
} as AITool,
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
}
|
package/runtime.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { MusicPluginRuntime } from "./runtime-core/service";
|
|
2
|
-
import {
|
|
3
|
-
getPluginRuntimeState,
|
|
4
|
-
resetPluginRuntimeState,
|
|
5
|
-
setPluginRuntimeState,
|
|
6
|
-
} from "mioku";
|
|
7
|
-
|
|
8
|
-
export interface MusicRuntimeState {
|
|
9
|
-
runtime?: MusicPluginRuntime;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const MUSIC_PLUGIN_NAME = "music";
|
|
13
|
-
|
|
14
|
-
export function getMusicRuntimeState(): MusicRuntimeState {
|
|
15
|
-
return getPluginRuntimeState(MUSIC_PLUGIN_NAME) as MusicRuntimeState;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function setMusicRuntimeState(nextState: MusicRuntimeState): MusicRuntimeState {
|
|
19
|
-
return setPluginRuntimeState(MUSIC_PLUGIN_NAME, nextState) as MusicRuntimeState;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function resetMusicRuntimeState(): void {
|
|
23
|
-
resetPluginRuntimeState(MUSIC_PLUGIN_NAME);
|
|
24
|
-
}
|
package/skills.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import type { AISkill, AITool } from "mioku";
|
|
2
|
-
import { getMusicRuntimeState } from "./runtime";
|
|
3
|
-
|
|
4
|
-
const musicSkills: AISkill[] = [
|
|
5
|
-
{
|
|
6
|
-
name: "music",
|
|
7
|
-
description:
|
|
8
|
-
"搜索音乐/歌曲/专辑/歌手,并可发送歌曲,注意,当用户想让你唱歌时,通常使用该技能而不是发送语音",
|
|
9
|
-
permission: "member",
|
|
10
|
-
tools: [
|
|
11
|
-
{
|
|
12
|
-
name: "search_music",
|
|
13
|
-
description: "按关键词搜索歌曲",
|
|
14
|
-
parameters: {
|
|
15
|
-
type: "object",
|
|
16
|
-
properties: {
|
|
17
|
-
query: {
|
|
18
|
-
type: "string",
|
|
19
|
-
description: "搜索关键词,可是歌曲名、歌手或专辑名",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
required: ["query"],
|
|
23
|
-
},
|
|
24
|
-
handler: async (args: any, runtimeCtx?: any) => {
|
|
25
|
-
const runtime = getMusicRuntimeState().runtime;
|
|
26
|
-
const ctx = runtimeCtx?.ctx;
|
|
27
|
-
const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
|
|
28
|
-
if (!runtime || !event) {
|
|
29
|
-
return "music 插件尚未初始化";
|
|
30
|
-
}
|
|
31
|
-
const query = String(args?.query || "").trim();
|
|
32
|
-
if (!query) {
|
|
33
|
-
return "query 不能为空";
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
const result = await runtime.searchSongs(event, query);
|
|
37
|
-
if (!result.tracks.length) {
|
|
38
|
-
return `没有找到「${query}」的歌曲`;
|
|
39
|
-
}
|
|
40
|
-
const list = result.tracks
|
|
41
|
-
.slice(0, 10)
|
|
42
|
-
.map(
|
|
43
|
-
(item, index) =>
|
|
44
|
-
`${index + 1}. ${item.title} - ${item.artist} (${item.album}) [${item.id}]`,
|
|
45
|
-
)
|
|
46
|
-
.join("\n");
|
|
47
|
-
return `已找到 ${result.tracks.length} 首:\n${list}`;
|
|
48
|
-
} catch (error) {
|
|
49
|
-
if (ctx) {
|
|
50
|
-
await runtime.notifyFailure(
|
|
51
|
-
ctx,
|
|
52
|
-
event,
|
|
53
|
-
`AI 发起音乐搜索失败。错误:${String(error)}。请简短告知稍后再试。`,
|
|
54
|
-
`搜索失败:${String(error)}`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
return `搜索失败:${String(error)}`;
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
} as AITool,
|
|
61
|
-
{
|
|
62
|
-
name: "send_music_song",
|
|
63
|
-
description:
|
|
64
|
-
"发送歌曲(通过 song_id 或 query)。默认发语音;传 as_file=true 时直接发源文件",
|
|
65
|
-
parameters: {
|
|
66
|
-
type: "object",
|
|
67
|
-
properties: {
|
|
68
|
-
song_id: {
|
|
69
|
-
type: "string",
|
|
70
|
-
description: "歌曲 ID,优先使用",
|
|
71
|
-
},
|
|
72
|
-
query: {
|
|
73
|
-
type: "string",
|
|
74
|
-
description: "关键词,内部会搜索后发送第一首",
|
|
75
|
-
},
|
|
76
|
-
as_file: {
|
|
77
|
-
type: "boolean",
|
|
78
|
-
description: "是否直接发送源文件",
|
|
79
|
-
default: false,
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
required: [],
|
|
83
|
-
},
|
|
84
|
-
handler: async (args: any, runtimeCtx?: any) => {
|
|
85
|
-
const runtimeState = getMusicRuntimeState();
|
|
86
|
-
const runtime = runtimeState.runtime;
|
|
87
|
-
const ctx = runtimeCtx?.ctx;
|
|
88
|
-
const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
|
|
89
|
-
if (!runtime || !ctx || !event) {
|
|
90
|
-
return "当前上下文不支持发送歌曲";
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const songId = String(args?.song_id || "").trim();
|
|
94
|
-
const query = String(args?.query || "").trim();
|
|
95
|
-
const asFile = args?.as_file === true;
|
|
96
|
-
if (!songId && !query) {
|
|
97
|
-
return "必须提供 song_id 或 query";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
ctx.logger.info(
|
|
101
|
-
`[music] send_music_song called: songId=${songId}, query=${query}, asFile=${asFile}`,
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
if (songId) {
|
|
106
|
-
await runtime.sendSongById(ctx, event, songId, undefined, {
|
|
107
|
-
forceFile: asFile,
|
|
108
|
-
});
|
|
109
|
-
return asFile ? "已发送歌曲源文件" : "已发送歌曲";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
await runtime.sendSongByQuery(ctx, event, query, {
|
|
113
|
-
forceFile: asFile,
|
|
114
|
-
});
|
|
115
|
-
return asFile ? "已按关键词发送歌曲源文件" : "已按关键词发送歌曲";
|
|
116
|
-
} catch (error) {
|
|
117
|
-
ctx.logger.error(`[music] send_music_song failed: ${error}`);
|
|
118
|
-
await runtime.notifyFailure(
|
|
119
|
-
ctx,
|
|
120
|
-
event,
|
|
121
|
-
`AI 发送歌曲失败。错误:${String(error)}。请简短提示稍后重试。`,
|
|
122
|
-
`发送失败:${String(error)}`,
|
|
123
|
-
);
|
|
124
|
-
return `发送失败:${String(error)}`;
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
} as AITool,
|
|
128
|
-
],
|
|
129
|
-
},
|
|
130
|
-
];
|
|
131
|
-
|
|
132
|
-
export default musicSkills;
|