diff --git a/.DS_Store b/.DS_Store index 1b41a7f..ce7e23b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/README.MD b/README.MD index a942d6e..7fa1827 100644 --- a/README.MD +++ b/README.MD @@ -22,7 +22,7 @@ ## 环境要求 -需要 NodeJS 14+ 环境 +需要 NodeJS 18+ 环境 ## 安装 @@ -70,6 +70,45 @@ npx NeteaseCloudMusicApi@latest 使用此命令,可直接启动服务,无需下载或者 clone 项目 +## Docker 容器运行 + +> 注意: 在 docker 中运行的时候, 由于使用了 request 来发请求, 所以会检查几个 proxy 相关的环境变量(如下所列), 这些环境变量 会影响到 request 的代理, 详情请参考[request 的文档](https://github.com/request/request#proxies), 如果这些环境变量 指向的代理不可用, 那么就会造成错误, 所以在使用 docker 的时候一定要注意这些环境变量. 不过, 要是你在 query 中加上了 proxy 参数, 那么环境变量会被覆盖, 就会用你通过 proxy 参数提供的代理了. + +request 相关的环境变量 + +1. http_proxy +2. https_proxy +3. HTTP_PROXY +4. HTTPS_PROXY +5. no_proxy +6. NO_PROXY + +```shell +docker pull binaryify/netease_cloud_music_api + +docker run -d -p 3000:3000 --name netease_cloud_music_api binaryify/netease_cloud_music_api + + +// 或者 +docker run -d -p 3000:3000 binaryify/netease_cloud_music_api + +// 去掉或者设置相关的环境变量 + +docker run -d -p 3000:3000 --name netease_cloud_music_api -e http_proxy= -e https_proxy= -e no_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= -e NO_PROXY= binaryify/netease_cloud_music_api + +// 或者 +docker run -d -p 3000:3000 -e http_proxy= -e https_proxy= -e no_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= -e NO_PROXY= binaryify/netease_cloud_music_api +``` + +> 以下是自行 build docker 镜像方式 + +``` +$ git clone https://gitlab.com/Binaryify/neteasecloudmusicapi.git && cd NeteaseCloudMusicApi + +$ sudo docker build . -t netease-music-api + +$ sudo docker run -d -p 3000:3000 netease-music-api + ## Vercel 部署 v4.0.8 加入了 Vercel 配置文件,可以直接在 Vercel 下部署了,不需要自己的服务器 diff --git a/module/login_cellphone.js b/module/login_cellphone.js index bdc4f0d..c1d8d45 100644 --- a/module/login_cellphone.js +++ b/module/login_cellphone.js @@ -18,7 +18,7 @@ module.exports = async (query, request) => { let result = await request( `/api/w/login/cellphone`, data, - createOption(query), + createOption(query, 'weapi'), ) if (result.body.code === 200) { diff --git a/node_modules/.bin/NeteaseCloudMusicApi b/node_modules/.bin/NeteaseCloudMusicApi index ae0f0f9..e575b31 100755 --- a/node_modules/.bin/NeteaseCloudMusicApi +++ b/node_modules/.bin/NeteaseCloudMusicApi @@ -10,9 +10,9 @@ case `uname` in esac if [ -z "$NODE_PATH" ]; then - export NODE_PATH="/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.0_c0457443e151ba9902c455ed8f2f0b0f/node_modules/NeteaseCloudMusicApi/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.0_c0457443e151ba9902c455ed8f2f0b0f/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/node_modules" + export NODE_PATH="/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.1_c316bfca3d49c7c85c2b0b37dab8c4e4/node_modules/NeteaseCloudMusicApi/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.1_c316bfca3d49c7c85c2b0b37dab8c4e4/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/node_modules" else - export NODE_PATH="/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.0_c0457443e151ba9902c455ed8f2f0b0f/node_modules/NeteaseCloudMusicApi/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.0_c0457443e151ba9902c455ed8f2f0b0f/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/node_modules:$NODE_PATH" + export NODE_PATH="/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.1_c316bfca3d49c7c85c2b0b37dab8c4e4/node_modules/NeteaseCloudMusicApi/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/NeteaseCloudMusicApi@4.28.1_c316bfca3d49c7c85c2b0b37dab8c4e4/node_modules:/Users/kudzu/Library/pnpm/global/5/.pnpm/node_modules:$NODE_PATH" fi if [ -x "$basedir/node" ]; then exec "$basedir/node" "$basedir/../../app.js" "$@" diff --git a/package.json b/package.json index 9074a26..88a9ede 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "NeteaseCloudMusicApi", - "version": "4.28.0", + "version": "4.28.1", "description": "网易云音乐 NodeJS 版 API", "scripts": { "start": "node app.js", diff --git a/public/.DS_Store b/public/.DS_Store index cd3267f..13f8f45 100644 Binary files a/public/.DS_Store and b/public/.DS_Store differ diff --git a/util/client-sign.js b/util/client-sign.js index 4736a3a..49e785e 100644 --- a/util/client-sign.js +++ b/util/client-sign.js @@ -1,169 +1,169 @@ -const crypto = require("crypto"); -const os = require("os"); - -class AdvancedClientSignGenerator { - /** - * 获取本机MAC地址 - */ - static getRealMacAddress() { - try { - const interfaces = os.networkInterfaces(); - for (let interfaceName in interfaces) { - const interface = interfaces[interfaceName]; - for (let i = 0; i < interface.length; i++) { - const alias = interface[i]; - // 排除内部地址和无效地址 - if ( - alias.mac && - alias.mac !== "00:00:00:00:00:00" && - !alias.internal - ) { - return alias.mac.toUpperCase(); - } - } - } - return null; - } catch (error) { - console.warn("获取MAC地址失败:", error.message); - return null; - } - } - - /** - * 生成随机MAC地址 - */ - static generateRandomMac() { - const chars = "0123456789ABCDEF"; - let mac = ""; - for (let i = 0; i < 6; i++) { - if (i > 0) mac += ":"; - mac += - chars[Math.floor(Math.random() * 16)] + - chars[Math.floor(Math.random() * 16)]; - } - // 确保第一个字节是单播地址(最低位为0) - const firstByte = parseInt(mac.substring(0, 2), 16); - const unicastFirstByte = (firstByte & 0xfe) - .toString(16) - .padStart(2, "0") - .toUpperCase(); - return unicastFirstByte + mac.substring(2); - } - - /** - * 获取MAC地址(优先真实,否则随机) - */ - static getMacAddress() { - const realMac = this.getRealMacAddress(); - if (realMac) { - return realMac; - } - console.warn("无法获取真实MAC地址,使用随机生成"); - return this.generateRandomMac(); - } - - /** - * 字符串转HEX编码 - */ - static stringToHex(str) { - return Buffer.from(str, "utf8").toString("hex").toUpperCase(); - } - - /** - * SHA-256哈希 - */ - static sha256(data) { - return crypto.createHash("sha256").update(data, "utf8").digest("hex"); - } - - /** - * 生成随机设备ID - */ - static generateRandomDeviceId() { - const partLengths = [4, 4, 4, 4, 4, 4, 4, 5]; // 各部分长度 - const chars = "0123456789ABCDEF"; - - const parts = partLengths.map((length) => { - let part = ""; - for (let i = 0; i < length; i++) { - part += chars[Math.floor(Math.random() * 16)]; - } - return part; - }); - - return parts.join("_"); - } - - /** - * 生成随机clientSign(优先使用真实MAC,否则随机) - */ - static generateRandomClientSign(secretKey = "") { - // 获取MAC地址(优先真实,否则随机) - const macAddress = this.getMacAddress(); - - // 生成随机设备ID - const deviceId = this.generateRandomDeviceId(); - - // 转换设备ID为HEX - const hexDeviceId = this.stringToHex(deviceId); - - // 构造签名字符串 - const signString = `${macAddress}@@@${hexDeviceId}`; - - // 生成哈希 - const hash = this.sha256(signString + secretKey); - - return `${signString}@@@@@@${hash}`; - } - - /** - * 批量生成多个随机签名 - */ - static generateMultipleRandomSigns(count, secretKey = "") { - const signs = []; - for (let i = 0; i < count; i++) { - signs.push(this.generateRandomClientSign(secretKey)); - } - return signs; - } - - /** - * 使用指定参数生成签名 - */ - static generateWithCustomDeviceId(macAddress, deviceId, secretKey = "") { - const hexDeviceId = this.stringToHex(deviceId); - const signString = `${macAddress}@@@${hexDeviceId}`; - const hash = this.sha256(signString + secretKey); - return `${signString}@@@@@@${hash}`; - } - - /** - * 验证签名格式是否正确 - */ - static validateClientSign(clientSign) { - try { - const parts = clientSign.split("@@@@@@"); - if (parts.length !== 2) return false; - - const [infoPart, hash] = parts; - const infoParts = infoPart.split("@@@"); - if (infoParts.length !== 2) return false; - - const [mac, hexDeviceId] = infoParts; - - // 验证MAC地址格式 - const macRegex = /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/; - if (!macRegex.test(mac)) return false; - - // 验证哈希格式 - const hashRegex = /^[0-9a-f]{64}$/; - if (!hashRegex.test(hash)) return false; - - return true; - } catch (error) { - return false; - } - } -} - -module.exports = AdvancedClientSignGenerator; \ No newline at end of file +const crypto = require('crypto') +const os = require('os') + +class AdvancedClientSignGenerator { + /** + * 获取本机MAC地址 + */ + static getRealMacAddress() { + try { + const interfaces = os.networkInterfaces() + for (let interfaceName in interfaces) { + const interfaceItem = interfaces[interfaceName] + for (let i = 0; i < interfaceItem.length; i++) { + const alias = interfaceItem[i] + // 排除内部地址和无效地址 + if ( + alias.mac && + alias.mac !== '00:00:00:00:00:00' && + !alias.internal + ) { + return alias.mac.toUpperCase() + } + } + } + return null + } catch (error) { + console.warn('获取MAC地址失败:', error.message) + return null + } + } + + /** + * 生成随机MAC地址 + */ + static generateRandomMac() { + const chars = '0123456789ABCDEF' + let mac = '' + for (let i = 0; i < 6; i++) { + if (i > 0) mac += ':' + mac += + chars[Math.floor(Math.random() * 16)] + + chars[Math.floor(Math.random() * 16)] + } + // 确保第一个字节是单播地址(最低位为0) + const firstByte = parseInt(mac.substring(0, 2), 16) + const unicastFirstByte = (firstByte & 0xfe) + .toString(16) + .padStart(2, '0') + .toUpperCase() + return unicastFirstByte + mac.substring(2) + } + + /** + * 获取MAC地址(优先真实,否则随机) + */ + static getMacAddress() { + const realMac = this.getRealMacAddress() + if (realMac) { + return realMac + } + console.warn('无法获取真实MAC地址,使用随机生成') + return this.generateRandomMac() + } + + /** + * 字符串转HEX编码 + */ + static stringToHex(str) { + return Buffer.from(str, 'utf8').toString('hex').toUpperCase() + } + + /** + * SHA-256哈希 + */ + static sha256(data) { + return crypto.createHash('sha256').update(data, 'utf8').digest('hex') + } + + /** + * 生成随机设备ID + */ + static generateRandomDeviceId() { + const partLengths = [4, 4, 4, 4, 4, 4, 4, 5] // 各部分长度 + const chars = '0123456789ABCDEF' + + const parts = partLengths.map((length) => { + let part = '' + for (let i = 0; i < length; i++) { + part += chars[Math.floor(Math.random() * 16)] + } + return part + }) + + return parts.join('_') + } + + /** + * 生成随机clientSign(优先使用真实MAC,否则随机) + */ + static generateRandomClientSign(secretKey = '') { + // 获取MAC地址(优先真实,否则随机) + const macAddress = this.getMacAddress() + + // 生成随机设备ID + const deviceId = this.generateRandomDeviceId() + + // 转换设备ID为HEX + const hexDeviceId = this.stringToHex(deviceId) + + // 构造签名字符串 + const signString = `${macAddress}@@@${hexDeviceId}` + + // 生成哈希 + const hash = this.sha256(signString + secretKey) + + return `${signString}@@@@@@${hash}` + } + + /** + * 批量生成多个随机签名 + */ + static generateMultipleRandomSigns(count, secretKey = '') { + const signs = [] + for (let i = 0; i < count; i++) { + signs.push(this.generateRandomClientSign(secretKey)) + } + return signs + } + + /** + * 使用指定参数生成签名 + */ + static generateWithCustomDeviceId(macAddress, deviceId, secretKey = '') { + const hexDeviceId = this.stringToHex(deviceId) + const signString = `${macAddress}@@@${hexDeviceId}` + const hash = this.sha256(signString + secretKey) + return `${signString}@@@@@@${hash}` + } + + /** + * 验证签名格式是否正确 + */ + static validateClientSign(clientSign) { + try { + const parts = clientSign.split('@@@@@@') + if (parts.length !== 2) return false + + const [infoPart, hash] = parts + const infoParts = infoPart.split('@@@') + if (infoParts.length !== 2) return false + + const [mac, hexDeviceId] = infoParts + + // 验证MAC地址格式 + const macRegex = /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/ + if (!macRegex.test(mac)) return false + + // 验证哈希格式 + const hashRegex = /^[0-9a-f]{64}$/ + if (!hashRegex.test(hash)) return false + + return true + } catch (error) { + return false + } + } +} + +module.exports = AdvancedClientSignGenerator diff --git a/util/request.js b/util/request.js index 79cc543..2d57d69 100644 --- a/util/request.js +++ b/util/request.js @@ -100,7 +100,7 @@ const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803]) // chooseUserAgent函数 const chooseUserAgent = (crypto, uaType = 'pc') => { - return userAgentMap[crypto]?.[uaType] || '' + return (userAgentMap[crypto] && userAgentMap[crypto][uaType]) || '' } // cookie处理 @@ -152,8 +152,7 @@ const createHeaderCookie = (header) => { const generateRequestId = () => { return `${now()}_${floor(random() * 1000) .toString() - .padStart(4, "0")}`; - + .padStart(4, '0')}` } const createRequest = (uri, data, options) => {