refactor: 重构模块加载

Refactored main.js to optimize module and server loading, improving code clarity and lazy loading. Updated several dependencies in package.json to their latest versions for better security and compatibility. Updated documentation cover page and home page to reflect project branding changes.

Co-Authored-By: binaryify <binaryify@gmail.com>
This commit is contained in:
ImFurina
2025-09-07 13:08:26 +08:00
co-authored by binaryify
parent 4f4aa134d5
commit abd0c9ca91
13 changed files with 656 additions and 294 deletions
+204 -145
View File
@@ -1,3 +1,4 @@
// 预先导入和绑定常用模块及函数
const encrypt = require('./crypto')
const CryptoJS = require('crypto-js')
const { default: axios } = require('axios')
@@ -8,31 +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')
const logger = require('./logger.js')
// 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: {
@@ -55,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 || ''
// logger.info(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':
@@ -147,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:
// 未知的加密方式
logger.info('[ERR]', 'Unknown Crypto:', crypto)
console.log('[ERR]', 'Unknown Crypto:', crypto)
break
}
const answer = { status: 500, body: {}, cookie: [] }
// logger.info(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) {
@@ -275,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) {
// logger.info(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)
})
})