@neteasecloudmusicapienhanced/api 4.30.0 → 4.30.2

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.
@@ -1,25 +1,26 @@
1
1
  const { default: axios } = require('axios')
2
+ const fs = require('fs')
2
3
  var xml2js = require('xml2js')
3
4
 
4
5
  const createOption = require('../util/option.js')
5
- var parser = new xml2js.Parser(/* options */)
6
+ const { getFileExtension, readFileChunk } = require('../util/fileHelper')
7
+
8
+ var parser = new xml2js.Parser()
9
+
6
10
  function createDupkey() {
7
- // 格式:3b443c7c-a87f-468d-ba38-46d407aaf23a
8
11
  var s = []
9
12
  var hexDigits = '0123456789abcdef'
10
13
  for (var i = 0; i < 36; i++) {
11
14
  s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
12
15
  }
13
- s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
14
- s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
16
+ s[14] = '4'
17
+ s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
15
18
  s[8] = s[13] = s[18] = s[23] = '-'
16
19
  return s.join('')
17
20
  }
21
+
18
22
  module.exports = async (query, request) => {
19
- let ext = 'mp3'
20
- if (query.songFile.name.indexOf('flac') > -1) {
21
- ext = 'flac'
22
- }
23
+ const ext = getFileExtension(query.songFile.name)
23
24
  const filename =
24
25
  query.songName ||
25
26
  query.songFile.name
@@ -50,43 +51,58 @@ module.exports = async (query, request) => {
50
51
  createOption(query, 'weapi'),
51
52
  )
52
53
 
53
- const objectKey = tokenRes.body.result.objectKey.replace('/', '%2F')
54
+ const objectKey = tokenRes.body.result.objectKey.replace(/\//g, '%2F')
54
55
  const docId = tokenRes.body.result.docId
55
56
  const res = await axios({
56
57
  method: 'post',
57
58
  url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploads`,
58
59
  headers: {
59
60
  'x-nos-token': tokenRes.body.result.token,
60
- 'X-Nos-Meta-Content-Type': 'audio/mpeg',
61
+ 'X-Nos-Meta-Content-Type': query.songFile.mimetype || 'audio/mpeg',
61
62
  },
62
63
  data: null,
63
64
  })
64
- // return xml
65
+
65
66
  const res2 = await parser.parseStringPromise(res.data)
66
67
 
67
- const fileSize = query.songFile.data.length
68
- const blockSize = 10 * 1024 * 1024 // 10MB
68
+ const useTempFile = !!query.songFile.tempFilePath
69
+ let fileSize = query.songFile.size
70
+
71
+ if (useTempFile) {
72
+ const stats = await fs.promises.stat(query.songFile.tempFilePath)
73
+ fileSize = stats.size
74
+ }
75
+
76
+ const blockSize = 10 * 1024 * 1024
69
77
  let offset = 0
70
78
  let blockIndex = 1
71
79
 
72
80
  let etags = []
73
81
 
74
82
  while (offset < fileSize) {
75
- const chunk = query.songFile.data.slice(
76
- offset,
77
- Math.min(offset + blockSize, fileSize),
78
- )
83
+ let chunk
84
+ if (useTempFile) {
85
+ chunk = await readFileChunk(
86
+ query.songFile.tempFilePath,
87
+ offset,
88
+ Math.min(blockSize, fileSize - offset),
89
+ )
90
+ } else {
91
+ chunk = query.songFile.data.slice(
92
+ offset,
93
+ Math.min(offset + blockSize, fileSize),
94
+ )
95
+ }
79
96
 
80
97
  const res3 = await axios({
81
98
  method: 'put',
82
99
  url: `https://ymusic.nos-hz.163yun.com/${objectKey}?partNumber=${blockIndex}&uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
83
100
  headers: {
84
101
  'x-nos-token': tokenRes.body.result.token,
85
- 'Content-Type': 'audio/mpeg',
102
+ 'Content-Type': query.songFile.mimetype || 'audio/mpeg',
86
103
  },
87
104
  data: chunk,
88
105
  })
89
- // get etag
90
106
  const etag = res3.headers.etag
91
107
  etags.push(etag)
92
108
  offset += blockSize
@@ -101,19 +117,17 @@ module.exports = async (query, request) => {
101
117
  }
102
118
  completeStr += '</CompleteMultipartUpload>'
103
119
 
104
- // 文件处理
105
120
  await axios({
106
121
  method: 'post',
107
122
  url: `https://ymusic.nos-hz.163yun.com/${objectKey}?uploadId=${res2.InitiateMultipartUploadResult.UploadId[0]}`,
108
123
  headers: {
109
124
  'Content-Type': 'text/plain;charset=UTF-8',
110
- 'X-Nos-Meta-Content-Type': 'audio/mpeg',
125
+ 'X-Nos-Meta-Content-Type': query.songFile.mimetype || 'audio/mpeg',
111
126
  'x-nos-token': tokenRes.body.result.token,
112
127
  },
113
128
  data: completeStr,
114
129
  })
115
130
 
116
- // preCheck
117
131
  await request(
118
132
  `/api/voice/workbench/voice/batch/upload/preCheck`,
119
133
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neteasecloudmusicapienhanced/api",
3
- "version": "4.30.0",
3
+ "version": "4.30.2",
4
4
  "description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护",
5
5
  "scripts": {
6
6
  "dev": "nodemon app.js",
@@ -20,7 +20,6 @@
20
20
  "node_modules/axios",
21
21
  "node_modules/express",
22
22
  "node_modules/express-fileupload",
23
- "node_modules/md5",
24
23
  "node_modules/music-metadata",
25
24
  "node_modules/pac-proxy-agent",
26
25
  "node_modules/qrcode",
@@ -66,14 +65,13 @@
66
65
  "data"
67
66
  ],
68
67
  "dependencies": {
69
- "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.2.2",
68
+ "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.2.3",
70
69
  "axios": "^1.13.5",
71
70
  "crypto-js": "^4.2.0",
72
- "dotenv": "^17.2.4",
71
+ "dotenv": "^17.3.1",
73
72
  "express": "^5.2.1",
74
73
  "express-fileupload": "^1.5.2",
75
- "md5": "^2.3.0",
76
- "music-metadata": "^11.12.0",
74
+ "music-metadata": "^11.12.1",
77
75
  "node-forge": "^1.3.3",
78
76
  "pac-proxy-agent": "^7.2.0",
79
77
  "qrcode": "^1.5.4",
@@ -84,26 +82,26 @@
84
82
  },
85
83
  "devDependencies": {
86
84
  "@eslint/eslintrc": "^3.3.3",
87
- "@eslint/js": "^9.39.2",
85
+ "@eslint/js": "^9.39.3",
88
86
  "@types/express": "^5.0.6",
89
87
  "@types/express-fileupload": "^1.5.1",
90
88
  "@types/mocha": "^10.0.10",
91
89
  "@types/node": "25.0.9",
92
- "@typescript-eslint/eslint-plugin": "8.46.3",
93
- "@typescript-eslint/parser": "8.53.0",
94
- "eslint": "9.39.0",
95
- "eslint-config-prettier": "10.1.8",
96
- "eslint-plugin-html": "8.1.3",
97
- "eslint-plugin-prettier": "5.5.5",
90
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
91
+ "@typescript-eslint/parser": "^8.56.0",
92
+ "eslint": "^9.39.3",
93
+ "eslint-config-prettier": "^10.1.8",
94
+ "eslint-plugin-html": "^8.1.4",
95
+ "eslint-plugin-prettier": "^5.5.5",
98
96
  "globals": "^16.5.0",
99
- "husky": "9.1.7",
100
- "intelli-espower-loader": "1.1.0",
101
- "lint-staged": "16.2.7",
102
- "mocha": "11.7.5",
103
- "nodemon": "^3.1.11",
97
+ "husky": "^9.1.7",
98
+ "intelli-espower-loader": "^1.1.0",
99
+ "lint-staged": "^16.2.7",
100
+ "mocha": "^11.7.5",
101
+ "nodemon": "^3.1.14",
104
102
  "pkg": "^5.8.1",
105
- "power-assert": "1.6.1",
106
- "prettier": "3.7.4",
107
- "typescript": "5.9.3"
103
+ "power-assert": "^1.6.1",
104
+ "prettier": "^3.8.1",
105
+ "typescript": "^5.9.3"
108
106
  }
109
107
  }
@@ -1,20 +1,17 @@
1
1
  const { default: axios } = require('axios')
2
2
  const createOption = require('../util/option.js')
3
3
  const logger = require('../util/logger.js')
4
+ const {
5
+ getUploadData,
6
+ getFileExtension,
7
+ sanitizeFilename,
8
+ } = require('../util/fileHelper')
9
+
4
10
  module.exports = async (query, request) => {
5
- let ext = 'mp3'
6
- // if (query.songFile.name.indexOf('flac') > -1) {
7
- // ext = 'flac'
8
- // }
9
- if (query.songFile.name.includes('.')) {
10
- ext = query.songFile.name.split('.').pop()
11
- }
12
- const filename = query.songFile.name
13
- .replace('.' + ext, '')
14
- .replace(/\s/g, '')
15
- .replace(/\./g, '_')
11
+ const ext = getFileExtension(query.songFile.name)
12
+ const filename = sanitizeFilename(query.songFile.name)
16
13
  const bucket = 'jd-musicrep-privatecloud-audio-public'
17
- // 获取key和token
14
+
18
15
  const tokenRes = await request(
19
16
  `/api/nos/token/alloc`,
20
17
  {
@@ -29,31 +26,82 @@ module.exports = async (query, request) => {
29
26
  createOption(query, 'weapi'),
30
27
  )
31
28
 
32
- // 上传
33
- const objectKey = tokenRes.body.result.objectKey.replace('/', '%2F')
29
+ if (!tokenRes.body.result || !tokenRes.body.result.objectKey) {
30
+ logger.error('Token分配失败:', tokenRes.body)
31
+ throw {
32
+ status: 500,
33
+ body: {
34
+ code: 500,
35
+ msg: '获取上传token失败',
36
+ detail: tokenRes.body,
37
+ },
38
+ }
39
+ }
40
+
41
+ const objectKey = tokenRes.body.result.objectKey.replace(/\//g, '%2F')
42
+ let lbs
34
43
  try {
35
- const lbs = (
44
+ lbs = (
36
45
  await axios({
37
46
  method: 'get',
38
47
  url: `https://wanproxy.127.net/lbs?version=1.0&bucketname=${bucket}`,
48
+ timeout: 10000,
39
49
  })
40
50
  ).data
51
+ } catch (error) {
52
+ logger.error('LBS获取失败:', error.message)
53
+ throw {
54
+ status: 500,
55
+ body: {
56
+ code: 500,
57
+ msg: '获取上传服务器地址失败',
58
+ detail: error.message,
59
+ },
60
+ }
61
+ }
62
+
63
+ if (!lbs || !lbs.upload || !lbs.upload[0]) {
64
+ logger.error('无效的LBS响应:', lbs)
65
+ throw {
66
+ status: 500,
67
+ body: {
68
+ code: 500,
69
+ msg: '获取上传服务器地址无效',
70
+ detail: lbs,
71
+ },
72
+ }
73
+ }
74
+
75
+ try {
41
76
  await axios({
42
77
  method: 'post',
43
78
  url: `${lbs.upload[0]}/${bucket}/${objectKey}?offset=0&complete=true&version=1.0`,
44
79
  headers: {
45
80
  'x-nos-token': tokenRes.body.result.token,
46
81
  'Content-MD5': query.songFile.md5,
47
- 'Content-Type': 'audio/mpeg',
82
+ 'Content-Type': query.songFile.mimetype || 'audio/mpeg',
48
83
  'Content-Length': String(query.songFile.size),
49
84
  },
50
- data: query.songFile.data,
85
+ data: getUploadData(query.songFile),
51
86
  maxContentLength: Infinity,
52
87
  maxBodyLength: Infinity,
88
+ timeout: 300000,
53
89
  })
90
+ logger.info('上传成功:', filename)
54
91
  } catch (error) {
55
- logger.info('error', error.response)
56
- throw error.response
92
+ logger.error('上传失败:', {
93
+ status: error.response?.status,
94
+ data: error.response?.data,
95
+ message: error.message,
96
+ })
97
+ throw {
98
+ status: error.response?.status || 500,
99
+ body: {
100
+ code: error.response?.status || 500,
101
+ msg: '文件上传失败',
102
+ detail: error.response?.data || error.message,
103
+ },
104
+ }
57
105
  }
58
106
  return {
59
107
  ...tokenRes,
package/plugins/upload.js CHANGED
@@ -1,5 +1,7 @@
1
1
  const { default: axios } = require('axios')
2
2
  const createOption = require('../util/option.js')
3
+ const { getUploadData } = require('../util/fileHelper')
4
+
3
5
  module.exports = async (query, request) => {
4
6
  const data = {
5
7
  bucket: 'yyimgs',
@@ -10,27 +12,23 @@ module.exports = async (query, request) => {
10
12
  return_body: `{"code":200,"size":"$(ObjectSize)"}`,
11
13
  type: 'other',
12
14
  }
13
- // 获取key和token
14
15
  const res = await request(
15
16
  `/api/nos/token/alloc`,
16
17
  data,
17
18
  createOption(query, 'weapi'),
18
19
  )
19
- // 上传图片
20
+
20
21
  const res2 = await axios({
21
22
  method: 'post',
22
23
  url: `https://nosup-hz1.127.net/yyimgs/${res.body.result.objectKey}?offset=0&complete=true&version=1.0`,
23
24
  headers: {
24
25
  'x-nos-token': res.body.result.token,
25
- 'Content-Type': 'image/jpeg',
26
+ 'Content-Type': query.imgFile.mimetype || 'image/jpeg',
26
27
  },
27
- data: query.imgFile.data,
28
+ data: getUploadData(query.imgFile),
28
29
  })
29
30
 
30
31
  return {
31
- // ...res.body.result,
32
- // ...res2.data,
33
- // ...res3.body,
34
32
  url_pre: 'https://p1.music.126.net/' + res.body.result.objectKey,
35
33
  imgId: res.body.result.docId,
36
34
  }