@zhin.js/plugin-music 7.0.8 → 7.0.10
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/commands/cookie/delete/[source].js +23 -0
- package/commands/cookie/delete/[source].ts +27 -0
- package/commands/cookie/get/[source].js +29 -0
- package/commands/cookie/get/[source].ts +33 -0
- package/commands/cookie/set/[source].js +23 -0
- package/commands/cookie/set/[source].ts +27 -0
- package/commands/cookie/status.js +25 -0
- package/commands/cookie/status.ts +25 -0
- package/commands/login/[platform].js +64 -0
- package/commands/login/[platform].ts +76 -0
- package/commands//347/202/271/346/255/214/[keyword].js +62 -0
- package/commands//347/202/271/346/255/214/[keyword].ts +74 -0
- package/components/share-music.js +6 -6
- package/components/share-music.ts +6 -6
- package/lib/config.d.ts +3 -3
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +36 -7
- package/lib/config.js.map +1 -1
- package/lib/credential-store.d.ts +34 -0
- package/lib/credential-store.d.ts.map +1 -0
- package/lib/credential-store.js +111 -0
- package/lib/credential-store.js.map +1 -0
- package/lib/index.d.ts +5 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -2
- package/lib/index.js.map +1 -1
- package/lib/login/index.d.ts +20 -0
- package/lib/login/index.d.ts.map +1 -0
- package/lib/login/index.js +100 -0
- package/lib/login/index.js.map +1 -0
- package/lib/login/netease.d.ts +6 -0
- package/lib/login/netease.d.ts.map +1 -0
- package/lib/login/netease.js +58 -0
- package/lib/login/netease.js.map +1 -0
- package/lib/login/qq.d.ts +7 -0
- package/lib/login/qq.d.ts.map +1 -0
- package/lib/login/qq.js +139 -0
- package/lib/login/qq.js.map +1 -0
- package/lib/login/types.d.ts +19 -0
- package/lib/login/types.d.ts.map +1 -0
- package/lib/login/types.js +11 -0
- package/lib/login/types.js.map +1 -0
- package/lib/music-lib.d.ts +13 -12
- package/lib/music-lib.d.ts.map +1 -1
- package/lib/music-lib.js +36 -22
- package/lib/music-lib.js.map +1 -1
- package/lib/session.d.ts +29 -0
- package/lib/session.d.ts.map +1 -0
- package/lib/session.js +39 -0
- package/lib/session.js.map +1 -0
- package/lib/sources/index.d.ts +2 -1
- package/lib/sources/index.d.ts.map +1 -1
- package/lib/sources/index.js +6 -2
- package/lib/sources/index.js.map +1 -1
- package/lib/sources/kugou.d.ts +6 -0
- package/lib/sources/kugou.d.ts.map +1 -0
- package/lib/sources/kugou.js +54 -0
- package/lib/sources/kugou.js.map +1 -0
- package/lib/sources/kuwo.d.ts +7 -0
- package/lib/sources/kuwo.d.ts.map +1 -0
- package/lib/sources/kuwo.js +69 -0
- package/lib/sources/kuwo.js.map +1 -0
- package/lib/sources/netease.d.ts +1 -1
- package/lib/sources/netease.d.ts.map +1 -1
- package/lib/sources/netease.js +19 -5
- package/lib/sources/netease.js.map +1 -1
- package/lib/sources/qq.d.ts +1 -14
- package/lib/sources/qq.d.ts.map +1 -1
- package/lib/sources/qq.js +114 -81
- package/lib/sources/qq.js.map +1 -1
- package/lib/types.d.ts +4 -24
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +0 -1
- package/lib/types.js.map +1 -1
- package/middlewares/music-selection.js +46 -0
- package/middlewares/music-selection.ts +56 -0
- package/package.json +10 -5
- package/plugin.js +24 -1
- package/src/config.ts +54 -30
- package/src/credential-store.ts +169 -0
- package/src/index.ts +43 -2
- package/src/login/index.ts +135 -0
- package/src/login/netease.ts +69 -0
- package/src/login/qq.ts +154 -0
- package/src/login/types.ts +27 -0
- package/src/music-lib.ts +39 -26
- package/src/session.ts +60 -0
- package/src/sources/index.ts +12 -8
- package/src/sources/kugou.ts +78 -0
- package/src/sources/kuwo.ts +94 -0
- package/src/sources/netease.ts +29 -12
- package/src/sources/qq.ts +158 -125
- package/src/types.ts +6 -28
- package/tools/music-search.js +6 -2
- package/tools/music-search.ts +6 -2
- package/tools/music-share.js +6 -2
- package/tools/music-share.ts +6 -2
package/lib/sources/qq.js
CHANGED
|
@@ -1,64 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import { getCredential } from '../credential-store.js';
|
|
3
|
+
const md5 = (text) => crypto.createHash('md5').update(text).digest('hex');
|
|
4
|
+
const MUSICU_API = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
|
|
5
|
+
function getHeaders(cookie) {
|
|
6
|
+
return {
|
|
7
|
+
Referer: 'https://y.qq.com',
|
|
8
|
+
Cookie: cookie ?? '',
|
|
9
|
+
'Content-Type': 'application/json; charset=UTF-8',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
2
12
|
export class QQMusicService {
|
|
3
13
|
async search(keyword, limit = 10) {
|
|
4
14
|
try {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const cookie = await getCredential('qq', 'cookie');
|
|
16
|
+
const response = await fetch(MUSICU_API, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: getHeaders(cookie),
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
comm: { uin: '0', authst: '', ct: 29 },
|
|
21
|
+
search: {
|
|
22
|
+
method: 'DoSearchForQQMusicMobile',
|
|
23
|
+
module: 'music.search.SearchCgiService',
|
|
24
|
+
param: {
|
|
25
|
+
query: keyword,
|
|
26
|
+
grp: 1,
|
|
27
|
+
num_per_page: limit,
|
|
28
|
+
page_num: 1,
|
|
29
|
+
search_type: 0,
|
|
30
|
+
searchid: String(Date.now()),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
const data = (await response.json());
|
|
36
|
+
if (data.code !== undefined && +data.code !== 0)
|
|
37
|
+
return [];
|
|
38
|
+
const body = data.search?.data?.body ?? {};
|
|
39
|
+
const list = (body.song?.list ?? body.item_song ?? []);
|
|
40
|
+
return list.slice(0, limit).map((e) => {
|
|
41
|
+
const mid = e?.mid ?? '';
|
|
42
|
+
const albumMid = e?.album?.pmid ?? e?.album?.mid ?? '';
|
|
43
|
+
const singerMid = e?.singer?.[0]?.pmid ?? e?.singer?.[0]?.mid ?? '';
|
|
44
|
+
const cover = albumMid
|
|
45
|
+
? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`
|
|
46
|
+
: singerMid
|
|
47
|
+
? `https://y.qq.com/music/photo_new/T001R300x300M000${singerMid}.jpg`
|
|
48
|
+
: '';
|
|
49
|
+
return {
|
|
50
|
+
id: String(e.id),
|
|
51
|
+
source: 'qq',
|
|
52
|
+
title: e.name,
|
|
53
|
+
artist: (e.singer ?? []).map((s) => s.name).join('/'),
|
|
54
|
+
album: e.album?.name,
|
|
55
|
+
url: `https://y.qq.com/n/ryqq/songDetail/${mid}`,
|
|
56
|
+
image: cover,
|
|
57
|
+
duration: e.interval,
|
|
58
|
+
_mid: mid,
|
|
59
|
+
_mediaMid: e.file?.media_mid,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
17
62
|
}
|
|
18
63
|
catch (error) {
|
|
19
64
|
console.error('QQ Music search failed:', error);
|
|
20
65
|
return [];
|
|
21
66
|
}
|
|
22
67
|
}
|
|
23
|
-
async getCover(id) {
|
|
24
|
-
try {
|
|
25
|
-
const url = new URL('https://u.y.qq.com/cgi-bin/musicu.fcg');
|
|
26
|
-
url.searchParams.set('format', 'json');
|
|
27
|
-
url.searchParams.set('inCharset', 'utf8');
|
|
28
|
-
url.searchParams.set('outCharset', 'utf-8');
|
|
29
|
-
url.searchParams.set('notice', '0');
|
|
30
|
-
url.searchParams.set('platform', 'yqq.json');
|
|
31
|
-
url.searchParams.set('needNewCode', '0');
|
|
32
|
-
url.searchParams.set('data', JSON.stringify({
|
|
33
|
-
comm: { ct: 24, cv: 0 },
|
|
34
|
-
songinfo: {
|
|
35
|
-
method: 'get_song_detail_yqq',
|
|
36
|
-
param: { song_type: 0, song_mid: '', song_id: parseInt(id) },
|
|
37
|
-
module: 'music.pf_song_detail_svr'
|
|
38
|
-
}
|
|
39
|
-
}));
|
|
40
|
-
const response = await fetch(url, { method: 'GET' });
|
|
41
|
-
const result = await response.json();
|
|
42
|
-
const albumMid = result?.songinfo?.data?.track_info?.album?.mid;
|
|
43
|
-
if (!albumMid)
|
|
44
|
-
return null;
|
|
45
|
-
return `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`;
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
console.error('QQ Music get cover failed:', error);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
68
|
async getDetail(id) {
|
|
53
|
-
const url = new URL(
|
|
69
|
+
const url = new URL(MUSICU_API);
|
|
54
70
|
url.searchParams.set('format', 'json');
|
|
55
71
|
url.searchParams.set('data', JSON.stringify({
|
|
56
72
|
comm: { ct: 24, cv: 0 },
|
|
57
73
|
songinfo: {
|
|
58
74
|
method: 'get_song_detail_yqq',
|
|
59
75
|
param: { song_type: 0, song_mid: '', song_id: parseInt(id) },
|
|
60
|
-
module: 'music.pf_song_detail_svr'
|
|
61
|
-
}
|
|
76
|
+
module: 'music.pf_song_detail_svr',
|
|
77
|
+
},
|
|
62
78
|
}));
|
|
63
79
|
const response = await fetch(url, { method: 'GET' });
|
|
64
80
|
const result = await response.json();
|
|
@@ -68,42 +84,65 @@ export class QQMusicService {
|
|
|
68
84
|
const albumMid = trackInfo.album?.mid;
|
|
69
85
|
const image = albumMid
|
|
70
86
|
? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`
|
|
71
|
-
:
|
|
87
|
+
: '';
|
|
72
88
|
return {
|
|
73
89
|
id,
|
|
74
90
|
source: 'qq',
|
|
75
91
|
title: trackInfo.name,
|
|
76
|
-
url: `https://y.qq.com/n/
|
|
77
|
-
audio: await this.getAudioUrl(id),
|
|
78
|
-
image
|
|
92
|
+
url: `https://y.qq.com/n/ryqq/songDetail/${trackInfo.mid}`,
|
|
93
|
+
audio: await this.getAudioUrl(id, trackInfo.mid, trackInfo.file?.media_mid),
|
|
94
|
+
image,
|
|
79
95
|
duration: trackInfo.interval,
|
|
80
96
|
};
|
|
81
97
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
async getAudioUrl(id, mid, mediaMid) {
|
|
99
|
+
const cookie = await getCredential('qq', 'cookie');
|
|
100
|
+
if (cookie) {
|
|
101
|
+
try {
|
|
102
|
+
const uin = /uin=o?(\d+)/.exec(cookie)?.[1] ?? '0';
|
|
103
|
+
const songMid = mid ?? id;
|
|
104
|
+
const response = await fetch(MUSICU_API, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: getHeaders(cookie),
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
comm: {
|
|
109
|
+
ct: 19,
|
|
110
|
+
cv: '1891',
|
|
111
|
+
uin: '0',
|
|
112
|
+
guid: md5(`${uin}music`),
|
|
113
|
+
tmeAppID: 'qqmusic',
|
|
114
|
+
tmeLoginType: 2,
|
|
115
|
+
},
|
|
116
|
+
vkey: {
|
|
117
|
+
module: 'vkey.GetVkeyServer',
|
|
118
|
+
method: 'CgiGetVkey',
|
|
119
|
+
param: {
|
|
120
|
+
guid: md5(String(Date.now())),
|
|
121
|
+
songmid: [songMid],
|
|
122
|
+
songtype: [0],
|
|
123
|
+
uin: '0',
|
|
124
|
+
ctx: 1,
|
|
125
|
+
filename: [`C400${id}${mediaMid ?? songMid}.m4a`],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
const result = await response.json();
|
|
131
|
+
if (result.vkey && +result.vkey.code === 0) {
|
|
132
|
+
const purl = result.vkey.data?.midurlinfo?.[0]?.purl;
|
|
133
|
+
if (purl)
|
|
134
|
+
return `https://ws.stream.qqmusic.qq.com/${purl}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// fall through to public fallback
|
|
139
|
+
}
|
|
96
140
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
141
|
+
// 无 cookie 的公开 fallback:md5 签名直链
|
|
142
|
+
const songMid = mid ?? id;
|
|
143
|
+
const code = md5(`${songMid}q;z(&l~sdf2!nK`).substring(0, 5).toUpperCase();
|
|
144
|
+
return `https://c6.y.qq.com/rsc/fcgi-bin/fcg_pyq_play.fcg?songmid=${songMid}&code=${code}`;
|
|
101
145
|
}
|
|
102
|
-
/**
|
|
103
|
-
* 获取歌词
|
|
104
|
-
* @param id 音乐 ID
|
|
105
|
-
* @returns 歌词文本
|
|
106
|
-
*/
|
|
107
146
|
async getLyric(id) {
|
|
108
147
|
try {
|
|
109
148
|
const url = new URL('https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg');
|
|
@@ -115,16 +154,10 @@ export class QQMusicService {
|
|
|
115
154
|
url.searchParams.set('nobase64', '1');
|
|
116
155
|
const response = await fetch(url, {
|
|
117
156
|
method: 'GET',
|
|
118
|
-
headers: {
|
|
119
|
-
'Referer': 'https://y.qq.com'
|
|
120
|
-
}
|
|
157
|
+
headers: { Referer: 'https://y.qq.com' },
|
|
121
158
|
});
|
|
122
|
-
const data = await response.json();
|
|
123
|
-
|
|
124
|
-
return null;
|
|
125
|
-
// 请求已带 nobase64=1,返回的是明文歌词,直接返回;
|
|
126
|
-
// 再按 base64 解码会得到乱码(Buffer.from(x,'base64') 对明文也不会抛错)
|
|
127
|
-
return data.lyric;
|
|
159
|
+
const data = (await response.json());
|
|
160
|
+
return data.lyric ?? null;
|
|
128
161
|
}
|
|
129
162
|
catch (error) {
|
|
130
163
|
console.error('QQ Music get lyric failed:', error);
|
package/lib/sources/qq.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qq.js","sourceRoot":"","sources":["../../src/sources/qq.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"qq.js","sourceRoot":"","sources":["../../src/sources/qq.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
export type MusicSource = "qq" | "netease";
|
|
3
|
-
/** 音乐分享内容 */
|
|
1
|
+
export type MusicSource = 'qq' | 'netease' | 'kuwo' | 'kugou';
|
|
4
2
|
export interface MusicInfo {
|
|
5
3
|
id: string;
|
|
6
4
|
source: MusicSource;
|
|
7
|
-
/** 跳转链接 */
|
|
8
5
|
url: string;
|
|
9
|
-
/** 音乐标题 */
|
|
10
6
|
title: string;
|
|
7
|
+
artist?: string;
|
|
8
|
+
album?: string;
|
|
9
|
+
duration?: number;
|
|
11
10
|
}
|
|
12
11
|
export interface MusicDetail extends MusicInfo {
|
|
13
12
|
image: string;
|
|
14
13
|
audio: string;
|
|
15
|
-
duration?: number;
|
|
16
14
|
}
|
|
17
|
-
/** 音乐源配置 */
|
|
18
15
|
export interface MusicSourceConfig {
|
|
19
16
|
appid: number;
|
|
20
17
|
package: string;
|
|
@@ -22,20 +19,6 @@ export interface MusicSourceConfig {
|
|
|
22
19
|
sign: string;
|
|
23
20
|
version: string;
|
|
24
21
|
}
|
|
25
|
-
/** QQ 音乐 API 响应 */
|
|
26
|
-
export interface MusicQQ {
|
|
27
|
-
id: string;
|
|
28
|
-
mid: string;
|
|
29
|
-
name: string;
|
|
30
|
-
docid: string;
|
|
31
|
-
singer: string;
|
|
32
|
-
album?: {
|
|
33
|
-
mid: string;
|
|
34
|
-
name: string;
|
|
35
|
-
};
|
|
36
|
-
interval?: number;
|
|
37
|
-
}
|
|
38
|
-
/** 网易云音乐 API 响应 */
|
|
39
22
|
export interface Music163 {
|
|
40
23
|
id: string;
|
|
41
24
|
name: string;
|
|
@@ -51,11 +34,8 @@ export interface Music163 {
|
|
|
51
34
|
img1v1Url: string;
|
|
52
35
|
};
|
|
53
36
|
}
|
|
54
|
-
/** 音乐搜索服务接口 */
|
|
55
37
|
export interface MusicSearchService {
|
|
56
|
-
/** 搜索音乐 */
|
|
57
38
|
search(keyword: string, limit?: number): Promise<MusicInfo[]>;
|
|
58
|
-
/** 获取音乐详情 */
|
|
59
39
|
getDetail(id: string): Promise<MusicDetail>;
|
|
60
40
|
}
|
|
61
41
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9D,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7C"}
|
package/lib/types.js
CHANGED
package/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import { sessionKey, resolveMessageIds, getPending, clearPending } from "../lib/session.js";
|
|
4
|
+
import { shareMusicDetail, buildMusicShareSegment } from "../lib/music-lib.js";
|
|
5
|
+
import { SOURCE_DISPLAY_NAME } from "../lib/config.js";
|
|
6
|
+
export default defineMiddleware({
|
|
7
|
+
target: 'inbound',
|
|
8
|
+
async handle(context, next) {
|
|
9
|
+
const raw = context.input.content?.trim() ?? '';
|
|
10
|
+
if (!raw) {
|
|
11
|
+
await next();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const ids = resolveMessageIds(context.input);
|
|
15
|
+
if (!ids) {
|
|
16
|
+
await next();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const key = sessionKey(ids.endpointId, ids.conversationId, ids.senderId);
|
|
20
|
+
const pending = getPending(key);
|
|
21
|
+
if (!pending) {
|
|
22
|
+
await next();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (raw === '取消') {
|
|
26
|
+
clearPending(key);
|
|
27
|
+
await context.input.$reply('已取消点歌');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const num = parseInt(raw, 10);
|
|
31
|
+
if (isNaN(num) || num < 1 || num > pending.results.length) {
|
|
32
|
+
await next();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
clearPending(key);
|
|
36
|
+
const selected = pending.results[num - 1];
|
|
37
|
+
const sourceName = SOURCE_DISPLAY_NAME[selected.source] ?? selected.source;
|
|
38
|
+
const detailResult = await shareMusicDetail(selected.id, selected.source);
|
|
39
|
+
if (!detailResult.success) {
|
|
40
|
+
await context.input.$reply(`[${sourceName}] 获取"${selected.title}"播放信息失败:${detailResult.error}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const segment = buildMusicShareSegment(detailResult.music);
|
|
44
|
+
await context.input.$reply(segment);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
2
|
+
import type { Message } from '@zhin.js/core/runtime';
|
|
3
|
+
import { sessionKey, resolveMessageIds, getPending, clearPending } from '../src/session.js';
|
|
4
|
+
import { shareMusicDetail, buildMusicShareSegment } from '../src/music-lib.js';
|
|
5
|
+
import { SOURCE_DISPLAY_NAME } from '../src/config.js';
|
|
6
|
+
|
|
7
|
+
export default defineMiddleware<Message>({
|
|
8
|
+
target: 'inbound',
|
|
9
|
+
async handle(context, next) {
|
|
10
|
+
const raw = context.input.content?.trim() ?? '';
|
|
11
|
+
if (!raw) {
|
|
12
|
+
await next();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ids = resolveMessageIds(context.input);
|
|
17
|
+
if (!ids) {
|
|
18
|
+
await next();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const key = sessionKey(ids.endpointId, ids.conversationId, ids.senderId);
|
|
23
|
+
const pending = getPending(key);
|
|
24
|
+
if (!pending) {
|
|
25
|
+
await next();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (raw === '取消') {
|
|
30
|
+
clearPending(key);
|
|
31
|
+
await context.input.$reply('已取消点歌');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const num = parseInt(raw, 10);
|
|
36
|
+
if (isNaN(num) || num < 1 || num > pending.results.length) {
|
|
37
|
+
await next();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
clearPending(key);
|
|
42
|
+
const selected = pending.results[num - 1]!;
|
|
43
|
+
const sourceName = SOURCE_DISPLAY_NAME[selected.source] ?? selected.source;
|
|
44
|
+
|
|
45
|
+
const detailResult = await shareMusicDetail(selected.id, selected.source);
|
|
46
|
+
if (!detailResult.success) {
|
|
47
|
+
await context.input.$reply(
|
|
48
|
+
`[${sourceName}] 获取"${selected.title}"播放信息失败:${detailResult.error}`,
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const segment = buildMusicShareSegment(detailResult.music);
|
|
54
|
+
await context.input.$reply(segment);
|
|
55
|
+
},
|
|
56
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-music",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.10",
|
|
4
4
|
"description": "Music search and sharing plugin for Zhin.js (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"plugin.js",
|
|
18
18
|
"schema.json",
|
|
19
|
+
"commands",
|
|
20
|
+
"middlewares",
|
|
19
21
|
"components",
|
|
20
22
|
"tools",
|
|
21
23
|
"src",
|
|
@@ -41,16 +43,19 @@
|
|
|
41
43
|
},
|
|
42
44
|
"license": "MIT",
|
|
43
45
|
"dependencies": {
|
|
44
|
-
"@zhin.js/
|
|
45
|
-
"@zhin.js/
|
|
46
|
-
"@zhin.js/
|
|
46
|
+
"@zhin.js/command": "1.0.13",
|
|
47
|
+
"@zhin.js/component": "1.0.10",
|
|
48
|
+
"@zhin.js/core": "1.5.9",
|
|
49
|
+
"@zhin.js/middleware": "1.0.10",
|
|
50
|
+
"@zhin.js/plugin-runtime": "1.1.6",
|
|
51
|
+
"@zhin.js/tool": "1.0.10"
|
|
47
52
|
},
|
|
48
53
|
"devDependencies": {
|
|
49
54
|
"typescript": "^6.0.3",
|
|
50
55
|
"vitest": "^4.1.10"
|
|
51
56
|
},
|
|
52
57
|
"peerDependencies": {
|
|
53
|
-
"@zhin.js/adapter-icqq": "8.0.
|
|
58
|
+
"@zhin.js/adapter-icqq": "8.0.10"
|
|
54
59
|
},
|
|
55
60
|
"peerDependenciesMeta": {
|
|
56
61
|
"@zhin.js/adapter-icqq": {
|
package/plugin.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
-
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { definePlugin, databaseHostToken } from '@zhin.js/plugin-runtime';
|
|
3
|
+
import { cleanExpired } from "./lib/session.js";
|
|
4
|
+
import { cleanExpiredLogins } from "./lib/login/index.js";
|
|
5
|
+
import { MUSIC_CREDENTIALS_TABLE, provideCredentialDb, } from "./lib/credential-store.js";
|
|
6
|
+
function defineCredentialTable(db) {
|
|
7
|
+
db.define(MUSIC_CREDENTIALS_TABLE, {
|
|
8
|
+
source: { type: 'text', nullable: false },
|
|
9
|
+
key: { type: 'text', nullable: false },
|
|
10
|
+
value: { type: 'text', nullable: false },
|
|
11
|
+
updated_at: { type: 'text', default: '' },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
3
14
|
export default definePlugin({
|
|
4
15
|
name: 'music',
|
|
5
16
|
metadata: {
|
|
6
17
|
displayName: 'Music',
|
|
7
18
|
},
|
|
19
|
+
setup(context) {
|
|
20
|
+
if (context.resources.has(databaseHostToken)) {
|
|
21
|
+
const host = context.resources.use(databaseHostToken);
|
|
22
|
+
defineCredentialTable(host);
|
|
23
|
+
provideCredentialDb(context, host);
|
|
24
|
+
}
|
|
25
|
+
const timer = setInterval(() => {
|
|
26
|
+
cleanExpired();
|
|
27
|
+
cleanExpiredLogins();
|
|
28
|
+
}, 60_000);
|
|
29
|
+
return () => clearInterval(timer);
|
|
30
|
+
},
|
|
8
31
|
});
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import type { MusicSourceConfig,MusicSource } from './types.js'
|
|
1
|
+
import type { MusicSourceConfig, MusicSource } from './types.js';
|
|
3
2
|
|
|
4
|
-
/** 音乐源配置映射 */
|
|
5
3
|
export const sourceConfigMap: Record<MusicSource, MusicSourceConfig> = {
|
|
6
4
|
qq: {
|
|
7
5
|
appid: 100497308,
|
|
@@ -17,38 +15,64 @@ export const sourceConfigMap: Record<MusicSource, MusicSourceConfig> = {
|
|
|
17
15
|
sign: 'da6b069da1e2982db3e386233f68d76d',
|
|
18
16
|
version: '9.1.92',
|
|
19
17
|
},
|
|
18
|
+
kuwo: {
|
|
19
|
+
appid: 100243533,
|
|
20
|
+
package: 'cn.kuwo.player',
|
|
21
|
+
icon: 'https://p.qpic.cn/qqconnect/0/app_100243533_1636498875/100?max-age=2592000&t=0',
|
|
22
|
+
sign: 'bf9ff4ffb4c558a34ee3fd52c223ebf5',
|
|
23
|
+
version: '10.1.3.5',
|
|
24
|
+
},
|
|
25
|
+
kugou: {
|
|
26
|
+
appid: 205141,
|
|
27
|
+
package: 'com.kugou.android',
|
|
28
|
+
icon: 'https://open.gtimg.cn/open/app_icon/00/20/51/41/205141_100_m.png',
|
|
29
|
+
sign: '5a84ee451a90ca88a1ea0ad2bef9e69c',
|
|
30
|
+
version: '12.2.8',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const SOURCE_ALIASES: Record<string, MusicSource> = {
|
|
35
|
+
'qq': 'qq',
|
|
36
|
+
'QQ': 'qq',
|
|
37
|
+
'QQ音乐': 'qq',
|
|
38
|
+
'qq音乐': 'qq',
|
|
39
|
+
'网易': 'netease',
|
|
40
|
+
'网易云': 'netease',
|
|
41
|
+
'163': 'netease',
|
|
42
|
+
'酷我': 'kuwo',
|
|
43
|
+
'酷狗': 'kugou',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const SOURCE_DISPLAY_NAME: Record<MusicSource, string> = {
|
|
47
|
+
qq: 'QQ音乐',
|
|
48
|
+
netease: '网易云',
|
|
49
|
+
kuwo: '酷我',
|
|
50
|
+
kugou: '酷狗',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function resolveSourceAlias(input: string): MusicSource | undefined {
|
|
54
|
+
return SOURCE_ALIASES[input.trim()];
|
|
20
55
|
}
|
|
21
56
|
|
|
22
|
-
/** 格式化时长 */
|
|
23
57
|
export function formatDuration(seconds?: number): string {
|
|
24
|
-
if (!seconds) return '未知'
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
return `${minutes}:${secs.toString().padStart(2, '0')}`
|
|
58
|
+
if (!seconds) return '未知';
|
|
59
|
+
const minutes = Math.floor(seconds / 60);
|
|
60
|
+
const secs = seconds % 60;
|
|
61
|
+
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
29
62
|
}
|
|
30
63
|
|
|
31
|
-
/** 格式化音乐信息 */
|
|
32
64
|
export function formatMusicInfo(music: {
|
|
33
|
-
title: string
|
|
34
|
-
artist?: string
|
|
35
|
-
album?: string
|
|
36
|
-
duration?: number
|
|
37
|
-
source: string
|
|
65
|
+
title: string;
|
|
66
|
+
artist?: string;
|
|
67
|
+
album?: string;
|
|
68
|
+
duration?: number;
|
|
69
|
+
source: string;
|
|
38
70
|
}): string {
|
|
39
|
-
const parts = [music.title]
|
|
40
|
-
|
|
41
|
-
if (music.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
parts.push(music.album)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const info = parts.join(' - ')
|
|
50
|
-
const duration = formatDuration(music.duration)
|
|
51
|
-
const source = music.source.toUpperCase()
|
|
52
|
-
|
|
53
|
-
return `${info} [${duration}] [${source}]`
|
|
71
|
+
const parts = [music.title];
|
|
72
|
+
if (music.artist) parts.push(music.artist);
|
|
73
|
+
if (music.album) parts.push(music.album);
|
|
74
|
+
const info = parts.join(' - ');
|
|
75
|
+
const duration = formatDuration(music.duration);
|
|
76
|
+
const source = music.source.toUpperCase();
|
|
77
|
+
return `${info} [${duration}] [${source}]`;
|
|
54
78
|
}
|