mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2025-10-22 14:43:10 +00:00
Merge branch 'main' into 'main'
增加部分接口;eapi返回值加密可被控制,默认不加密;代码优化 See merge request Binaryify/neteasecloudmusicapi!15
This commit is contained in:
commit
2469823df2
@ -426,6 +426,9 @@ banner({ type: 0 }).then((res) => {
|
|||||||
280. 获取专辑歌曲的音质
|
280. 获取专辑歌曲的音质
|
||||||
281. 歌手动态信息
|
281. 歌手动态信息
|
||||||
282. 最近听歌列表
|
282. 最近听歌列表
|
||||||
|
283. 云盘导入歌曲
|
||||||
|
284. 获取客户端歌曲下载链接 - 新版
|
||||||
|
285. 当前账号关注的用户/歌手
|
||||||
|
|
||||||
## 单元测试
|
## 单元测试
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// 实际请求参数如下, 部分内容省略, 敏感信息已进行混淆
|
// 实际请求参数如下, 部分内容省略, 敏感信息已进行混淆
|
||||||
// 可按需修改此 API 的代码
|
// 可按需修改此 API 的代码
|
||||||
/* {"extInfo":"{\"lastRequestTimestamp\":1692358373509,\"lbsInfoList\":[{\"lat\":40.23076381,\"lon\":129.07545186,\"time\":1692358543},{\"lat\":40.23076381,\"lon\":129.07545186,\"time\":1692055283}],\"listenedTs\":false,\"noAidjToAidj\":true}","header":"{}","e_r":true} */
|
/* {"extInfo":"{\"lastRequestTimestamp\":1692358373509,\"lbsInfoList\":[{\"lat\":40.23076381,\"lon\":129.07545186,\"time\":1692358543},{\"lat\":40.23076381,\"lon\":129.07545186,\"time\":1692055283}],\"listenedTs\":false,\"noAidjToAidj\":true}","header":"{}"} */
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
|
@ -2,8 +2,14 @@ const createOption = require('../util/option.js')
|
|||||||
module.exports = (query, request) => {
|
module.exports = (query, request) => {
|
||||||
const method = query.method || 'POST'
|
const method = query.method || 'POST'
|
||||||
const uri = query.uri
|
const uri = query.uri
|
||||||
const data =
|
let data = {}
|
||||||
|
try {
|
||||||
|
data =
|
||||||
typeof query.data === 'string' ? JSON.parse(query.data) : query.data || {}
|
typeof query.data === 'string' ? JSON.parse(query.data) : query.data || {}
|
||||||
|
} catch (e) {
|
||||||
|
data = {}
|
||||||
|
}
|
||||||
|
|
||||||
const crypto = query.crypto || ''
|
const crypto = query.crypto || ''
|
||||||
|
|
||||||
const res = request(method, uri, data, createOption(query, crypto))
|
const res = request(method, uri, data, createOption(query, crypto))
|
||||||
|
18
module/song_download_url_v1.js
Normal file
18
module/song_download_url_v1.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 获取客户端歌曲下载链接 - v1
|
||||||
|
// 此版本不再采用 br 作为音质区分的标准
|
||||||
|
// 而是采用 standard, exhigh, lossless, hires, jyeffect(高清环绕声), sky(沉浸环绕声), jymaster(超清母带) 进行音质判断
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const data = {
|
||||||
|
id: query.id,
|
||||||
|
immerseType: 'c51',
|
||||||
|
level: query.level,
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
'POST',
|
||||||
|
`/api/song/enhance/download/url/v1`,
|
||||||
|
data,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
24
module/user_follow_mixed.js
Normal file
24
module/user_follow_mixed.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 当前账号关注的用户/歌手
|
||||||
|
|
||||||
|
const createOption = require('../util/option.js')
|
||||||
|
module.exports = (query, request) => {
|
||||||
|
const size = query.size || 30
|
||||||
|
const cursor = query.cursor || 0
|
||||||
|
const scene = query.scene || 0 // 0: 所有关注 1: 关注的歌手 2: 关注的用户
|
||||||
|
const data = {
|
||||||
|
authority: 'false',
|
||||||
|
page: JSON.stringify({
|
||||||
|
size: size,
|
||||||
|
cursor: cursor,
|
||||||
|
}),
|
||||||
|
scene: scene,
|
||||||
|
size: size,
|
||||||
|
sortType: '0',
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
'POST',
|
||||||
|
`/api/user/follow/users/mixed/get/v2`,
|
||||||
|
data,
|
||||||
|
createOption(query),
|
||||||
|
)
|
||||||
|
}
|
@ -299,6 +299,8 @@
|
|||||||
281. 歌手动态信息
|
281. 歌手动态信息
|
||||||
282. 最近听歌列表
|
282. 最近听歌列表
|
||||||
283. 云盘导入歌曲
|
283. 云盘导入歌曲
|
||||||
|
284. 获取客户端歌曲下载链接 - 新版
|
||||||
|
285. 当前账号关注的用户/歌手
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
@ -4685,6 +4687,33 @@ bitrate = Math.floor(br / 1000)
|
|||||||
```
|
```
|
||||||
导入后的文件名后缀均为 `.mp3` 。但用 `获取音乐url` 获取到的文件格式仍然是正确的。
|
导入后的文件名后缀均为 `.mp3` 。但用 `获取音乐url` 获取到的文件格式仍然是正确的。
|
||||||
|
|
||||||
|
### 获取客户端歌曲下载链接 - 新版
|
||||||
|
|
||||||
|
说明 : 使用 `/song/url/v1` 接口获取的是歌曲试听 url, 但存在部分歌曲在非 VIP 账号上可以下载无损音质而不能试听无损音质, 使用此接口可使非 VIP 账号获取这些歌曲的无损音频
|
||||||
|
|
||||||
|
**必选参数 :** `id` : 音乐 id
|
||||||
|
`level`: 播放音质等级, 分为 `standard` => `标准`,`higher` => `较高`, `exhigh`=>`极高`,
|
||||||
|
`lossless`=>`无损`, `hires`=>`Hi-Res`, `jyeffect` => `高清环绕声`, `sky` => `沉浸环绕声`,
|
||||||
|
`jymaster` => `超清母带`
|
||||||
|
|
||||||
|
**接口地址 :** `/song/download/url/v1`
|
||||||
|
|
||||||
|
**调用例子 :** `/song/download/url/v1?id=2058263032&level=lossless`
|
||||||
|
|
||||||
|
### 当前账号关注的用户/歌手
|
||||||
|
|
||||||
|
说明 : 调用此接口, 可获得当前账号关注的用户/歌手
|
||||||
|
|
||||||
|
**可选参数 :** `size` : 返回数量 , 默认为 30
|
||||||
|
|
||||||
|
`cursor` : 返回数据的 cursor, 默认为 0 , 传入上一次返回结果的 cursor,将会返回下一页的数据
|
||||||
|
|
||||||
|
`scene` : 场景, 0 表示所有关注, 1 表示关注的歌手, 2 表示关注的用户, 默认为 0
|
||||||
|
|
||||||
|
**接口地址 :** `/user/follow/mixed`
|
||||||
|
|
||||||
|
**调用例子 :** `/user/follow/mixed?scene=1`
|
||||||
|
|
||||||
## 离线访问此文档
|
## 离线访问此文档
|
||||||
|
|
||||||
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
|
此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问
|
||||||
|
@ -13,6 +13,6 @@
|
|||||||
"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": true
|
"encryptResponse": false
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ const createOption = (query, crypto = '') => {
|
|||||||
ua: query.ua || '',
|
ua: query.ua || '',
|
||||||
proxy: query.proxy,
|
proxy: query.proxy,
|
||||||
realIP: query.realIP,
|
realIP: query.realIP,
|
||||||
|
e_r: query.e_r || undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = createOption
|
module.exports = createOption
|
||||||
|
@ -85,32 +85,36 @@ const createRequest = (method, uri, data = {}, options) => {
|
|||||||
}
|
}
|
||||||
// console.log(options.cookie, headers['Cookie'])
|
// console.log(options.cookie, headers['Cookie'])
|
||||||
|
|
||||||
let url = ''
|
let url = '',
|
||||||
// 目前任意uri都支持三种加密方式
|
encryptData = '',
|
||||||
if (options.crypto === 'weapi') {
|
crypto = options.crypto,
|
||||||
|
csrfToken = cookie['__csrf'] || ''
|
||||||
|
// 根据加密方式加密请求数据;目前任意uri都支持四种加密方式
|
||||||
|
switch (crypto) {
|
||||||
|
case 'weapi':
|
||||||
headers['Referer'] = 'https://music.163.com'
|
headers['Referer'] = 'https://music.163.com'
|
||||||
headers['User-Agent'] = options.ua || chooseUserAgent('pc')
|
headers['User-Agent'] = options.ua || chooseUserAgent('pc')
|
||||||
let csrfToken = (headers['Cookie'] || '').match(/_csrf=([^(;|$)]+)/)
|
data.csrf_token = csrfToken
|
||||||
data.csrf_token = csrfToken ? csrfToken[1] : ''
|
encryptData = encrypt.weapi(data)
|
||||||
data = encrypt.weapi(data)
|
|
||||||
url = APP_CONF.domain + '/weapi/' + uri.substr(5)
|
url = APP_CONF.domain + '/weapi/' + uri.substr(5)
|
||||||
} else if (options.crypto === 'linuxapi') {
|
break
|
||||||
|
|
||||||
|
case 'linuxapi':
|
||||||
headers['User-Agent'] =
|
headers['User-Agent'] =
|
||||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
|
||||||
data = encrypt.linuxapi({
|
encryptData = encrypt.linuxapi({
|
||||||
method: method,
|
method: method,
|
||||||
url: APP_CONF.apiDomain + uri,
|
url: APP_CONF.apiDomain + uri,
|
||||||
params: data,
|
params: data,
|
||||||
})
|
})
|
||||||
url = 'https://music.163.com/api/linux/forward'
|
url = 'https://music.163.com/api/linux/forward'
|
||||||
} else if (
|
break
|
||||||
options.crypto === 'eapi' ||
|
|
||||||
options.crypto === 'api' ||
|
case 'eapi':
|
||||||
options.crypto === ''
|
case 'api':
|
||||||
) {
|
case '':
|
||||||
// 两种加密方式,都应生成客户端的cookie
|
// 两种加密方式,都应生成客户端的cookie
|
||||||
const cookie = options.cookie || {}
|
const cookie = options.cookie || {}
|
||||||
const csrfToken = cookie['__csrf'] || ''
|
|
||||||
const header = {
|
const header = {
|
||||||
osver: cookie.osver || '17.4.1', //系统版本
|
osver: cookie.osver || '17.4.1', //系统版本
|
||||||
deviceId: cookie.deviceId || global.deviceId,
|
deviceId: cookie.deviceId || global.deviceId,
|
||||||
@ -135,24 +139,41 @@ const createRequest = (method, uri, data = {}, options) => {
|
|||||||
)
|
)
|
||||||
.join('; ')
|
.join('; ')
|
||||||
|
|
||||||
let eapiEncrypt = () => {
|
let eapi = () => {
|
||||||
|
// 使用eapi加密
|
||||||
data.header = header
|
data.header = header
|
||||||
data = encrypt.eapi(uri, data)
|
data.e_r =
|
||||||
|
options.e_r != undefined
|
||||||
|
? options.e_r
|
||||||
|
: data.e_r != undefined
|
||||||
|
? data.e_r
|
||||||
|
: APP_CONF.encryptResponse // 用于加密接口返回值
|
||||||
|
encryptData = encrypt.eapi(uri, data)
|
||||||
url = APP_CONF.apiDomain + '/eapi/' + uri.substr(5)
|
url = APP_CONF.apiDomain + '/eapi/' + uri.substr(5)
|
||||||
}
|
}
|
||||||
if (options.crypto === 'eapi') {
|
let api = () => {
|
||||||
eapiEncrypt()
|
// 不使用任何加密
|
||||||
} else if (options.crypto === 'api') {
|
|
||||||
url = APP_CONF.apiDomain + uri
|
url = APP_CONF.apiDomain + uri
|
||||||
} else if (options.crypto === '') {
|
encryptData = data
|
||||||
|
}
|
||||||
|
if (crypto === 'eapi') {
|
||||||
|
eapi()
|
||||||
|
} else if (crypto === 'api') {
|
||||||
|
api()
|
||||||
|
} else if (crypto === '') {
|
||||||
// 加密方式为空,以配置文件的加密方式为准
|
// 加密方式为空,以配置文件的加密方式为准
|
||||||
if (APP_CONF.encrypt) {
|
if (APP_CONF.encrypt) {
|
||||||
eapiEncrypt()
|
eapi()
|
||||||
} else url = APP_CONF.apiDomain + uri
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
api()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
// 未知的加密方式
|
// 未知的加密方式
|
||||||
console.log('[ERR]', 'Unknown Crypto:', options.crypto)
|
console.log('[ERR]', 'Unknown Crypto:', crypto)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
const answer = { status: 500, body: {}, cookie: [] }
|
const answer = { status: 500, body: {}, cookie: [] }
|
||||||
// console.log(headers, 'headers')
|
// console.log(headers, 'headers')
|
||||||
@ -160,11 +181,19 @@ const createRequest = (method, uri, data = {}, options) => {
|
|||||||
method: method,
|
method: method,
|
||||||
url: url,
|
url: url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
data: new URLSearchParams(data).toString(),
|
data: new URLSearchParams(encryptData).toString(),
|
||||||
httpAgent: new http.Agent({ keepAlive: true }),
|
httpAgent: new http.Agent({ keepAlive: true }),
|
||||||
httpsAgent: new https.Agent({ keepAlive: true }),
|
httpsAgent: new https.Agent({ keepAlive: true }),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.e_r) {
|
||||||
|
settings = {
|
||||||
|
...settings,
|
||||||
|
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)
|
settings.httpAgent = new PacProxyAgent(options.proxy)
|
||||||
@ -201,7 +230,16 @@ const createRequest = (method, uri, data = {}, options) => {
|
|||||||
x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
|
x.replace(/\s*Domain=[^(;|$)]+;*/, ''),
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
answer.body = JSON.parse(body.toString())
|
if (data.e_r) {
|
||||||
|
// eapi接口返回值被加密,需要解密
|
||||||
|
answer.body = encrypt.eapiResDecrypt(
|
||||||
|
body.toString('hex').toUpperCase(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
answer.body =
|
||||||
|
typeof body == 'object' ? body : JSON.parse(body.toString())
|
||||||
|
}
|
||||||
|
|
||||||
if (answer.body.code) {
|
if (answer.body.code) {
|
||||||
answer.body.code = Number(answer.body.code)
|
answer.body.code = Number(answer.body.code)
|
||||||
}
|
}
|
||||||
@ -216,13 +254,8 @@ const createRequest = (method, uri, data = {}, options) => {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e)
|
// console.log(e)
|
||||||
try {
|
|
||||||
answer.body = JSON.parse(encrypt.decrypt(body))
|
|
||||||
} catch (err) {
|
|
||||||
// console.log(err)
|
|
||||||
// can't decrypt and can't parse directly
|
// can't decrypt and can't parse directly
|
||||||
answer.body = body
|
answer.body = body
|
||||||
}
|
|
||||||
answer.status = res.status
|
answer.status = res.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user