mioku-plugin-music 2.1.0 → 2.3.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 +32 -0
- package/config.ts +4 -0
- package/index.ts +5 -0
- package/package.json +19 -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 +10 -2
- package/types.ts +11 -3
package/config.md
CHANGED
|
@@ -15,6 +15,8 @@ fields:
|
|
|
15
15
|
options:
|
|
16
16
|
- value: applemusic
|
|
17
17
|
label: Apple Music
|
|
18
|
+
- value: netease
|
|
19
|
+
label: NetEase Cloud Music
|
|
18
20
|
|
|
19
21
|
- key: base.applemusic.storefront
|
|
20
22
|
label: Apple Music Storefront
|
|
@@ -33,6 +35,26 @@ fields:
|
|
|
33
35
|
type: secret
|
|
34
36
|
description: Apple Music 下载高质量 AAC 必需 token,无token仅能下载30s音频
|
|
35
37
|
placeholder: eyJ...
|
|
38
|
+
|
|
39
|
+
- key: base.netease.quality
|
|
40
|
+
label: NetEase 音质
|
|
41
|
+
type: select
|
|
42
|
+
description: 下载音质档位,lossless/hires 需要 VIP cookie
|
|
43
|
+
options:
|
|
44
|
+
- value: standard
|
|
45
|
+
label: 标准 (128k)
|
|
46
|
+
- value: exhigh
|
|
47
|
+
label: 极高 (320k,推荐)
|
|
48
|
+
- value: lossless
|
|
49
|
+
label: 无损 FLAC
|
|
50
|
+
- value: hires
|
|
51
|
+
label: Hi-Res
|
|
52
|
+
|
|
53
|
+
- key: base.netease.defaultCookie
|
|
54
|
+
label: NetEase Cookie
|
|
55
|
+
type: secret
|
|
56
|
+
description: 网易云 MUSIC_U cookie,无 cookie 仅能搜索;VIP 音质必须配置
|
|
57
|
+
placeholder: MUSIC_U=...; os=pc; appver=8.9.75;
|
|
36
58
|
---
|
|
37
59
|
|
|
38
60
|
```mioku-fields
|
|
@@ -41,9 +63,19 @@ keys:
|
|
|
41
63
|
- base.defaultProvider
|
|
42
64
|
```
|
|
43
65
|
|
|
66
|
+
### 以下为 Apple Music 的配置区域
|
|
67
|
+
|
|
44
68
|
```mioku-fields
|
|
45
69
|
keys:
|
|
46
70
|
- base.applemusic.storefront
|
|
47
71
|
- base.applemusic.language
|
|
48
72
|
- base.applemusic.defaultMediaUserToken
|
|
49
73
|
```
|
|
74
|
+
|
|
75
|
+
### 以下为网易云音乐的配置区域
|
|
76
|
+
|
|
77
|
+
```mioku-fields
|
|
78
|
+
keys:
|
|
79
|
+
- base.netease.quality
|
|
80
|
+
- base.netease.defaultCookie
|
|
81
|
+
```
|
package/config.ts
CHANGED
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.3.0",
|
|
4
4
|
"description": "音乐插件:点歌、搜索音乐、听歌语音发送",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,20 @@
|
|
|
17
17
|
"ai",
|
|
18
18
|
"screenshot"
|
|
19
19
|
],
|
|
20
|
+
"accessHooks": [
|
|
21
|
+
{
|
|
22
|
+
"id": "点歌",
|
|
23
|
+
"match": "/^\\/?点歌\\s+/"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "听",
|
|
27
|
+
"match": "/^听\\d+$/"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "原曲",
|
|
31
|
+
"match": "/^原曲\\d+$/"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
20
34
|
"help": {
|
|
21
35
|
"title": "音乐",
|
|
22
36
|
"description": "点歌并发送语音,支持搜索结果图片列表",
|
|
@@ -45,11 +59,13 @@
|
|
|
45
59
|
"peerDependencies": {
|
|
46
60
|
"mioku": "^0.8.0",
|
|
47
61
|
"mioki": "^0.16.0",
|
|
48
|
-
"mioku-service-applemusic": "^2.0.0"
|
|
62
|
+
"mioku-service-applemusic": "^2.0.0",
|
|
63
|
+
"mioku-service-netease": "^1.0.0"
|
|
49
64
|
},
|
|
50
65
|
"devDependencies": {
|
|
51
66
|
"mioku": "workspace:*",
|
|
52
67
|
"mioki": "^0.16.0",
|
|
53
|
-
"mioku-service-applemusic": "workspace:*"
|
|
68
|
+
"mioku-service-applemusic": "workspace:*",
|
|
69
|
+
"mioku-service-netease": "workspace:*"
|
|
54
70
|
}
|
|
55
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;
|
|
@@ -350,14 +352,19 @@ export class MusicPluginRuntime {
|
|
|
350
352
|
String(mediaUserToken || "").trim() ||
|
|
351
353
|
String(this.config.applemusic.defaultMediaUserToken || "").trim() ||
|
|
352
354
|
undefined;
|
|
355
|
+
const neteaseCookie =
|
|
356
|
+
String(this.config.netease?.defaultCookie || "").trim() || undefined;
|
|
353
357
|
|
|
354
358
|
return createMusicProvider(
|
|
355
359
|
resolvedProvider,
|
|
356
360
|
{
|
|
357
361
|
applemusic: this.deps.applemusicService,
|
|
362
|
+
netease: this.deps.neteaseService,
|
|
358
363
|
},
|
|
359
364
|
{
|
|
360
365
|
mediaUserToken: finalMediaUserToken,
|
|
366
|
+
neteaseCookie,
|
|
367
|
+
neteaseQuality: this.config.netease?.quality,
|
|
361
368
|
storefront: this.config.applemusic.storefront,
|
|
362
369
|
language: this.config.applemusic.language,
|
|
363
370
|
},
|
|
@@ -389,6 +396,7 @@ export class MusicPluginRuntime {
|
|
|
389
396
|
private getProviderServices() {
|
|
390
397
|
return {
|
|
391
398
|
applemusic: this.deps.applemusicService,
|
|
399
|
+
netease: this.deps.neteaseService,
|
|
392
400
|
};
|
|
393
401
|
}
|
|
394
402
|
|
package/types.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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;
|
|
@@ -10,6 +12,10 @@ export interface MusicBaseConfig {
|
|
|
10
12
|
language: string;
|
|
11
13
|
defaultMediaUserToken?: string;
|
|
12
14
|
};
|
|
15
|
+
netease: {
|
|
16
|
+
quality: MusicProviderQuality;
|
|
17
|
+
defaultCookie: string;
|
|
18
|
+
};
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
export interface MusicTrack {
|
|
@@ -59,13 +65,15 @@ export interface MusicSearchResult {
|
|
|
59
65
|
|
|
60
66
|
export interface DownloadSongResult {
|
|
61
67
|
filePath: string;
|
|
62
|
-
sourceType: "hls" | "preview";
|
|
68
|
+
sourceType: "hls" | "preview" | "file";
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export interface MusicProviderClientOptions {
|
|
66
72
|
mediaUserToken?: string;
|
|
67
73
|
storefront?: string;
|
|
68
74
|
language?: string;
|
|
75
|
+
neteaseCookie?: string;
|
|
76
|
+
neteaseQuality?: MusicProviderQuality;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
export interface MusicProvider {
|