@neteasecloudmusicapienhanced/api 4.30.1 → 4.30.3

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/module/cloud.js CHANGED
@@ -1,25 +1,26 @@
1
1
  const uploadPlugin = require('../plugins/songUpload')
2
- const md5 = require('md5')
3
2
  const createOption = require('../util/option.js')
4
3
  const logger = require('../util/logger.js')
4
+ const {
5
+ isTempFile,
6
+ getFileSize,
7
+ getFileMd5,
8
+ cleanupTempFile,
9
+ getFileExtension,
10
+ sanitizeFilename,
11
+ } = require('../util/fileHelper')
12
+
5
13
  let mm
6
14
  module.exports = async (query, request) => {
7
15
  mm = require('music-metadata')
8
- let ext = 'mp3'
9
- // if (query.songFile.name.indexOf('flac') > -1) {
10
- // ext = 'flac'
11
- // }
12
- if (query.songFile.name.includes('.')) {
13
- ext = query.songFile.name.split('.').pop()
14
- }
16
+
15
17
  query.songFile.name = Buffer.from(query.songFile.name, 'latin1').toString(
16
18
  'utf-8',
17
19
  )
18
- const filename = query.songFile.name
19
- .replace('.' + ext, '')
20
- .replace(/\s/g, '')
21
- .replace(/\./g, '_')
20
+ const ext = getFileExtension(query.songFile.name)
21
+ const filename = sanitizeFilename(query.songFile.name)
22
22
  const bitrate = 999000
23
+
23
24
  if (!query.songFile) {
24
25
  return Promise.reject({
25
26
  status: 500,
@@ -29,119 +30,135 @@ module.exports = async (query, request) => {
29
30
  },
30
31
  })
31
32
  }
32
- if (!query.songFile.md5) {
33
- // 命令行上传没有md5和size信息,需要填充
34
- query.songFile.md5 = md5(query.songFile.data)
35
- query.songFile.size = query.songFile.data.byteLength
36
- }
37
- const res = await request(
38
- `/api/cloud/upload/check`,
39
- {
40
- bitrate: String(bitrate),
41
- ext: '',
42
- length: query.songFile.size,
43
- md5: query.songFile.md5,
44
- songId: '0',
45
- version: 1,
46
- },
47
- createOption(query),
48
- )
49
- let artist = ''
50
- let album = ''
51
- let songName = ''
33
+
34
+ const useTemp = isTempFile(query.songFile)
35
+ let fileSize = await getFileSize(query.songFile)
36
+ let fileMd5 = await getFileMd5(query.songFile)
37
+
38
+ query.songFile.md5 = fileMd5
39
+ query.songFile.size = fileSize
40
+
52
41
  try {
53
- const metadata = await mm.parseBuffer(
54
- query.songFile.data,
55
- query.songFile.mimetype,
42
+ const res = await request(
43
+ `/api/cloud/upload/check`,
44
+ {
45
+ bitrate: String(bitrate),
46
+ ext: '',
47
+ length: fileSize,
48
+ md5: fileMd5,
49
+ songId: '0',
50
+ version: 1,
51
+ },
52
+ createOption(query),
56
53
  )
57
- const info = metadata.common
58
54
 
59
- if (info.title) {
60
- songName = info.title
55
+ let artist = ''
56
+ let album = ''
57
+ let songName = ''
58
+
59
+ try {
60
+ let metadata
61
+ if (useTemp) {
62
+ metadata = await mm.parseFile(query.songFile.tempFilePath)
63
+ } else {
64
+ metadata = await mm.parseBuffer(
65
+ query.songFile.data,
66
+ query.songFile.mimetype,
67
+ )
68
+ }
69
+ const info = metadata.common
70
+ if (info.title) songName = info.title
71
+ if (info.album) album = info.album
72
+ if (info.artist) artist = info.artist
73
+ } catch (error) {
74
+ logger.info('元数据解析错误:', error.message)
61
75
  }
62
- if (info.album) {
63
- album = info.album
76
+
77
+ const tokenRes = await request(
78
+ `/api/nos/token/alloc`,
79
+ {
80
+ bucket: '',
81
+ ext: ext,
82
+ filename: filename,
83
+ local: false,
84
+ nos_product: 3,
85
+ type: 'audio',
86
+ md5: fileMd5,
87
+ },
88
+ createOption(query),
89
+ )
90
+
91
+ if (!tokenRes.body.result || !tokenRes.body.result.resourceId) {
92
+ logger.error('Token分配失败:', tokenRes.body)
93
+ return Promise.reject({
94
+ status: 500,
95
+ body: {
96
+ code: 500,
97
+ msg: '获取上传token失败',
98
+ detail: tokenRes.body,
99
+ },
100
+ })
64
101
  }
65
- if (info.artist) {
66
- artist = info.artist
102
+
103
+ if (res.body.needUpload) {
104
+ logger.info('需要上传,开始上传流程...')
105
+ try {
106
+ const uploadInfo = await uploadPlugin(query, request)
107
+ logger.info('上传完成:', uploadInfo?.body?.result?.resourceId)
108
+ } catch (uploadError) {
109
+ logger.error('上传失败:', uploadError)
110
+ return Promise.reject(uploadError)
111
+ }
112
+ } else {
113
+ logger.info('文件已存在,跳过上传')
67
114
  }
68
- // if (metadata.native.ID3v1) {
69
- // metadata.native.ID3v1.forEach((item) => {
70
- // // logger.info(item.id, item.value)
71
- // if (item.id === 'title') {
72
- // songName = item.value
73
- // }
74
- // if (item.id === 'artist') {
75
- // artist = item.value
76
- // }
77
- // if (item.id === 'album') {
78
- // album = item.value
79
- // }
80
- // })
81
- // // logger.info({
82
- // // songName,
83
- // // album,
84
- // // songName,
85
- // // })
86
- // }
87
- // logger.info({
88
- // songName,
89
- // album,
90
- // songName,
91
- // })
92
- } catch (error) {
93
- logger.info(error)
94
- }
95
- const tokenRes = await request(
96
- `/api/nos/token/alloc`,
97
- {
98
- bucket: '',
99
- ext: ext,
100
- filename: filename,
101
- local: false,
102
- nos_product: 3,
103
- type: 'audio',
104
- md5: query.songFile.md5,
105
- },
106
- createOption(query),
107
- )
108
115
 
109
- if (res.body.needUpload) {
110
- const uploadInfo = await uploadPlugin(query, request)
111
- // logger.info('uploadInfo', uploadInfo.body.result.resourceId)
112
- }
113
- // logger.info(tokenRes.body.result)
114
- const res2 = await request(
115
- `/api/upload/cloud/info/v2`,
116
- {
117
- md5: query.songFile.md5,
118
- songid: res.body.songId,
119
- filename: query.songFile.name,
120
- song: songName || filename,
121
- album: album || '未知专辑',
122
- artist: artist || '未知艺术家',
123
- bitrate: String(bitrate),
124
- resourceId: tokenRes.body.result.resourceId,
125
- },
126
- createOption(query),
127
- )
128
- // logger.info({ res2, privateCloud: res2.body.privateCloud })
129
- // logger.info(res.body.songId, 'songid')
130
- const res3 = await request(
131
- `/api/cloud/pub/v2`,
132
- {
133
- songid: res2.body.songId,
134
- },
135
- createOption(query),
136
- )
137
- // logger.info({ res3 })
138
- return {
139
- status: 200,
140
- body: {
141
- ...res.body,
142
- ...res3.body,
143
- // ...uploadInfo,
144
- },
145
- cookie: res.cookie,
116
+ const res2 = await request(
117
+ `/api/upload/cloud/info/v2`,
118
+ {
119
+ md5: fileMd5,
120
+ songid: res.body.songId,
121
+ filename: query.songFile.name,
122
+ song: songName || filename,
123
+ album: album || '未知专辑',
124
+ artist: artist || '未知艺术家',
125
+ bitrate: String(bitrate),
126
+ resourceId: tokenRes.body.result.resourceId,
127
+ },
128
+ createOption(query),
129
+ )
130
+
131
+ if (res2.body.code !== 200) {
132
+ logger.error('云盘信息上传失败:', res2.body)
133
+ return Promise.reject({
134
+ status: res2.status || 500,
135
+ body: {
136
+ code: res2.body.code || 500,
137
+ msg: res2.body.msg || '上传云盘信息失败',
138
+ detail: res2.body,
139
+ },
140
+ })
141
+ }
142
+
143
+ const res3 = await request(
144
+ `/api/cloud/pub/v2`,
145
+ {
146
+ songid: res2.body.songId,
147
+ },
148
+ createOption(query),
149
+ )
150
+
151
+ return {
152
+ status: 200,
153
+ body: {
154
+ ...res.body,
155
+ ...res3.body,
156
+ },
157
+ cookie: res.cookie,
158
+ }
159
+ } finally {
160
+ if (useTemp) {
161
+ await cleanupTempFile(query.songFile.tempFilePath)
162
+ }
146
163
  }
147
164
  }
@@ -0,0 +1,72 @@
1
+ const createOption = require('../util/option.js')
2
+
3
+ module.exports = async (query, request) => {
4
+ const {
5
+ songId,
6
+ resourceId,
7
+ md5,
8
+ filename,
9
+ song,
10
+ artist,
11
+ album,
12
+ bitrate = 999000,
13
+ } = query
14
+
15
+ if (!songId || !resourceId || !md5 || !filename) {
16
+ return Promise.reject({
17
+ status: 400,
18
+ body: {
19
+ code: 400,
20
+ msg: '缺少必要参数: songId, resourceId, md5, filename',
21
+ },
22
+ })
23
+ }
24
+
25
+ const songName = song || filename.replace(/\.[^.]+$/, '')
26
+
27
+ const res2 = await request(
28
+ `/api/upload/cloud/info/v2`,
29
+ {
30
+ md5: md5,
31
+ songid: songId,
32
+ filename: filename,
33
+ song: songName,
34
+ album: album || '未知专辑',
35
+ artist: artist || '未知艺术家',
36
+ bitrate: String(bitrate),
37
+ resourceId: resourceId,
38
+ },
39
+ createOption(query),
40
+ )
41
+
42
+ if (res2.body.code !== 200) {
43
+ return Promise.reject({
44
+ status: res2.status || 500,
45
+ body: {
46
+ code: res2.body.code || 500,
47
+ msg: res2.body.msg || '上传云盘信息失败',
48
+ detail: res2.body,
49
+ },
50
+ })
51
+ }
52
+
53
+ const res3 = await request(
54
+ `/api/cloud/pub/v2`,
55
+ {
56
+ songid: res2.body.songId,
57
+ },
58
+ createOption(query),
59
+ )
60
+
61
+ return {
62
+ status: 200,
63
+ body: {
64
+ code: 200,
65
+ data: {
66
+ songId: res2.body.songId,
67
+ ...res3.body,
68
+ },
69
+ },
70
+ cookie: res2.cookie,
71
+ }
72
+ }
@@ -0,0 +1,111 @@
1
+ const { default: axios } = require('axios')
2
+ const createOption = require('../util/option.js')
3
+
4
+ module.exports = async (query, request) => {
5
+ const { md5, fileSize, filename, bitrate = 999000 } = query
6
+
7
+ if (!md5 || !fileSize || !filename) {
8
+ return Promise.reject({
9
+ status: 400,
10
+ body: {
11
+ code: 400,
12
+ msg: '缺少必要参数: md5, fileSize, filename',
13
+ },
14
+ })
15
+ }
16
+
17
+ const ext = filename.includes('.') ? filename.split('.').pop() : 'mp3'
18
+
19
+ const checkRes = await request(
20
+ `/api/cloud/upload/check`,
21
+ {
22
+ bitrate: String(bitrate),
23
+ ext: '',
24
+ length: fileSize,
25
+ md5: md5,
26
+ songId: '0',
27
+ version: 1,
28
+ },
29
+ createOption(query),
30
+ )
31
+
32
+ const bucket = 'jd-musicrep-privatecloud-audio-public'
33
+ const tokenRes = await request(
34
+ `/api/nos/token/alloc`,
35
+ {
36
+ bucket: bucket,
37
+ ext: ext,
38
+ filename: filename
39
+ .replace(/\.[^.]+$/, '')
40
+ .replace(/\s/g, '')
41
+ .replace(/\./g, '_'),
42
+ local: false,
43
+ nos_product: 3,
44
+ type: 'audio',
45
+ md5: md5,
46
+ },
47
+ createOption(query, 'weapi'),
48
+ )
49
+
50
+ if (!tokenRes.body.result || !tokenRes.body.result.objectKey) {
51
+ return Promise.reject({
52
+ status: 500,
53
+ body: {
54
+ code: 500,
55
+ msg: '获取上传token失败',
56
+ detail: tokenRes.body,
57
+ },
58
+ })
59
+ }
60
+
61
+ let lbs
62
+ try {
63
+ lbs = (
64
+ await axios({
65
+ method: 'get',
66
+ url: `https://wanproxy.127.net/lbs?version=1.0&bucketname=${bucket}`,
67
+ timeout: 10000,
68
+ })
69
+ ).data
70
+ } catch (error) {
71
+ return Promise.reject({
72
+ status: 500,
73
+ body: {
74
+ code: 500,
75
+ msg: '获取上传服务器地址失败',
76
+ detail: error.message,
77
+ },
78
+ })
79
+ }
80
+
81
+ if (!lbs || !lbs.upload || !lbs.upload[0]) {
82
+ return Promise.reject({
83
+ status: 500,
84
+ body: {
85
+ code: 500,
86
+ msg: '获取上传服务器地址无效',
87
+ detail: lbs,
88
+ },
89
+ })
90
+ }
91
+
92
+ return {
93
+ status: 200,
94
+ body: {
95
+ code: 200,
96
+ data: {
97
+ needUpload: checkRes.body.needUpload,
98
+ songId: checkRes.body.songId,
99
+ uploadToken: tokenRes.body.result.token,
100
+ objectKey: tokenRes.body.result.objectKey,
101
+ resourceId: tokenRes.body.result.resourceId,
102
+ uploadUrl: `${lbs.upload[0]}/${bucket}/${tokenRes.body.result.objectKey.replace(/\//g, '%2F')}?offset=0&complete=true&version=1.0`,
103
+ bucket: bucket,
104
+ md5: md5,
105
+ fileSize: fileSize,
106
+ filename: filename,
107
+ },
108
+ },
109
+ cookie: checkRes.cookie,
110
+ }
111
+ }
@@ -0,0 +1,15 @@
1
+ // 对某一首歌曲发表评论
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ threadId: 'R_SO_4_' + query.id,
7
+ content: query.content,
8
+ resourceType: '0',
9
+ resourceId: '0',
10
+ expressionPicId: '-1',
11
+ bubbleId: '-1',
12
+ checkToken: '',
13
+ }
14
+ return request('/api/resource/comments/add', data, createOption(query))
15
+ }
@@ -0,0 +1,10 @@
1
+ // 删除评论
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ commentId: query.cid,
7
+ threadId: 'R_SO_4_' + query.id,
8
+ }
9
+ return request(`/api/resource/comments/delete`, data, createOption(query))
10
+ }
@@ -0,0 +1,30 @@
1
+ // 评论统计数据
2
+ // type: 0=歌曲 1=MV 2=歌单 3=专辑 4=电台节目 5=视频 6=动态 7=电台
3
+ // ids: 资源 ID 列表,多个用逗号分隔,如 "123,456"
4
+ const { resourceTypeMap } = require('../util/config.json')
5
+ const createOption = require('../util/option.js')
6
+
7
+ // 从 resourceTypeMap 的前缀值中提取网易云内部资源类型编号
8
+ // 例如 "R_SO_4_" -> "4", "A_DR_14_" -> "14"
9
+ const resourceTypeIdMap = Object.fromEntries(
10
+ Object.entries(resourceTypeMap).map(([key, prefix]) => [
11
+ key,
12
+ prefix.replace(/_$/, '').split('_').pop(),
13
+ ]),
14
+ )
15
+
16
+ module.exports = (query, request) => {
17
+ const ids = String(query.ids || query.id || '')
18
+ .split(',')
19
+ .map((id) => id.trim())
20
+ .filter(Boolean)
21
+
22
+ return request(
23
+ `/api/resource/commentInfo/list`,
24
+ {
25
+ resourceType: resourceTypeIdMap[String(query.type || 0)],
26
+ resourceIds: JSON.stringify(ids),
27
+ },
28
+ createOption(query, 'weapi'),
29
+ )
30
+ }
@@ -0,0 +1,13 @@
1
+ // 回复评论
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ threadId: query.id,
7
+ commentId: query.commentId,
8
+ content: query.content,
9
+ resourceType: '0',
10
+ resourceId: '0',
11
+ }
12
+ return request(`/api/v1/resource/comments/reply`, data, createOption(query))
13
+ }
@@ -0,0 +1,13 @@
1
+ // 搜索建议pc端
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const data = {
6
+ keyword: query.keyword || '',
7
+ }
8
+ return request(
9
+ `/api/search/pc/suggest/keyword/get`,
10
+ data,
11
+ createOption(query),
12
+ )
13
+ }
@@ -0,0 +1,12 @@
1
+ // 喜欢歌曲
2
+
3
+ const createOption = require('../util/option.js')
4
+ module.exports = (query, request) => {
5
+ const like = query.like !== 'false'
6
+ const data = {
7
+ trackId: query.id,
8
+ userid: query.uid,
9
+ like: like,
10
+ }
11
+ return request(`/api/song/like`, data, createOption(query))
12
+ }
@@ -1,77 +1,5 @@
1
- // GD音乐台get(适配SPlayer的UNM-Server)
2
- // 感谢来自GD Studio的开发API
3
- // https://music.gdstudio.xyz/
4
-
5
- const createOption = require('../util/option.js')
1
+ // 夹带私货的东西就不要放在这里了
6
2
 
7
3
  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
- }
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)
38
-
39
- const response = await fetch(apiUrl.toString())
40
- if (!response.ok) throw new Error(`API 响应状态: ${response.status}`)
41
- const result = await response.json()
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
- }
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'
71
- ? { error: error.message }
72
- : {}),
73
- data: [],
74
- },
75
- }
76
- }
4
+ return { status: 200, body: { code: 200, data: [] } }
77
5
  }
@@ -0,0 +1,53 @@
1
+ // 获取客户端歌曲下载链接 - v1
2
+ // 此版本不再采用 br 作为音质区分的标准
3
+ // 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
4
+
5
+ const createOption = require('../util/option.js')
6
+ module.exports = async (query, request) => {
7
+ const data = {
8
+ id: query.id,
9
+ immerseType: 'c51',
10
+ level: query.level,
11
+ }
12
+ const response = await request(
13
+ `/api/song/enhance/download/url/v1`,
14
+ data,
15
+ createOption(query),
16
+ )
17
+ let url = response?.body?.data?.[0]?.url
18
+
19
+ if (!url) {
20
+ const fallbackData = {
21
+ ids: `[${query.id}]`,
22
+ level: query.level,
23
+ encodeType: 'flac',
24
+ }
25
+ if (query.level === 'sky') {
26
+ fallbackData.immerseType = 'c51'
27
+ }
28
+ const fallback = await request(
29
+ `/api/song/enhance/player/url/v1`,
30
+ fallbackData,
31
+ createOption(query),
32
+ )
33
+ url = fallback?.body?.data?.[0]?.url
34
+
35
+ if (!url) {
36
+ return fallback
37
+ }
38
+
39
+ return {
40
+ status: 302,
41
+ body: '',
42
+ cookie: fallback.cookie || [],
43
+ redirectUrl: url,
44
+ }
45
+ }
46
+
47
+ return {
48
+ status: 302,
49
+ body: '',
50
+ cookie: response.cookie || [],
51
+ redirectUrl: url,
52
+ }
53
+ }