mioku-plugin-music 2.4.3 → 2.4.4
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 +15 -23
- package/package.json +1 -1
- 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
1
|
import { definePlugin } from "mioki";
|
|
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";
|
|
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
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;
|