diff --git a/README.MD b/README.MD index fc7d8e4..d95c7d3 100644 --- a/README.MD +++ b/README.MD @@ -30,7 +30,7 @@ ## 项目简介 -网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署(如果原版诈尸, 我会及时同步 or 归档)。 +网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署 > [!IMPORTANT] > @@ -215,7 +215,7 @@ pnpm test 原作者 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 项目为本项目基础 (该项目在`npmjs`网站上仍持续维护, 但 github 仓库已不再更新) -感谢大佬们为逆向eapi, weapi等加密算法所做的贡献 +感谢大佬们为逆向eapi, weapi, xeapi等加密算法所做的贡献 项目参考: diff --git a/module/register_xeapikey.js b/module/register_xeapikey.js new file mode 100644 index 0000000..9372f35 --- /dev/null +++ b/module/register_xeapikey.js @@ -0,0 +1,74 @@ +const { default: axios } = require('axios') +const encrypt = require('../util/crypto') +const { APP_CONF } = require('../util/config.json') + +const generateNonce = () => { + let nonce = '' + for (let i = 0; i < 16; i++) { + nonce += Math.floor(Math.random() * 10).toString() + } + return nonce +} + +module.exports = async (query, request) => { + const nonce = generateNonce() + const timestamp = String(Date.now()) + const deviceId = query.deviceId || global.deviceId || '' + const currentKeyVersion = query.currentKeyVersion || '' + + const data = { + appVersion: '9.1.65', + currentKeyVersion, + deviceId, + nonce, + os: 'android', + requestType: 'active', + signature: encrypt.xeapiSign(timestamp, nonce), + t1: '', + t2: '', + timestamp, + uid: '', + } + + const res = await axios({ + method: 'POST', + url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get', + headers: { + 'User-Agent': + 'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)', + Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '', + }, + data: new URLSearchParams(data).toString(), + proxy: false, + }) + + if ( + !res.data || + res.data.code !== 200 || + !res.data.data || + !res.data.data.encryptedData + ) { + throw new Error('xeapi public key request failed') + } + if ( + !res.data.data.signature || + encrypt.xeapiSign(res.data.data.timestamp, nonce) !== + res.data.data.signature + ) { + throw new Error('xeapi public key response signature mismatch') + } + + const publicKey = encrypt.xeapiDecryptPublicKey(res.data.data.encryptedData) + if (!publicKey.sk) { + throw new Error('xeapi public key response missing sk') + } + + return { + status: 200, + body: { + ...publicKey, + deviceId, + }, + cookie: [], + } +} diff --git a/util/config.json b/util/config.json index e9a57f0..189e5fb 100644 --- a/util/config.json +++ b/util/config.json @@ -11,6 +11,7 @@ }, "APP_CONF": { "apiDomain": "https://interface.music.163.com", + "xeapiDomain": "https://interface3.music.163.com", "domain": "https://music.163.com", "encrypt": true, "encryptResponse": false, diff --git a/util/request.js b/util/request.js index c697fea..1b12d2e 100644 --- a/util/request.js +++ b/util/request.js @@ -110,7 +110,7 @@ const userAgentMap = { // 预先定义常量 const DOMAIN = APP_CONF.domain const API_DOMAIN = APP_CONF.apiDomain -const XEAPI_DOMAIN = 'https://interface3.music.163.com' +const XEAPI_DOMAIN = APP_CONF.xeapiDomain const ENCRYPT_RESPONSE = APP_CONF.encryptResponse const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803]) diff --git a/util/xeapiKey.js b/util/xeapiKey.js index ba6ced4..d308df5 100644 --- a/util/xeapiKey.js +++ b/util/xeapiKey.js @@ -1,59 +1,15 @@ -const { default: axios } = require('axios') -const encrypt = require('./crypto') -const { APP_CONF } = require('./config.json') - -const generateNonce = () => { - let nonce = '' - for (let i = 0; i < 16; i++) { - nonce += Math.floor(Math.random() * 10).toString() - } - return nonce -} +const registerXeapiKey = require('../module/register_xeapikey') const getXeapiPublicKey = async (currentPublicKey = {}, deviceId = '') => { - const nonce = generateNonce() - const timestamp = String(Date.now()) - const data = { - appVersion: '9.1.65', - currentKeyVersion: currentPublicKey.version || '', - deviceId, - nonce, - os: 'android', - requestType: 'active', - signature: encrypt.xeapiSign(timestamp, nonce), - t1: '', - t2: '', - timestamp, - uid: '', - } - const res = await axios({ - method: 'POST', - url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get', - headers: { - 'User-Agent': - 'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)', - Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '', + const result = await registerXeapiKey( + { + deviceId, + currentKeyVersion: currentPublicKey.version || '', }, - data: new URLSearchParams(data).toString(), - proxy: false, - }) - if ( - !res.data || - res.data.code !== 200 || - !res.data.data || - !res.data.data.encryptedData - ) { - throw new Error('xeapi public key request failed') - } - if ( - !res.data.data.signature || - encrypt.xeapiSign(res.data.data.timestamp, nonce) !== - res.data.data.signature - ) { - throw new Error('xeapi public key response signature mismatch') - } + null, + ) - const publicKey = encrypt.xeapiDecryptPublicKey(res.data.data.encryptedData) + const publicKey = result.body if (!publicKey.sk && currentPublicKey.sk) { publicKey.sk = currentPublicKey.sk }