@neteasecloudmusicapienhanced/api 4.30.1 → 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.
- package/README.MD +47 -23
- package/data/china_ip_ranges.txt +4147 -0
- package/module/cloud.js +136 -119
- package/module/cloud_upload_complete.js +72 -0
- package/module/cloud_upload_token.js +111 -0
- package/module/comment_info_list.js +30 -0
- package/module/song_url_ncmget.js +2 -74
- package/module/voice_upload.js +36 -22
- package/package.json +19 -25
- package/plugins/songUpload.js +67 -19
- package/plugins/upload.js +5 -7
- package/public/cloud.html +406 -39
- package/public/docs/home.md +134 -4
- package/public/docs/index.html +1 -1
- package/public/docs/logo.svg +6 -0
- package/public/docs/netease.png +0 -0
- package/public/index.html +29 -4
- package/public/static/docs.png +0 -0
- package/server.js +37 -17
- package/util/fileHelper.js +88 -0
- package/util/index.js +55 -52
- package/data/ChineseIPGenerate.csv +0 -26
- package/public/docs/ncmapireborn.png +0 -0
- /package/public/static/{screenshot1.png → module_test.png} +0 -0
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
|
-
|
|
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
|
|
19
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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,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
|
+
}
|
|
@@ -1,77 +1,5 @@
|
|
|
1
|
-
//
|
|
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
|
-
|
|
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
|
}
|
package/module/voice_upload.js
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
14
|
-
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
65
|
+
|
|
65
66
|
const res2 = await parser.parseStringPromise(res.data)
|
|
66
67
|
|
|
67
|
-
const
|
|
68
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
{
|