mioku-plugin-music 1.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/runtime.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { MusicPluginRuntime } from "./runtime-core/service";
2
+ import {
3
+ getPluginRuntimeState,
4
+ resetPluginRuntimeState,
5
+ setPluginRuntimeState,
6
+ } from "../../src";
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<MusicRuntimeState>(MUSIC_PLUGIN_NAME);
16
+ }
17
+
18
+ export function setMusicRuntimeState(nextState: MusicRuntimeState): MusicRuntimeState {
19
+ return setPluginRuntimeState<MusicRuntimeState>(MUSIC_PLUGIN_NAME, nextState);
20
+ }
21
+
22
+ export function resetMusicRuntimeState(): void {
23
+ resetPluginRuntimeState(MUSIC_PLUGIN_NAME);
24
+ }
package/skills.ts ADDED
@@ -0,0 +1,115 @@
1
+ import type { AISkill, AITool } from "../../src";
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: "发送歌曲语音(通过 song_id 或 query)",
64
+ parameters: {
65
+ type: "object",
66
+ properties: {
67
+ song_id: {
68
+ type: "string",
69
+ description: "歌曲 ID,优先使用",
70
+ },
71
+ query: {
72
+ type: "string",
73
+ description: "关键词,内部会搜索后发送第一首",
74
+ },
75
+ },
76
+ required: [],
77
+ },
78
+ handler: async (args: any, runtimeCtx?: any) => {
79
+ const runtime = getMusicRuntimeState().runtime;
80
+ const ctx = runtimeCtx?.ctx;
81
+ const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
82
+ if (!runtime || !ctx || !event) {
83
+ return "当前上下文不支持发送歌曲";
84
+ }
85
+
86
+ const songId = String(args?.song_id || "").trim();
87
+ const query = String(args?.query || "").trim();
88
+ if (!songId && !query) {
89
+ return "必须提供 song_id 或 query";
90
+ }
91
+
92
+ try {
93
+ if (songId) {
94
+ await runtime.sendSongById(ctx, event, songId);
95
+ return "已发送歌曲";
96
+ }
97
+
98
+ await runtime.sendSongByQuery(ctx, event, query);
99
+ return "已按关键词发送歌曲";
100
+ } catch (error) {
101
+ await runtime.notifyFailure(
102
+ ctx,
103
+ event,
104
+ `AI 发送歌曲失败。错误:${String(error)}。请简短提示稍后重试。`,
105
+ `发送失败:${String(error)}`,
106
+ );
107
+ return `发送失败:${String(error)}`;
108
+ }
109
+ },
110
+ } as AITool,
111
+ ],
112
+ },
113
+ ];
114
+
115
+ export default musicSkills;
package/types.ts ADDED
@@ -0,0 +1,85 @@
1
+ export type MusicProviderName = "applemusic";
2
+
3
+ export const MUSIC_PROVIDER_NAMES: MusicProviderName[] = ["applemusic"];
4
+
5
+ export interface MusicBaseConfig {
6
+ searchLimit: number;
7
+ defaultProvider?: MusicProviderName | string;
8
+ applemusic: {
9
+ storefront: string;
10
+ language: string;
11
+ defaultMediaUserToken?: string;
12
+ };
13
+ }
14
+
15
+ export interface MusicTrack {
16
+ id: string;
17
+ provider: MusicProviderName;
18
+ title: string;
19
+ artist: string;
20
+ album: string;
21
+ coverUrl?: string;
22
+ durationMs?: number;
23
+ previewUrl?: string;
24
+ }
25
+
26
+ export interface MusicSongDetail {
27
+ id: string;
28
+ provider: MusicProviderName;
29
+ title: string;
30
+ artist: string;
31
+ album: string;
32
+ coverUrl?: string;
33
+ releaseDate?: string;
34
+ durationMs?: number;
35
+ previewUrl?: string;
36
+ audioTraits?: string[];
37
+ }
38
+
39
+ export interface MusicAlbumDetail {
40
+ id: string;
41
+ provider: MusicProviderName;
42
+ title: string;
43
+ artist: string;
44
+ coverUrl?: string;
45
+ releaseDate?: string;
46
+ tracks: Array<{
47
+ id: string;
48
+ title: string;
49
+ artist: string;
50
+ durationMs?: number;
51
+ }>;
52
+ }
53
+
54
+ export interface MusicSearchResult {
55
+ query: string;
56
+ provider: MusicProviderName;
57
+ tracks: MusicTrack[];
58
+ }
59
+
60
+ export interface DownloadSongResult {
61
+ filePath: string;
62
+ sourceType: "hls" | "preview";
63
+ }
64
+
65
+ export interface MusicProviderClientOptions {
66
+ mediaUserToken?: string;
67
+ storefront?: string;
68
+ language?: string;
69
+ }
70
+
71
+ export interface MusicProvider {
72
+ readonly name: MusicProviderName;
73
+ searchSongs(query: string, limit?: number): Promise<MusicSearchResult>;
74
+ getSongDetail(songId: string): Promise<MusicSongDetail>;
75
+ getAlbumDetail(albumId: string): Promise<MusicAlbumDetail>;
76
+ downloadSong(songId: string): Promise<DownloadSongResult>;
77
+ downloadCover(coverUrl: string, fileName?: string): Promise<string>;
78
+ }
79
+
80
+ export interface MusicSessionState {
81
+ provider: MusicProviderName;
82
+ mediaUserToken?: string;
83
+ lastSearch?: MusicSearchResult;
84
+ updatedAt: number;
85
+ }