diff --git a/README.MD b/README.MD index 7ccece5..0b216c8 100644 --- a/README.MD +++ b/README.MD @@ -100,15 +100,15 @@ $ sudo docker run -d -p 3000:3000 ncm-api ## 3. 环境变量 -| 变量名 | 默认值 | 说明 | -| -------------------------- | ------------------------------------ | ------------------------------------------------------------------------------ | +| 变量名 | 默认值 | 说明 | +|----------------------------|--------------------------------------|----------------------------------------------------| | **CORS_ALLOW_ORIGIN** | `*` | 允许跨域请求的域名。若需要限制,请指定具体域名(例如 `https://example.com`)。 | -| **ENABLE_PROXY** | `false` | 是否启用反向代理功能。 | -| **PROXY_URL** | `https://your-proxy-url.com/?proxy=` | 代理服务地址。仅当 `ENABLE_PROXY=true` 时生效。 | -| **ENABLE_GENERAL_UNBLOCK** | `true` | 是否启用全局解灰(推荐开启)。开启后所有歌曲都尝试自动解锁。 | -| **ENABLE_FLAC** | `true` | 是否启用无损音质(FLAC)。 | -| **SELECT_MAX_BR** | `false` | 启用无损音质时,是否选择最高码率音质。 | -| **FOLLOW_SOURCE_ORDER** | `true` | 是否严格按照音源列表顺序进行匹配。 | +| **ENABLE_PROXY** | `false` | 是否启用反向代理功能。 | +| **PROXY_URL** | `https://your-proxy-url.com/?proxy=` | 代理服务地址。仅当 `ENABLE_PROXY=true` 时生效。 | +| **ENABLE_GENERAL_UNBLOCK** | `true` | 是否启用全局解灰(推荐开启)。开启后所有歌曲都尝试自动解锁。 | +| **ENABLE_FLAC** | `true` | 是否启用无损音质(FLAC)。 | +| **SELECT_MAX_BR** | `false` | 启用无损音质时,是否选择最高码率音质。 | +| **FOLLOW_SOURCE_ORDER** | `true` | 是否严格按照音源列表顺序进行匹配。 | --- @@ -208,11 +208,11 @@ pnpm test ### SDK 生态 -| 语言 | 作者 | 地址 | 类型 | -| ------ | ------------------------------------------- | ---------------------------------------------------------------------------------------- | ------ | +| 语言 | 作者 | 地址 | 类型 | +|--------|---------------------------------------------|------------------------------------------------------------------------------------------|-----| | Java | [JackuXL](https://github.com/JackuXL) | [NeteaseCloudMusicApi-SDK](https://github.com/JackuXL/NeteaseCloudMusicApi-SDK) | 第三方 | | Java | [1015770492](https://github.com/1015770492) | https://github.com/1015770492/yumbo-music-utils | 第三方 | -| Python | [盧瞳](https://github.com/2061360308) | [NeteaseCloudMusic_PythonSDK](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK) | 第三方 | +| Python | [盧瞳](https://github.com/2061360308) | [NeteaseCloudMusic_PythonSDK](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK) | 第三方 | ### 依赖此项目的优秀开源项目 diff --git a/module/cloud_upload_complete.js b/module/cloud_upload_complete.js new file mode 100644 index 0000000..10e650b --- /dev/null +++ b/module/cloud_upload_complete.js @@ -0,0 +1,73 @@ +const createOption = require('../util/option.js') + +module.exports = async (query, request) => { + const { + songId, + resourceId, + md5, + filename, + song, + artist, + album, + bitrate = 999000, + } = query + + if (!songId || !resourceId || !md5 || !filename) { + return Promise.reject({ + status: 400, + body: { + code: 400, + msg: '缺少必要参数: songId, resourceId, md5, filename', + }, + }) + } + + const songName = song || filename.replace(/\.[^.]+$/, '') + const ext = filename.includes('.') ? filename.split('.').pop() : 'mp3' + + const res2 = await request( + `/api/upload/cloud/info/v2`, + { + md5: md5, + songid: songId, + filename: filename, + song: songName, + album: album || '未知专辑', + artist: artist || '未知艺术家', + bitrate: String(bitrate), + resourceId: resourceId, + }, + createOption(query), + ) + + if (res2.body.code !== 200) { + return Promise.reject({ + status: res2.status || 500, + body: { + code: res2.body.code || 500, + msg: res2.body.msg || '上传云盘信息失败', + detail: res2.body, + }, + }) + } + + const res3 = await request( + `/api/cloud/pub/v2`, + { + songid: res2.body.songId, + }, + createOption(query), + ) + + return { + status: 200, + body: { + code: 200, + data: { + songId: res2.body.songId, + ...res3.body, + }, + }, + cookie: res2.cookie, + } +} diff --git a/module/cloud_upload_token.js b/module/cloud_upload_token.js new file mode 100644 index 0000000..4238526 --- /dev/null +++ b/module/cloud_upload_token.js @@ -0,0 +1,109 @@ +const createOption = require('../util/option.js') +const crypto = require('crypto') + +module.exports = async (query, request) => { + const { md5, fileSize, filename, bitrate = 999000 } = query + + if (!md5 || !fileSize || !filename) { + return Promise.reject({ + status: 400, + body: { + code: 400, + msg: '缺少必要参数: md5, fileSize, filename', + }, + }) + } + + const ext = filename.includes('.') ? filename.split('.').pop() : 'mp3' + + const checkRes = await request( + `/api/cloud/upload/check`, + { + bitrate: String(bitrate), + ext: '', + length: fileSize, + md5: md5, + songId: '0', + version: 1, + }, + createOption(query), + ) + + const bucket = 'jd-musicrep-privatecloud-audio-public' + const tokenRes = await request( + `/api/nos/token/alloc`, + { + bucket: bucket, + ext: ext, + filename: filename.replace(/\.[^.]+$/, '').replace(/\s/g, '').replace(/\./g, '_'), + local: false, + nos_product: 3, + type: 'audio', + md5: md5, + }, + createOption(query, 'weapi'), + ) + + if (!tokenRes.body.result || !tokenRes.body.result.objectKey) { + return Promise.reject({ + status: 500, + body: { + code: 500, + msg: '获取上传token失败', + detail: tokenRes.body, + }, + }) + } + + const { default: axios } = require('axios') + let lbs + try { + lbs = ( + await axios({ + method: 'get', + url: `https://wanproxy.127.net/lbs?version=1.0&bucketname=${bucket}`, + timeout: 10000, + }) + ).data + } catch (error) { + return Promise.reject({ + status: 500, + body: { + code: 500, + msg: '获取上传服务器地址失败', + detail: error.message, + }, + }) + } + + if (!lbs || !lbs.upload || !lbs.upload[0]) { + return Promise.reject({ + status: 500, + body: { + code: 500, + msg: '获取上传服务器地址无效', + detail: lbs, + }, + }) + } + + return { + status: 200, + body: { + code: 200, + data: { + needUpload: checkRes.body.needUpload, + songId: checkRes.body.songId, + uploadToken: tokenRes.body.result.token, + objectKey: tokenRes.body.result.objectKey, + resourceId: tokenRes.body.result.resourceId, + uploadUrl: `${lbs.upload[0]}/${bucket}/${tokenRes.body.result.objectKey.replace('/', '%2F')}?offset=0&complete=true&version=1.0`, + bucket: bucket, + md5: md5, + fileSize: fileSize, + filename: filename, + }, + }, + cookie: checkRes.cookie, + } +} diff --git a/public/cloud.html b/public/cloud.html index 53bd60a..eda78ce 100644 --- a/public/cloud.html +++ b/public/cloud.html @@ -47,6 +47,59 @@ text-decoration: underline; } + .mode-section { + margin-bottom: 24px; + padding: 16px; + background: #f9f9f9; + border-radius: 8px; + } + + .mode-section label { + display: block; + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 12px; + } + + .mode-options { + display: flex; + gap: 16px; + flex-wrap: wrap; + } + + .mode-option { + display: flex; + align-items: flex-start; + gap: 8px; + cursor: pointer; + } + + .mode-option input[type="radio"] { + margin-top: 3px; + } + + .mode-option-text { + display: flex; + flex-direction: column; + } + + .mode-option-title { + font-size: 14px; + color: #333; + } + + .mode-option-desc { + font-size: 12px; + color: #999; + margin-top: 2px; + } + + .mode-option input[type="radio"]:checked + .mode-option-text .mode-option-title { + color: #333; + font-weight: 500; + } + .upload-section { margin-bottom: 32px; } @@ -72,6 +125,11 @@ display: none; } + .upload-btn.disabled { + background: #ccc; + cursor: not-allowed; + } + .songs-list { list-style: none; } @@ -99,6 +157,74 @@ padding: 20px; color: #666; } + + .progress-section { + margin-bottom: 24px; + display: none; + } + + .progress-section.active { + display: block; + } + + .progress-item { + margin-bottom: 12px; + padding: 12px; + background: #f9f9f9; + border-radius: 6px; + } + + .progress-item .name { + font-size: 14px; + color: #333; + margin-bottom: 8px; + word-break: break-all; + } + + .progress-item .status { + font-size: 12px; + color: #666; + margin-bottom: 6px; + } + + .progress-bar { + height: 6px; + background: #e0e0e0; + border-radius: 3px; + overflow: hidden; + } + + .progress-bar .fill { + height: 100%; + background: #333; + border-radius: 3px; + transition: width 0.3s ease; + width: 0%; + } + + .progress-item.success .fill { + background: #4caf50; + } + + .progress-item.error .fill { + background: #f44336; + } + + .progress-item.error .status { + color: #f44336; + } + + .info-text { + font-size: 12px; + color: #999; + margin-top: 8px; + } + + .warning-text { + font-size: 12px; + color: #e65100; + margin-top: 8px; + } @@ -107,13 +233,36 @@