@eldment/meting-mcp 1.6.1

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.
@@ -0,0 +1,417 @@
1
+ import crypto from 'crypto';
2
+ import BaseProvider from './base.js';
3
+
4
+ /**
5
+ * 酷狗音乐平台提供者
6
+ */
7
+ export default class KugouProvider extends BaseProvider {
8
+ constructor(meting) {
9
+ super(meting);
10
+ this.name = 'kugou';
11
+ }
12
+
13
+ /**
14
+ * 获取酷狗音乐的请求头配置
15
+ */
16
+ getHeaders() {
17
+ return {
18
+ 'User-Agent': 'IPhone-8990-searchSong',
19
+ 'UNI-UserAgent': 'iOS11.4-Phone8990-1009-0-WiFi'
20
+ };
21
+ }
22
+
23
+ /**
24
+ * 搜索歌曲
25
+ */
26
+ search(keyword, option = {}) {
27
+ return {
28
+ method: 'GET',
29
+ url: 'http://mobilecdn.kugou.com/api/v3/search/song',
30
+ body: {
31
+ api_ver: 1,
32
+ area_code: 1,
33
+ correct: 1,
34
+ pagesize: option.limit || 30,
35
+ plat: 2,
36
+ tag: 1,
37
+ sver: 5,
38
+ showtype: 10,
39
+ page: option.page || 1,
40
+ keyword: keyword,
41
+ version: 8990
42
+ },
43
+ format: 'data.info'
44
+ };
45
+ }
46
+
47
+ /**
48
+ * 获取歌曲详情
49
+ */
50
+ song(id) {
51
+ return {
52
+ method: 'POST',
53
+ url: 'http://m.kugou.com/app/i/getSongInfo.php',
54
+ body: {
55
+ cmd: 'playInfo',
56
+ hash: id,
57
+ from: 'mkugou'
58
+ },
59
+ format: ''
60
+ };
61
+ }
62
+
63
+ /**
64
+ * 获取专辑信息
65
+ */
66
+ album(id) {
67
+ return {
68
+ method: 'GET',
69
+ url: 'http://mobilecdn.kugou.com/api/v3/album/song',
70
+ body: {
71
+ albumid: id,
72
+ area_code: 1,
73
+ plat: 2,
74
+ page: 1,
75
+ pagesize: -1,
76
+ version: 8990
77
+ },
78
+ format: 'data.info'
79
+ };
80
+ }
81
+
82
+ /**
83
+ * 获取艺术家作品
84
+ */
85
+ artist(id, limit = 50) {
86
+ return {
87
+ method: 'GET',
88
+ url: 'http://mobilecdn.kugou.com/api/v3/singer/song',
89
+ body: {
90
+ singerid: id,
91
+ area_code: 1,
92
+ page: 1,
93
+ plat: 0,
94
+ pagesize: limit,
95
+ version: 8990
96
+ },
97
+ format: 'data.info'
98
+ };
99
+ }
100
+
101
+ /**
102
+ * 获取播放列表
103
+ */
104
+ playlist(id) {
105
+ return {
106
+ method: 'GET',
107
+ url: 'http://mobilecdn.kugou.com/api/v3/special/song',
108
+ body: {
109
+ specialid: id,
110
+ area_code: 1,
111
+ page: 1,
112
+ plat: 2,
113
+ pagesize: -1,
114
+ version: 8990
115
+ },
116
+ format: 'data.info'
117
+ };
118
+ }
119
+
120
+ /**
121
+ * 获取音频播放链接
122
+ * 有 cookie 时走新接口(songinfo + 签名),无 cookie 走老接口
123
+ */
124
+ url(id, br = 320) {
125
+ const cookie = this.parseCookie(this.meting.header['Cookie'] || '');
126
+ const hasToken = !!(cookie.t && cookie.KugooID);
127
+
128
+ if (hasToken) {
129
+ const now = Date.now();
130
+ const params = {
131
+ srcappid: '2919',
132
+ clientver: '20000',
133
+ clienttime: String(now),
134
+ mid: cookie.mid || cookie.kg_mid || '',
135
+ uuid: cookie.uuid || cookie.mid || cookie.kg_mid || '',
136
+ dfid: cookie.dfid || cookie.kg_dfid || '',
137
+ appid: '1014',
138
+ platid: '4',
139
+ hash: id,
140
+ token: cookie.t || '',
141
+ userid: cookie.KugooID || ''
142
+ };
143
+
144
+ return {
145
+ method: 'GET',
146
+ url: this.buildSonginfoUrl(params),
147
+ body: null,
148
+ decode: 'kugou_url_new'
149
+ };
150
+ }
151
+
152
+ // 老接口,无需 cookie
153
+ return {
154
+ method: 'POST',
155
+ url: 'http://media.store.kugou.com/v1/get_res_privilege',
156
+ body: JSON.stringify({
157
+ relate: 1,
158
+ userid: '0',
159
+ vip: 0,
160
+ appid: 1000,
161
+ token: '',
162
+ behavior: 'download',
163
+ area_code: '1',
164
+ clientver: '8990',
165
+ resource: [{
166
+ id: 0,
167
+ type: 'audio',
168
+ hash: id
169
+ }]
170
+ }),
171
+ decode: 'kugou_url_legacy'
172
+ };
173
+ }
174
+
175
+ /**
176
+ * 获取歌词
177
+ */
178
+ lyric(id) {
179
+ return {
180
+ method: 'GET',
181
+ url: 'http://krcs.kugou.com/search',
182
+ body: {
183
+ keyword: '%20-%20',
184
+ ver: 1,
185
+ hash: id,
186
+ client: 'mobi',
187
+ man: 'yes'
188
+ },
189
+ decode: 'kugou_lyric'
190
+ };
191
+ }
192
+
193
+ /**
194
+ * 获取封面图片
195
+ */
196
+ async pic(id, size = 300) {
197
+ const format = this.meting.isFormat;
198
+ const data = await this.meting.format(false).song(id);
199
+ this.meting.isFormat = format;
200
+ const songData = JSON.parse(data);
201
+ let url = songData.imgUrl;
202
+ url = url.replace('{size}', '400');
203
+ return JSON.stringify({ url: url });
204
+ }
205
+
206
+ /**
207
+ * 格式化酷狗音乐数据
208
+ */
209
+ format(data) {
210
+ const filename = data.filename || data.fileName;
211
+ const result = {
212
+ id: data.hash,
213
+ name: data.songName || filename,
214
+ artist: [],
215
+ album: data.album_name || '',
216
+ url_id: data.encode_album_audio_id || data.hash,
217
+ pic_id: data.hash,
218
+ lyric_id: data.hash,
219
+ source: 'kugou'
220
+ };
221
+
222
+ if (data.authors && Array.isArray(data.authors)) {
223
+ result.artist = data.authors.map(a => a.author_name);
224
+ } else if (filename) {
225
+ const parts = filename.split(' - ');
226
+ if (parts.length >= 2) {
227
+ result.artist = parts[0].split('、');
228
+ result.name = parts[1];
229
+ }
230
+ }
231
+
232
+ return result;
233
+ }
234
+
235
+ /**
236
+ * 解析 Cookie 字符串为对象
237
+ */
238
+ parseCookie(cookieStr) {
239
+ const cookies = {};
240
+ if (!cookieStr) return cookies;
241
+ cookieStr.split(';').forEach(pair => {
242
+ const idx = pair.indexOf('=');
243
+ if (idx > 0) {
244
+ const key = pair.substring(0, idx).trim();
245
+ const val = pair.substring(idx + 1).trim();
246
+ cookies[key] = val;
247
+ }
248
+ });
249
+ return cookies;
250
+ }
251
+
252
+ /**
253
+ * 生成酷狗 API 签名
254
+ */
255
+ getSignature(params) {
256
+ const MD5_KEY = 'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt';
257
+ const paramStr = Object.entries(params)
258
+ .map(([k, v]) => `${k}=${v}`)
259
+ .join('&');
260
+ const sorted = paramStr.split('&').sort().join('');
261
+ return crypto.createHash('md5').update(`${MD5_KEY}${sorted}${MD5_KEY}`).digest('hex');
262
+ }
263
+
264
+ /**
265
+ * 处理酷狗音乐的解码逻辑
266
+ */
267
+ async handleDecode(decodeType, data) {
268
+ if (decodeType === 'kugou_url_new') {
269
+ return this.urlDecodeNew(data);
270
+ } else if (decodeType === 'kugou_url_legacy') {
271
+ return this.urlDecodeLegacy(data);
272
+ } else if (decodeType === 'kugou_lyric') {
273
+ return this.lyricDecode(data);
274
+ }
275
+ return data;
276
+ }
277
+
278
+ /**
279
+ * 构建带签名的 songinfo 请求 URL
280
+ */
281
+ buildSonginfoUrl(params) {
282
+ const signature = this.getSignature(params);
283
+ const queryString = Object.entries(params)
284
+ .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
285
+ .join('&');
286
+ return `https://wwwapi.kugou.com/play/songinfo?${queryString}&signature=${signature}`;
287
+ }
288
+
289
+ /**
290
+ * 酷狗音乐 URL 解码(新接口,需要 cookie)
291
+ * 第一步用 hash 查询获取 encode_album_audio_id,
292
+ * 第二步用 encode_album_audio_id 查询获取播放链接
293
+ */
294
+ async urlDecodeNew(result) {
295
+ try {
296
+ const json = JSON.parse(result);
297
+ const data = json.data;
298
+ if (!data || !data.encode_album_audio_id) {
299
+ return JSON.stringify({ url: '', size: 0, br: -1 });
300
+ }
301
+
302
+ // 第二步:用 encode_album_audio_id 重新查询
303
+ const cookie = this.parseCookie(this.meting.header['Cookie'] || '');
304
+ const now = Date.now();
305
+ const params = {
306
+ srcappid: '2919',
307
+ clientver: '20000',
308
+ clienttime: String(now),
309
+ mid: cookie.mid || cookie.kg_mid || '',
310
+ uuid: cookie.uuid || cookie.mid || cookie.kg_mid || '',
311
+ dfid: cookie.dfid || cookie.kg_dfid || '',
312
+ appid: '1014',
313
+ platid: '4',
314
+ encode_album_audio_id: data.encode_album_audio_id,
315
+ token: cookie.t || '',
316
+ userid: cookie.KugooID || ''
317
+ };
318
+
319
+ const api = {
320
+ method: 'GET',
321
+ url: this.buildSonginfoUrl(params),
322
+ body: null
323
+ };
324
+ const response = JSON.parse(await this.meting._exec(api));
325
+ const detail = response.data;
326
+ if (detail) {
327
+ const url = detail.play_url || detail.play_backup_url || '';
328
+ return JSON.stringify({
329
+ url: url,
330
+ size: detail.filesize || 0,
331
+ br: detail.bitrate || -1
332
+ });
333
+ }
334
+ } catch (e) {
335
+ // parse error
336
+ }
337
+ return JSON.stringify({ url: '', size: 0, br: -1 });
338
+ }
339
+
340
+ /**
341
+ * 酷狗音乐 URL 解码(老接口,无需 cookie)
342
+ */
343
+ async urlDecodeLegacy(result) {
344
+ try {
345
+ const data = JSON.parse(result);
346
+
347
+ let maxBr = 0;
348
+ let url;
349
+
350
+ for (const item of data.data[0].relate_goods) {
351
+ if (item.info.bitrate <= this.meting.temp.br && item.info.bitrate > maxBr) {
352
+ const api = {
353
+ method: 'GET',
354
+ url: 'http://trackercdn.kugou.com/i/v2/',
355
+ body: {
356
+ hash: item.hash,
357
+ key: crypto.createHash('md5').update(item.hash + 'kgcloudv2').digest('hex'),
358
+ pid: 3,
359
+ behavior: 'play',
360
+ cmd: '25',
361
+ version: 8990
362
+ }
363
+ };
364
+
365
+ const response = JSON.parse(await this.meting._exec(api));
366
+ if (response.url) {
367
+ maxBr = response.bitRate / 1000;
368
+ url = {
369
+ url: Array.isArray(response.url) ? response.url[0] : response.url,
370
+ size: response.fileSize,
371
+ br: response.bitRate / 1000
372
+ };
373
+ }
374
+ }
375
+ }
376
+
377
+ if (url) {
378
+ return JSON.stringify(url);
379
+ }
380
+ } catch (e) {
381
+ // parse error
382
+ }
383
+ return JSON.stringify({ url: '', size: 0, br: -1 });
384
+ }
385
+
386
+ /**
387
+ * 酷狗音乐歌词解码
388
+ */
389
+ async lyricDecode(result) {
390
+ const data = JSON.parse(result);
391
+
392
+ if (!data.candidates || data.candidates.length === 0) {
393
+ return JSON.stringify({ lyric: '', tlyric: '' });
394
+ }
395
+
396
+ const api = {
397
+ method: 'GET',
398
+ url: 'http://lyrics.kugou.com/download',
399
+ body: {
400
+ charset: 'utf8',
401
+ accesskey: data.candidates[0].accesskey,
402
+ id: data.candidates[0].id,
403
+ client: 'mobi',
404
+ fmt: 'lrc',
405
+ ver: 1
406
+ }
407
+ };
408
+
409
+ const response = JSON.parse(await this.meting._exec(api));
410
+ const lyricData = {
411
+ lyric: Buffer.from(response.content, 'base64').toString(),
412
+ tlyric: ''
413
+ };
414
+
415
+ return JSON.stringify(lyricData);
416
+ }
417
+ }
@@ -0,0 +1,226 @@
1
+ import BaseProvider from './base.js';
2
+
3
+ /**
4
+ * 酷我音乐平台提供者
5
+ */
6
+ export default class KuwoProvider extends BaseProvider {
7
+ constructor(meting) {
8
+ super(meting);
9
+ this.name = 'kuwo';
10
+ }
11
+
12
+ /**
13
+ * 获取酷我音乐的请求头配置
14
+ */
15
+ getHeaders() {
16
+ return {
17
+ 'Cookie': 'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1623339177,1623339183; _ga=GA1.2.1195980605.1579367081; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1623339982; kw_token=3E7JFQ7MRPL; _gid=GA1.2.747985028.1623339179; _gat=1',
18
+ 'csrf': '3E7JFQ7MRPL',
19
+ 'Host': 'www.kuwo.cn',
20
+ 'Referer': 'http://www.kuwo.cn/',
21
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
22
+ };
23
+ }
24
+
25
+ /**
26
+ * 搜索歌曲
27
+ */
28
+ search(keyword, option = {}) {
29
+ return {
30
+ method: 'GET',
31
+ url: 'http://www.kuwo.cn/api/www/search/searchMusicBykeyWord',
32
+ body: {
33
+ key: keyword,
34
+ pn: option.page || 1,
35
+ rn: option.limit || 30,
36
+ httpsStatus: 1
37
+ },
38
+ format: 'data.list'
39
+ };
40
+ }
41
+
42
+ /**
43
+ * 获取歌曲详情
44
+ */
45
+ song(id) {
46
+ return {
47
+ method: 'GET',
48
+ url: 'http://www.kuwo.cn/api/www/music/musicInfo',
49
+ body: {
50
+ mid: id,
51
+ httpsStatus: 1
52
+ },
53
+ format: 'data'
54
+ };
55
+ }
56
+
57
+ /**
58
+ * 获取专辑信息
59
+ */
60
+ album(id) {
61
+ return {
62
+ method: 'GET',
63
+ url: 'http://www.kuwo.cn/api/www/album/albumInfo',
64
+ body: {
65
+ albumId: id,
66
+ pn: 1,
67
+ rn: 1000,
68
+ httpsStatus: 1
69
+ },
70
+ format: 'data.musicList'
71
+ };
72
+ }
73
+
74
+ /**
75
+ * 获取艺术家作品
76
+ */
77
+ artist(id, limit = 50) {
78
+ return {
79
+ method: 'GET',
80
+ url: 'http://www.kuwo.cn/api/www/artist/artistMusic',
81
+ body: {
82
+ artistid: id,
83
+ pn: 1,
84
+ rn: limit,
85
+ httpsStatus: 1
86
+ },
87
+ format: 'data.list'
88
+ };
89
+ }
90
+
91
+ /**
92
+ * 获取播放列表
93
+ */
94
+ playlist(id) {
95
+ return {
96
+ method: 'GET',
97
+ url: 'http://www.kuwo.cn/api/www/playlist/playListInfo',
98
+ body: {
99
+ pid: id,
100
+ pn: 1,
101
+ rn: 1000,
102
+ httpsStatus: 1
103
+ },
104
+ format: 'data.musicList'
105
+ };
106
+ }
107
+
108
+ /**
109
+ * 获取音频播放链接
110
+ */
111
+ url(id, br = 320) {
112
+ return {
113
+ method: 'GET',
114
+ url: 'http://www.kuwo.cn/api/v1/www/music/playUrl',
115
+ body: {
116
+ mid: id,
117
+ type: 'music',
118
+ httpsStatus: 1
119
+ },
120
+ decode: 'kuwo_url'
121
+ };
122
+ }
123
+
124
+ /**
125
+ * 获取歌词
126
+ */
127
+ lyric(id) {
128
+ return {
129
+ method: 'GET',
130
+ url: 'http://m.kuwo.cn/newh5/singles/songinfoandlrc',
131
+ body: {
132
+ musicId: id,
133
+ httpsStatus: 1
134
+ },
135
+ decode: 'kuwo_lyric'
136
+ };
137
+ }
138
+
139
+ /**
140
+ * 获取封面图片
141
+ */
142
+ async pic(id, size = 300) {
143
+ const format = this.meting.isFormat;
144
+ const data = await this.meting.format(false).song(id);
145
+ this.meting.isFormat = format;
146
+ const songData = JSON.parse(data);
147
+ const url = songData.data.pic || songData.data.albumpic;
148
+ return JSON.stringify({ url: url });
149
+ }
150
+
151
+ /**
152
+ * 格式化酷我音乐数据
153
+ */
154
+ format(data) {
155
+ return {
156
+ id: data.rid,
157
+ name: data.name,
158
+ artist: data.artist ? data.artist.split('&') : [],
159
+ album: data.album || '',
160
+ pic_id: data.rid,
161
+ url_id: data.rid,
162
+ lyric_id: data.rid,
163
+ source: 'kuwo'
164
+ };
165
+ }
166
+
167
+ /**
168
+ * 处理酷我音乐的解码逻辑
169
+ */
170
+ async handleDecode(decodeType, data) {
171
+ if (decodeType === 'kuwo_url') {
172
+ return this.urlDecode(data);
173
+ } else if (decodeType === 'kuwo_lyric') {
174
+ return this.lyricDecode(data);
175
+ }
176
+ return data;
177
+ }
178
+
179
+ /**
180
+ * 酷我音乐 URL 解码
181
+ */
182
+ urlDecode(result) {
183
+ const data = JSON.parse(result);
184
+
185
+ let url;
186
+ if (data.code === 200 && data.data && data.data.url) {
187
+ url = {
188
+ url: data.data.url,
189
+ br: 128
190
+ };
191
+ } else {
192
+ url = {
193
+ url: '',
194
+ br: -1
195
+ };
196
+ }
197
+
198
+ return JSON.stringify(url);
199
+ }
200
+
201
+ /**
202
+ * 酷我音乐歌词解码
203
+ */
204
+ lyricDecode(result) {
205
+ const data = JSON.parse(result);
206
+
207
+ let lyric = '';
208
+ if (data.data && data.data.lrclist && data.data.lrclist.length > 0) {
209
+ data.data.lrclist.forEach(item => {
210
+ const time = parseFloat(item.time);
211
+ const min = Math.floor(time / 60).toString().padStart(2, '0');
212
+ const sec = Math.floor(time % 60).toString().padStart(2, '0');
213
+ const msec = ((time % 1) * 100).toFixed(0).padStart(2, '0');
214
+
215
+ lyric += `[${min}:${sec}.${msec}]${item.lineLyric}\n`;
216
+ });
217
+ }
218
+
219
+ const lyricData = {
220
+ lyric: lyric,
221
+ tlyric: ''
222
+ };
223
+
224
+ return JSON.stringify(lyricData);
225
+ }
226
+ }