mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2025-10-23 15:43:10 +00:00
chore(version): 更新api到4.28.0
Co-Authored-By: binaryify <binaryify@gmail.com>
This commit is contained in:
parent
5105a9a09d
commit
5478041ddd
54
main.js
54
main.js
@ -3,47 +3,61 @@ const path = require('path')
|
|||||||
const tmpPath = require('os').tmpdir()
|
const tmpPath = require('os').tmpdir()
|
||||||
const { cookieToJson } = require('./util')
|
const { cookieToJson } = require('./util')
|
||||||
|
|
||||||
if (!fs.existsSync(path.resolve(tmpPath, 'anonymous_token'))) {
|
const anonymousTokenPath = path.resolve(tmpPath, 'anonymous_token')
|
||||||
fs.writeFileSync(path.resolve(tmpPath, 'anonymous_token'), '', 'utf-8')
|
if (!fs.existsSync(anonymousTokenPath)) {
|
||||||
|
fs.writeFileSync(anonymousTokenPath, '', 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
let firstRun = true
|
|
||||||
/** @type {Record<string, any>} */
|
/** @type {Record<string, any>} */
|
||||||
let obj = {}
|
let obj = {}
|
||||||
fs.readdirSync(path.join(__dirname, 'module'))
|
|
||||||
.reverse()
|
const modulePath = path.join(__dirname, 'module')
|
||||||
.forEach((file) => {
|
const moduleFiles = fs.readdirSync(modulePath).reverse()
|
||||||
|
|
||||||
|
let requestModule = null
|
||||||
|
|
||||||
|
moduleFiles.forEach((file) => {
|
||||||
if (!file.endsWith('.js')) return
|
if (!file.endsWith('.js')) return
|
||||||
let fileModule = require(path.join(__dirname, 'module', file))
|
|
||||||
|
const filePath = path.join(modulePath, file)
|
||||||
|
let fileModule = require(filePath)
|
||||||
let fn = file.split('.').shift() || ''
|
let fn = file.split('.').shift() || ''
|
||||||
|
|
||||||
obj[fn] = function (data = {}) {
|
obj[fn] = function (data = {}) {
|
||||||
if (typeof data.cookie === 'string') {
|
const cookie =
|
||||||
data.cookie = cookieToJson(data.cookie)
|
typeof data.cookie === 'string'
|
||||||
}
|
? cookieToJson(data.cookie)
|
||||||
|
: data.cookie || {}
|
||||||
|
|
||||||
return fileModule(
|
return fileModule(
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
cookie: data.cookie ? data.cookie : {},
|
cookie,
|
||||||
},
|
},
|
||||||
async (...args) => {
|
async (...args) => {
|
||||||
if (firstRun) {
|
if (!requestModule) {
|
||||||
firstRun = false
|
requestModule = require('./util/request')
|
||||||
const generateConfig = require('./generateConfig')
|
|
||||||
await generateConfig()
|
|
||||||
}
|
}
|
||||||
// 待优化
|
|
||||||
const request = require('./util/request')
|
|
||||||
|
|
||||||
return request(...args)
|
return requestModule(...args)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let serverModule = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Record<string, any> & import("./server")}
|
* @type {Record<string, any> & import("./server")}
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...require('./server'),
|
get server() {
|
||||||
|
if (!serverModule) {
|
||||||
|
serverModule = require('./server')
|
||||||
|
}
|
||||||
|
return serverModule
|
||||||
|
},
|
||||||
...obj,
|
...obj,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.assign(module.exports, require('./server'))
|
||||||
|
@ -4,6 +4,7 @@ const createOption = require('../util/option.js')
|
|||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const data = {
|
const data = {
|
||||||
ctcode: query.ctcode || '86',
|
ctcode: query.ctcode || '86',
|
||||||
|
secrete: 'music_middleuser_pclogin',
|
||||||
cellphone: query.phone,
|
cellphone: query.phone,
|
||||||
}
|
}
|
||||||
return request(`/api/sms/captcha/sent`, data, createOption(query, 'weapi'))
|
return request(`/api/sms/captcha/sent`, data, createOption(query, 'weapi'))
|
||||||
|
@ -13,7 +13,7 @@ module.exports = async (query, request) => {
|
|||||||
[query.captcha ? 'captcha' : 'password']: query.captcha
|
[query.captcha ? 'captcha' : 'password']: query.captcha
|
||||||
? query.captcha
|
? query.captcha
|
||||||
: query.md5_password || CryptoJS.MD5(query.password).toString(),
|
: query.md5_password || CryptoJS.MD5(query.password).toString(),
|
||||||
rememberLogin: 'true',
|
remember: 'true',
|
||||||
}
|
}
|
||||||
let result = await request(
|
let result = await request(
|
||||||
`/api/w/login/cellphone`,
|
`/api/w/login/cellphone`,
|
||||||
|
@ -1,9 +1,20 @@
|
|||||||
const QRCode = require('qrcode')
|
const QRCode = require('qrcode')
|
||||||
|
const { generateChainId } = require('../util/index')
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
module.exports = (query) => {
|
||||||
module.exports = (query, request) => {
|
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const url = `https://music.163.com/login?codekey=${query.key}`
|
const platform = query.platform || 'pc'
|
||||||
|
const cookie = query.cookie || ''
|
||||||
|
|
||||||
|
// 构建基础URL
|
||||||
|
let url = `https://music.163.com/login?codekey=${query.key}`
|
||||||
|
|
||||||
|
// 如果是web平台,则添加chainId参数
|
||||||
|
|
||||||
|
if (platform === 'web') {
|
||||||
|
const chainId = generateChainId(cookie)
|
||||||
|
url += `&chainId=${chainId}`
|
||||||
|
}
|
||||||
return resolve({
|
return resolve({
|
||||||
code: 200,
|
code: 200,
|
||||||
status: 200,
|
status: 200,
|
||||||
|
11
module/playlist_category_list.js
Normal file
11
module/playlist_category_list.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// 歌单分类列表
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
cat: query.cat || '全部',
|
||||||
|
limit: query.limit || 24,
|
||||||
|
newStyle: true,
|
||||||
|
}
|
||||||
|
return request(`/api/playlist/category/list`, data, createOption(query))
|
||||||
|
}
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
return request(`/api/playlist/catalogue`, {}, createOption(query, 'weapi'))
|
return request(`/api/playlist/catalogue`, {}, createOption(query, 'eapi'))
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
// 收藏与取消收藏歌单
|
// 收藏与取消收藏歌单
|
||||||
|
const { APP_CONF } = require('../util/config.json')
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
query.t = query.t == 1 ? 'subscribe' : 'unsubscribe'
|
const path = query.t == 1 ? 'subscribe' : 'unsubscribe'
|
||||||
const data = {
|
const data = {
|
||||||
id: query.id,
|
id: query.id,
|
||||||
|
...(query.t === 1
|
||||||
|
? { checkToken: query.checkToken || APP_CONF.checkToken }
|
||||||
|
: {}),
|
||||||
}
|
}
|
||||||
return request(`/api/playlist/${query.t}`, data, createOption(query, 'weapi'))
|
query.checkToken = true // 强制开启checkToken
|
||||||
|
return request(`/api/playlist/${path}`, data, createOption(query, 'eapi'))
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,13 @@ const CryptoJS = require('crypto-js')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const ID_XOR_KEY_1 = '3go8&$8*3*3h0k(2)2'
|
const ID_XOR_KEY_1 = '3go8&$8*3*3h0k(2)2'
|
||||||
const deviceidText = fs.readFileSync(
|
|
||||||
path.resolve(__dirname, '../data/deviceid.txt'),
|
|
||||||
'utf-8',
|
|
||||||
)
|
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
const deviceidList = deviceidText.split('\n')
|
const { generateDeviceId } = require('../util/index')
|
||||||
|
|
||||||
function getRandomFromList(list) {
|
// function getRandomFromList(list) {
|
||||||
return list[Math.floor(Math.random() * list.length)]
|
// return list[Math.floor(Math.random() * list.length)]
|
||||||
}
|
// }
|
||||||
function cloudmusic_dll_encode_id(some_id) {
|
function cloudmusic_dll_encode_id(some_id) {
|
||||||
let xoredString = ''
|
let xoredString = ''
|
||||||
for (let i = 0; i < some_id.length; i++) {
|
for (let i = 0; i < some_id.length; i++) {
|
||||||
@ -26,7 +22,8 @@ function cloudmusic_dll_encode_id(some_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = async (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
const deviceId = getRandomFromList(deviceidList)
|
const deviceId = generateDeviceId()
|
||||||
|
console.log(`[register_anonimous] 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(
|
||||||
|
@ -9,6 +9,7 @@ module.exports = (query, request) => {
|
|||||||
password: CryptoJS.MD5(query.password).toString(),
|
password: CryptoJS.MD5(query.password).toString(),
|
||||||
nickname: query.nickname,
|
nickname: query.nickname,
|
||||||
countrycode: query.countrycode || '86',
|
countrycode: query.countrycode || '86',
|
||||||
|
force: 'false',
|
||||||
}
|
}
|
||||||
return request(`/api/register/cellphone`, data, createOption(query))
|
return request(`/api/w/register/cellphone`, data, createOption(query))
|
||||||
}
|
}
|
||||||
|
6
module/toplist_detail_v2.js
Normal file
6
module/toplist_detail_v2.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// 所有榜单内容摘要v2
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
return request(`/api/toplist/detail/v2`, {}, createOption(query, 'weapi'))
|
||||||
|
}
|
20
module/user_detail_new.js
Normal file
20
module/user_detail_new.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 用户详情
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = async (query, request) => {
|
||||||
|
const data = {
|
||||||
|
all: 'true',
|
||||||
|
userId: query.uid,
|
||||||
|
}
|
||||||
|
const res = await request(
|
||||||
|
`/api/w/v1/user/detail/${query.uid}`,
|
||||||
|
data,
|
||||||
|
createOption(query, 'eapi'),
|
||||||
|
)
|
||||||
|
// const result = JSON.stringify(res).replace(
|
||||||
|
// /avatarImgId_str/g,
|
||||||
|
// "avatarImgIdStr"
|
||||||
|
// );
|
||||||
|
// return JSON.parse(result);
|
||||||
|
return res
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "NeteaseCloudMusicApi",
|
"name": "NeteaseCloudMusicApi",
|
||||||
"version": "4.25.0",
|
"version": "4.28.0",
|
||||||
"description": "网易云音乐 NodeJS 版 API",
|
"description": "网易云音乐 NodeJS 版 API",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
@ -65,9 +65,7 @@
|
|||||||
"data"
|
"data"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@unblockneteasemusic/server": "latest",
|
|
||||||
"axios": "^1.2.2",
|
"axios": "^1.2.2",
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-fileupload": "^1.1.9",
|
"express-fileupload": "^1.1.9",
|
||||||
|
@ -8,6 +8,19 @@
|
|||||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.decode-result {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app" class="p-5 flex flex-col">
|
<div id="app" class="p-5 flex flex-col">
|
||||||
<h1 class="text-2xl font-bold mb-5">eapi 参数和返回内容解析</h1>
|
<h1 class="text-2xl font-bold mb-5">eapi 参数和返回内容解析</h1>
|
||||||
@ -23,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>解密结果:
|
<p>解密结果:
|
||||||
<pre>{{ JSON.stringify(JSON.parse(result), null, 2) }}</pre>
|
<pre class="decode-result">{{ JSON.stringify(JSON.parse(result), null, 2) }}</pre>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -85,7 +85,6 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>网易云音乐 API <span id="api-version"></span></h1>
|
<h1>网易云音乐 API <span id="api-version"></span></h1>
|
||||||
<p>本项目基于binaryify的网易云API二改, 添加了解灰接口</p>
|
|
||||||
<p>当你看到这个页面时,这个服务已经成功跑起来了~</p>
|
<p>当你看到这个页面时,这个服务已经成功跑起来了~</p>
|
||||||
<p class="current-url"><span id="current-url"></span></p>
|
<p class="current-url"><span id="current-url"></span></p>
|
||||||
<a href="/docs">查看文档</a>
|
<a href="/docs">查看文档</a>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
})
|
})
|
||||||
const key = res.data.data.unikey
|
const key = res.data.data.unikey
|
||||||
const res2 = await axios({
|
const res2 = await axios({
|
||||||
url: `/login/qr/create?key=${key}&qrimg=true×tamp=${Date.now()}`,
|
url: `/login/qr/create?key=${key}&platform=web&qrimg=true×tamp=${Date.now()}`,
|
||||||
})
|
})
|
||||||
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
})
|
})
|
||||||
const key = res.data.data.unikey
|
const key = res.data.data.unikey
|
||||||
const res2 = await axios({
|
const res2 = await axios({
|
||||||
url: `/login/qr/create?key=${key}&qrimg=true×tamp=${Date.now()}`,
|
url: `/login/qr/create?key=${key}&platform=web&qrimg=true×tamp=${Date.now()}`,
|
||||||
})
|
})
|
||||||
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
document.querySelector('#qrImg').src = res2.data.data.qrimg
|
||||||
|
|
||||||
|
@ -224,8 +224,8 @@ async function consturctServer(moduleDefs) {
|
|||||||
const obj = [...params]
|
const obj = [...params]
|
||||||
let ip = req.ip
|
let ip = req.ip
|
||||||
|
|
||||||
if (ip.substr(0, 7) == '::ffff:') {
|
if (ip.substring(0, 7) == '::ffff:') {
|
||||||
ip = ip.substr(7)
|
ip = ip.substring(7)
|
||||||
}
|
}
|
||||||
if (ip == '::1') {
|
if (ip == '::1') {
|
||||||
ip = global.cnIp
|
ip = global.cnIp
|
||||||
|
169
util/client-sign.js
Normal file
169
util/client-sign.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
const crypto = require("crypto");
|
||||||
|
const os = require("os");
|
||||||
|
|
||||||
|
class AdvancedClientSignGenerator {
|
||||||
|
/**
|
||||||
|
* 获取本机MAC地址
|
||||||
|
*/
|
||||||
|
static getRealMacAddress() {
|
||||||
|
try {
|
||||||
|
const interfaces = os.networkInterfaces();
|
||||||
|
for (let interfaceName in interfaces) {
|
||||||
|
const interface = interfaces[interfaceName];
|
||||||
|
for (let i = 0; i < interface.length; i++) {
|
||||||
|
const alias = interface[i];
|
||||||
|
// 排除内部地址和无效地址
|
||||||
|
if (
|
||||||
|
alias.mac &&
|
||||||
|
alias.mac !== "00:00:00:00:00:00" &&
|
||||||
|
!alias.internal
|
||||||
|
) {
|
||||||
|
return alias.mac.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("获取MAC地址失败:", error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机MAC地址
|
||||||
|
*/
|
||||||
|
static generateRandomMac() {
|
||||||
|
const chars = "0123456789ABCDEF";
|
||||||
|
let mac = "";
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
if (i > 0) mac += ":";
|
||||||
|
mac +=
|
||||||
|
chars[Math.floor(Math.random() * 16)] +
|
||||||
|
chars[Math.floor(Math.random() * 16)];
|
||||||
|
}
|
||||||
|
// 确保第一个字节是单播地址(最低位为0)
|
||||||
|
const firstByte = parseInt(mac.substring(0, 2), 16);
|
||||||
|
const unicastFirstByte = (firstByte & 0xfe)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(2, "0")
|
||||||
|
.toUpperCase();
|
||||||
|
return unicastFirstByte + mac.substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取MAC地址(优先真实,否则随机)
|
||||||
|
*/
|
||||||
|
static getMacAddress() {
|
||||||
|
const realMac = this.getRealMacAddress();
|
||||||
|
if (realMac) {
|
||||||
|
return realMac;
|
||||||
|
}
|
||||||
|
console.warn("无法获取真实MAC地址,使用随机生成");
|
||||||
|
return this.generateRandomMac();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串转HEX编码
|
||||||
|
*/
|
||||||
|
static stringToHex(str) {
|
||||||
|
return Buffer.from(str, "utf8").toString("hex").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SHA-256哈希
|
||||||
|
*/
|
||||||
|
static sha256(data) {
|
||||||
|
return crypto.createHash("sha256").update(data, "utf8").digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机设备ID
|
||||||
|
*/
|
||||||
|
static generateRandomDeviceId() {
|
||||||
|
const partLengths = [4, 4, 4, 4, 4, 4, 4, 5]; // 各部分长度
|
||||||
|
const chars = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
const parts = partLengths.map((length) => {
|
||||||
|
let part = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
part += chars[Math.floor(Math.random() * 16)];
|
||||||
|
}
|
||||||
|
return part;
|
||||||
|
});
|
||||||
|
|
||||||
|
return parts.join("_");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机clientSign(优先使用真实MAC,否则随机)
|
||||||
|
*/
|
||||||
|
static generateRandomClientSign(secretKey = "") {
|
||||||
|
// 获取MAC地址(优先真实,否则随机)
|
||||||
|
const macAddress = this.getMacAddress();
|
||||||
|
|
||||||
|
// 生成随机设备ID
|
||||||
|
const deviceId = this.generateRandomDeviceId();
|
||||||
|
|
||||||
|
// 转换设备ID为HEX
|
||||||
|
const hexDeviceId = this.stringToHex(deviceId);
|
||||||
|
|
||||||
|
// 构造签名字符串
|
||||||
|
const signString = `${macAddress}@@@${hexDeviceId}`;
|
||||||
|
|
||||||
|
// 生成哈希
|
||||||
|
const hash = this.sha256(signString + secretKey);
|
||||||
|
|
||||||
|
return `${signString}@@@@@@${hash}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量生成多个随机签名
|
||||||
|
*/
|
||||||
|
static generateMultipleRandomSigns(count, secretKey = "") {
|
||||||
|
const signs = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
signs.push(this.generateRandomClientSign(secretKey));
|
||||||
|
}
|
||||||
|
return signs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用指定参数生成签名
|
||||||
|
*/
|
||||||
|
static generateWithCustomDeviceId(macAddress, deviceId, secretKey = "") {
|
||||||
|
const hexDeviceId = this.stringToHex(deviceId);
|
||||||
|
const signString = `${macAddress}@@@${hexDeviceId}`;
|
||||||
|
const hash = this.sha256(signString + secretKey);
|
||||||
|
return `${signString}@@@@@@${hash}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证签名格式是否正确
|
||||||
|
*/
|
||||||
|
static validateClientSign(clientSign) {
|
||||||
|
try {
|
||||||
|
const parts = clientSign.split("@@@@@@");
|
||||||
|
if (parts.length !== 2) return false;
|
||||||
|
|
||||||
|
const [infoPart, hash] = parts;
|
||||||
|
const infoParts = infoPart.split("@@@");
|
||||||
|
if (infoParts.length !== 2) return false;
|
||||||
|
|
||||||
|
const [mac, hexDeviceId] = infoParts;
|
||||||
|
|
||||||
|
// 验证MAC地址格式
|
||||||
|
const macRegex = /^([0-9A-F]{2}:){5}[0-9A-F]{2}$/;
|
||||||
|
if (!macRegex.test(mac)) return false;
|
||||||
|
|
||||||
|
// 验证哈希格式
|
||||||
|
const hashRegex = /^[0-9a-f]{64}$/;
|
||||||
|
if (!hashRegex.test(hash)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AdvancedClientSignGenerator;
|
@ -13,6 +13,8 @@
|
|||||||
"apiDomain": "https://interface.music.163.com",
|
"apiDomain": "https://interface.music.163.com",
|
||||||
"domain": "https://music.163.com",
|
"domain": "https://music.163.com",
|
||||||
"encrypt": true,
|
"encrypt": true,
|
||||||
"encryptResponse": false
|
"encryptResponse": false,
|
||||||
|
"clientSign": "18:C0:4D:B9:8F:FE@@@453832335F384641365F424635335F303030315F303031425F343434415F343643365F333638332@@@@@@6ff673ef74955b38bce2fa8562d95c976ed4758b1227c4e9ee345987cee17bc9",
|
||||||
|
"checkToken": "9ca17ae2e6ffcda170e2e6ee8af14fbabdb988f225b3868eb2c15a879b9a83d274a790ac8ff54a97b889d5d42af0feaec3b92af58cff99c470a7eafd88f75e839a9ea7c14e909da883e83fb692a3abdb6b92adee9e"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -87,8 +87,13 @@ const eapi = (url, object) => {
|
|||||||
}
|
}
|
||||||
const eapiResDecrypt = (encryptedParams) => {
|
const eapiResDecrypt = (encryptedParams) => {
|
||||||
// 使用aesDecrypt解密参数
|
// 使用aesDecrypt解密参数
|
||||||
|
try {
|
||||||
const decryptedData = aesDecrypt(encryptedParams, eapiKey, '', 'hex')
|
const decryptedData = aesDecrypt(encryptedParams, eapiKey, '', 'hex')
|
||||||
return JSON.parse(decryptedData)
|
return JSON.parse(decryptedData)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('eapiResDecrypt error:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const eapiReqDecrypt = (encryptedParams) => {
|
const eapiReqDecrypt = (encryptedParams) => {
|
||||||
// 使用aesDecrypt解密参数
|
// 使用aesDecrypt解密参数
|
||||||
|
159
util/index.js
159
util/index.js
@ -1,49 +1,156 @@
|
|||||||
|
// 预先定义常量和函数引用
|
||||||
|
const chinaIPPrefixes = [
|
||||||
|
'116.25',
|
||||||
|
'116.76',
|
||||||
|
'116.77',
|
||||||
|
'116.78',
|
||||||
|
'116.79',
|
||||||
|
'116.80',
|
||||||
|
'116.81',
|
||||||
|
'116.82',
|
||||||
|
'116.83',
|
||||||
|
'116.84',
|
||||||
|
'116.85',
|
||||||
|
'116.86',
|
||||||
|
'116.87',
|
||||||
|
'116.88',
|
||||||
|
'116.89',
|
||||||
|
'116.90',
|
||||||
|
'116.91',
|
||||||
|
'116.92',
|
||||||
|
'116.93',
|
||||||
|
'116.94',
|
||||||
|
]
|
||||||
|
const prefixesLength = chinaIPPrefixes.length
|
||||||
|
const floor = Math.floor
|
||||||
|
const random = Math.random
|
||||||
|
const keys = Object.keys
|
||||||
|
|
||||||
|
// 预编译encodeURIComponent以减少查找开销
|
||||||
|
const encode = encodeURIComponent
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
toBoolean(val) {
|
toBoolean(val) {
|
||||||
if (typeof val === 'boolean') return val
|
if (typeof val === 'boolean') return val
|
||||||
if (val === '') return val
|
if (val === '') return val
|
||||||
return val === 'true' || val == '1'
|
return val === 'true' || val == '1'
|
||||||
},
|
},
|
||||||
|
|
||||||
cookieToJson(cookie) {
|
cookieToJson(cookie) {
|
||||||
if (!cookie) return {}
|
if (!cookie) return {}
|
||||||
let cookieArr = cookie.split(';')
|
let cookieArr = cookie.split(';')
|
||||||
let obj = {}
|
let obj = {}
|
||||||
cookieArr.forEach((i) => {
|
|
||||||
let arr = i.split('=')
|
// 优化:使用for循环替代forEach,性能更好
|
||||||
if (arr.length == 2) obj[arr[0].trim()] = arr[1].trim()
|
for (let i = 0, len = cookieArr.length; i < len; i++) {
|
||||||
})
|
let item = cookieArr[i]
|
||||||
|
let arr = item.split('=')
|
||||||
|
// 优化:使用严格等于
|
||||||
|
if (arr.length === 2) {
|
||||||
|
obj[arr[0].trim()] = arr[1].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
return obj
|
return obj
|
||||||
},
|
},
|
||||||
cookieObjToString(cookie) {
|
|
||||||
return Object.keys(cookie)
|
|
||||||
.map(
|
|
||||||
(key) =>
|
|
||||||
`${encodeURIComponent(key)}=${encodeURIComponent(cookie[key])}`,
|
|
||||||
)
|
|
||||||
.join('; ')
|
|
||||||
},
|
|
||||||
getRandom(num) {
|
|
||||||
var random = Math.floor(
|
|
||||||
(Math.random() + Math.floor(Math.random() * 9 + 1)) *
|
|
||||||
Math.pow(10, num - 1),
|
|
||||||
)
|
|
||||||
return random
|
|
||||||
},
|
|
||||||
generateRandomChineseIP() {
|
|
||||||
const chinaIPPrefixes = ['116.25', '116.76', '116.77', '116.78']
|
|
||||||
|
|
||||||
const randomPrefix =
|
cookieObjToString(cookie) {
|
||||||
chinaIPPrefixes[Math.floor(Math.random() * chinaIPPrefixes.length)]
|
// 优化:使用预绑定的keys函数和for循环
|
||||||
|
const cookieKeys = keys(cookie)
|
||||||
|
const result = []
|
||||||
|
|
||||||
|
// 优化:使用for循环和预分配数组
|
||||||
|
for (let i = 0, len = cookieKeys.length; i < len; i++) {
|
||||||
|
const key = cookieKeys[i]
|
||||||
|
result[i] = `${encode(key)}=${encode(cookie[key])}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.join('; ')
|
||||||
|
},
|
||||||
|
|
||||||
|
getRandom(num) {
|
||||||
|
// 优化:简化随机数生成逻辑
|
||||||
|
// 原逻辑看起来有问题,这里保持原意但优化性能
|
||||||
|
var randomValue = random()
|
||||||
|
var floorValue = floor(randomValue * 9 + 1)
|
||||||
|
var powValue = Math.pow(10, num - 1)
|
||||||
|
var randomNum = floor((randomValue + floorValue) * powValue)
|
||||||
|
return randomNum
|
||||||
|
},
|
||||||
|
|
||||||
|
generateRandomChineseIP() {
|
||||||
|
// 优化:使用预绑定的函数和常量
|
||||||
|
const randomPrefix = chinaIPPrefixes[floor(random() * prefixesLength)]
|
||||||
return `${randomPrefix}.${generateIPSegment()}.${generateIPSegment()}`
|
return `${randomPrefix}.${generateIPSegment()}.${generateIPSegment()}`
|
||||||
},
|
},
|
||||||
|
// 生成chainId的函数
|
||||||
|
generateChainId(cookie) {
|
||||||
|
const version = 'v1'
|
||||||
|
const randomNum = Math.floor(Math.random() * 1e6)
|
||||||
|
const deviceId =
|
||||||
|
getCookieValue(cookie, 'sDeviceId') || 'unknown-' + randomNum
|
||||||
|
const platform = 'web'
|
||||||
|
const action = 'login'
|
||||||
|
const timestamp = Date.now()
|
||||||
|
|
||||||
|
return `${version}_${deviceId}_${platform}_${action}_${timestamp}`
|
||||||
|
},
|
||||||
|
|
||||||
|
generateDeviceId() {
|
||||||
|
const hexChars = '0123456789ABCDEF'
|
||||||
|
const chars = []
|
||||||
|
for (let i = 0; i < 52; i++) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * hexChars.length)
|
||||||
|
chars.push(hexChars[randomIndex])
|
||||||
|
}
|
||||||
|
return chars.join('')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成一个随机整数
|
// 优化:预先绑定函数
|
||||||
function getRandomInt(min, max) {
|
function getRandomInt(min, max) {
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
// 优化:简化计算
|
||||||
|
return floor(random() * (max - min + 1)) + min
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成一个随机IP地址段
|
// 优化:预先绑定generateIPSegment函数引用
|
||||||
function generateIPSegment() {
|
function generateIPSegment() {
|
||||||
|
// 优化:内联常量
|
||||||
return getRandomInt(1, 255)
|
return getRandomInt(1, 255)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 进一步优化版本(如果需要更高性能):
|
||||||
|
/*
|
||||||
|
const cookieToJsonOptimized = (function() {
|
||||||
|
// 预编译trim函数
|
||||||
|
const trim = String.prototype.trim
|
||||||
|
|
||||||
|
return function(cookie) {
|
||||||
|
if (!cookie) return {}
|
||||||
|
|
||||||
|
const cookieArr = cookie.split(';')
|
||||||
|
const obj = {}
|
||||||
|
|
||||||
|
for (let i = 0, len = cookieArr.length; i < len; i++) {
|
||||||
|
const item = cookieArr[i]
|
||||||
|
const eqIndex = item.indexOf('=')
|
||||||
|
|
||||||
|
if (eqIndex > 0 && eqIndex < item.length - 1) {
|
||||||
|
const key = trim.call(item.substring(0, eqIndex))
|
||||||
|
const value = trim.call(item.substring(eqIndex + 1))
|
||||||
|
obj[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 用于从cookie字符串中获取指定值的辅助函数
|
||||||
|
function getCookieValue(cookieStr, name) {
|
||||||
|
if (!cookieStr) return ''
|
||||||
|
|
||||||
|
const cookies = '; ' + cookieStr
|
||||||
|
const parts = cookies.split('; ' + name + '=')
|
||||||
|
if (parts.length === 2) return parts.pop().split(';').shift()
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
@ -1,63 +1,71 @@
|
|||||||
function MemoryCache() {
|
class MemoryCache {
|
||||||
this.cache = {}
|
constructor() {
|
||||||
|
this.cache = new Map()
|
||||||
this.size = 0
|
this.size = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryCache.prototype.add = function (key, value, time, timeoutCallback) {
|
add(key, value, time, timeoutCallback) {
|
||||||
var old = this.cache[key]
|
// 移除旧的条目(如果存在)
|
||||||
var instance = this
|
const old = this.cache.get(key)
|
||||||
|
if (old) {
|
||||||
|
clearTimeout(old.timeout)
|
||||||
|
}
|
||||||
|
|
||||||
var entry = {
|
// 创建新的缓存条目
|
||||||
value: value,
|
const entry = {
|
||||||
|
value,
|
||||||
expire: time + Date.now(),
|
expire: time + Date.now(),
|
||||||
timeout: setTimeout(function () {
|
timeout: setTimeout(() => {
|
||||||
instance.delete(key)
|
this.delete(key)
|
||||||
return (
|
if (typeof timeoutCallback === 'function') {
|
||||||
timeoutCallback &&
|
|
||||||
typeof timeoutCallback === 'function' &&
|
|
||||||
timeoutCallback(value, key)
|
timeoutCallback(value, key)
|
||||||
)
|
}
|
||||||
}, time),
|
}, time),
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cache[key] = entry
|
this.cache.set(key, entry)
|
||||||
this.size = Object.keys(this.cache).length
|
this.size = this.cache.size
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
}
|
|
||||||
|
|
||||||
MemoryCache.prototype.delete = function (key) {
|
|
||||||
var entry = this.cache[key]
|
|
||||||
|
|
||||||
if (entry) {
|
|
||||||
clearTimeout(entry.timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this.cache[key]
|
delete(key) {
|
||||||
|
const entry = this.cache.get(key)
|
||||||
this.size = Object.keys(this.cache).length
|
if (entry) {
|
||||||
|
clearTimeout(entry.timeout)
|
||||||
|
this.cache.delete(key)
|
||||||
|
this.size = this.cache.size
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryCache.prototype.get = function (key) {
|
get(key) {
|
||||||
var entry = this.cache[key]
|
return this.cache.get(key) || null
|
||||||
|
}
|
||||||
|
|
||||||
return entry
|
getValue(key) {
|
||||||
}
|
const entry = this.cache.get(key)
|
||||||
|
return entry ? entry.value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
MemoryCache.prototype.getValue = function (key) {
|
clear() {
|
||||||
var entry = this.get(key)
|
this.cache.forEach((entry) => clearTimeout(entry.timeout))
|
||||||
|
this.cache.clear()
|
||||||
|
this.size = 0
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return entry && entry.value
|
has(key) {
|
||||||
}
|
const entry = this.cache.get(key)
|
||||||
|
if (!entry) return false
|
||||||
|
|
||||||
MemoryCache.prototype.clear = function () {
|
if (Date.now() > entry.expire) {
|
||||||
Object.keys(this.cache).forEach(function (key) {
|
|
||||||
this.delete(key)
|
this.delete(key)
|
||||||
}, this)
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MemoryCache
|
module.exports = MemoryCache
|
||||||
|
@ -6,6 +6,8 @@ const createOption = (query, crypto = '') => {
|
|||||||
proxy: query.proxy,
|
proxy: query.proxy,
|
||||||
realIP: query.realIP,
|
realIP: query.realIP,
|
||||||
e_r: query.e_r || undefined,
|
e_r: query.e_r || undefined,
|
||||||
|
domain: query.domain || '',
|
||||||
|
checkToken: query.checkToken || false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = createOption
|
module.exports = createOption
|
||||||
|
280
util/request.js
280
util/request.js
@ -1,3 +1,4 @@
|
|||||||
|
// 预先导入和绑定常用模块及函数
|
||||||
const encrypt = require('./crypto')
|
const encrypt = require('./crypto')
|
||||||
const CryptoJS = require('crypto-js')
|
const CryptoJS = require('crypto-js')
|
||||||
const { default: axios } = require('axios')
|
const { default: axios } = require('axios')
|
||||||
@ -8,30 +9,50 @@ const tunnel = require('tunnel')
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const tmpPath = require('os').tmpdir()
|
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(
|
const anonymous_token = fs.readFileSync(
|
||||||
path.resolve(tmpPath, './anonymous_token'),
|
path.resolve(tmpPath, './anonymous_token'),
|
||||||
'utf-8',
|
'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 WNMCID = (function () {
|
||||||
const characters = 'abcdefghijklmnopqrstuvwxyz'
|
|
||||||
let randomString = ''
|
let randomString = ''
|
||||||
for (let i = 0; i < 6; i++)
|
for (let i = 0; i < 6; i++) {
|
||||||
randomString += characters.charAt(
|
randomString += characters.charAt(floor(random() * charactersLength))
|
||||||
Math.floor(Math.random() * characters.length),
|
}
|
||||||
)
|
return `${randomString}.${now().toString()}.01.0`
|
||||||
return `${randomString}.${Date.now().toString()}.01.0`
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
// 预先定义osMap
|
||||||
const osMap = {
|
const osMap = {
|
||||||
pc: {
|
pc: {
|
||||||
os: 'pc',
|
os: 'pc',
|
||||||
appver: '3.0.18.203152',
|
appver: '3.1.17.204416',
|
||||||
osver: 'Microsoft-Windows-10-Professional-build-22631-64bit',
|
osver: 'Microsoft-Windows-10-Professional-build-19045-64bit',
|
||||||
channel: 'netease',
|
channel: 'netease',
|
||||||
},
|
},
|
||||||
linux: {
|
linux: {
|
||||||
@ -54,8 +75,8 @@ const osMap = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const chooseUserAgent = (crypto, uaType = 'pc') => {
|
// 预先定义userAgentMap
|
||||||
const userAgentMap = {
|
const userAgentMap = {
|
||||||
weapi: {
|
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',
|
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',
|
||||||
},
|
},
|
||||||
@ -69,76 +90,113 @@ const chooseUserAgent = (crypto, uaType = 'pc') => {
|
|||||||
'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)',
|
'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)',
|
iphone: 'NeteaseMusic 9.0.90/5038 (iPhone; iOS 16.2; zh_CN)',
|
||||||
},
|
},
|
||||||
}
|
|
||||||
return userAgentMap[crypto][uaType] || ''
|
|
||||||
}
|
}
|
||||||
const createRequest = (uri, data, options) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let headers = options.headers || {}
|
|
||||||
let ip = options.realIP || options.ip || ''
|
|
||||||
// console.log(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') {
|
const DOMAIN = APP_CONF.domain
|
||||||
cookie = cookieToJson(cookie)
|
const API_DOMAIN = APP_CONF.apiDomain
|
||||||
}
|
const ENCRYPT_RESPONSE = APP_CONF.encryptResponse
|
||||||
if (typeof cookie === 'object') {
|
const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803])
|
||||||
let _ntes_nuid = CryptoJS.lib.WordArray.random(32).toString()
|
|
||||||
let os = osMap[cookie.os] || osMap['iphone']
|
// chooseUserAgent函数
|
||||||
cookie = {
|
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,
|
...cookie,
|
||||||
__remember_me: 'true',
|
__remember_me: 'true',
|
||||||
// NMTID: CryptoJS.lib.WordArray.random(16).toString(),
|
|
||||||
ntes_kaola_ad: '1',
|
ntes_kaola_ad: '1',
|
||||||
_ntes_nuid: cookie._ntes_nuid || _ntes_nuid,
|
_ntes_nuid: cookie._ntes_nuid || _ntes_nuid,
|
||||||
_ntes_nnid:
|
_ntes_nnid: cookie._ntes_nnid || `${_ntes_nuid},${now().toString()}`,
|
||||||
cookie._ntes_nnid || `${_ntes_nuid},${Date.now().toString()}`,
|
|
||||||
WNMCID: cookie.WNMCID || WNMCID,
|
WNMCID: cookie.WNMCID || WNMCID,
|
||||||
WEVNSM: cookie.WEVNSM || '1.0.0',
|
WEVNSM: cookie.WEVNSM || '1.0.0',
|
||||||
|
|
||||||
osver: cookie.osver || os.osver,
|
osver: cookie.osver || os.osver,
|
||||||
deviceId: cookie.deviceId || global.deviceId,
|
deviceId: cookie.deviceId || global.deviceId,
|
||||||
os: cookie.os || os.os,
|
os: cookie.os || os.os,
|
||||||
channel: cookie.channel || os.channel,
|
channel: cookie.channel || os.channel,
|
||||||
appver: cookie.appver || os.appver,
|
appver: cookie.appver || os.appver,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri.indexOf('login') === -1) {
|
if (uri.indexOf('login') === -1) {
|
||||||
cookie['NMTID'] = CryptoJS.lib.WordArray.random(16).toString()
|
processedCookie['NMTID'] = CryptoJS.lib.WordArray.random(16).toString()
|
||||||
}
|
}
|
||||||
if (!cookie.MUSIC_U) {
|
|
||||||
// 游客
|
if (!processedCookie.MUSIC_U) {
|
||||||
cookie.MUSIC_A = cookie.MUSIC_A || anonymous_token
|
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) => {
|
||||||
|
// 变量声明和初始化
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = options.cookie || {}
|
||||||
|
if (typeof cookie === 'string') {
|
||||||
|
cookie = cookieToJson(cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof cookie === 'object') {
|
||||||
|
cookie = processCookieObject(cookie, uri)
|
||||||
headers['Cookie'] = cookieObjToString(cookie)
|
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 (crypto === '') {
|
||||||
// 加密方式为空,以配置文件的加密方式为准
|
crypto = APP_CONF.encrypt ? 'eapi' : 'api'
|
||||||
if (APP_CONF.encrypt) {
|
|
||||||
crypto = 'eapi'
|
|
||||||
} else {
|
|
||||||
crypto = 'api'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据加密方式加密请求数据;目前任意uri都支持四种加密方式
|
const answer = { status: 500, body: {}, cookie: [] }
|
||||||
|
|
||||||
|
// 根据加密方式处理
|
||||||
switch (crypto) {
|
switch (crypto) {
|
||||||
case 'weapi':
|
case 'weapi':
|
||||||
headers['Referer'] = APP_CONF.domain
|
headers['Referer'] = DOMAIN
|
||||||
headers['User-Agent'] = options.ua || chooseUserAgent('weapi')
|
headers['User-Agent'] = options.ua || chooseUserAgent('weapi')
|
||||||
data.csrf_token = csrfToken
|
data.csrf_token = csrfToken
|
||||||
encryptData = encrypt.weapi(data)
|
encryptData = encrypt.weapi(data)
|
||||||
url = APP_CONF.domain + '/weapi/' + uri.substr(5)
|
url = DOMAIN + '/weapi/' + uri.substr(5)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'linuxapi':
|
case 'linuxapi':
|
||||||
@ -146,93 +204,89 @@ const createRequest = (uri, data, options) => {
|
|||||||
options.ua || chooseUserAgent('linuxapi', 'linux')
|
options.ua || chooseUserAgent('linuxapi', 'linux')
|
||||||
encryptData = encrypt.linuxapi({
|
encryptData = encrypt.linuxapi({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: APP_CONF.domain + uri,
|
url: DOMAIN + uri,
|
||||||
params: data,
|
params: data,
|
||||||
})
|
})
|
||||||
url = APP_CONF.domain + '/api/linux/forward'
|
url = DOMAIN + '/api/linux/forward'
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'eapi':
|
case 'eapi':
|
||||||
case 'api':
|
case 'api':
|
||||||
// 两种加密方式,都应生成客户端的cookie
|
// header创建
|
||||||
const header = {
|
const header = {
|
||||||
osver: cookie.osver, //系统版本
|
osver: cookie.osver,
|
||||||
deviceId: cookie.deviceId,
|
deviceId: cookie.deviceId,
|
||||||
os: cookie.os, //系统类型
|
os: cookie.os,
|
||||||
appver: cookie.appver, // app版本
|
appver: cookie.appver,
|
||||||
versioncode: cookie.versioncode || '140', //版本号
|
versioncode: cookie.versioncode || '140',
|
||||||
mobilename: cookie.mobilename || '', //设备model
|
mobilename: cookie.mobilename || '',
|
||||||
buildver: cookie.buildver || Date.now().toString().substr(0, 10),
|
buildver: cookie.buildver || now().toString().substr(0, 10),
|
||||||
resolution: cookie.resolution || '1920x1080', //设备分辨率
|
resolution: cookie.resolution || '1920x1080',
|
||||||
__csrf: csrfToken,
|
__csrf: csrfToken,
|
||||||
channel: cookie.channel, //下载渠道
|
channel: cookie.channel,
|
||||||
requestId: `${Date.now()}_${Math.floor(Math.random() * 1000)
|
requestId: generateRequestId(),
|
||||||
.toString()
|
...(options.checkToken
|
||||||
.padStart(4, '0')}`,
|
? { 'X-antiCheatToken': APP_CONF.checkToken }
|
||||||
|
: {}),
|
||||||
|
// clientSign: APP_CONF.clientSign,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cookie.MUSIC_U) header['MUSIC_U'] = cookie.MUSIC_U
|
if (cookie.MUSIC_U) header['MUSIC_U'] = cookie.MUSIC_U
|
||||||
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
||||||
headers['Cookie'] = Object.keys(header)
|
|
||||||
.map(
|
headers['Cookie'] = createHeaderCookie(header)
|
||||||
(key) =>
|
|
||||||
encodeURIComponent(key) + '=' + encodeURIComponent(header[key]),
|
|
||||||
)
|
|
||||||
.join('; ')
|
|
||||||
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone')
|
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone')
|
||||||
|
|
||||||
if (crypto === 'eapi') {
|
if (crypto === 'eapi') {
|
||||||
// 使用eapi加密
|
|
||||||
data.header = header
|
data.header = header
|
||||||
data.e_r = toBoolean(
|
data.e_r = toBoolean(
|
||||||
options.e_r !== undefined
|
options.e_r !== undefined
|
||||||
? options.e_r
|
? options.e_r
|
||||||
: data.e_r !== undefined
|
: data.e_r !== undefined
|
||||||
? data.e_r
|
? data.e_r
|
||||||
: APP_CONF.encryptResponse,
|
: ENCRYPT_RESPONSE,
|
||||||
) // 用于加密接口返回值
|
)
|
||||||
encryptData = encrypt.eapi(uri, data)
|
encryptData = encrypt.eapi(uri, data)
|
||||||
url = APP_CONF.apiDomain + '/eapi/' + uri.substr(5)
|
url = API_DOMAIN + '/eapi/' + uri.substr(5)
|
||||||
} else if (crypto === 'api') {
|
} else if (crypto === 'api') {
|
||||||
// 不使用任何加密
|
url = API_DOMAIN + uri
|
||||||
url = APP_CONF.apiDomain + uri
|
|
||||||
encryptData = data
|
encryptData = data
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// 未知的加密方式
|
|
||||||
console.log('[ERR]', 'Unknown Crypto:', crypto)
|
console.log('[ERR]', 'Unknown Crypto:', crypto)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
const answer = { status: 500, body: {}, cookie: [] }
|
// console.log(url);
|
||||||
// console.log(headers, 'headers')
|
// settings创建
|
||||||
let settings = {
|
let settings = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: url,
|
url: url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
data: new URLSearchParams(encryptData).toString(),
|
data: new URLSearchParams(encryptData).toString(),
|
||||||
httpAgent: new http.Agent({ keepAlive: true }),
|
httpAgent: createHttpAgent(),
|
||||||
httpsAgent: new https.Agent({ keepAlive: true }),
|
httpsAgent: createHttpsAgent(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// e_r处理
|
||||||
if (data.e_r) {
|
if (data.e_r) {
|
||||||
settings = {
|
settings.encoding = null
|
||||||
...settings,
|
settings.responseType = 'arraybuffer'
|
||||||
encoding: null,
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 代理处理
|
||||||
if (options.proxy) {
|
if (options.proxy) {
|
||||||
if (options.proxy.indexOf('pac') > -1) {
|
if (options.proxy.indexOf('pac') > -1) {
|
||||||
settings.httpAgent = new PacProxyAgent(options.proxy)
|
const agent = new PacProxyAgent(options.proxy)
|
||||||
settings.httpsAgent = new PacProxyAgent(options.proxy)
|
settings.httpAgent = agent
|
||||||
|
settings.httpsAgent = agent
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
const purl = new URL(options.proxy)
|
const purl = new URL(options.proxy)
|
||||||
if (purl.hostname) {
|
if (purl.hostname) {
|
||||||
const agent = tunnel[
|
const isHttps = purl.protocol === 'https:'
|
||||||
purl.protocol === 'https' ? 'httpsOverHttp' : 'httpOverHttp'
|
const agent = tunnel[isHttps ? 'httpsOverHttp' : 'httpOverHttp']({
|
||||||
]({
|
|
||||||
proxy: {
|
proxy: {
|
||||||
host: purl.hostname,
|
host: purl.hostname,
|
||||||
port: purl.port || 80,
|
port: purl.port || 80,
|
||||||
@ -248,25 +302,29 @@ const createRequest = (uri, data, options) => {
|
|||||||
} else {
|
} else {
|
||||||
console.error('代理配置无效,不使用代理')
|
console.error('代理配置无效,不使用代理')
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('代理URL解析失败:', e.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
settings.proxy = false
|
settings.proxy = false
|
||||||
}
|
}
|
||||||
|
// console.log(settings.headers);
|
||||||
axios(settings)
|
axios(settings)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const body = res.data
|
const body = res.data
|
||||||
answer.cookie = (res.headers['set-cookie'] || []).map((x) =>
|
answer.cookie = (res.headers['set-cookie'] || []).map((x) =>
|
||||||
x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
|
x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (crypto === 'eapi' && data.e_r) {
|
if (crypto === 'eapi' && data.e_r) {
|
||||||
// eapi接口返回值被加密,需要解密
|
|
||||||
answer.body = encrypt.eapiResDecrypt(
|
answer.body = encrypt.eapiResDecrypt(
|
||||||
body.toString('hex').toUpperCase(),
|
body.toString('hex').toUpperCase(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
answer.body =
|
answer.body =
|
||||||
typeof body == 'object' ? body : JSON.parse(body.toString())
|
typeof body === 'object' ? body : parse(body.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (answer.body.code) {
|
if (answer.body.code) {
|
||||||
@ -274,28 +332,30 @@ const createRequest = (uri, data, options) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
answer.status = Number(answer.body.code || res.status)
|
answer.status = Number(answer.body.code || res.status)
|
||||||
if (
|
|
||||||
[201, 302, 400, 502, 800, 801, 802, 803].indexOf(answer.body.code) >
|
// 状态码检查(使用Set提升查找性能)
|
||||||
-1
|
if (SPECIAL_STATUS_CODES.has(answer.body.code)) {
|
||||||
) {
|
|
||||||
// 特殊状态码
|
|
||||||
answer.status = 200
|
answer.status = 200
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e)
|
|
||||||
// can't decrypt and can't parse directly
|
|
||||||
answer.body = body
|
answer.body = body
|
||||||
answer.status = res.status
|
answer.status = res.status
|
||||||
}
|
}
|
||||||
|
|
||||||
answer.status =
|
answer.status =
|
||||||
100 < answer.status && answer.status < 600 ? answer.status : 400
|
answer.status > 100 && answer.status < 600 ? answer.status : 400
|
||||||
if (answer.status === 200) resolve(answer)
|
|
||||||
else reject(answer)
|
if (answer.status === 200) {
|
||||||
|
resolve(answer)
|
||||||
|
} else {
|
||||||
|
console.log('[ERR]', answer)
|
||||||
|
reject(answer)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
answer.status = 502
|
answer.status = 502
|
||||||
answer.body = { code: 502, msg: err }
|
answer.body = { code: 502, msg: err.message || err }
|
||||||
|
console.log('[ERR]', answer)
|
||||||
reject(answer)
|
reject(answer)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user