mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-06-15 12:05:09 +00:00
171 lines
5.0 KiB
JavaScript
171 lines
5.0 KiB
JavaScript
const fs = require('fs')
|
||
const path = require('path')
|
||
const { register_anonimous } = require('./main')
|
||
const { cookieToJson, generateRandomChineseIP } = require('./util/index')
|
||
const { getXeapiPublicKey } = require('./util/xeapiKey')
|
||
const tmpPath = require('os').tmpdir()
|
||
const logger = require('./util/logger')
|
||
|
||
const MAX_RETRIES = 3
|
||
const RETRY_DELAY_MS = 1000
|
||
|
||
function sleep(ms) {
|
||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||
}
|
||
|
||
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(
|
||
path.resolve(tmpPath, 'xeapi_public_key'),
|
||
JSON.stringify(publicKey),
|
||
'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 }
|
||
}
|
||
}
|
||
return { success: false, error: new Error('unreachable') }
|
||
}
|
||
|
||
/**
|
||
* 生成配置(匿名 token + xeapi public key),带容错重试
|
||
* @returns {{ tokenOk: boolean, keyOk: boolean }}
|
||
*/
|
||
async function generateConfig() {
|
||
global.cnIp = generateRandomChineseIP()
|
||
|
||
// 两个任务并行执行,互不影响喵~
|
||
const [tokenResult, keyResult] = await Promise.all([
|
||
fetchAnonymousToken(),
|
||
fetchXeapiPublicKey(),
|
||
])
|
||
|
||
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
|