@neteasecloudmusicapienhanced/api 4.29.12 → 4.29.14

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,11 @@
1
+ // 获取云盘歌词
2
+ const createOption = require('../util/option.js')
3
+ module.exports = (query, request) => {
4
+ const data = {
5
+ userId: query.uid,
6
+ songId: query.sid,
7
+ lv: -1,
8
+ kv: -1,
9
+ }
10
+ return request(`/api/cloud/lyric/get`, data, createOption(query, 'eapi'))
11
+ }
@@ -5,31 +5,34 @@ const createOption = require('../util/option.js')
5
5
  const logger = require('../util/logger.js')
6
6
 
7
7
  module.exports = async (query, request) => {
8
- try {
9
- const match = require("@unblockneteasemusic/server")
10
- const source = query.source
11
- ? query.source.split(',') : ['pyncmd', 'bodian','kuwo', 'qq', 'migu', 'kugou']
12
- const server = query.server ? query.server.split(',') : query.server
13
- const result = await match(query.id, !server? source : server)
14
- const proxy = process.env.PROXY_URL;
15
- logger.info("开始解灰", query.id, result)
16
- const useProxy = process.env.ENABLE_PROXY || "false"
17
- if (result.url.includes('kuwo')) { result.proxyUrl = useProxy === 'true' ? proxy + result.url : result.url }
18
- return {
19
- status: 200,
20
- body: {
21
- code: 200,
22
- data: result,
23
- },
24
- }
25
- } catch (e) {
26
- return {
27
- status: 500,
28
- body: {
29
- code: 500,
30
- msg: e.message || 'unblock error',
31
- data: [],
32
- },
33
- }
8
+ try {
9
+ const match = require('@unblockneteasemusic/server')
10
+ const source = query.source
11
+ ? query.source.split(',')
12
+ : ['pyncmd', 'bodian', 'kuwo', 'qq', 'migu', 'kugou']
13
+ const server = query.server ? query.server.split(',') : query.server
14
+ const result = await match(query.id, !server ? source : server)
15
+ const proxy = process.env.PROXY_URL
16
+ logger.info('开始解灰', query.id, result)
17
+ const useProxy = process.env.ENABLE_PROXY || 'false'
18
+ if (result.url.includes('kuwo')) {
19
+ result.proxyUrl = useProxy === 'true' ? proxy + result.url : result.url
34
20
  }
35
- }
21
+ return {
22
+ status: 200,
23
+ body: {
24
+ code: 200,
25
+ data: result,
26
+ },
27
+ }
28
+ } catch (e) {
29
+ return {
30
+ status: 500,
31
+ body: {
32
+ code: 500,
33
+ msg: e.message || 'unblock error',
34
+ data: [],
35
+ },
36
+ }
37
+ }
38
+ }
@@ -5,71 +5,73 @@
5
5
  const createOption = require('../util/option.js')
6
6
 
7
7
  module.exports = async (query, request) => {
8
- try {
9
- const { id, br = "320" } = query;
10
- if (!id) {
11
- return {
12
- status: 400,
13
- body: {
14
- code: 400,
15
- message: "缺少必要参数 id",
16
- data: [],
17
- },
18
- };
19
- }
20
- const validBR = ["128", "192", "320", "740", "999"];
21
- // const covertBR = ["128000", "192000", "320000","740000", "999000"];
22
- if (!validBR.includes(br)) {
23
- return {
24
- status: 400,
25
- body: {
26
- code: 400,
27
- message: "无效音质参数",
28
- allowed_values: validBR,
29
- data: [],
30
- },
31
- };
32
- }
8
+ try {
9
+ const { id, br = '320' } = query
10
+ if (!id) {
11
+ return {
12
+ status: 400,
13
+ body: {
14
+ code: 400,
15
+ message: '缺少必要参数 id',
16
+ data: [],
17
+ },
18
+ }
19
+ }
20
+ const validBR = ['128', '192', '320', '740', '999']
21
+ // const covertBR = ['128000', '192000', '320000','740000', '999000']
22
+ if (!validBR.includes(br)) {
23
+ return {
24
+ status: 400,
25
+ body: {
26
+ code: 400,
27
+ message: '无效音质参数',
28
+ allowed_values: validBR,
29
+ data: [],
30
+ },
31
+ }
32
+ }
33
33
 
34
- const apiUrl = new URL("https://music-api.gdstudio.xyz/api.php");
35
- apiUrl.searchParams.append("types", "url");
36
- apiUrl.searchParams.append("id", id);
37
- apiUrl.searchParams.append("br", br);
34
+ const apiUrl = new URL('https://music-api.gdstudio.xyz/api.php')
35
+ apiUrl.searchParams.append('types', 'url')
36
+ apiUrl.searchParams.append('id', id)
37
+ apiUrl.searchParams.append('br', br)
38
38
 
39
- const response = await fetch(apiUrl.toString());
40
- if (!response.ok) throw new Error(`API 响应状态: ${response.status}`);
41
- const result = await response.json();
39
+ const response = await fetch(apiUrl.toString())
40
+ if (!response.ok) throw new Error(`API 响应状态: ${response.status}`)
41
+ const result = await response.json()
42
42
 
43
- // 代理逻辑
44
- const useProxy = process.env.ENABLE_PROXY || false;
45
- const proxy = process.env.PROXY_URL;
46
- if (useProxy && result.url && result.url.includes("kuwo")) {
47
- result.proxyUrl = proxy + result.url.replace(/^http:\/\//, "http/");
48
- }
43
+ // 代理逻辑
44
+ const useProxy = process.env.ENABLE_PROXY || false
45
+ const proxy = process.env.PROXY_URL
46
+ if (useProxy && result.url && result.url.includes('kuwo')) {
47
+ result.proxyUrl = proxy + result.url.replace(/^http:\/\//, 'http/')
48
+ }
49
49
 
50
- return {
51
- status: 200,
52
- body: {
53
- code: 200,
54
- message: "请求成功",
55
- data: {
56
- id,
57
- br,
58
- url: result.url,
59
- ...(proxy && result.proxyUrl ? { proxyUrl: result.proxyUrl } : {})
60
- }
61
- }
62
- };
63
- } catch (error) {
64
- console.error("Error in song_url_ncmget:", error);
65
- return {
66
- status: 500,
67
- body: {
68
- code: 500,
69
- message: "服务器处理请求失败",
70
- ...(process.env.NODE_ENV === "development" ? { error: error.message } : {}),
71
- data: [],
72
- },
73
- };
50
+ return {
51
+ status: 200,
52
+ body: {
53
+ code: 200,
54
+ message: '请求成功',
55
+ data: {
56
+ id,
57
+ br,
58
+ url: result.url,
59
+ ...(proxy && result.proxyUrl ? { proxyUrl: result.proxyUrl } : {}),
60
+ },
61
+ },
62
+ }
63
+ } catch (error) {
64
+ console.error('Error in song_url_ncmget:', error)
65
+ return {
66
+ status: 500,
67
+ body: {
68
+ code: 500,
69
+ message: '服务器处理请求失败',
70
+ ...(process.env.NODE_ENV === 'development'
71
+ ? { error: error.message }
72
+ : {}),
73
+ data: [],
74
+ },
74
75
  }
76
+ }
75
77
  }
@@ -5,31 +5,34 @@ const createOption = require('../util/option.js')
5
5
  const logger = require('../util/logger.js')
6
6
 
7
7
  module.exports = async (query, request) => {
8
- try {
9
- const match = require("@unblockneteasemusic/server")
10
- const source = query.source
11
- ? query.source.split(',') : ['pyncmd', 'bodian', 'kuwo', 'qq', 'migu', 'kugou']
12
- const server = query.server ? query.server.split(',') : query.server
13
- const result = await match(query.id, !server? source : server)
14
- const proxy = process.env.PROXY_URL;
15
- logger.info("开始解灰", query.id, result)
16
- const useProxy = process.env.ENABLE_PROXY || "false"
17
- if (result.url.includes('kuwo') && useProxy === "true") { result.proxyUrl = proxy + result.url }
18
- return {
19
- status: 200,
20
- body: {
21
- code: 200,
22
- data: result,
23
- },
24
- }
25
- } catch (e) {
26
- return {
27
- status: 500,
28
- body: {
29
- code: 500,
30
- msg: e.message || 'unblock error',
31
- data: [],
32
- },
33
- }
8
+ try {
9
+ const match = require('@unblockneteasemusic/server')
10
+ const source = query.source
11
+ ? query.source.split(',')
12
+ : ['pyncmd', 'bodian', 'kuwo', 'qq', 'migu', 'kugou']
13
+ const server = query.server ? query.server.split(',') : query.server
14
+ const result = await match(query.id, !server ? source : server)
15
+ const proxy = process.env.PROXY_URL
16
+ logger.info('开始解灰', query.id, result)
17
+ const useProxy = process.env.ENABLE_PROXY || 'false'
18
+ if (result.url.includes('kuwo')) {
19
+ result.proxyUrl = useProxy === 'true' ? proxy + result.url : result.url
34
20
  }
35
- }
21
+ return {
22
+ status: 200,
23
+ body: {
24
+ code: 200,
25
+ data: result,
26
+ },
27
+ }
28
+ } catch (e) {
29
+ return {
30
+ status: 500,
31
+ body: {
32
+ code: 500,
33
+ msg: e.message || 'unblock error',
34
+ data: [],
35
+ },
36
+ }
37
+ }
38
+ }
@@ -18,12 +18,24 @@ module.exports = async (query, request) => {
18
18
  try {
19
19
  const result = await match(query.id, source)
20
20
  logger.info('开始解灰', query.id, result)
21
- if (result.url.includes('kuwo')) {
22
- const useProxy = process.env.ENABLE_PROXY || 'false'
23
- var proxyUrl = useProxy === 'true' ? process.env.PROXY_URL + result.url : result.url
21
+ // avoid optional chaining for compatibility
22
+ let url
23
+ if (Array.isArray(result)) {
24
+ url = result[0] && result[0].url ? result[0].url : result[0]
25
+ } else {
26
+ url = result && result.url ? result.url : result
24
27
  }
25
- let url = Array.isArray(result) ? (result[0]?.url || result[0]) : (result.url || result)
28
+ // decide proxyUrl after we resolved the actual url value
29
+ let proxyUrl = ''
26
30
  if (url) {
31
+ if (url.includes('kuwo')) {
32
+ const useProxy = process.env.ENABLE_PROXY || 'false'
33
+ if (useProxy === 'true' && process.env.PROXY_URL) {
34
+ proxyUrl = process.env.PROXY_URL + url
35
+ } else {
36
+ proxyUrl = url
37
+ }
38
+ }
27
39
  return {
28
40
  status: 200,
29
41
  body: {
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@neteasecloudmusicapienhanced/api",
3
- "version": "4.29.12",
3
+ "version": "4.29.14",
4
4
  "description": "为停更的网易云音乐 NodeJs API 提供持续的维护!",
5
5
  "scripts": {
6
+ "dev": "nodemon app.js",
6
7
  "start": "node app.js",
7
8
  "test": "mocha -r intelli-espower-loader -t 60000 server.test.js main.test.js --exit",
8
9
  "lint": "eslint \"**/*.{js,ts}\"",
@@ -66,7 +67,7 @@
66
67
  ],
67
68
  "dependencies": {
68
69
  "@unblockneteasemusic/server": "^0.28.0",
69
- "axios": "^1.12.2",
70
+ "axios": "^1.13.2",
70
71
  "crypto-js": "^4.2.0",
71
72
  "dotenv": "^17.2.3",
72
73
  "express": "^5.1.0",
@@ -79,26 +80,30 @@
79
80
  "safe-decode-uri-component": "^1.2.1",
80
81
  "tunnel": "^0.0.6",
81
82
  "xml2js": "^0.6.2",
82
- "yargs": "^17.7.2"
83
+ "yargs": "^18.0.0"
83
84
  },
84
85
  "devDependencies": {
85
- "@types/express": "^5.0.4",
86
+ "@eslint/eslintrc": "^3.3.1",
87
+ "@eslint/js": "^9.39.1",
88
+ "@types/express": "^5.0.5",
86
89
  "@types/express-fileupload": "^1.5.1",
87
- "@types/mocha": "^9.1.1",
88
- "@types/node": "24.6.1",
89
- "@typescript-eslint/eslint-plugin": "5.62.0",
90
- "@typescript-eslint/parser": "5.0.0",
91
- "eslint": "8.7.0",
92
- "eslint-config-prettier": "8.5.0",
90
+ "@types/mocha": "^10.0.10",
91
+ "@types/node": "24.9.1",
92
+ "@typescript-eslint/eslint-plugin": "8.46.2",
93
+ "@typescript-eslint/parser": "8.46.2",
94
+ "eslint": "9.39.0",
95
+ "eslint-config-prettier": "10.1.8",
93
96
  "eslint-plugin-html": "8.1.3",
94
- "eslint-plugin-prettier": "4.0.0",
95
- "husky": "7.0.4",
97
+ "eslint-plugin-prettier": "5.5.4",
98
+ "globals": "^16.5.0",
99
+ "husky": "9.1.7",
96
100
  "intelli-espower-loader": "1.1.0",
97
- "lint-staged": "16.2.4",
98
- "mocha": "11.7.3",
101
+ "lint-staged": "16.2.6",
102
+ "mocha": "11.7.4",
103
+ "nodemon": "^3.1.10",
99
104
  "pkg": "^5.8.1",
100
105
  "power-assert": "1.6.1",
101
106
  "prettier": "3.6.2",
102
- "typescript": "4.5.2"
107
+ "typescript": "5.9.3"
103
108
  }
104
109
  }
@@ -4848,6 +4848,19 @@ let data = encodeURIComponent(
4848
4848
 
4849
4849
  **调用例子:** `/broadcast/channel/list`
4850
4850
 
4851
+ ### 获取云盘歌词
4852
+ 说明: 调用此接口, 获取云盘歌曲的歌词,歌词来自此文件的音乐元数据`LYRICS`标签。
4853
+
4854
+ **可选参数 :**
4855
+
4856
+ `uid`: 用户 id
4857
+
4858
+ `sid`: 云盘的歌曲 id
4859
+
4860
+ **接口地址:** `/cloud/lyric/get`
4861
+
4862
+ **调用例子:** `/cloud/lyric/get`
4863
+
4851
4864
  ## 离线访问此文档
4852
4865
 
4853
4866
  此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
package/public/index.html CHANGED
@@ -60,10 +60,10 @@
60
60
  <section class="block">
61
61
  <h2>常用接口</h2>
62
62
  <ul class="links">
63
- <li><a href="/search?keywords=这么可爱真是抱歉">搜索音乐: <code>GET /search</code></a></li>
64
- <li><a href="/song/detail?ids=1969519579">获取音乐详情: <code>GET /song/detail</code></a></li>
65
- <li><a href="/comment/music?id=1969519579&limit=1">获取音乐评论: <code>GET /comment/music</code></a></li>
66
- <li><a href="/song/url/v1?id=1969519579&level=exhigh">获取音乐播放链接: <code>GET /song/url/v1</code></a></li>
63
+ <li><a href="/search?keywords=妖精小姐的魔法邀约">搜索音乐: <code>GET /search</code></a></li>
64
+ <li><a href="/song/detail?ids=2756058128">获取音乐详情: <code>GET /song/detail</code></a></li>
65
+ <li><a href="/comment/music?id=2756058128&limit=1">获取音乐评论: <code>GET /comment/music</code></a></li>
66
+ <li><a href="/song/url/v1?id=2756058128&level=exhigh">获取音乐播放链接: <code>GET /song/url/v1</code></a></li>
67
67
  </ul>
68
68
  </section>
69
69
 
@@ -71,7 +71,7 @@
71
71
  <h2>调试部分</h2>
72
72
  <pre><code>curl -s {origin}/inner/version
73
73
  curl -s {origin}/search?keywords=网易云</code></pre>
74
- <p style="margin-top:10px"> · <a href="/api.html">交互式调试</a> · <a href="/qrlogin.html">二维码登录示例</a> · <a href="/unblock_test.html">解灰测试</a></p> · <a href="/audio_match_demo/index.html">听歌识曲 Demo</a></p> · <a href="/unblock_test.html">云盘上传</a></p> · <a href="/playlist_import.html">歌单导入</a></p> · <a href="/eapi_decrypt.html">EAPI 解密</p>
74
+ <p style="margin-top:10px"> · <a href="/api.html">交互式调试</a> · <a href="/qrlogin.html">二维码登录示例</a> · <a href="/unblock_test.html">解灰测试</a></p> · <a href="/audio_match_demo/index.html">听歌识曲 Demo</a></p> · <a href="/cloud.html">云盘上传</a></p> · <a href="/playlist_import.html">歌单导入</a></p> · <a href="/eapi_decrypt.html">EAPI 解密</p>
75
75
  </section>
76
76
 
77
77
  <footer class="site-footer">
package/server.js CHANGED
@@ -1,4 +1,4 @@
1
- require("dotenv").config();
1
+ require('dotenv').config()
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
4
  const express = require('express')
@@ -248,20 +248,28 @@ async function consturctServer(moduleDefs) {
248
248
  (req.baseUrl === '/song/url/v1' || req.baseUrl === '/song/url') &&
249
249
  process.env.ENABLE_GENERAL_UNBLOCK === 'true'
250
250
  ) {
251
- const song = moduleResponse['body']['data'][0]
252
- if (song.freeTrialInfo !== null || !song.url || [1, 4].includes(song.fee)) {
253
- const match = require('@unblockneteasemusic/server')
254
- const source = process.env.UNBLOCK_SOURCE ? process.env.UNBLOCK_SOURCE.split(',') : ['pyncmd', 'bodian', 'kuwo', 'qq', 'migu', 'kugou']
255
- logger.info("开始解灰", source)
256
- const { url } = await match(req.query.id, source)
257
- song.url = url
258
- song.freeTrialInfo = 'null'
259
- logger.info("解灰成功!")
251
+ const song = moduleResponse.body.data[0]
252
+ if (
253
+ song.freeTrialInfo !== null ||
254
+ !song.url ||
255
+ [1, 4].includes(song.fee)
256
+ ) {
257
+ const match = require('@unblockneteasemusic/server')
258
+ const source = process.env.UNBLOCK_SOURCE
259
+ ? process.env.UNBLOCK_SOURCE.split(',')
260
+ : ['pyncmd', 'bodian', 'kuwo', 'qq', 'migu', 'kugou']
261
+ logger.info('开始解灰', source)
262
+ const { url } = await match(req.query.id, source)
263
+ song.url = url
264
+ song.freeTrialInfo = 'null'
265
+ logger.info('解灰成功!')
260
266
  }
261
- if (song.url.includes('kuwo')) {
262
- const proxy = process.env.PROXY_URL;
267
+ if (song.url && song.url.includes('kuwo')) {
268
+ const proxy = process.env.PROXY_URL
263
269
  const useProxy = process.env.ENABLE_PROXY || 'false'
264
- if (useProxy === 'true' && proxy) {song.proxyUrl = proxy + song.url}
270
+ if (useProxy === 'true' && proxy) {
271
+ song.proxyUrl = proxy + song.url
272
+ }
265
273
  }
266
274
  }
267
275
 
package/util/apicache.js CHANGED
@@ -155,9 +155,12 @@ function ApiCache() {
155
155
  }
156
156
 
157
157
  // add automatic cache clearing from duration, includes max limit on setTimeout
158
- timers[key] = setTimeout(function () {
159
- instance.clear(key, true)
160
- }, Math.min(duration, 2147483647))
158
+ timers[key] = setTimeout(
159
+ function () {
160
+ instance.clear(key, true)
161
+ },
162
+ Math.min(duration, 2147483647),
163
+ )
161
164
  }
162
165
 
163
166
  function accumulateContent(res, content) {
@@ -1,5 +1,5 @@
1
- const crypto = require("crypto");
2
- const os = require("os");
1
+ const crypto = require('crypto')
2
+ const os = require('os')
3
3
 
4
4
  class AdvancedClientSignGenerator {
5
5
  /**
@@ -7,25 +7,25 @@ class AdvancedClientSignGenerator {
7
7
  */
8
8
  static getRealMacAddress() {
9
9
  try {
10
- const interfaces = os.networkInterfaces();
10
+ const interfaces = os.networkInterfaces()
11
11
  for (let interfaceName in interfaces) {
12
- const interface = interfaces[interfaceName];
13
- for (let i = 0; i < interface.length; i++) {
14
- const alias = interface[i];
12
+ const networkInterface = interfaces[interfaceName]
13
+ for (let i = 0; i < networkInterface.length; i++) {
14
+ const alias = networkInterface[i]
15
15
  // 排除内部地址和无效地址
16
16
  if (
17
17
  alias.mac &&
18
- alias.mac !== "00:00:00:00:00:00" &&
18
+ alias.mac !== '00:00:00:00:00:00' &&
19
19
  !alias.internal
20
20
  ) {
21
- return alias.mac.toUpperCase();
21
+ return alias.mac.toUpperCase()
22
22
  }
23
23
  }
24
24
  }
25
- return null;
25
+ return null
26
26
  } catch (error) {
27
- console.warn("获取MAC地址失败:", error.message);
28
- return null;
27
+ console.warn('获取MAC地址失败:', error.message)
28
+ return null
29
29
  }
30
30
  }
31
31
 
@@ -33,108 +33,108 @@ class AdvancedClientSignGenerator {
33
33
  * 生成随机MAC地址
34
34
  */
35
35
  static generateRandomMac() {
36
- const chars = "0123456789ABCDEF";
37
- let mac = "";
36
+ const chars = '0123456789ABCDEF'
37
+ let mac = ''
38
38
  for (let i = 0; i < 6; i++) {
39
- if (i > 0) mac += ":";
39
+ if (i > 0) mac += ':'
40
40
  mac +=
41
41
  chars[Math.floor(Math.random() * 16)] +
42
- chars[Math.floor(Math.random() * 16)];
42
+ chars[Math.floor(Math.random() * 16)]
43
43
  }
44
44
  // 确保第一个字节是单播地址(最低位为0)
45
- const firstByte = parseInt(mac.substring(0, 2), 16);
45
+ const firstByte = parseInt(mac.substring(0, 2), 16)
46
46
  const unicastFirstByte = (firstByte & 0xfe)
47
47
  .toString(16)
48
- .padStart(2, "0")
49
- .toUpperCase();
50
- return unicastFirstByte + mac.substring(2);
48
+ .padStart(2, '0')
49
+ .toUpperCase()
50
+ return unicastFirstByte + mac.substring(2)
51
51
  }
52
52
 
53
53
  /**
54
54
  * 获取MAC地址(优先真实,否则随机)
55
55
  */
56
56
  static getMacAddress() {
57
- const realMac = this.getRealMacAddress();
57
+ const realMac = this.getRealMacAddress()
58
58
  if (realMac) {
59
- return realMac;
59
+ return realMac
60
60
  }
61
- console.warn("无法获取真实MAC地址,使用随机生成");
62
- return this.generateRandomMac();
61
+ console.warn('无法获取真实MAC地址,使用随机生成')
62
+ return this.generateRandomMac()
63
63
  }
64
64
 
65
65
  /**
66
66
  * 字符串转HEX编码
67
67
  */
68
68
  static stringToHex(str) {
69
- return Buffer.from(str, "utf8").toString("hex").toUpperCase();
69
+ return Buffer.from(str, 'utf8').toString('hex').toUpperCase()
70
70
  }
71
71
 
72
72
  /**
73
73
  * SHA-256哈希
74
74
  */
75
75
  static sha256(data) {
76
- return crypto.createHash("sha256").update(data, "utf8").digest("hex");
76
+ return crypto.createHash('sha256').update(data, 'utf8').digest('hex')
77
77
  }
78
78
 
79
79
  /**
80
80
  * 生成随机设备ID
81
81
  */
82
82
  static generateRandomDeviceId() {
83
- const partLengths = [4, 4, 4, 4, 4, 4, 4, 5]; // 各部分长度
84
- const chars = "0123456789ABCDEF";
83
+ const partLengths = [4, 4, 4, 4, 4, 4, 4, 5] // 各部分长度
84
+ const chars = '0123456789ABCDEF'
85
85
 
86
86
  const parts = partLengths.map((length) => {
87
- let part = "";
87
+ let part = ''
88
88
  for (let i = 0; i < length; i++) {
89
- part += chars[Math.floor(Math.random() * 16)];
89
+ part += chars[Math.floor(Math.random() * 16)]
90
90
  }
91
- return part;
92
- });
91
+ return part
92
+ })
93
93
 
94
- return parts.join("_");
94
+ return parts.join('_')
95
95
  }
96
96
 
97
97
  /**
98
98
  * 生成随机clientSign(优先使用真实MAC,否则随机)
99
99
  */
100
- static generateRandomClientSign(secretKey = "") {
100
+ static generateRandomClientSign(secretKey = '') {
101
101
  // 获取MAC地址(优先真实,否则随机)
102
- const macAddress = this.getMacAddress();
102
+ const macAddress = this.getMacAddress()
103
103
 
104
104
  // 生成随机设备ID
105
- const deviceId = this.generateRandomDeviceId();
105
+ const deviceId = this.generateRandomDeviceId()
106
106
 
107
107
  // 转换设备ID为HEX
108
- const hexDeviceId = this.stringToHex(deviceId);
108
+ const hexDeviceId = this.stringToHex(deviceId)
109
109
 
110
110
  // 构造签名字符串
111
- const signString = `${macAddress}@@@${hexDeviceId}`;
111
+ const signString = `${macAddress}@@@${hexDeviceId}`
112
112
 
113
113
  // 生成哈希
114
- const hash = this.sha256(signString + secretKey);
114
+ const hash = this.sha256(signString + secretKey)
115
115
 
116
- return `${signString}@@@@@@${hash}`;
116
+ return `${signString}@@@@@@${hash}`
117
117
  }
118
118
 
119
119
  /**
120
120
  * 批量生成多个随机签名
121
121
  */
122
- static generateMultipleRandomSigns(count, secretKey = "") {
123
- const signs = [];
122
+ static generateMultipleRandomSigns(count, secretKey = '') {
123
+ const signs = []
124
124
  for (let i = 0; i < count; i++) {
125
- signs.push(this.generateRandomClientSign(secretKey));
125
+ signs.push(this.generateRandomClientSign(secretKey))
126
126
  }
127
- return signs;
127
+ return signs
128
128
  }
129
129
 
130
130
  /**
131
131
  * 使用指定参数生成签名
132
132
  */
133
- static generateWithCustomDeviceId(macAddress, deviceId, secretKey = "") {
134
- const hexDeviceId = this.stringToHex(deviceId);
135
- const signString = `${macAddress}@@@${hexDeviceId}`;
136
- const hash = this.sha256(signString + secretKey);
137
- return `${signString}@@@@@@${hash}`;
133
+ static generateWithCustomDeviceId(macAddress, deviceId, secretKey = '') {
134
+ const hexDeviceId = this.stringToHex(deviceId)
135
+ const signString = `${macAddress}@@@${hexDeviceId}`
136
+ const hash = this.sha256(signString + secretKey)
137
+ return `${signString}@@@@@@${hash}`
138
138
  }
139
139
 
140
140
  /**
@@ -142,28 +142,28 @@ class AdvancedClientSignGenerator {
142
142
  */
143
143
  static validateClientSign(clientSign) {
144
144
  try {
145
- const parts = clientSign.split("@@@@@@");
146
- if (parts.length !== 2) return false;
145
+ const parts = clientSign.split('@@@@@@')
146
+ if (parts.length !== 2) return false
147
147
 
148
- const [infoPart, hash] = parts;
149
- const infoParts = infoPart.split("@@@");
150
- if (infoParts.length !== 2) return false;
148
+ const [infoPart, hash] = parts
149
+ const infoParts = infoPart.split('@@@')
150
+ if (infoParts.length !== 2) return false
151
151
 
152
- const [mac, hexDeviceId] = infoParts;
152
+ const [mac, hexDeviceId] = infoParts
153
153
 
154
154
  // 验证MAC地址格式
155
- const macRegex = /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/;
156
- if (!macRegex.test(mac)) return false;
155
+ const macRegex = /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/
156
+ if (!macRegex.test(mac)) return false
157
157
 
158
158
  // 验证哈希格式
159
- const hashRegex = /^[0-9a-f]{64}$/;
160
- if (!hashRegex.test(hash)) return false;
159
+ const hashRegex = /^[0-9a-f]{64}$/
160
+ if (!hashRegex.test(hash)) return false
161
161
 
162
- return true;
162
+ return true
163
163
  } catch (error) {
164
- return false;
164
+ return false
165
165
  }
166
166
  }
167
167
  }
168
168
 
169
- module.exports = AdvancedClientSignGenerator;
169
+ module.exports = AdvancedClientSignGenerator
package/util/logger.js CHANGED
@@ -1,29 +1,42 @@
1
1
  // ANSI 颜色代码
2
2
  const colors = {
3
- reset: '\x1b[0m',
4
- bright: '\x1b[1m',
5
- dim: '\x1b[2m',
6
- black: '\x1b[30m',
7
- red: '\x1b[31m',
8
- green: '\x1b[32m',
9
- yellow: '\x1b[33m',
10
- blue: '\x1b[34m',
11
- magenta: '\x1b[35m',
12
- cyan: '\x1b[36m',
13
- white: '\x1b[37m',
14
- bgRed: '\x1b[41m',
15
- bgGreen: '\x1b[42m',
16
- bgYellow: '\x1b[43m'
17
- };
3
+ reset: '\x1b[0m',
4
+ bright: '\x1b[1m',
5
+ dim: '\x1b[2m',
6
+ black: '\x1b[30m',
7
+ red: '\x1b[31m',
8
+ green: '\x1b[32m',
9
+ yellow: '\x1b[33m',
10
+ blue: '\x1b[34m',
11
+ magenta: '\x1b[35m',
12
+ cyan: '\x1b[36m',
13
+ white: '\x1b[37m',
14
+ bgRed: '\x1b[41m',
15
+ bgGreen: '\x1b[42m',
16
+ bgYellow: '\x1b[43m',
17
+ }
18
18
 
19
19
  const logger = {
20
- debug: (msg, ...args) => console.info(`${colors.cyan}[DEBUG]${colors.reset}`, msg, ...args),
21
- info: (msg, ...args) => console.info(`${colors.green}[INFO]${colors.reset}`, msg, ...args),
22
- warn: (msg, ...args) => console.info(`${colors.yellow}[WARN]${colors.reset}`, msg, ...args),
23
- error: (msg, ...args) => console.error(`${colors.red}[ERROR]${colors.reset}`, msg, ...args),
24
- success: (msg, ...args) => console.log(`${colors.bright}${colors.green}[SUCCESS]${colors.reset}`, msg, ...args),
25
- critical: (msg, ...args) => console.error(`${colors.bright}${colors.bgRed}[CRITICAL]${colors.reset}`, msg, ...args)
26
- };
20
+ debug: (msg, ...args) =>
21
+ console.info(`${colors.cyan}[DEBUG]${colors.reset}`, msg, ...args),
22
+ info: (msg, ...args) =>
23
+ console.info(`${colors.green}[INFO]${colors.reset}`, msg, ...args),
24
+ warn: (msg, ...args) =>
25
+ console.info(`${colors.yellow}[WARN]${colors.reset}`, msg, ...args),
26
+ error: (msg, ...args) =>
27
+ console.error(`${colors.red}[ERROR]${colors.reset}`, msg, ...args),
28
+ success: (msg, ...args) =>
29
+ console.log(
30
+ `${colors.bright}${colors.green}[SUCCESS]${colors.reset}`,
31
+ msg,
32
+ ...args,
33
+ ),
34
+ critical: (msg, ...args) =>
35
+ console.error(
36
+ `${colors.bright}${colors.bgRed}[CRITICAL]${colors.reset}`,
37
+ msg,
38
+ ...args,
39
+ ),
40
+ }
27
41
 
28
- // 导出logger
29
- module.exports = logger;
42
+ module.exports = logger
package/util/request.js CHANGED
@@ -101,7 +101,7 @@ const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803])
101
101
 
102
102
  // chooseUserAgent函数
103
103
  const chooseUserAgent = (crypto, uaType = 'pc') => {
104
- return userAgentMap[crypto]?.[uaType] || ''
104
+ return (userAgentMap[crypto] && userAgentMap[crypto][uaType]) || ''
105
105
  }
106
106
 
107
107
  // cookie处理
@@ -153,15 +153,17 @@ const createHeaderCookie = (header) => {
153
153
  const generateRequestId = () => {
154
154
  return `${now()}_${floor(random() * 1000)
155
155
  .toString()
156
- .padStart(4, "0")}`;
157
-
156
+ .padStart(4, '0')}`
158
157
  }
159
158
 
160
159
  const createRequest = (uri, data, options) => {
161
160
  return new Promise((resolve, reject) => {
162
161
  // 变量声明和初始化
163
162
  const headers = options.headers ? { ...options.headers } : {}
164
- const ip = options.realIP || options.ip || (options.randomCNIP ? generateRandomChineseIP() : '')
163
+ const ip =
164
+ options.realIP ||
165
+ options.ip ||
166
+ (options.randomCNIP ? generateRandomChineseIP() : '')
165
167
  // IP头设置
166
168
  if (ip) {
167
169
  headers['X-Real-IP'] = ip
@@ -243,8 +245,8 @@ const createRequest = (uri, data, options) => {
243
245
  options.e_r !== undefined
244
246
  ? options.e_r
245
247
  : data.e_r !== undefined
246
- ? data.e_r
247
- : ENCRYPT_RESPONSE,
248
+ ? data.e_r
249
+ : ENCRYPT_RESPONSE,
248
250
  )
249
251
  encryptData = encrypt.eapi(uri, data)
250
252
  url = API_DOMAIN + '/eapi/' + uri.substr(5)