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 +8 -27
- package/config.ts +1 -6
- package/index.ts +5 -0
- package/package.json +5 -3
- package/providers/factory.ts +17 -0
- package/providers/netease-provider.ts +88 -0
- package/providers/provider-labels.ts +1 -0
- package/runtime-core/service.ts +17 -5
- package/types.ts +7 -8
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
|
-
|
|
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: "
|
|
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.
|
|
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
|
}
|
package/providers/factory.ts
CHANGED
|
@@ -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 {
|
package/runtime-core/service.ts
CHANGED
|
@@ -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("
|
|
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(
|
|
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
|
-
|
|
362
|
-
|
|
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 {
|