merge: 同步 main(neapi 支持、v9.6.13 接口、cookie 解析修复)并解决冲突

This commit is contained in:
yukka-agent[bot]
2026-10-25 04:36:42 +00:00
40 changed files with 1440 additions and 397 deletions
+111 -7
View File
@@ -11,17 +11,21 @@ const runtimeState = require('./runtimeState')
const {
cookieToJson,
cookieObjToString,
getCookieHeader,
hasCookie,
toBoolean,
generateRandomChineseIP,
} = require('./index')
const { URLSearchParams, URL } = require('url')
const { APP_CONF } = require('./config.json')
const { loadNeapiKey } = require('./neapiKey')
const {
getToken: antiCheatTokenV2,
} = require('../module/register_checktoken_v2')
const {
getToken: antiCheatTokenV3,
} = require('../module/register_checktoken_v3')
const registerNeapikey = require('../module/register_neapikey')
// 预先绑定常用函数和常量
const floor = Math.floor
@@ -49,6 +53,20 @@ const WNMCID = (function () {
let NMTID = ''
let NMTID_RETRIES_LEFT = 3
// neapi 配置过期时只让一个请求去刷新,并发的其余请求共用同一次结果
let neapiUserStatus = ''
let neapiRefreshing = null
const refreshNeapiKey = (version) => {
if (!neapiRefreshing) {
neapiRefreshing = registerNeapikey({ version, behavior: 'update' }, null)
.catch((error) => console.log('[ERR]', error))
.finally(() => {
neapiRefreshing = null
})
}
return neapiRefreshing
}
// 预先定义osMap
const osMap = {
pc: {
@@ -105,6 +123,7 @@ const DOMAIN = APP_CONF.domain
const API_DOMAIN = APP_CONF.apiDomain
const EAPI_DOMAIN = APP_CONF.eapiDomain
const XEAPI_DOMAIN = APP_CONF.xeapiDomain
const NEAPI_DOMAIN = APP_CONF.neapiDomain
const ENCRYPT_RESPONSE = APP_CONF.encryptResponse
const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803])
@@ -258,13 +277,19 @@ const createRequest = async (uri, data, options) => {
headers['X-Forwarded-For'] = ip
}
let cookie = options.cookie || {}
let cookie = hasCookie(options.cookie)
? options.cookie
: getCookieHeader(options.headers)
if (!hasCookie(cookie)) cookie = {}
if (typeof cookie === 'string') {
cookie = cookieToJson(cookie)
}
if (typeof cookie === 'object') {
cookie = processCookieObject(cookie, crypto)
Object.keys(headers).forEach((name) => {
if (name.toLowerCase() === 'cookie') delete headers[name]
})
headers['Cookie'] = cookieObjToString(cookie)
}
let url = ''
@@ -353,6 +378,47 @@ const createRequest = async (uri, data, options) => {
})
break
case 'neapi':
const neapiKey = loadNeapiKey()
if (!neapiKey) {
throw new Error('neapi config is missing')
}
// 超过 circleTime 就后台刷新,本次仍用手上的配置
if (now() - neapiKey.fetchedAt >= neapiKey.circleTime) {
refreshNeapiKey(neapiKey.version)
}
const neapiCookie = {
osver: cookie.osver,
deviceId: cookie.deviceId,
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: generateRequestId(),
}
if (cookie.MUSIC_U) neapiCookie['MUSIC_U'] = cookie.MUSIC_U
if (cookie.MUSIC_A) neapiCookie['MUSIC_A'] = cookie.MUSIC_A
headers['Cookie'] = createHeaderCookie(neapiCookie)
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'android')
headers['content-type'] =
'application/x-www-form-urlencoded;charset=utf-8'
// 服务端按 X-ER 选密钥,配置过期时由下面的 777/999 分支重拉并重试
headers['X-ER'] = String(neapiKey.version)
if (neapiUserStatus) headers['X-USER-STATUS'] = neapiUserStatus
const neapiBody = { ...data }
delete neapiBody.e_r
encryptData = encrypt.neapi(
new URLSearchParams(neapiBody).toString(),
neapiKey,
)
url = (options.domain || NEAPI_DOMAIN) + '/neapi/' + uri.substr(5)
break
case 'eapi':
case 'api':
// header创建
@@ -399,12 +465,19 @@ const createRequest = async (uri, data, options) => {
console.log('[ERR]', 'Unknown Crypto:', crypto)
break
}
const use_e_r = (crypto === 'eapi' || crypto === 'weapi') && data.e_r
const use_xeapi = crypto === 'xeapi'
const use_neapi = crypto === 'neapi'
// settings创建
let settings = {
method: 'POST',
url: url,
headers: headers,
data: new URLSearchParams(encryptData).toString(),
// neapi 的请求体就是容器本身,不是表单字段
data: use_neapi
? encryptData
: new URLSearchParams(encryptData).toString(),
httpAgent: createHttpAgent(),
httpsAgent: createHttpsAgent(),
}
@@ -415,9 +488,7 @@ const createRequest = async (uri, data, options) => {
}
// 使用返回值加密
const use_e_r = (crypto === 'eapi' || crypto === 'weapi') && data.e_r
const use_xeapi = crypto === 'xeapi'
if (use_e_r || use_xeapi) {
if (use_e_r || use_xeapi || use_neapi) {
settings.encoding = null
settings.responseType = 'arraybuffer'
}
@@ -458,7 +529,7 @@ const createRequest = async (uri, data, options) => {
}
// console.log(settings.headers);
axios(settings)
.then((res) => {
.then(async (res) => {
const body = res.data
const setCookies = res.headers['set-cookie'] || []
@@ -472,7 +543,11 @@ const createRequest = async (uri, data, options) => {
!cookie.NMTID
) {
NMTID_RETRIES_LEFT--
answer.cookie = setCookies.map((x) => {
answer.cookie = (
typeof res.headers['set-cookie'] === 'string'
? [res.headers['set-cookie']]
: res.headers['set-cookie'] || []
).map((x) => {
const cleaned = cleanCookie(x)
const match = x.match(/(?:^|;\s*)NMTID=([^;]+)/)
if (match) {
@@ -485,6 +560,30 @@ const createRequest = async (uri, data, options) => {
answer.cookie = setCookies.map(cleanCookie)
}
// 空值同样要记下来,否则状态恢复后会一直带着上一次的值
const userStatus = res.headers['x-user-status'] || ''
if (use_neapi) {
neapiUserStatus = userStatus
if (
(userStatus === '777' || userStatus === '999') &&
!options.neapiRetried
) {
// 配置已轮换:刷新后重试一次
await refreshNeapiKey(loadNeapiKey().version)
try {
resolve(
await createRequest(uri, data, {
...options,
neapiRetried: true,
}),
)
} catch (error) {
reject(error)
}
return
}
}
// debug: 统一注释块,需要时取消注释查看请求/返回的原始密文
// logger.debug(`[${crypto}]`, uri)
@@ -503,6 +602,11 @@ const createRequest = async (uri, data, options) => {
xeapiSessionKey = res.headers['x-encr-sskey']
}
answer.body = encrypt.xeapiResDecrypt(Buffer.from(body))
} else if (use_neapi) {
answer.body = encrypt.neapiResDecrypt(
Buffer.from(body).toString(),
loadNeapiKey(),
)
} else if (use_e_r) {
answer.body = encrypt.eapiResDecrypt(
body.toString('hex').toUpperCase(),