@kuriyona/cecilia 0.2.0 → 1.0.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/dist/index.cjs CHANGED
@@ -2,6 +2,646 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
+ let node_crypto = require("node:crypto");
6
+ //#region src/cookies.ts
7
+ const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
8
+ const randomLowercase = (length) => {
9
+ let out = "";
10
+ for (let i = 0; i < length; i++) out += LOWERCASE[Math.floor(Math.random() * 26)];
11
+ return out;
12
+ };
13
+ const cookieToObject = (cookie) => {
14
+ if (!cookie) return {};
15
+ if (typeof cookie !== "string") return { ...cookie };
16
+ const result = {};
17
+ for (const part of cookie.split(";")) {
18
+ const index = part.indexOf("=");
19
+ if (index === -1) continue;
20
+ const key = part.slice(0, index).trim();
21
+ if (!key) continue;
22
+ result[key] = part.slice(index + 1).trim();
23
+ }
24
+ return result;
25
+ };
26
+ const cookieToString = (cookie) => Object.entries(cookie).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("; ");
27
+ const buildCookieObject = (cookie) => {
28
+ const input = cookieToObject(cookie);
29
+ const nuid = input._ntes_nuid || (0, node_crypto.randomBytes)(32).toString("hex");
30
+ const timestamp = Date.now();
31
+ return {
32
+ _ntes_nuid: nuid,
33
+ _ntes_nnid: `${nuid},${timestamp}`,
34
+ WNMCID: `${randomLowercase(6)}.${timestamp}.01.0`,
35
+ __remember_me: "true",
36
+ ntes_kaola_ad: "1",
37
+ WEVNSM: "1.0.0",
38
+ os: "pc",
39
+ appver: "3.1.17.204416",
40
+ osver: "Microsoft-Windows-10-Professional-build-19045-64bit",
41
+ channel: "netease",
42
+ deviceId: (0, node_crypto.randomBytes)(32).toString("hex").toUpperCase(),
43
+ ...input
44
+ };
45
+ };
46
+ //#endregion
47
+ //#region src/crypto/constants.ts
48
+ const PRESET_KEY = "0CoJUm6Qyw8W8jud";
49
+ const IV = "0102030405060708";
50
+ const BASE62 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
51
+ const EAPI_KEY = "e82ckenh8dichen8";
52
+ const EAPI_SEP = "-36cd479b6b5-";
53
+ const WEAPI_DOMAIN = "https://music.163.com";
54
+ const EAPI_DOMAIN = "https://interfacepc.music.163.com";
55
+ const API_DOMAIN = "https://interface.music.163.com";
56
+ const WEAPI_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
57
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB
58
+ -----END PUBLIC KEY-----`;
59
+ //#endregion
60
+ //#region src/crypto/eapi.ts
61
+ const eapiEncrypt = (uri, data) => {
62
+ const text = JSON.stringify(data);
63
+ const digest = (0, node_crypto.createHash)("md5").update(`nobody${uri}use${text}md5forencrypt`, "utf8").digest("hex");
64
+ const cipher = (0, node_crypto.createCipheriv)("aes-128-ecb", EAPI_KEY, null);
65
+ const plain = `${uri}${EAPI_SEP}${text}${EAPI_SEP}${digest}`;
66
+ return Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]).toString("hex");
67
+ };
68
+ //#endregion
69
+ //#region src/crypto/weapi.ts
70
+ const aes128Cbc = (text, key, iv) => {
71
+ const cipher = (0, node_crypto.createCipheriv)("aes-128-cbc", Buffer.from(key, "utf8"), Buffer.from(iv, "utf8"));
72
+ return Buffer.concat([cipher.update(text, "utf8"), cipher.final()]).toString("base64");
73
+ };
74
+ const randomSecretKey = () => {
75
+ let out = "";
76
+ for (let i = 0; i < 16; i++) out += BASE62[(0, node_crypto.randomInt)(62)];
77
+ return out;
78
+ };
79
+ const rsaEncryptNoPadding = (text) => {
80
+ const key = (0, node_crypto.createPublicKey)(WEAPI_PUBLIC_KEY);
81
+ const modulusLength = key.asymmetricKeyDetails?.modulusLength;
82
+ const size = modulusLength ? modulusLength / 8 : 128;
83
+ const message = Buffer.from(text, "utf8");
84
+ const padded = Buffer.concat([Buffer.alloc(Math.max(0, size - message.length)), message]);
85
+ return (0, node_crypto.publicEncrypt)({
86
+ key,
87
+ padding: node_crypto.constants.RSA_NO_PADDING
88
+ }, padded).toString("hex");
89
+ };
90
+ const weapiEncrypt = (data, options) => {
91
+ const secretKey = options?.secretKey ?? randomSecretKey();
92
+ return {
93
+ params: aes128Cbc(aes128Cbc(JSON.stringify(data), PRESET_KEY, IV), secretKey, IV),
94
+ encSecKey: rsaEncryptNoPadding([...secretKey].reverse().join(""))
95
+ };
96
+ };
97
+ //#endregion
98
+ //#region src/errors.ts
99
+ const pickDetail = (body) => {
100
+ if (typeof body !== "object" || body === null) return void 0;
101
+ if ("msg" in body && typeof body.msg === "string" && body.msg.length > 0) return body.msg;
102
+ if ("message" in body && typeof body.message === "string" && body.message.length > 0) return body.message;
103
+ };
104
+ var NeteaseApiError = class extends Error {
105
+ code;
106
+ uri;
107
+ status;
108
+ body;
109
+ constructor(uri, code, status, body) {
110
+ const detail = pickDetail(body);
111
+ super(`${uri} 请求失败: code ${code}${detail ? ` ${detail}` : ""}`);
112
+ this.name = "NeteaseApiError";
113
+ this.code = code;
114
+ this.uri = uri;
115
+ this.status = status;
116
+ this.body = body;
117
+ }
118
+ };
119
+ //#endregion
120
+ //#region src/http.ts
121
+ const getSetCookies = (res) => {
122
+ try {
123
+ return res.headers.getSetCookie();
124
+ } catch {
125
+ return [];
126
+ }
127
+ };
128
+ async function postForm(url, body, headers, timeout) {
129
+ const res = await fetch(url, {
130
+ method: "POST",
131
+ headers: {
132
+ "Content-Type": "application/x-www-form-urlencoded",
133
+ ...headers
134
+ },
135
+ body,
136
+ signal: AbortSignal.timeout(timeout)
137
+ });
138
+ const text = await res.text();
139
+ let json;
140
+ try {
141
+ json = JSON.parse(text);
142
+ } catch {
143
+ json = void 0;
144
+ }
145
+ return {
146
+ json,
147
+ setCookies: getSetCookies(res),
148
+ status: res.status,
149
+ text
150
+ };
151
+ }
152
+ async function getText(url, timeout, headers = {}) {
153
+ const res = await fetch(url, {
154
+ headers,
155
+ signal: AbortSignal.timeout(timeout)
156
+ });
157
+ const text = await res.text();
158
+ return {
159
+ json: void 0,
160
+ setCookies: getSetCookies(res),
161
+ status: res.status,
162
+ text
163
+ };
164
+ }
165
+ //#endregion
166
+ //#region src/utils/num.ts
167
+ const num = (v) => {
168
+ if (v === null || v === void 0 || v === "") return void 0;
169
+ const n = Number(v);
170
+ return Number.isNaN(n) ? void 0 : n;
171
+ };
172
+ //#endregion
173
+ //#region src/client.ts
174
+ const generateRequestId = () => `${Date.now()}_${String((0, node_crypto.randomInt)(1e3)).padStart(4, "0")}`;
175
+ const readCode = (body) => {
176
+ if (typeof body !== "object" || body === null || !("code" in body)) return;
177
+ return num(body.code);
178
+ };
179
+ async function request(uri, data, crypto, options, acceptCodes = [200]) {
180
+ const timeout = options?.timeout ?? 1e4;
181
+ const cookie = buildCookieObject(options?.cookie);
182
+ const csrf = cookie.__csrf ?? "";
183
+ const payload = {
184
+ ...data,
185
+ e_r: false
186
+ };
187
+ const headers = {};
188
+ let url;
189
+ let body;
190
+ if (crypto === "eapi") {
191
+ const header = {
192
+ osver: cookie.osver ?? "",
193
+ deviceId: cookie.deviceId ?? "",
194
+ os: cookie.os ?? "",
195
+ appver: cookie.appver ?? "",
196
+ versioncode: "140",
197
+ mobilename: "",
198
+ buildver: String(Date.now()).slice(0, 10),
199
+ resolution: "1920x1080",
200
+ __csrf: csrf,
201
+ channel: cookie.channel ?? "",
202
+ requestId: generateRequestId()
203
+ };
204
+ if (cookie.MUSIC_U) header.MUSIC_U = cookie.MUSIC_U;
205
+ payload.header = header;
206
+ url = EAPI_DOMAIN + uri.replace(/^\/api/, "/eapi");
207
+ headers["User-Agent"] = options?.ua ?? "NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)";
208
+ headers.Cookie = cookieToString(header);
209
+ body = new URLSearchParams({ params: eapiEncrypt(uri, payload) }).toString();
210
+ } else if (crypto === "weapi") {
211
+ payload.csrf_token = csrf;
212
+ url = `${WEAPI_DOMAIN}/weapi/${uri.slice(5)}`;
213
+ headers.Referer = WEAPI_DOMAIN;
214
+ headers["User-Agent"] = options?.ua ?? "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0";
215
+ headers.Cookie = cookieToString(cookie);
216
+ body = new URLSearchParams({ ...weapiEncrypt(payload) }).toString();
217
+ } else {
218
+ const form = new URLSearchParams();
219
+ for (const [key, value] of Object.entries(payload)) form.append(key, String(value));
220
+ url = API_DOMAIN + uri;
221
+ headers.Referer = WEAPI_DOMAIN;
222
+ headers["User-Agent"] = options?.ua ?? "NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)";
223
+ headers.Cookie = cookieToString(cookie);
224
+ body = form.toString();
225
+ }
226
+ if (options?.realIP) {
227
+ headers["X-Real-IP"] = options.realIP;
228
+ headers["X-Forwarded-For"] = options.realIP;
229
+ }
230
+ let res;
231
+ try {
232
+ res = await postForm(url, body, headers, timeout);
233
+ } catch (error) {
234
+ throw new NeteaseApiError(uri, -1, 0, {
235
+ code: -1,
236
+ msg: error instanceof Error ? error.message : String(error)
237
+ });
238
+ }
239
+ if (res.json === void 0) throw new NeteaseApiError(uri, res.status, res.status, res.text);
240
+ const code = readCode(res.json);
241
+ if (code !== void 0) {
242
+ if (!acceptCodes.includes(code)) throw new NeteaseApiError(uri, code, res.status, res.json);
243
+ return {
244
+ status: res.status,
245
+ body: res.json,
246
+ cookies: res.setCookies
247
+ };
248
+ }
249
+ if (res.status < 200 || res.status >= 300) throw new NeteaseApiError(uri, res.status, res.status, res.json);
250
+ return {
251
+ status: res.status,
252
+ body: res.json,
253
+ cookies: res.setCookies
254
+ };
255
+ }
256
+ //#endregion
257
+ //#region src/apis/adapters.ts
258
+ const toArtistRef = (raw) => ({
259
+ id: num(raw.id) ?? 0,
260
+ name: raw.name ?? ""
261
+ });
262
+ const toUserRef = (raw) => ({
263
+ id: num(raw.userId ?? raw.id) ?? 0,
264
+ name: raw.nickname ?? raw.name ?? ""
265
+ });
266
+ const toAlbumRef = (raw) => ({
267
+ id: num(raw.id) ?? 0,
268
+ name: raw.name ?? "",
269
+ coverUrl: raw.picUrl ?? raw.coverImgUrl ?? ""
270
+ });
271
+ /** id/duration 缺失的条目返回 undefined,由调用方丢弃。 */
272
+ const toSong = (raw) => {
273
+ const id = num(raw.id);
274
+ const duration = num(raw.dt ?? raw.duration);
275
+ if (id === void 0 || duration === void 0) return void 0;
276
+ const album = raw.al ?? raw.album;
277
+ const alias = raw.alia ?? raw.alias;
278
+ const fee = num(raw.fee);
279
+ const state = num(raw.st);
280
+ return {
281
+ id,
282
+ name: raw.name ?? "",
283
+ artists: (raw.ar ?? raw.artists ?? []).map(toArtistRef),
284
+ album: album ? toAlbumRef(album) : {
285
+ id: 0,
286
+ name: "",
287
+ coverUrl: ""
288
+ },
289
+ duration,
290
+ ...alias?.length ? { alias } : {},
291
+ ...fee !== void 0 ? { fee } : {},
292
+ ...state !== void 0 ? { available: state >= 0 } : {}
293
+ };
294
+ };
295
+ const toSongs = (raws) => {
296
+ const songs = [];
297
+ for (const raw of raws ?? []) {
298
+ const song = toSong(raw);
299
+ if (song) songs.push(song);
300
+ }
301
+ return songs;
302
+ };
303
+ const toArtist = (raw) => {
304
+ const albumCount = num(raw.albumSize);
305
+ const songCount = num(raw.musicSize);
306
+ const mvCount = num(raw.mvSize);
307
+ const avatarUrl = raw.picUrl ?? raw.img1v1Url ?? raw.avatar;
308
+ return {
309
+ id: num(raw.id) ?? 0,
310
+ name: raw.name ?? "",
311
+ ...raw.alias?.length ? { alias: raw.alias } : {},
312
+ ...avatarUrl ? { avatarUrl } : {},
313
+ ...raw.briefDesc ? { briefDesc: raw.briefDesc } : {},
314
+ ...albumCount !== void 0 ? { albumCount } : {},
315
+ ...songCount !== void 0 ? { songCount } : {},
316
+ ...mvCount !== void 0 ? { mvCount } : {},
317
+ ...raw.followed !== void 0 ? { followed: raw.followed } : {}
318
+ };
319
+ };
320
+ const toArtists = (raws) => (raws ?? []).map(toArtist);
321
+ const toAlbum = (raw) => {
322
+ const publishTime = num(raw.publishTime);
323
+ const size = num(raw.size);
324
+ const description = raw.description ?? raw.desc;
325
+ const artists = raw.artists ?? raw.ar;
326
+ return {
327
+ id: num(raw.id) ?? 0,
328
+ name: raw.name ?? "",
329
+ coverUrl: raw.picUrl ?? raw.coverImgUrl ?? "",
330
+ ...artists?.length ? { artists: artists.map(toArtistRef) } : {},
331
+ ...publishTime !== void 0 ? { publishTime } : {},
332
+ ...raw.company ? { company: raw.company } : {},
333
+ ...description ? { description } : {},
334
+ ...size !== void 0 ? { size } : {}
335
+ };
336
+ };
337
+ const toAlbums = (raws) => (raws ?? []).map(toAlbum);
338
+ const toPlaylistSummary = (raw) => {
339
+ const trackCount = num(raw.trackCount);
340
+ const playCount = num(raw.playCount);
341
+ const updateTime = num(raw.updateTime);
342
+ return {
343
+ id: num(raw.id) ?? 0,
344
+ name: raw.name ?? "",
345
+ coverUrl: raw.coverImgUrl ?? "",
346
+ ...trackCount !== void 0 ? { trackCount } : {},
347
+ ...playCount !== void 0 ? { playCount } : {},
348
+ ...raw.creator ? { creator: toUserRef(raw.creator) } : {},
349
+ ...raw.description ? { description: raw.description } : {},
350
+ ...updateTime !== void 0 ? { updateTime } : {},
351
+ ...raw.tags?.length ? { tags: raw.tags } : {}
352
+ };
353
+ };
354
+ const toPlaylistSummaries = (raws) => (raws ?? []).map(toPlaylistSummary);
355
+ const toMvRef = (raw) => {
356
+ const playCount = num(raw.playCount);
357
+ const duration = num(raw.duration);
358
+ return {
359
+ id: num(raw.id) ?? 0,
360
+ name: raw.name ?? "",
361
+ coverUrl: raw.cover ?? raw.picUrl ?? raw.imgurl16v9 ?? raw.imgurl ?? "",
362
+ ...playCount !== void 0 ? { playCount } : {},
363
+ ...duration !== void 0 ? { duration } : {},
364
+ ...raw.artistName ? { artistName: raw.artistName } : {}
365
+ };
366
+ };
367
+ const toMvRefs = (raws) => (raws ?? []).map(toMvRef);
368
+ const toVideoRef = (raw) => {
369
+ const playCount = num(raw.playCount);
370
+ const duration = num(raw.duration);
371
+ return {
372
+ id: num(raw.vid ?? raw.id) ?? 0,
373
+ name: raw.title ?? raw.name ?? "",
374
+ coverUrl: raw.coverUrl ?? "",
375
+ ...playCount !== void 0 ? { playCount } : {},
376
+ ...duration !== void 0 ? { duration } : {},
377
+ ...raw.creator ? { creator: toUserRef(raw.creator) } : {}
378
+ };
379
+ };
380
+ const toVideoRefs = (raws) => (raws ?? []).map(toVideoRef);
381
+ //#endregion
382
+ //#region src/apis/require.ts
383
+ /** 上游响应缺少预期容器时直接报错,不返回伪造的空结果。 */
384
+ const requireField = (value, uri, status, field) => {
385
+ if (value === void 0) throw new NeteaseApiError(uri, -1, status, {
386
+ code: -1,
387
+ msg: `响应缺少 ${field}`
388
+ });
389
+ return value;
390
+ };
391
+ //#endregion
392
+ //#region src/apis/album.ts
393
+ const URI_ALBUM = "/api/v1/album";
394
+ const URI_ALBUM_PRODUCT = "/api/vipmall/albumproduct/detail";
395
+ const URI_ALBUM_DYNAMIC = "/api/album/detail/dynamic";
396
+ const URI_ALBUM_SALE_BOARD = "/api/feealbum/songsaleboard";
397
+ const URI_ALBUM_PRIVILEGE = "/api/album/privilege";
398
+ const URI_ALBUM_LIST = "/api/vipmall/albumproduct/list";
399
+ const URI_NEW_ALBUM = "/api/discovery/newAlbum";
400
+ /** 数字专辑/单曲产品与销售榜条目共用的「产品式」字段命名。 */
401
+ const toProductAlbum = (raw) => {
402
+ const publishTime = num(raw.publishTime ?? raw.pubTime);
403
+ return {
404
+ id: num(raw.albumId ?? raw.id) ?? 0,
405
+ name: raw.albumName ?? raw.name ?? "",
406
+ coverUrl: raw.coverUrl ?? raw.picUrl ?? "",
407
+ ...publishTime !== void 0 ? { publishTime } : {}
408
+ };
409
+ };
410
+ const toAlbumPrivilege = (raw) => {
411
+ const pl = num(raw.pl);
412
+ const dl = num(raw.dl);
413
+ const fl = num(raw.fl);
414
+ const st = num(raw.st);
415
+ const fee = num(raw.fee);
416
+ return {
417
+ id: num(raw.id) ?? 0,
418
+ ...raw.maxBrLevel ? { maxBrLevel: raw.maxBrLevel } : {},
419
+ ...raw.playMaxBrLevel ? { playMaxBrLevel: raw.playMaxBrLevel } : {},
420
+ ...raw.downloadMaxBrLevel ? { downloadMaxBrLevel: raw.downloadMaxBrLevel } : {},
421
+ ...pl !== void 0 ? { pl } : {},
422
+ ...dl !== void 0 ? { dl } : {},
423
+ ...fl !== void 0 ? { fl } : {},
424
+ ...st !== void 0 ? { st } : {},
425
+ ...fee !== void 0 ? { fee } : {}
426
+ };
427
+ };
428
+ const getAlbum = async (id, options) => {
429
+ const uri = `${URI_ALBUM}/${id}`;
430
+ const res = await request(uri, {}, "weapi", options);
431
+ const body = res.body;
432
+ return {
433
+ ...toAlbum(requireField(body.album, uri, res.status, "album")),
434
+ ...body.songs ? { songs: toSongs(body.songs) } : {}
435
+ };
436
+ };
437
+ const getAlbumProduct = async (id, options) => {
438
+ const res = await request(URI_ALBUM_PRODUCT, { id }, "weapi", options);
439
+ const body = res.body;
440
+ const album = requireField(body.album, URI_ALBUM_PRODUCT, res.status, "album");
441
+ const price = num(body.product?.price);
442
+ const publishTime = num(body.product?.pubTime);
443
+ return {
444
+ id: num(album.albumId) ?? 0,
445
+ name: album.albumName ?? "",
446
+ coverUrl: album.coverUrl ?? "",
447
+ ...album.artistName ? { artistName: album.artistName } : {},
448
+ ...price !== void 0 ? { price } : {},
449
+ ...publishTime !== void 0 ? { publishTime } : {}
450
+ };
451
+ };
452
+ const getAlbumDynamic = async (id, options) => {
453
+ const body = (await request(URI_ALBUM_DYNAMIC, { id }, "weapi", options)).body;
454
+ const commentCount = num(body.commentCount);
455
+ const shareCount = num(body.shareCount);
456
+ const subCount = num(body.subCount);
457
+ const likedCount = num(body.likedCount);
458
+ return {
459
+ ...commentCount !== void 0 ? { commentCount } : {},
460
+ ...shareCount !== void 0 ? { shareCount } : {},
461
+ ...subCount !== void 0 ? { subCount } : {},
462
+ ...likedCount !== void 0 ? { likedCount } : {},
463
+ ...body.isSub !== void 0 ? { isSub: body.isSub } : {}
464
+ };
465
+ };
466
+ const getAlbumSaleBoard = async (params, options) => {
467
+ const type = params.type ?? "daily";
468
+ const uri = `${URI_ALBUM_SALE_BOARD}/${type}/type`;
469
+ const data = { albumType: params.albumType ?? 0 };
470
+ if (type === "year") data.year = params.year;
471
+ return { albums: ((await request(uri, data, "weapi", options)).body.products ?? []).map(toProductAlbum) };
472
+ };
473
+ const getAlbumPrivileges = async (id, options) => {
474
+ return ((await request(URI_ALBUM_PRIVILEGE, { id }, "eapi", options)).body.data ?? []).map(toAlbumPrivilege);
475
+ };
476
+ const getAlbumList = async (params, options) => {
477
+ const body = (await request(URI_ALBUM_LIST, {
478
+ limit: params.limit ?? 30,
479
+ offset: params.offset ?? 0,
480
+ total: true,
481
+ area: params.area ?? "ALL",
482
+ type: params.type
483
+ }, "weapi", options)).body;
484
+ const total = num(body.total);
485
+ const products = body.products ?? body.albums;
486
+ return {
487
+ ...total !== void 0 ? { total } : {},
488
+ ...body.more !== void 0 ? { more: body.more } : {},
489
+ albums: products?.some(hasProductFields) ? products.map(toProductAlbum) : toAlbums(body.albums)
490
+ };
491
+ };
492
+ const PRODUCT_KEYS = [
493
+ "albumId",
494
+ "albumName",
495
+ "artistName"
496
+ ];
497
+ const hasProductFields = (raw) => PRODUCT_KEYS.some((key) => key in raw);
498
+ const getNewestAlbums = async (options) => {
499
+ const body = (await request(URI_NEW_ALBUM, {}, "weapi", options)).body;
500
+ return toAlbums(body.albums);
501
+ };
502
+ //#endregion
503
+ //#region src/apis/artist.ts
504
+ const URI_ARTIST = "/api/v1/artist";
505
+ const URI_ARTIST_HEAD_INFO = "/api/artist/head/info/get";
506
+ const URI_ARTIST_SONGS = "/api/v1/artist/songs";
507
+ const URI_ARTIST_TOP_SONG = "/api/artist/top/song";
508
+ const URI_ARTIST_ALBUMS = "/api/artist/albums";
509
+ const URI_ARTIST_LIST = "/api/v1/artist/list";
510
+ const URI_ARTIST_INTRO = "/api/artist/introduction";
511
+ const URI_ARTIST_MVS = "/api/artist/mvs";
512
+ const URI_ARTIST_VIDEO = "/api/mlog/artist/video";
513
+ const toArtistInitial = (value) => {
514
+ if (typeof value !== "string" || !Number.isNaN(Number(value))) return value;
515
+ return value.toUpperCase().charCodeAt(0) || void 0;
516
+ };
517
+ const getArtist = async (id, options) => {
518
+ const uri = `${URI_ARTIST}/${id}`;
519
+ const res = await request(uri, {}, "weapi", options);
520
+ const body = res.body;
521
+ return {
522
+ ...toArtist(requireField(body.artist, uri, res.status, "artist")),
523
+ ...body.hotSongs ? { topSongs: toSongs(body.hotSongs) } : {}
524
+ };
525
+ };
526
+ const getArtistDetail = async (id, options) => {
527
+ const res = await request(URI_ARTIST_HEAD_INFO, { id }, "eapi", options);
528
+ const body = res.body;
529
+ return toArtist(body.data?.artist ?? body.data ?? requireField(body.artist, URI_ARTIST_HEAD_INFO, res.status, "artist"));
530
+ };
531
+ const getArtistSongs = async (params, options) => {
532
+ const body = (await request(URI_ARTIST_SONGS, {
533
+ id: params.id,
534
+ private_cloud: "true",
535
+ work_type: 1,
536
+ order: params.order ?? "hot",
537
+ offset: params.offset ?? 0,
538
+ limit: params.limit ?? 100
539
+ }, "eapi", options)).body;
540
+ return toSongs(body.songs);
541
+ };
542
+ const getArtistTopSongs = async (id, options) => {
543
+ const body = (await request(URI_ARTIST_TOP_SONG, { id }, "weapi", options)).body;
544
+ return toSongs(body.songs);
545
+ };
546
+ const getArtistAlbums = async (params, options) => {
547
+ const body = (await request(`${URI_ARTIST_ALBUMS}/${params.id}`, {
548
+ limit: params.limit ?? 30,
549
+ offset: params.offset ?? 0,
550
+ total: true
551
+ }, "weapi", options)).body;
552
+ const total = num(body.total);
553
+ return {
554
+ ...total !== void 0 ? { total } : {},
555
+ ...body.more !== void 0 ? { more: body.more } : {},
556
+ albums: toAlbums(body.hotAlbums)
557
+ };
558
+ };
559
+ const getArtistList = async (params, options) => {
560
+ const body = (await request(URI_ARTIST_LIST, {
561
+ initial: toArtistInitial(params.initial),
562
+ offset: params.offset ?? 0,
563
+ limit: params.limit ?? 30,
564
+ total: true,
565
+ type: params.type ?? "1",
566
+ area: params.area
567
+ }, "weapi", options)).body;
568
+ const total = num(body.total ?? body.artistCount);
569
+ return {
570
+ ...total !== void 0 ? { total } : {},
571
+ ...body.more !== void 0 ? { more: body.more } : {},
572
+ artists: toArtists(body.artists)
573
+ };
574
+ };
575
+ const getArtistDesc = async (id, options) => {
576
+ const body = (await request(URI_ARTIST_INTRO, { id }, "weapi", options)).body;
577
+ return {
578
+ briefDesc: body.briefDesc ?? "",
579
+ sections: (body.introduction ?? []).map((item) => ({
580
+ title: item.ti ?? "",
581
+ text: item.tx ?? ""
582
+ }))
583
+ };
584
+ };
585
+ const getArtistMvs = async (params, options) => {
586
+ const body = (await request(URI_ARTIST_MVS, {
587
+ artistId: params.id,
588
+ limit: params.limit,
589
+ offset: params.offset,
590
+ total: true
591
+ }, "weapi", options)).body;
592
+ return toMvRefs(body.mvs);
593
+ };
594
+ const getArtistVideos = async (params, options) => {
595
+ const body = (await request(URI_ARTIST_VIDEO, {
596
+ artistId: params.id,
597
+ page: JSON.stringify({
598
+ size: params.size ?? 10,
599
+ cursor: params.cursor ?? 0
600
+ }),
601
+ tab: 0,
602
+ order: params.order ?? 0
603
+ }, "weapi", options)).body;
604
+ const data = body.data ?? {};
605
+ const videos = [];
606
+ for (const record of data.records ?? []) {
607
+ const base = record.resource?.mlogBaseData;
608
+ const id = num(base?.id);
609
+ if (id === void 0) continue;
610
+ const playCount = num(record.resource?.mlogExtVO?.playCount);
611
+ const duration = num(base?.duration);
612
+ videos.push({
613
+ id,
614
+ name: base?.text ?? "",
615
+ coverUrl: base?.coverUrl ?? "",
616
+ ...playCount !== void 0 ? { playCount } : {},
617
+ ...duration !== void 0 ? { duration } : {}
618
+ });
619
+ }
620
+ return {
621
+ hasMore: data.page?.more ?? body.hasMore ?? false,
622
+ videos
623
+ };
624
+ };
625
+ //#endregion
626
+ //#region src/utils/parseRelatedPlaylists.ts
627
+ const RELATED_PLAYLIST = /<div class="cver u-cover u-cover-3">[\s\S]*?<img src="([^"]+)">[\s\S]*?<a class="sname f-fs1 s-fc0" href="([^"]+)"[^>]*>([^<]+?)<\/a>[\s\S]*?<a class="nm nm f-thide s-fc3" href="([^"]+)"[^>]*>([^<]+?)<\/a>/g;
628
+ const parseRelatedPlaylists = (html) => {
629
+ const playlists = [];
630
+ for (const match of html.matchAll(RELATED_PLAYLIST)) {
631
+ const [, cover = "", playlistHref = "", name = "", userHref = "", user = ""] = match;
632
+ playlists.push({
633
+ id: Number(playlistHref.replace("/playlist?id=", "")),
634
+ name,
635
+ coverUrl: cover.replace(/\?param=.*$/, ""),
636
+ creator: {
637
+ id: Number(userHref.replace("/user/home?id=", "")),
638
+ name: user
639
+ }
640
+ });
641
+ }
642
+ return playlists;
643
+ };
644
+ //#endregion
5
645
  //#region src/utils/mergeLyricTimelines.ts
6
646
  function mergeLyricTimelines(original, translation) {
7
647
  const originalMap = /* @__PURE__ */ new Map();
@@ -27,64 +667,498 @@ function mergeLyricTimelines(original, translation) {
27
667
  return result;
28
668
  }
29
669
  //#endregion
30
- //#region src/index.ts
31
- const BASE_URL = "https://music.163.com/api/";
32
- const getPlaylistDetail = async (id) => {
33
- const data = await (await fetch(`${BASE_URL}/v6/playlist/detail?id=${id}`)).json();
34
- return {
35
- id: data.playlist.id,
36
- name: data.playlist.name,
37
- coverImgId: data.playlist.coverImgId,
38
- coverImgUrl: data.playlist.coverImgUrl,
39
- userId: data.playlist.userId,
40
- createTime: data.playlist.createTime,
41
- songs: data.playlist.trackIds.map((t) => ({
42
- id: t.id,
43
- addTime: t.at
44
- }))
45
- };
46
- };
670
+ //#region src/utils/parseLrc.ts
47
671
  const parseLrc = (lrc) => lrc.split("\n").filter((l) => l.startsWith("[")).map((l) => {
48
672
  const match = l.match(/^\[(\d{2}):(\d{2}(?:\.\d{2,3})?)\](.*)/);
49
673
  if (!match) return null;
50
- const minutes = parseInt(match[1], 10);
51
- const seconds = parseFloat(match[2]);
674
+ const [, minutes = "0", seconds = "0", text = ""] = match;
52
675
  return {
53
- time: minutes * 60 + seconds,
54
- text: match[3].trim()
676
+ time: parseInt(minutes, 10) * 60 + parseFloat(seconds),
677
+ text: text.trim()
55
678
  };
56
679
  }).filter((e) => e !== null);
57
- const getLyric = async (id) => {
58
- const data = await (await fetch(`${BASE_URL}/song/lyric?id=${id}&lv=-1&tv=-1`)).json();
59
- return {
60
- lines: mergeLyricTimelines(parseLrc(data.lrc.lyric), data.tlyric?.lyric ? parseLrc(data.tlyric.lyric) : []),
61
- ...data.transUser ? { translator: data.transUser } : {}
62
- };
63
- };
64
- const getSongsDetail = async (ids) => {
65
- const query = JSON.stringify(ids.map((id) => ({ id })));
66
- return (await (await fetch(`${BASE_URL}/v3/song/detail?c=${query}`)).json()).songs.map((s) => ({
67
- id: s.id,
68
- name: s.name,
69
- artists: s.ar.map((a) => ({
70
- id: a.id,
71
- name: a.name
72
- })),
73
- album: {
74
- id: s.al.id,
75
- name: s.al.name,
76
- picUrl: s.al.picUrl
77
- },
78
- duration: s.dt
680
+ //#endregion
681
+ //#region src/utils/parseYrc.ts
682
+ const YRC_LINE = /^\[(\d+),(\d+)\](.*)$/;
683
+ const YRC_WORD = /(\d+),(\d+),\d+,(\d+)\)([^()]*)/g;
684
+ const parseYrc = (yrc) => {
685
+ const lines = [];
686
+ for (const raw of yrc.split("\n")) {
687
+ const match = raw.match(YRC_LINE);
688
+ if (!match) continue;
689
+ const [, start = "0", span = "0", body = ""] = match;
690
+ const words = [];
691
+ for (const word of body.matchAll(YRC_WORD)) {
692
+ const [, wordStart = "0", , wordSpan = "0", text = ""] = word;
693
+ words.push({
694
+ time: Number(wordStart),
695
+ duration: Number(wordSpan),
696
+ text
697
+ });
698
+ }
699
+ lines.push({
700
+ time: Number(start),
701
+ duration: Number(span),
702
+ words
703
+ });
704
+ }
705
+ return lines;
706
+ };
707
+ //#endregion
708
+ //#region src/apis/song.ts
709
+ const URI_SONG_DETAIL = "/api/v3/song/detail";
710
+ const URI_SONG_URL = "/api/song/enhance/player/url";
711
+ const URI_LYRIC = "/api/song/lyric";
712
+ const URI_LYRIC_NEW = "/api/song/lyric/v1";
713
+ const URI_SIMI_SONG = "/api/link/position/show/resource";
714
+ const buildSongDetailQuery = (ids) => `[${ids.map((id) => `{"id":${id}}`).join(",")}]`;
715
+ const getSongsDetail = async (ids, options) => {
716
+ const body = (await request(URI_SONG_DETAIL, { c: buildSongDetailQuery(ids) }, "weapi", options)).body;
717
+ return toSongs(body.songs);
718
+ };
719
+ const readFreeTrialInfo = (raw) => {
720
+ if (!raw) return void 0;
721
+ const start = num(raw.start);
722
+ const end = num(raw.end);
723
+ if (start === void 0 || end === void 0) return void 0;
724
+ return {
725
+ start,
726
+ end
727
+ };
728
+ };
729
+ const getSongUrl = async (params, options) => {
730
+ const ids = String(params.id).split(",");
731
+ const items = [...(await request(URI_SONG_URL, {
732
+ ids: JSON.stringify(ids),
733
+ br: params.br ?? 999e3
734
+ }, "eapi", options)).body.data ?? []].sort((a, b) => ids.indexOf(String(a.id)) - ids.indexOf(String(b.id)));
735
+ const urls = [];
736
+ for (const item of items) {
737
+ const id = num(item.id);
738
+ if (id === void 0) continue;
739
+ const fee = num(item.fee);
740
+ const freeTrialInfo = readFreeTrialInfo(item.freeTrialInfo);
741
+ urls.push({
742
+ id,
743
+ url: item.url ?? null,
744
+ br: num(item.br) ?? 0,
745
+ size: num(item.size) ?? 0,
746
+ ...item.level ? { level: item.level } : {},
747
+ ...fee !== void 0 ? { fee } : {},
748
+ ...freeTrialInfo !== void 0 ? { freeTrialInfo } : {}
749
+ });
750
+ }
751
+ return urls;
752
+ };
753
+ const getLyric = async (id, options) => {
754
+ const body = (await request(URI_LYRIC, {
755
+ id,
756
+ tv: -1,
757
+ lv: -1,
758
+ rv: -1,
759
+ kv: -1,
760
+ _nmclfl: 1
761
+ }, "eapi", options)).body;
762
+ const translationText = body.tlyric?.lyric;
763
+ const translatorId = num(body.transUser?.id);
764
+ return {
765
+ lines: mergeLyricTimelines(parseLrc(body.lrc?.lyric ?? ""), translationText ? parseLrc(translationText) : []),
766
+ ...translatorId !== void 0 ? { translator: {
767
+ id: translatorId,
768
+ nickname: body.transUser?.nickname ?? ""
769
+ } } : {}
770
+ };
771
+ };
772
+ const getLyricNew = async (id, options) => {
773
+ const body = (await request(URI_LYRIC_NEW, {
774
+ id,
775
+ cp: false,
776
+ tv: 0,
777
+ lv: 0,
778
+ rv: 0,
779
+ kv: 0,
780
+ yv: 0,
781
+ ytv: 0,
782
+ yrv: 0
783
+ }, "eapi", options)).body;
784
+ const translationText = body.tlyric?.lyric;
785
+ const romaText = body.romalrc?.lyric;
786
+ const yrcText = body.yrc?.lyric;
787
+ const wordLines = yrcText ? parseYrc(yrcText) : void 0;
788
+ return {
789
+ lines: mergeLyricTimelines(parseLrc(body.lrc?.lyric ?? ""), translationText ? parseLrc(translationText) : []),
790
+ ...wordLines ? { wordLines } : {},
791
+ ...romaText ? { romaLines: parseLrc(romaText) } : {}
792
+ };
793
+ };
794
+ const checkMusic = async (params, options) => {
795
+ const body = (await request(URI_SONG_URL, {
796
+ ids: `[${params.id}]`,
797
+ br: params.br ?? 999e3
798
+ }, "weapi", options)).body;
799
+ const first = body.data?.[0];
800
+ const available = num(body.code) === 200 && num(first?.code) === 200;
801
+ return {
802
+ available,
803
+ message: available ? "ok" : first?.message ?? "亲爱的,暂无版权"
804
+ };
805
+ };
806
+ const getSimilarSongs = async (id, options) => {
807
+ const res = await request(URI_SIMI_SONG, {
808
+ positionCode: "toolBarRcmdSong",
809
+ resourceId: id,
810
+ resourceType: "song"
811
+ }, "eapi", options);
812
+ const body = res.body;
813
+ const ids = [];
814
+ for (const item of body.data?.commonResourceList ?? []) {
815
+ const resourceId = num(item.extraMap?.songId ?? item.resourceId);
816
+ if (resourceId !== void 0) ids.push(resourceId);
817
+ }
818
+ if (ids.length > 0) return getSongsDetail(ids, options);
819
+ const songs = toSongs(body.songs);
820
+ if (songs.length > 0) return songs;
821
+ throw new NeteaseApiError(URI_SIMI_SONG, -1, res.status, {
822
+ code: -1,
823
+ msg: "相似歌曲响应中未找到歌曲 id"
824
+ });
825
+ };
826
+ //#endregion
827
+ //#region src/apis/playlist.ts
828
+ const URI_PLAYLIST_DETAIL = "/api/v6/playlist/detail";
829
+ const URI_PLAYLIST_DYNAMIC = "/api/playlist/detail/dynamic";
830
+ const URI_HIGH_QUALITY_TAGS = "/api/playlist/highquality/tags";
831
+ const URI_PLAYLIST_LIST = "/api/playlist/list";
832
+ const URI_HIGH_QUALITY_LIST = "/api/playlist/highquality/list";
833
+ const URI_PLAYLIST_CATALOGUE = "/api/playlist/catalogue";
834
+ const getPlaylistDetail = async (id, options) => {
835
+ const { playlist } = (await request(URI_PLAYLIST_DETAIL, {
836
+ id,
837
+ n: 1e5,
838
+ s: 8
839
+ }, "eapi", options)).body;
840
+ const songs = [];
841
+ for (const track of playlist.trackIds ?? []) {
842
+ const trackId = num(track.id);
843
+ const addTime = num(track.at);
844
+ if (trackId === void 0 || addTime === void 0) continue;
845
+ songs.push({
846
+ id: trackId,
847
+ addTime
848
+ });
849
+ }
850
+ return {
851
+ id: num(playlist.id) ?? 0,
852
+ name: playlist.name ?? "",
853
+ creatorId: num(playlist.userId) ?? 0,
854
+ coverUrl: playlist.coverImgUrl ?? "",
855
+ createTime: num(playlist.createTime) ?? 0,
856
+ songs
857
+ };
858
+ };
859
+ const getPlaylistTracks = async (params, options) => {
860
+ const { playlist } = (await request(URI_PLAYLIST_DETAIL, {
861
+ id: params.id,
862
+ n: 1e5,
863
+ s: 8
864
+ }, "eapi", options)).body;
865
+ const offset = params.offset ?? 0;
866
+ const limit = params.limit ?? 1e3;
867
+ const ids = [];
868
+ for (const track of (playlist.trackIds ?? []).slice(offset, offset + limit)) {
869
+ const trackId = num(track.id);
870
+ if (trackId !== void 0) ids.push(trackId);
871
+ }
872
+ return getSongsDetail(ids, options);
873
+ };
874
+ const getPlaylistDetailDynamic = async (id, options) => {
875
+ const body = (await request(URI_PLAYLIST_DYNAMIC, {
876
+ id,
877
+ n: 1e5,
878
+ s: 8
879
+ }, "eapi", options)).body;
880
+ const commentCount = num(body.commentCount);
881
+ const shareCount = num(body.shareCount);
882
+ const playCount = num(body.playCount);
883
+ const subscribedCount = num(body.subscribedCount);
884
+ const playedCount = num(body.playedCount);
885
+ return {
886
+ ...commentCount !== void 0 ? { commentCount } : {},
887
+ ...shareCount !== void 0 ? { shareCount } : {},
888
+ ...playCount !== void 0 ? { playCount } : {},
889
+ ...subscribedCount !== void 0 ? { subscribedCount } : {},
890
+ ...playedCount !== void 0 ? { playedCount } : {}
891
+ };
892
+ };
893
+ const getHighQualityTags = async (options) => {
894
+ return ((await request(URI_HIGH_QUALITY_TAGS, {}, "weapi", options)).body.tags ?? []).map((tag) => ({
895
+ name: tag.name ?? "",
896
+ hot: tag.hot ?? false
897
+ }));
898
+ };
899
+ const toPlaylistPage = (body) => {
900
+ const total = num(body.total);
901
+ return {
902
+ ...total !== void 0 ? { total } : {},
903
+ ...body.more !== void 0 ? { more: body.more } : {},
904
+ playlists: toPlaylistSummaries(body.playlists)
905
+ };
906
+ };
907
+ const getTopPlaylists = async (params, options) => {
908
+ return toPlaylistPage((await request(URI_PLAYLIST_LIST, {
909
+ cat: params.cat ?? "全部",
910
+ order: params.order ?? "hot",
911
+ limit: params.limit ?? 50,
912
+ offset: params.offset ?? 0,
913
+ total: true
914
+ }, "weapi", options)).body);
915
+ };
916
+ const getHighQualityPlaylists = async (params, options) => {
917
+ return toPlaylistPage((await request(URI_HIGH_QUALITY_LIST, {
918
+ cat: params.cat ?? "全部",
919
+ limit: params.limit ?? 50,
920
+ lasttime: params.before ?? 0,
921
+ total: true
922
+ }, "weapi", options)).body);
923
+ };
924
+ const getPlaylistCategories = async (options) => {
925
+ const body = (await request(URI_PLAYLIST_CATALOGUE, {}, "eapi", options)).body;
926
+ const categories = body.categories ?? {};
927
+ const sub = body.sub ?? [];
928
+ return Object.keys(categories).sort((a, b) => Number(a) - Number(b)).map((key) => {
929
+ const category = Number(key);
930
+ return {
931
+ name: categories[key] ?? "",
932
+ subcategories: sub.filter((item) => num(item.category) === category).map((item) => ({
933
+ name: item.name ?? "",
934
+ category
935
+ }))
936
+ };
937
+ });
938
+ };
939
+ const getRelatedPlaylists = async (id, options) => {
940
+ const url = `${WEAPI_DOMAIN}/playlist?id=${id}`;
941
+ const res = await getText(url, options?.timeout ?? 1e4);
942
+ if (res.status !== 200) throw new NeteaseApiError(url, -1, res.status, {
943
+ code: -1,
944
+ msg: res.text.slice(0, 200)
945
+ });
946
+ const playlists = parseRelatedPlaylists(res.text);
947
+ if (playlists.length === 0 || playlists.some((item) => !item.coverUrl)) throw new NeteaseApiError(url, -1, res.status, {
948
+ code: -1,
949
+ msg: "相关歌单页面结构可能已变化"
950
+ });
951
+ return playlists;
952
+ };
953
+ //#endregion
954
+ //#region src/apis/search.ts
955
+ const URI_SEARCH = "/api/search/get";
956
+ const URI_SEARCH_VOICE = "/api/search/voice/get";
957
+ const URI_CLOUD_SEARCH = "/api/cloudsearch/pc";
958
+ const URI_SEARCH_SUGGEST = "/api/search/suggest/web";
959
+ const URI_SEARCH_SUGGEST_KEYWORD = "/api/search/suggest/keyword";
960
+ const URI_SEARCH_HOT = "/api/search/hot";
961
+ const URI_HOT_SEARCH_DETAIL = "/api/hotsearchlist/get";
962
+ const URI_DEFAULT_KEYWORD = "/api/search/defaultkeyword/get";
963
+ const URI_SEARCH_MULTIMATCH = "/api/search/suggest/multimatch";
964
+ const toSearchResult = (raw, total) => {
965
+ const lyrics = raw.lyrics?.map((item) => ({
966
+ id: num(item.id) ?? 0,
967
+ name: item.name ?? "",
968
+ artists: (item.artists ?? []).map(toArtistRef)
969
+ }));
970
+ return {
971
+ ...raw.songs ? { songs: toSongs(raw.songs) } : {},
972
+ ...raw.artists ? { artists: toArtists(raw.artists) } : {},
973
+ ...raw.albums ? { albums: toAlbums(raw.albums) } : {},
974
+ ...raw.playlists ? { playlists: toPlaylistSummaries(raw.playlists) } : {},
975
+ ...raw.userprofiles ? { users: raw.userprofiles.map(toUserRef) } : {},
976
+ ...raw.mvs ? { mvs: toMvRefs(raw.mvs) } : {},
977
+ ...raw.videos ? { videos: toVideoRefs(raw.videos) } : {},
978
+ ...lyrics ? { lyrics } : {},
979
+ ...total !== void 0 ? { total } : {}
980
+ };
981
+ };
982
+ const toVoiceSearchResult = (raw, status) => {
983
+ const container = raw.result ?? {};
984
+ const songs = container.songs ?? raw.songs;
985
+ if (songs) return toSearchResult({
986
+ ...container,
987
+ songs
988
+ }, num(container.songCount ?? raw.total));
989
+ const resources = container.resources ?? raw.resources;
990
+ if (resources) {
991
+ const radios = [];
992
+ for (const item of resources) {
993
+ const id = num(item.id ?? item.resourceId);
994
+ if (id === void 0) continue;
995
+ radios.push({
996
+ id,
997
+ name: item.name ?? item.title ?? "",
998
+ coverUrl: item.coverUrl ?? item.picUrl ?? "",
999
+ ...item.djName ? { djName: item.djName } : {}
1000
+ });
1001
+ }
1002
+ return { radios };
1003
+ }
1004
+ throw new NeteaseApiError(URI_SEARCH_VOICE, -1, status, {
1005
+ code: -1,
1006
+ msg: "响应中既没有 songs 也没有 resources"
1007
+ });
1008
+ };
1009
+ const search = async (params, options) => {
1010
+ if (params.type === 2e3) {
1011
+ const voice = await request(URI_SEARCH_VOICE, {
1012
+ keyword: params.keywords,
1013
+ scene: "normal",
1014
+ limit: params.limit ?? 30,
1015
+ offset: params.offset ?? 0
1016
+ }, "eapi", options);
1017
+ return toVoiceSearchResult(voice.body, voice.status);
1018
+ }
1019
+ const body = (await request(URI_SEARCH, {
1020
+ s: params.keywords,
1021
+ type: params.type ?? 1,
1022
+ limit: params.limit ?? 30,
1023
+ offset: params.offset ?? 0
1024
+ }, "eapi", options)).body;
1025
+ const container = body.result ?? {};
1026
+ return toSearchResult(container, num(container.songCount ?? container.albumCount ?? body.total));
1027
+ };
1028
+ const cloudSearch = async (params, options) => {
1029
+ const body = (await request(URI_CLOUD_SEARCH, {
1030
+ s: params.keywords,
1031
+ type: params.type ?? 1,
1032
+ limit: params.limit ?? 30,
1033
+ offset: params.offset ?? 0,
1034
+ total: true
1035
+ }, "eapi", options)).body;
1036
+ const container = body.result ?? {};
1037
+ return toSearchResult(container, num(container.songCount ?? container.albumCount ?? body.total));
1038
+ };
1039
+ const getSearchSuggest = async (params, options) => {
1040
+ const container = (await request(params.mobile ? URI_SEARCH_SUGGEST_KEYWORD : URI_SEARCH_SUGGEST, { s: params.keywords ?? "" }, "weapi", options)).body.result ?? {};
1041
+ return {
1042
+ keywords: container.order ?? [],
1043
+ songs: toSongs(container.songs),
1044
+ artists: toArtists(container.artists),
1045
+ albums: toAlbums(container.albums)
1046
+ };
1047
+ };
1048
+ const getHotSearches = async (options) => {
1049
+ return ((await request(URI_SEARCH_HOT, { type: 1111 }, "eapi", options)).body.result?.hots ?? []).map((item) => ({
1050
+ first: item.first ?? "",
1051
+ ...item.second !== void 0 && item.second !== null ? { second: item.second } : {},
1052
+ ...typeof item.third === "string" ? { third: item.third } : {}
1053
+ }));
1054
+ };
1055
+ const getHotSearchDetail = async (options) => {
1056
+ return ((await request(URI_HOT_SEARCH_DETAIL, {}, "weapi", options)).body.data ?? []).map((item, index) => ({
1057
+ keyword: item.searchWord ?? "",
1058
+ position: num(item.position) ?? index + 1,
1059
+ ...item.content ? { content: item.content } : {},
1060
+ ...item.iconUrl ? { iconUrl: item.iconUrl } : {}
79
1061
  }));
80
1062
  };
1063
+ const getDefaultSearchKeyword = async (options) => {
1064
+ const body = (await request(URI_DEFAULT_KEYWORD, {}, "eapi", options)).body;
1065
+ return body.data?.realkeyword ?? body.data?.showKeyword ?? "";
1066
+ };
1067
+ const searchMultimatch = async (params, options) => {
1068
+ const container = (await request(URI_SEARCH_MULTIMATCH, {
1069
+ type: params.type ?? 1,
1070
+ s: params.keywords ?? ""
1071
+ }, "weapi", options)).body.result ?? {};
1072
+ const songs = container.songs ?? container.song;
1073
+ const artists = container.artists ?? container.artist;
1074
+ const albums = container.albums ?? container.album;
1075
+ const playlists = container.playlists ?? container.playlist;
1076
+ return {
1077
+ ...songs ? { songs: toSongs(songs) } : {},
1078
+ ...artists ? { artists: toArtists(artists) } : {},
1079
+ ...albums ? { albums: toAlbums(albums) } : {},
1080
+ ...playlists ? { playlists: toPlaylistSummaries(playlists) } : {}
1081
+ };
1082
+ };
1083
+ //#endregion
1084
+ //#region src/index.ts
81
1085
  var src_default = {
82
- getPlaylistDetail,
1086
+ search,
1087
+ cloudSearch,
1088
+ getSearchSuggest,
1089
+ getHotSearches,
1090
+ getHotSearchDetail,
1091
+ getDefaultSearchKeyword,
1092
+ searchMultimatch,
1093
+ getSongsDetail,
1094
+ getSongUrl,
83
1095
  getLyric,
84
- getSongsDetail
1096
+ getLyricNew,
1097
+ checkMusic,
1098
+ getSimilarSongs,
1099
+ getPlaylistDetail,
1100
+ getPlaylistTracks,
1101
+ getPlaylistDetailDynamic,
1102
+ getHighQualityTags,
1103
+ getTopPlaylists,
1104
+ getHighQualityPlaylists,
1105
+ getPlaylistCategories,
1106
+ getRelatedPlaylists,
1107
+ getArtist,
1108
+ getArtistDetail,
1109
+ getArtistSongs,
1110
+ getArtistTopSongs,
1111
+ getArtistAlbums,
1112
+ getArtistList,
1113
+ getArtistDesc,
1114
+ getArtistMvs,
1115
+ getArtistVideos,
1116
+ getAlbum,
1117
+ getAlbumProduct,
1118
+ getAlbumDynamic,
1119
+ getAlbumSaleBoard,
1120
+ getAlbumPrivileges,
1121
+ getAlbumList,
1122
+ getNewestAlbums
85
1123
  };
86
1124
  //#endregion
1125
+ exports.NeteaseApiError = NeteaseApiError;
1126
+ exports.checkMusic = checkMusic;
1127
+ exports.cloudSearch = cloudSearch;
87
1128
  exports.default = src_default;
1129
+ exports.getAlbum = getAlbum;
1130
+ exports.getAlbumDynamic = getAlbumDynamic;
1131
+ exports.getAlbumList = getAlbumList;
1132
+ exports.getAlbumPrivileges = getAlbumPrivileges;
1133
+ exports.getAlbumProduct = getAlbumProduct;
1134
+ exports.getAlbumSaleBoard = getAlbumSaleBoard;
1135
+ exports.getArtist = getArtist;
1136
+ exports.getArtistAlbums = getArtistAlbums;
1137
+ exports.getArtistDesc = getArtistDesc;
1138
+ exports.getArtistDetail = getArtistDetail;
1139
+ exports.getArtistList = getArtistList;
1140
+ exports.getArtistMvs = getArtistMvs;
1141
+ exports.getArtistSongs = getArtistSongs;
1142
+ exports.getArtistTopSongs = getArtistTopSongs;
1143
+ exports.getArtistVideos = getArtistVideos;
1144
+ exports.getDefaultSearchKeyword = getDefaultSearchKeyword;
1145
+ exports.getHighQualityPlaylists = getHighQualityPlaylists;
1146
+ exports.getHighQualityTags = getHighQualityTags;
1147
+ exports.getHotSearchDetail = getHotSearchDetail;
1148
+ exports.getHotSearches = getHotSearches;
88
1149
  exports.getLyric = getLyric;
1150
+ exports.getLyricNew = getLyricNew;
1151
+ exports.getNewestAlbums = getNewestAlbums;
1152
+ exports.getPlaylistCategories = getPlaylistCategories;
89
1153
  exports.getPlaylistDetail = getPlaylistDetail;
1154
+ exports.getPlaylistDetailDynamic = getPlaylistDetailDynamic;
1155
+ exports.getPlaylistTracks = getPlaylistTracks;
1156
+ exports.getRelatedPlaylists = getRelatedPlaylists;
1157
+ exports.getSearchSuggest = getSearchSuggest;
1158
+ exports.getSimilarSongs = getSimilarSongs;
1159
+ exports.getSongUrl = getSongUrl;
90
1160
  exports.getSongsDetail = getSongsDetail;
1161
+ exports.getTopPlaylists = getTopPlaylists;
1162
+ exports.request = request;
1163
+ exports.search = search;
1164
+ exports.searchMultimatch = searchMultimatch;