mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-06-15 12:05:09 +00:00
feat: 增强配置生成逻辑,添加重试机制和日志记录
This commit is contained in:
parent
e3a1c041b6
commit
c959866436
@ -4,38 +4,167 @@ const { register_anonimous } = require('./main')
|
|||||||
const { cookieToJson, generateRandomChineseIP } = require('./util/index')
|
const { cookieToJson, generateRandomChineseIP } = require('./util/index')
|
||||||
const { getXeapiPublicKey } = require('./util/xeapiKey')
|
const { getXeapiPublicKey } = require('./util/xeapiKey')
|
||||||
const tmpPath = require('os').tmpdir()
|
const tmpPath = require('os').tmpdir()
|
||||||
|
const logger = require('./util/logger')
|
||||||
|
|
||||||
async function generateConfig() {
|
const MAX_RETRIES = 3
|
||||||
global.cnIp = generateRandomChineseIP()
|
const RETRY_DELAY_MS = 1000
|
||||||
try {
|
|
||||||
const res = await register_anonimous()
|
function sleep(ms) {
|
||||||
const cookie = res.body.cookie
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
if (cookie) {
|
}
|
||||||
const cookieObj = cookieToJson(cookie)
|
|
||||||
|
function isRetryableError(err) {
|
||||||
|
const msg = (err && err.message) || ''
|
||||||
|
const status =
|
||||||
|
(err && err.status) || (err && err.response && err.response.status)
|
||||||
|
if (
|
||||||
|
msg.includes('ETIMEDOUT') ||
|
||||||
|
msg.includes('ECONNRESET') ||
|
||||||
|
msg.includes('ECONNREFUSED') ||
|
||||||
|
msg.includes('socket hang up') ||
|
||||||
|
msg.includes('request timeout') ||
|
||||||
|
msg.includes('timeout') ||
|
||||||
|
msg.includes('network') ||
|
||||||
|
msg.includes('Network')
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (status && status >= 500) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {{ success: boolean, error?: Error }} */
|
||||||
|
async function fetchAnonymousToken() {
|
||||||
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||||
|
try {
|
||||||
|
const res = await register_anonimous()
|
||||||
|
const cookie = res.body.cookie
|
||||||
|
if (cookie) {
|
||||||
|
const cookieObj = cookieToJson(cookie)
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(tmpPath, 'anonymous_token'),
|
||||||
|
cookieObj.MUSIC_A,
|
||||||
|
'utf-8',
|
||||||
|
)
|
||||||
|
logger.success('[generateConfig] 匿名 token 注册成功')
|
||||||
|
return { success: true }
|
||||||
|
}
|
||||||
|
// 返回了但没有 cookie,视为异常但不再重试
|
||||||
|
logger.warn(
|
||||||
|
`[generateConfig] 匿名注册返回了空 cookie (attempt ${attempt})`,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: new Error('empty cookie from anonymous register'),
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (isRetryableError(err) && attempt < MAX_RETRIES) {
|
||||||
|
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1)
|
||||||
|
logger.warn(
|
||||||
|
`[generateConfig] 获取匿名 token 失败 (attempt ${attempt}/${MAX_RETRIES}), ${delay}ms 后重试...`,
|
||||||
|
)
|
||||||
|
await sleep(delay)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 不可重试 或 已达最大次数
|
||||||
|
if (attempt >= MAX_RETRIES) {
|
||||||
|
logger.error(
|
||||||
|
`[generateConfig] 获取匿名 token 已达最大重试次数 (${MAX_RETRIES}):`,
|
||||||
|
err.message,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.error(
|
||||||
|
`[generateConfig] 获取匿名 token 失败 (不可重试):`,
|
||||||
|
err.message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { success: false, error: err }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { success: false, error: new Error('unreachable') }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 xeapi public key,带重试
|
||||||
|
* @returns {{ success: boolean, error?: Error }}
|
||||||
|
*/
|
||||||
|
async function fetchXeapiPublicKey() {
|
||||||
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||||
|
try {
|
||||||
|
let currentPublicKey = {}
|
||||||
|
try {
|
||||||
|
currentPublicKey = JSON.parse(
|
||||||
|
fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'),
|
||||||
|
)
|
||||||
|
} catch (_) {
|
||||||
|
// 本地无缓存文件,用空对象正常请求
|
||||||
|
}
|
||||||
|
const publicKey = await getXeapiPublicKey(
|
||||||
|
currentPublicKey,
|
||||||
|
global.deviceId,
|
||||||
|
)
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.resolve(tmpPath, 'anonymous_token'),
|
path.resolve(tmpPath, 'xeapi_public_key'),
|
||||||
cookieObj.MUSIC_A,
|
JSON.stringify(publicKey),
|
||||||
'utf-8',
|
'utf-8',
|
||||||
)
|
)
|
||||||
|
logger.success('[generateConfig] xeapi public key 获取成功')
|
||||||
|
return { success: true }
|
||||||
|
} catch (err) {
|
||||||
|
if (isRetryableError(err) && attempt < MAX_RETRIES) {
|
||||||
|
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1)
|
||||||
|
logger.warn(
|
||||||
|
`[generateConfig] 获取 xeapi public key 失败 (attempt ${attempt}/${MAX_RETRIES}), ${delay}ms 后重试...`,
|
||||||
|
)
|
||||||
|
await sleep(delay)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (attempt >= MAX_RETRIES) {
|
||||||
|
logger.error(
|
||||||
|
`[generateConfig] 获取 xeapi public key 已达最大重试次数 (${MAX_RETRIES}):`,
|
||||||
|
err.message,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.error(
|
||||||
|
`[generateConfig] 获取 xeapi public key 失败 (不可重试):`,
|
||||||
|
err.message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { success: false, error: err }
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
}
|
||||||
try {
|
return { success: false, error: new Error('unreachable') }
|
||||||
let currentPublicKey = {}
|
}
|
||||||
try {
|
|
||||||
currentPublicKey = JSON.parse(
|
/**
|
||||||
fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'),
|
* 生成配置(匿名 token + xeapi public key),带容错重试
|
||||||
)
|
* @returns {{ tokenOk: boolean, keyOk: boolean }}
|
||||||
} catch (_) {}
|
*/
|
||||||
const publicKey = await getXeapiPublicKey(currentPublicKey, global.deviceId)
|
async function generateConfig() {
|
||||||
fs.writeFileSync(
|
global.cnIp = generateRandomChineseIP()
|
||||||
path.resolve(tmpPath, 'xeapi_public_key'),
|
|
||||||
JSON.stringify(publicKey),
|
// 两个任务并行执行,互不影响喵~
|
||||||
'utf-8',
|
const [tokenResult, keyResult] = await Promise.all([
|
||||||
)
|
fetchAnonymousToken(),
|
||||||
} catch (error) {
|
fetchXeapiPublicKey(),
|
||||||
console.log(error)
|
])
|
||||||
|
|
||||||
|
if (!tokenResult.success) {
|
||||||
|
logger.warn('[generateConfig] 匿名 token 获取失败')
|
||||||
|
}
|
||||||
|
if (!keyResult.success) {
|
||||||
|
logger.warn('[generateConfig] xeapi public key 获取失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenResult.success && keyResult.success) {
|
||||||
|
logger.success('[generateConfig] 配置初始化完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokenOk: tokenResult.success,
|
||||||
|
keyOk: keyResult.success,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = generateConfig
|
module.exports = generateConfig
|
||||||
|
|||||||
@ -26,7 +26,6 @@ function cloudmusic_dll_encode_id(some_id) {
|
|||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
const deviceId = generateDeviceId()
|
const deviceId = generateDeviceId()
|
||||||
logger.info(`Successfully registered anonimous token, deviceId: ${deviceId}`)
|
|
||||||
global.deviceId = deviceId
|
global.deviceId = deviceId
|
||||||
const encodedId = CryptoJS.enc.Base64.stringify(
|
const encodedId = CryptoJS.enc.Base64.stringify(
|
||||||
CryptoJS.enc.Utf8.parse(
|
CryptoJS.enc.Utf8.parse(
|
||||||
|
|||||||
16
server.js
16
server.js
@ -10,6 +10,7 @@ const { cookieToJson } = require('./util/index')
|
|||||||
const fileUpload = require('express-fileupload')
|
const fileUpload = require('express-fileupload')
|
||||||
const decode = require('safe-decode-uri-component')
|
const decode = require('safe-decode-uri-component')
|
||||||
const logger = require('./util/logger.js')
|
const logger = require('./util/logger.js')
|
||||||
|
const { APP_CONF } = require('./util/config.json')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version check result.
|
* The version check result.
|
||||||
@ -299,15 +300,15 @@ async function constructServer(moduleDefs) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let usedCrypto = ''
|
||||||
const moduleResponse = await moduleDef.module(query, (...params) => {
|
const moduleResponse = await moduleDef.module(query, (...params) => {
|
||||||
// 参数注入客户端IP
|
|
||||||
const obj = [...params]
|
const obj = [...params]
|
||||||
const options = obj[2] || {}
|
const options = obj[2] || {}
|
||||||
|
usedCrypto = options.crypto || ''
|
||||||
let ip = ''
|
let ip = ''
|
||||||
|
|
||||||
if (options.randomCNIP) {
|
if (options.randomCNIP) {
|
||||||
ip = global.cnIp
|
ip = global.cnIp
|
||||||
// logger.info('Using random Chinese IP for request:', ip)
|
|
||||||
} else {
|
} else {
|
||||||
ip = req.ip
|
ip = req.ip
|
||||||
|
|
||||||
@ -317,7 +318,6 @@ async function constructServer(moduleDefs) {
|
|||||||
if (ip == '::1') {
|
if (ip == '::1') {
|
||||||
ip = global.cnIp
|
ip = global.cnIp
|
||||||
}
|
}
|
||||||
// logger.info('Requested from ip:', ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
obj[2] = {
|
obj[2] = {
|
||||||
@ -327,7 +327,10 @@ async function constructServer(moduleDefs) {
|
|||||||
|
|
||||||
return request(...obj)
|
return request(...obj)
|
||||||
})
|
})
|
||||||
logger.info(`Request Success: ${decode(req.originalUrl)}`)
|
const displayCrypto = usedCrypto || (APP_CONF.encrypt ? 'eapi' : 'api')
|
||||||
|
logger.info(
|
||||||
|
`Request Success: [${displayCrypto}] ${decode(req.originalUrl)}`,
|
||||||
|
)
|
||||||
|
|
||||||
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
||||||
if (
|
if (
|
||||||
@ -445,10 +448,7 @@ async function serveNcmApi(options) {
|
|||||||
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
||||||
`)
|
`)
|
||||||
logger.info(`
|
logger.info(`
|
||||||
- Server started successfully @ http://${host ? host : 'localhost'}:${port}
|
- Server started successfully @ http://${host ? host : 'localhost'}:${port}`)
|
||||||
- Environment: ${process.env.NODE_ENV || 'development'}
|
|
||||||
- Node Version: ${process.version}
|
|
||||||
- Process ID: ${process.pid}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return appExt
|
return appExt
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user