mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-10-22 15:41:23 +08:00
chore(version): 更新api到4.28.0
Co-Authored-By: binaryify <binaryify@gmail.com>
This commit is contained in:
+203
-143
@@ -1,3 +1,4 @@
|
||||
// 预先导入和绑定常用模块及函数
|
||||
const encrypt = require('./crypto')
|
||||
const CryptoJS = require('crypto-js')
|
||||
const { default: axios } = require('axios')
|
||||
@@ -8,30 +9,50 @@ const tunnel = require('tunnel')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const tmpPath = require('os').tmpdir()
|
||||
const { cookieToJson, cookieObjToString, toBoolean } = require('./index')
|
||||
const {
|
||||
cookieToJson,
|
||||
cookieObjToString,
|
||||
toBoolean,
|
||||
generateRandomChineseIP,
|
||||
} = require('./index')
|
||||
const { URLSearchParams, URL } = require('url')
|
||||
const { APP_CONF } = require('../util/config.json')
|
||||
|
||||
// 预先读取匿名token并缓存
|
||||
const anonymous_token = fs.readFileSync(
|
||||
path.resolve(tmpPath, './anonymous_token'),
|
||||
'utf-8',
|
||||
)
|
||||
const { URLSearchParams, URL } = require('url')
|
||||
const { APP_CONF } = require('../util/config.json')
|
||||
// request.debug = true // 开启可看到更详细信息
|
||||
|
||||
// 预先绑定常用函数和常量
|
||||
const floor = Math.floor
|
||||
const random = Math.random
|
||||
const now = Date.now
|
||||
const keys = Object.keys
|
||||
const stringify = JSON.stringify
|
||||
const parse = JSON.parse
|
||||
const characters = 'abcdefghijklmnopqrstuvwxyz'
|
||||
const charactersLength = characters.length
|
||||
|
||||
// 预先创建HTTP/HTTPS agents并重用
|
||||
const createHttpAgent = () => new http.Agent({ keepAlive: true })
|
||||
const createHttpsAgent = () => new https.Agent({ keepAlive: true })
|
||||
|
||||
// 预先计算WNMCID(只计算一次)
|
||||
const WNMCID = (function () {
|
||||
const characters = 'abcdefghijklmnopqrstuvwxyz'
|
||||
let randomString = ''
|
||||
for (let i = 0; i < 6; i++)
|
||||
randomString += characters.charAt(
|
||||
Math.floor(Math.random() * characters.length),
|
||||
)
|
||||
return `${randomString}.${Date.now().toString()}.01.0`
|
||||
for (let i = 0; i < 6; i++) {
|
||||
randomString += characters.charAt(floor(random() * charactersLength))
|
||||
}
|
||||
return `${randomString}.${now().toString()}.01.0`
|
||||
})()
|
||||
|
||||
// 预先定义osMap
|
||||
const osMap = {
|
||||
pc: {
|
||||
os: 'pc',
|
||||
appver: '3.0.18.203152',
|
||||
osver: 'Microsoft-Windows-10-Professional-build-22631-64bit',
|
||||
appver: '3.1.17.204416',
|
||||
osver: 'Microsoft-Windows-10-Professional-build-19045-64bit',
|
||||
channel: 'netease',
|
||||
},
|
||||
linux: {
|
||||
@@ -54,91 +75,128 @@ const osMap = {
|
||||
},
|
||||
}
|
||||
|
||||
const chooseUserAgent = (crypto, uaType = 'pc') => {
|
||||
const userAgentMap = {
|
||||
weapi: {
|
||||
pc: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
|
||||
},
|
||||
linuxapi: {
|
||||
linux:
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
|
||||
},
|
||||
api: {
|
||||
pc: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/91.0.4472.164 NeteaseMusicDesktop/3.0.18.203152',
|
||||
android:
|
||||
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
|
||||
iphone: 'NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)',
|
||||
},
|
||||
}
|
||||
return userAgentMap[crypto][uaType] || ''
|
||||
// 预先定义userAgentMap
|
||||
const userAgentMap = {
|
||||
weapi: {
|
||||
pc: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
|
||||
},
|
||||
linuxapi: {
|
||||
linux:
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
|
||||
},
|
||||
api: {
|
||||
pc: 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/91.0.4472.164 NeteaseMusicDesktop/3.0.18.203152',
|
||||
android:
|
||||
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
|
||||
iphone: 'NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)',
|
||||
},
|
||||
}
|
||||
|
||||
// 预先定义常量
|
||||
const DOMAIN = APP_CONF.domain
|
||||
const API_DOMAIN = APP_CONF.apiDomain
|
||||
const ENCRYPT_RESPONSE = APP_CONF.encryptResponse
|
||||
const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803])
|
||||
|
||||
// chooseUserAgent函数
|
||||
const chooseUserAgent = (crypto, uaType = 'pc') => {
|
||||
return userAgentMap[crypto]?.[uaType] || ''
|
||||
}
|
||||
|
||||
// cookie处理
|
||||
const processCookieObject = (cookie, uri) => {
|
||||
const _ntes_nuid = CryptoJS.lib.WordArray.random(32).toString()
|
||||
const os = osMap[cookie.os] || osMap['pc']
|
||||
|
||||
const processedCookie = {
|
||||
...cookie,
|
||||
__remember_me: 'true',
|
||||
ntes_kaola_ad: '1',
|
||||
_ntes_nuid: cookie._ntes_nuid || _ntes_nuid,
|
||||
_ntes_nnid: cookie._ntes_nnid || `${_ntes_nuid},${now().toString()}`,
|
||||
WNMCID: cookie.WNMCID || WNMCID,
|
||||
WEVNSM: cookie.WEVNSM || '1.0.0',
|
||||
osver: cookie.osver || os.osver,
|
||||
deviceId: cookie.deviceId || global.deviceId,
|
||||
os: cookie.os || os.os,
|
||||
channel: cookie.channel || os.channel,
|
||||
appver: cookie.appver || os.appver,
|
||||
}
|
||||
|
||||
if (uri.indexOf('login') === -1) {
|
||||
processedCookie['NMTID'] = CryptoJS.lib.WordArray.random(16).toString()
|
||||
}
|
||||
|
||||
if (!processedCookie.MUSIC_U) {
|
||||
processedCookie.MUSIC_A = processedCookie.MUSIC_A || anonymous_token
|
||||
}
|
||||
|
||||
return processedCookie
|
||||
}
|
||||
|
||||
// header cookie生成
|
||||
const createHeaderCookie = (header) => {
|
||||
const headerKeys = keys(header)
|
||||
const cookieParts = new Array(headerKeys.length)
|
||||
|
||||
for (let i = 0, len = headerKeys.length; i < len; i++) {
|
||||
const key = headerKeys[i]
|
||||
cookieParts[i] =
|
||||
encodeURIComponent(key) + '=' + encodeURIComponent(header[key])
|
||||
}
|
||||
|
||||
return cookieParts.join('; ')
|
||||
}
|
||||
|
||||
// requestId生成
|
||||
const generateRequestId = () => {
|
||||
return `${now()}_${floor(random() * 1000)
|
||||
.toString()
|
||||
.padStart(4, "0")}`;
|
||||
|
||||
}
|
||||
|
||||
const createRequest = (uri, data, options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let headers = options.headers || {}
|
||||
let ip = options.realIP || options.ip || ''
|
||||
// console.log(ip)
|
||||
// 变量声明和初始化
|
||||
const headers = options.headers ? { ...options.headers } : {}
|
||||
const ip = options.realIP || options.ip || ''
|
||||
|
||||
// IP头设置
|
||||
if (ip) {
|
||||
headers['X-Real-IP'] = ip
|
||||
headers['X-Forwarded-For'] = ip
|
||||
}
|
||||
// headers['X-Real-IP'] = '118.88.88.88'
|
||||
|
||||
let cookie = options.cookie || {}
|
||||
if (typeof cookie === 'string') {
|
||||
cookie = cookieToJson(cookie)
|
||||
}
|
||||
if (typeof cookie === 'object') {
|
||||
let _ntes_nuid = CryptoJS.lib.WordArray.random(32).toString()
|
||||
let os = osMap[cookie.os] || osMap['iphone']
|
||||
cookie = {
|
||||
...cookie,
|
||||
__remember_me: 'true',
|
||||
// NMTID: CryptoJS.lib.WordArray.random(16).toString(),
|
||||
ntes_kaola_ad: '1',
|
||||
_ntes_nuid: cookie._ntes_nuid || _ntes_nuid,
|
||||
_ntes_nnid:
|
||||
cookie._ntes_nnid || `${_ntes_nuid},${Date.now().toString()}`,
|
||||
WNMCID: cookie.WNMCID || WNMCID,
|
||||
WEVNSM: cookie.WEVNSM || '1.0.0',
|
||||
|
||||
osver: cookie.osver || os.osver,
|
||||
deviceId: cookie.deviceId || global.deviceId,
|
||||
os: cookie.os || os.os,
|
||||
channel: cookie.channel || os.channel,
|
||||
appver: cookie.appver || os.appver,
|
||||
}
|
||||
if (uri.indexOf('login') === -1) {
|
||||
cookie['NMTID'] = CryptoJS.lib.WordArray.random(16).toString()
|
||||
}
|
||||
if (!cookie.MUSIC_U) {
|
||||
// 游客
|
||||
cookie.MUSIC_A = cookie.MUSIC_A || anonymous_token
|
||||
}
|
||||
if (typeof cookie === 'object') {
|
||||
cookie = processCookieObject(cookie, uri)
|
||||
headers['Cookie'] = cookieObjToString(cookie)
|
||||
}
|
||||
let url = ''
|
||||
let encryptData = ''
|
||||
let crypto = options.crypto
|
||||
const csrfToken = cookie['__csrf'] || ''
|
||||
|
||||
let url = '',
|
||||
encryptData = '',
|
||||
crypto = options.crypto,
|
||||
csrfToken = cookie['__csrf'] || ''
|
||||
|
||||
// 加密方式选择
|
||||
if (crypto === '') {
|
||||
// 加密方式为空,以配置文件的加密方式为准
|
||||
if (APP_CONF.encrypt) {
|
||||
crypto = 'eapi'
|
||||
} else {
|
||||
crypto = 'api'
|
||||
}
|
||||
crypto = APP_CONF.encrypt ? 'eapi' : 'api'
|
||||
}
|
||||
|
||||
// 根据加密方式加密请求数据;目前任意uri都支持四种加密方式
|
||||
const answer = { status: 500, body: {}, cookie: [] }
|
||||
|
||||
// 根据加密方式处理
|
||||
switch (crypto) {
|
||||
case 'weapi':
|
||||
headers['Referer'] = APP_CONF.domain
|
||||
headers['Referer'] = DOMAIN
|
||||
headers['User-Agent'] = options.ua || chooseUserAgent('weapi')
|
||||
data.csrf_token = csrfToken
|
||||
encryptData = encrypt.weapi(data)
|
||||
url = APP_CONF.domain + '/weapi/' + uri.substr(5)
|
||||
url = DOMAIN + '/weapi/' + uri.substr(5)
|
||||
break
|
||||
|
||||
case 'linuxapi':
|
||||
@@ -146,127 +204,127 @@ const createRequest = (uri, data, options) => {
|
||||
options.ua || chooseUserAgent('linuxapi', 'linux')
|
||||
encryptData = encrypt.linuxapi({
|
||||
method: 'POST',
|
||||
url: APP_CONF.domain + uri,
|
||||
url: DOMAIN + uri,
|
||||
params: data,
|
||||
})
|
||||
url = APP_CONF.domain + '/api/linux/forward'
|
||||
url = DOMAIN + '/api/linux/forward'
|
||||
break
|
||||
|
||||
case 'eapi':
|
||||
case 'api':
|
||||
// 两种加密方式,都应生成客户端的cookie
|
||||
// header创建
|
||||
const header = {
|
||||
osver: cookie.osver, //系统版本
|
||||
osver: cookie.osver,
|
||||
deviceId: cookie.deviceId,
|
||||
os: cookie.os, //系统类型
|
||||
appver: cookie.appver, // app版本
|
||||
versioncode: cookie.versioncode || '140', //版本号
|
||||
mobilename: cookie.mobilename || '', //设备model
|
||||
buildver: cookie.buildver || Date.now().toString().substr(0, 10),
|
||||
resolution: cookie.resolution || '1920x1080', //设备分辨率
|
||||
os: cookie.os,
|
||||
appver: cookie.appver,
|
||||
versioncode: cookie.versioncode || '140',
|
||||
mobilename: cookie.mobilename || '',
|
||||
buildver: cookie.buildver || now().toString().substr(0, 10),
|
||||
resolution: cookie.resolution || '1920x1080',
|
||||
__csrf: csrfToken,
|
||||
channel: cookie.channel, //下载渠道
|
||||
requestId: `${Date.now()}_${Math.floor(Math.random() * 1000)
|
||||
.toString()
|
||||
.padStart(4, '0')}`,
|
||||
channel: cookie.channel,
|
||||
requestId: generateRequestId(),
|
||||
...(options.checkToken
|
||||
? { 'X-antiCheatToken': APP_CONF.checkToken }
|
||||
: {}),
|
||||
// clientSign: APP_CONF.clientSign,
|
||||
}
|
||||
|
||||
if (cookie.MUSIC_U) header['MUSIC_U'] = cookie.MUSIC_U
|
||||
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
||||
headers['Cookie'] = Object.keys(header)
|
||||
.map(
|
||||
(key) =>
|
||||
encodeURIComponent(key) + '=' + encodeURIComponent(header[key]),
|
||||
)
|
||||
.join('; ')
|
||||
|
||||
headers['Cookie'] = createHeaderCookie(header)
|
||||
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone')
|
||||
|
||||
if (crypto === 'eapi') {
|
||||
// 使用eapi加密
|
||||
data.header = header
|
||||
data.e_r = toBoolean(
|
||||
options.e_r !== undefined
|
||||
? options.e_r
|
||||
: data.e_r !== undefined
|
||||
? data.e_r
|
||||
: APP_CONF.encryptResponse,
|
||||
) // 用于加密接口返回值
|
||||
: ENCRYPT_RESPONSE,
|
||||
)
|
||||
encryptData = encrypt.eapi(uri, data)
|
||||
url = APP_CONF.apiDomain + '/eapi/' + uri.substr(5)
|
||||
url = API_DOMAIN + '/eapi/' + uri.substr(5)
|
||||
} else if (crypto === 'api') {
|
||||
// 不使用任何加密
|
||||
url = APP_CONF.apiDomain + uri
|
||||
url = API_DOMAIN + uri
|
||||
encryptData = data
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
// 未知的加密方式
|
||||
console.log('[ERR]', 'Unknown Crypto:', crypto)
|
||||
break
|
||||
}
|
||||
const answer = { status: 500, body: {}, cookie: [] }
|
||||
// console.log(headers, 'headers')
|
||||
// console.log(url);
|
||||
// settings创建
|
||||
let settings = {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
headers: headers,
|
||||
data: new URLSearchParams(encryptData).toString(),
|
||||
httpAgent: new http.Agent({ keepAlive: true }),
|
||||
httpsAgent: new https.Agent({ keepAlive: true }),
|
||||
httpAgent: createHttpAgent(),
|
||||
httpsAgent: createHttpsAgent(),
|
||||
}
|
||||
|
||||
// e_r处理
|
||||
if (data.e_r) {
|
||||
settings = {
|
||||
...settings,
|
||||
encoding: null,
|
||||
responseType: 'arraybuffer',
|
||||
}
|
||||
settings.encoding = null
|
||||
settings.responseType = 'arraybuffer'
|
||||
}
|
||||
|
||||
// 代理处理
|
||||
if (options.proxy) {
|
||||
if (options.proxy.indexOf('pac') > -1) {
|
||||
settings.httpAgent = new PacProxyAgent(options.proxy)
|
||||
settings.httpsAgent = new PacProxyAgent(options.proxy)
|
||||
const agent = new PacProxyAgent(options.proxy)
|
||||
settings.httpAgent = agent
|
||||
settings.httpsAgent = agent
|
||||
} else {
|
||||
const purl = new URL(options.proxy)
|
||||
if (purl.hostname) {
|
||||
const agent = tunnel[
|
||||
purl.protocol === 'https' ? 'httpsOverHttp' : 'httpOverHttp'
|
||||
]({
|
||||
proxy: {
|
||||
host: purl.hostname,
|
||||
port: purl.port || 80,
|
||||
proxyAuth:
|
||||
purl.username && purl.password
|
||||
? purl.username + ':' + purl.password
|
||||
: '',
|
||||
},
|
||||
})
|
||||
settings.httpsAgent = agent
|
||||
settings.httpAgent = agent
|
||||
settings.proxy = false
|
||||
} else {
|
||||
console.error('代理配置无效,不使用代理')
|
||||
try {
|
||||
const purl = new URL(options.proxy)
|
||||
if (purl.hostname) {
|
||||
const isHttps = purl.protocol === 'https:'
|
||||
const agent = tunnel[isHttps ? 'httpsOverHttp' : 'httpOverHttp']({
|
||||
proxy: {
|
||||
host: purl.hostname,
|
||||
port: purl.port || 80,
|
||||
proxyAuth:
|
||||
purl.username && purl.password
|
||||
? purl.username + ':' + purl.password
|
||||
: '',
|
||||
},
|
||||
})
|
||||
settings.httpsAgent = agent
|
||||
settings.httpAgent = agent
|
||||
settings.proxy = false
|
||||
} else {
|
||||
console.error('代理配置无效,不使用代理')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('代理URL解析失败:', e.message)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
settings.proxy = false
|
||||
}
|
||||
// console.log(settings.headers);
|
||||
axios(settings)
|
||||
.then((res) => {
|
||||
const body = res.data
|
||||
answer.cookie = (res.headers['set-cookie'] || []).map((x) =>
|
||||
x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
|
||||
)
|
||||
|
||||
try {
|
||||
if (crypto === 'eapi' && data.e_r) {
|
||||
// eapi接口返回值被加密,需要解密
|
||||
answer.body = encrypt.eapiResDecrypt(
|
||||
body.toString('hex').toUpperCase(),
|
||||
)
|
||||
} else {
|
||||
answer.body =
|
||||
typeof body == 'object' ? body : JSON.parse(body.toString())
|
||||
typeof body === 'object' ? body : parse(body.toString())
|
||||
}
|
||||
|
||||
if (answer.body.code) {
|
||||
@@ -274,28 +332,30 @@ const createRequest = (uri, data, options) => {
|
||||
}
|
||||
|
||||
answer.status = Number(answer.body.code || res.status)
|
||||
if (
|
||||
[201, 302, 400, 502, 800, 801, 802, 803].indexOf(answer.body.code) >
|
||||
-1
|
||||
) {
|
||||
// 特殊状态码
|
||||
|
||||
// 状态码检查(使用Set提升查找性能)
|
||||
if (SPECIAL_STATUS_CODES.has(answer.body.code)) {
|
||||
answer.status = 200
|
||||
}
|
||||
} catch (e) {
|
||||
// console.log(e)
|
||||
// can't decrypt and can't parse directly
|
||||
answer.body = body
|
||||
answer.status = res.status
|
||||
}
|
||||
|
||||
answer.status =
|
||||
100 < answer.status && answer.status < 600 ? answer.status : 400
|
||||
if (answer.status === 200) resolve(answer)
|
||||
else reject(answer)
|
||||
answer.status > 100 && answer.status < 600 ? answer.status : 400
|
||||
|
||||
if (answer.status === 200) {
|
||||
resolve(answer)
|
||||
} else {
|
||||
console.log('[ERR]', answer)
|
||||
reject(answer)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
answer.status = 502
|
||||
answer.body = { code: 502, msg: err }
|
||||
answer.body = { code: 502, msg: err.message || err }
|
||||
console.log('[ERR]', answer)
|
||||
reject(answer)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user