mioku-plugin-music 2.2.0 → 2.4.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/config.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Music 插件配置
3
- description: 在这里调整 music 插件的配置项目
3
+ description: 在这里调整 music 插件的配置项目。Token/Cookie、Storefront、语言、音质等请分别在 Apple Music / NetEase 的「服务配置」页面配置。
4
4
  fields:
5
5
  - key: base.searchLimit
6
6
  label: 默认搜索条数
@@ -11,39 +11,20 @@ fields:
11
11
  - key: base.defaultProvider
12
12
  label: 默认音乐源
13
13
  type: select
14
- description: 需要安装对应的服务
14
+ description: 初次安装为「无」,需要选择对应的提供商并安装其服务后使用
15
15
  options:
16
+ - value: ""
17
+ label: 无(未选择)
16
18
  - value: applemusic
17
19
  label: Apple Music
18
-
19
- - key: base.applemusic.storefront
20
- label: Apple Music Storefront
21
- type: text
22
- description: 默认地区代码,例如 cn、us、jp。
23
- placeholder: cn
24
-
25
- - key: base.applemusic.language
26
- label: Apple Music 语言
27
- type: text
28
- description: 默认语言代码,例如 zh-CN、en-US。
29
- placeholder: zh-CN
30
-
31
- - key: base.applemusic.defaultMediaUserToken
32
- label: 默认 Media User Token
33
- type: secret
34
- description: Apple Music 下载高质量 AAC 必需 token,无token仅能下载30s音频
35
- placeholder: eyJ...
20
+ - value: netease
21
+ label: NetEase Cloud Music
36
22
  ---
37
23
 
24
+ > 默认音乐源初始为「无」。请先在上方选择提供商,**重启 Bot 后会自动下载对应服务**。
25
+
38
26
  ```mioku-fields
39
27
  keys:
40
28
  - base.searchLimit
41
29
  - base.defaultProvider
42
30
  ```
43
-
44
- ```mioku-fields
45
- keys:
46
- - base.applemusic.storefront
47
- - base.applemusic.language
48
- - base.applemusic.defaultMediaUserToken
49
- ```
package/config.ts CHANGED
@@ -2,10 +2,5 @@ import type { MusicBaseConfig } from "./types";
2
2
 
3
3
  export const MUSIC_DEFAULTS: MusicBaseConfig = {
4
4
  searchLimit: 15,
5
- defaultProvider: "applemusic",
6
- applemusic: {
7
- storefront: "cn",
8
- language: "zh-CN",
9
- defaultMediaUserToken: "",
10
- },
5
+ defaultProvider: "",
11
6
  };
package/index.ts CHANGED
@@ -3,6 +3,7 @@ import type { AIService } from "mioku";
3
3
  import type { ScreenshotService } from "mioku";
4
4
  import type { ConfigService } from "mioku";
5
5
  import type { AppleMusicServiceApi } from "mioku-service-applemusic";
6
+ import type { NeteaseServiceApi } from "mioku-service-netease";
6
7
  import { resetMusicRuntimeState, setMusicRuntimeState } from "./runtime";
7
8
  import { MusicPluginRuntime } from "./runtime-core/service";
8
9
  import { MUSIC_DEFAULTS } from "./config";
@@ -25,6 +26,9 @@ export default definePlugin({
25
26
  const applemusicService = ctx.services?.applemusic as
26
27
  | AppleMusicServiceApi
27
28
  | undefined;
29
+ const neteaseService = ctx.services?.netease as
30
+ | NeteaseServiceApi
31
+ | undefined;
28
32
  let baseConfig = cloneConfig(MUSIC_DEFAULTS);
29
33
 
30
34
  if (configService) {
@@ -42,6 +46,7 @@ export default definePlugin({
42
46
  aiService,
43
47
  screenshotService,
44
48
  applemusicService,
49
+ neteaseService,
45
50
  });
46
51
  runtime.updateConfig(baseConfig);
47
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-music",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "音乐插件:点歌、搜索音乐、听歌语音发送",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -59,11 +59,13 @@
59
59
  "peerDependencies": {
60
60
  "mioku": "^0.8.0",
61
61
  "mioki": "^0.16.0",
62
- "mioku-service-applemusic": "^2.0.0"
62
+ "mioku-service-applemusic": "^2.0.0",
63
+ "mioku-service-netease": "^1.0.0"
63
64
  },
64
65
  "devDependencies": {
65
66
  "mioku": "workspace:*",
66
67
  "mioki": "^0.16.0",
67
- "mioku-service-applemusic": "workspace:*"
68
+ "mioku-service-applemusic": "workspace:*",
69
+ "mioku-service-netease": "workspace:*"
68
70
  }
69
71
  }
@@ -1,5 +1,7 @@
1
1
  import type { AppleMusicServiceApi } from "mioku-service-applemusic";
2
+ import type { NeteaseServiceApi } from "mioku-service-netease";
2
3
  import { AppleMusicProvider } from "./applemusic-provider";
4
+ import { NeteaseProvider } from "./netease-provider";
3
5
  import {
4
6
  type MusicProvider,
5
7
  type MusicProviderClientOptions,
@@ -8,6 +10,7 @@ import {
8
10
 
9
11
  export interface MusicProviderFactoryOptions {
10
12
  applemusic?: AppleMusicServiceApi;
13
+ netease?: NeteaseServiceApi;
11
14
  }
12
15
 
13
16
  interface MusicProviderRegistryItem {
@@ -32,6 +35,20 @@ const MUSIC_PROVIDER_REGISTRY: MusicProviderRegistryItem[] = [
32
35
  return new AppleMusicProvider(services.applemusic, clientOptions);
33
36
  },
34
37
  },
38
+ {
39
+ name: "netease",
40
+ serviceName: "netease",
41
+ isAvailable: (services) => Boolean(services.netease),
42
+ create: (services, clientOptions) => {
43
+ if (!services.netease) {
44
+ throw new Error("netease 服务未加载");
45
+ }
46
+ return new NeteaseProvider(services.netease, {
47
+ neteaseCookie: clientOptions?.neteaseCookie,
48
+ neteaseQuality: clientOptions?.neteaseQuality,
49
+ });
50
+ },
51
+ },
35
52
  ];
36
53
 
37
54
  function getProviderRegistryItem(
@@ -0,0 +1,88 @@
1
+ import type { NeteaseServiceApi } from "mioku-service-netease";
2
+ import type {
3
+ DownloadSongResult,
4
+ MusicAlbumDetail,
5
+ MusicProvider,
6
+ MusicProviderClientOptions,
7
+ MusicProviderName,
8
+ MusicSearchResult,
9
+ MusicSongDetail,
10
+ } from "../types";
11
+
12
+ export class NeteaseProvider implements MusicProvider {
13
+ readonly name: MusicProviderName = "netease";
14
+ private readonly client: ReturnType<NeteaseServiceApi["createClient"]>;
15
+
16
+ constructor(api: NeteaseServiceApi, options?: MusicProviderClientOptions) {
17
+ this.client = api.createClient({
18
+ cookie: options?.neteaseCookie,
19
+ quality: options?.neteaseQuality,
20
+ });
21
+ }
22
+
23
+ async searchSongs(query: string, limit: number = 15): Promise<MusicSearchResult> {
24
+ const result = await this.client.searchSongs({ query, limit });
25
+ return {
26
+ query,
27
+ provider: this.name,
28
+ tracks: result.songs.map((item) => ({
29
+ id: item.id,
30
+ provider: this.name,
31
+ title: item.name,
32
+ artist: item.artistName,
33
+ album: item.albumName,
34
+ coverUrl: item.artworkUrl,
35
+ durationMs: item.durationInMillis,
36
+ })),
37
+ };
38
+ }
39
+
40
+ async getSongDetail(songId: string): Promise<MusicSongDetail> {
41
+ const detail = await this.client.getSongDetail({ songId });
42
+ return {
43
+ id: detail.id,
44
+ provider: this.name,
45
+ title: detail.name,
46
+ artist: detail.artistName,
47
+ album: detail.albumName,
48
+ coverUrl: detail.artworkUrl,
49
+ releaseDate: detail.releaseDate,
50
+ durationMs: detail.durationInMillis,
51
+ };
52
+ }
53
+
54
+ async getAlbumDetail(albumId: string): Promise<MusicAlbumDetail> {
55
+ const detail = await this.client.getAlbumDetail({ albumId });
56
+ return {
57
+ id: detail.id,
58
+ provider: this.name,
59
+ title: detail.name,
60
+ artist: detail.artistName,
61
+ coverUrl: detail.artworkUrl,
62
+ releaseDate: detail.releaseDate,
63
+ tracks: detail.tracks.map((track) => ({
64
+ id: track.id,
65
+ title: track.name,
66
+ artist: track.artistName,
67
+ durationMs: track.durationInMillis,
68
+ })),
69
+ };
70
+ }
71
+
72
+ async downloadSong(songId: string): Promise<DownloadSongResult> {
73
+ const result = await this.client.downloadSongAac({ songId });
74
+ return {
75
+ filePath: result.filePath,
76
+ sourceType: result.sourceType,
77
+ };
78
+ }
79
+
80
+ async downloadCover(coverUrl: string, fileName?: string): Promise<string> {
81
+ const result = await this.client.downloadCover({
82
+ artworkUrl: coverUrl,
83
+ fileName: fileName || "cover",
84
+ size: 1200,
85
+ });
86
+ return result.filePath;
87
+ }
88
+ }
@@ -2,6 +2,7 @@ import type { MusicProviderName } from "../types";
2
2
 
3
3
  export const MUSIC_PROVIDER_LABELS: Record<MusicProviderName, string> = {
4
4
  applemusic: "Apple Music",
5
+ netease: "NetEase Cloud Music",
5
6
  };
6
7
 
7
8
  export function getMusicProviderLabel(provider: MusicProviderName | string): string {
@@ -1,6 +1,7 @@
1
1
  import type { AIService } from "mioku";
2
2
  import type { ScreenshotService } from "mioku";
3
3
  import type { AppleMusicServiceApi } from "mioku-service-applemusic";
4
+ import type { NeteaseServiceApi } from "mioku-service-netease";
4
5
  import { MUSIC_DEFAULTS } from "../config";
5
6
  import {
6
7
  createMusicProvider,
@@ -28,6 +29,7 @@ interface MusicPluginRuntimeDeps {
28
29
  aiService?: AIService;
29
30
  screenshotService?: ScreenshotService;
30
31
  applemusicService?: AppleMusicServiceApi;
32
+ neteaseService?: NeteaseServiceApi;
31
33
  }
32
34
 
33
35
  function parseListenIndex(text: string): number | null {
@@ -193,8 +195,8 @@ export class MusicPluginRuntime {
193
195
  if ("tracks" in result && Array.isArray((result as any).tracks)) {
194
196
  this.deps.logger.warn(`[music] downloadSong 返回了 tracks 字段`);
195
197
  }
196
- if (result.sourceType !== "hls") {
197
- throw new Error("当前未获取到高质量 HLS 音频,请检查 media user token");
198
+ if (result.sourceType !== "hls" && result.sourceType !== "file") {
199
+ throw new Error("当前未获取到高质量音频,请检查 provider 配置");
198
200
  }
199
201
  const isPrivate = event?.message_type !== "group";
200
202
  const sendAsFile = isPrivate || options?.forceFile === true;
@@ -346,20 +348,29 @@ export class MusicPluginRuntime {
346
348
 
347
349
  private createProvider(provider: MusicProviderName, mediaUserToken?: string) {
348
350
  const resolvedProvider = this.resolveProviderName(provider);
351
+
352
+ const amDefaults = this.deps.applemusicService?.getDefaultOptions();
353
+ const neDefaults = this.deps.neteaseService?.getDefaultOptions();
354
+
349
355
  const finalMediaUserToken =
350
356
  String(mediaUserToken || "").trim() ||
351
- String(this.config.applemusic.defaultMediaUserToken || "").trim() ||
357
+ String(amDefaults?.mediaUserToken || "").trim() ||
352
358
  undefined;
359
+ const neteaseCookie =
360
+ String(neDefaults?.cookie || "").trim() || undefined;
353
361
 
354
362
  return createMusicProvider(
355
363
  resolvedProvider,
356
364
  {
357
365
  applemusic: this.deps.applemusicService,
366
+ netease: this.deps.neteaseService,
358
367
  },
359
368
  {
360
369
  mediaUserToken: finalMediaUserToken,
361
- storefront: this.config.applemusic.storefront,
362
- language: this.config.applemusic.language,
370
+ neteaseCookie,
371
+ neteaseQuality: (neDefaults?.quality || "exhigh") as any,
372
+ storefront: String(amDefaults?.storefront || "cn"),
373
+ language: String(amDefaults?.language || "zh-CN"),
363
374
  },
364
375
  );
365
376
  }
@@ -389,6 +400,7 @@ export class MusicPluginRuntime {
389
400
  private getProviderServices() {
390
401
  return {
391
402
  applemusic: this.deps.applemusicService,
403
+ netease: this.deps.neteaseService,
392
404
  };
393
405
  }
394
406
 
package/types.ts CHANGED
@@ -1,15 +1,12 @@
1
- export type MusicProviderName = "applemusic";
1
+ export type MusicProviderName = "applemusic" | "netease";
2
2
 
3
- export const MUSIC_PROVIDER_NAMES: MusicProviderName[] = ["applemusic"];
3
+ export const MUSIC_PROVIDER_NAMES: MusicProviderName[] = ["applemusic", "netease"];
4
+
5
+ export type MusicProviderQuality = "standard" | "exhigh" | "lossless" | "hires";
4
6
 
5
7
  export interface MusicBaseConfig {
6
8
  searchLimit: number;
7
9
  defaultProvider?: MusicProviderName | string;
8
- applemusic: {
9
- storefront: string;
10
- language: string;
11
- defaultMediaUserToken?: string;
12
- };
13
10
  }
14
11
 
15
12
  export interface MusicTrack {
@@ -59,13 +56,15 @@ export interface MusicSearchResult {
59
56
 
60
57
  export interface DownloadSongResult {
61
58
  filePath: string;
62
- sourceType: "hls" | "preview";
59
+ sourceType: "hls" | "preview" | "file";
63
60
  }
64
61
 
65
62
  export interface MusicProviderClientOptions {
66
63
  mediaUserToken?: string;
67
64
  storefront?: string;
68
65
  language?: string;
66
+ neteaseCookie?: string;
67
+ neteaseQuality?: MusicProviderQuality;
69
68
  }
70
69
 
71
70
  export interface MusicProvider {