提交 8bb37684 编写于 作者: B binaryify

修复取消喜欢歌曲失败问题 #360 补充已喜欢音乐列表接口说明文档 #370 默认关闭 debug 模式 #365 更新 Dockerfile 文件 #367

上级 aea538c8
# 更新日志
### 3.0.2 | 2018.10.31
### 3.0.3 | 2018.11.09
- 修复取消喜欢歌曲失败问题 [#360](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/360)
- 补充已喜欢音乐列表接口说明文档 [#370](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/370)
- 默认关闭 debug 模式 [#365](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/365)
- 更新 Dockerfile 文件 [#367](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/367)
### 3.0.1 | 2018.10.21
......
......@@ -99,6 +99,14 @@
77. 热门评论
78. 视频评论
79. 退出登录
80. 所有榜单内容摘要
81. 收藏视频
82. 收藏 MV
83. 视频详情
84. 相关视频
85. 关注用户
86. 新歌速递
87. 喜欢音乐列表(无序)
## 环境要求
......
......@@ -102,30 +102,46 @@
84. 相关视频
85. 关注用户
86. 新歌速递
87. 喜欢音乐列表(无序)
## 安装
```shell
$ git clone git@github.com:Binaryify/NeteaseCloudMusicApi.git
$ npm install
```
## 运行
```shell
$ node app.js
```
服务器启动默认端口为 3000, 若不想使用 3000 端口 , 可使用以下命令 : Mac/Linux
```shell
$ PORT=4000 node app.js
```
windows 下使用 git-bash 或者 cmder 等终端执行以下命令 :
```shell
$ set PORT=4000 && node app.js
```
## 可以使用代理
......@@ -162,18 +178,42 @@ request 相关的环境变量
```shell
docker pull twesix/netease-cloud-music
docker run -d -p 3000:3000 --name netease-cloud-music twesix/netease-music-api
// 去掉或者设置相关的环境变量
docker run -d -p 3000:3000 --name netease-cloud-music -e http_proxy= -e https_proxy= -e no_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= -e NO_PROXY= netease-cloud-music
```
> 由于 docker 镜像更新不是很及时,推荐自己 build, 以下为 build 镜像的方式
```
$ git clone https://github.com/Binaryify/NeteaseCloudMusicApi && cd NeteaseCloudMusicApi
$ sudo docker build . -t netease-music-api
$ sudo docker run -d -p 3000:3000 netease-music-api
```
## 接口文档
......@@ -182,7 +222,7 @@ $ sudo docker run -d -p 3000:3000 netease-music-api
!> 为使用方便,降低门槛,登录接口直接使用了 get 明文请求,请按实际需求对源码修改
!> 由于接口做了缓存处理 ( 缓存 2 分钟 , 可在 app.js 设置 , 可能会导致登陆后获取不
!> 由于接口做了缓存处理 ( 缓存 2 分钟,不缓存数据极容易引起网易服务器高频ip错误 , 可在 app.js 设置 , 可能会导致登陆后获取不
到 cookie), 相同的 url 会在两分钟内只向网易服务器发一次请求 , 如果遇到不需要缓
存结果的接口 , 可在请求 url 后面加一个时间戳参数使 url 不同 , 例子 :
`/simi/playlist?id=347230&timestamp=1503019930000`
......@@ -280,11 +320,29 @@ Cookies
```
gender: 性别 0:保密 1:男性 2:女性
birthday: 出生日期,时间戳 unix timestamp
nickname: 用户昵称
province: 省份id
city: 城市id
signature:用户签名
```
**接口地址 :** `/user/subcount`
......@@ -311,9 +369,21 @@ signature:用户签名
```
id:歌单id
name:歌单名字
desc:歌单描述
tags:歌单tag
```
**接口地址 :** `/playlist/update`
......@@ -369,6 +439,7 @@ tags:歌单tag
**必选参数 :** `uid` : 用户 id
**可选参数 :**
`limit` : 返回数量 , 默认为 30
`offset` : 偏移数量,用于分页 , 如
......@@ -443,6 +514,7 @@ tags:歌单tag
说明 : 调用此接口,可获取歌手分类列表
**必选参数 :** `cat` : 即 category Code,歌手类型,默认 1001,返回华语男歌手数据
**可选参数 :**
`limit` : 返回数量 , 默认为 30
`offset` : 偏移数量,用于分页 , 如
......@@ -453,21 +525,69 @@ category Code 取值:
```
入驻歌手 5001
华语男歌手 1001
华语女歌手 1002
华语组合/乐队 1003
欧美男歌手 2001
欧美女歌手 2002
欧美组合/乐队 2003
日本男歌手 6001
日本女歌手 6002
日本组合/乐队 6003
韩国男歌手 7001
韩国女歌手 7002
韩国组合/乐队 7003
其他男歌手 4001
其他女歌手 4002
其他组合/乐队 4003
```
**接口地址 :** `/artist/list`
......@@ -560,7 +680,9 @@ category Code 取值:
返回数据如下图 :
![精选碟](https://raw.githubusercontent.com/Binaryify/NeteaseCloudMusicApi/master/static/top_playlist.png)
![对应位置](https://ws2.sinaimg.cn/large/006tKfTcgy1fr3wnpyg6jj317e0vcqdc.jpg)
![返回数据](https://ws4.sinaimg.cn/large/006tKfTcgy1fr3wqs5lw9j31ic1re4c4.jpg)
### 获取精品歌单
......@@ -673,6 +795,7 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
**必选参数 :** `keywords` : 关键词
**可选参数 :**
`limit` : 返回数量 , 默认为 30
`offset` : 偏移数量,用于分页 , 如
......@@ -713,6 +836,7 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
说明 : 调用此接口 , 传入类型和歌单 id 可收藏歌单或者取消收藏歌单
**必选参数 :**
`t` : 类型,1:收藏,2:取消收藏
`id` : 歌单 id
......@@ -728,6 +852,7 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
说明 : 调用此接口 , 可以添加歌曲到歌单或者从歌单删除某首歌曲 ( 需要登录 )
**必选参数 :**
`op`: 从歌单增加单曲为 add, 删除为 del
`pid`: 歌单 id
......@@ -760,10 +885,25 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
```
全部:0
华语:7
欧美:96
日本:8
韩国:16
```
`limit`: 取出数量 , 默认为 100
......@@ -879,11 +1019,29 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
```
0: 歌曲
1: mv
2: 歌单
3: 专辑
4: 电台
5: 视频
```
**接口地址 :** `/comment/hot`
......@@ -905,11 +1063,29 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
```
0: 歌曲
1: mv
2: 歌单
3: 专辑
4: 电台
5: 视频
```
**接口地址 :** `comment/like`
......@@ -925,17 +1101,36 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
1. 发送评论
**必选参数**
`t`:1 发送
`tpye`: 数字,资源类型,对应歌曲,mv,专辑,歌单,电台,视频对应以下类型
```
0: 歌曲
1: mv
2: 歌单
3: 专辑
4: 电台
5: 视频
```
`id`:对应资源 id
......@@ -947,17 +1142,36 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
2. 删除评论
**必选参数**
`t`:0 删除
`tpye`: 数字,资源类型,对应歌曲,mv,专辑,歌单,电台,视频对应以下类型
```
0: 歌曲
1: mv
2: 歌单
3: 专辑
4: 电台
5: 视频
```
`id`:对应资源 id
......@@ -983,8 +1197,17 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
```
1: mv
4: 电台
5: 视频
```
`t`: 操作,1 为点赞,其他未取消点赞
......@@ -1195,6 +1418,16 @@ mp3url 不能直接用 , 可通过 `/song/url` 接口传入歌曲 id 获取具
![喜欢成功截图](https://raw.githubusercontent.com/Binaryify/NeteaseCloudMusicApi/master/static/likeSuccess.png)
### 喜欢音乐列表
说明 : 调用此接口 , 传入用户 id, 可获取已喜欢音乐id列表(id数组)
**必选参数 :** `uid`: 用户 id
**接口地址 :** `/likelist`
**调用例子 :** `/likelist?uid=32953014`
### 垃圾桶
说明 : 调用此接口 , 传入音乐 id, 可把该音乐从私人 FM 中移除至垃圾桶
......@@ -1340,6 +1573,7 @@ MV 数据 , 数据包含 mv 名字 , 歌手 , 发布时间 , mv 视频地址等
**接口地址 :** `/mv/url`
**调用例子 :**
`/mv/url?id=5436712`
### 相关视频
......@@ -1385,29 +1619,101 @@ MV 数据 , 数据包含 mv 名字 , 歌手 , 发布时间 , mv 视频地址等
```
"0": 云音乐新歌榜,
"1": 云音乐热歌榜,
"2": 网易原创歌曲榜,
"3": 云音乐飙升榜,
"4": 云音乐电音榜,
"5": UK排行榜周榜,
"6": 美国Billboard周榜
"7": KTV嗨榜,
"8": iTunes榜,
"9": Hit FM Top榜,
"10": 日本Oricon周榜
"11": 韩国Melon排行榜周榜,
"12": 韩国Mnet排行榜周榜,
"13": 韩国Melon原声周榜,
"14": 中国TOP排行榜(港台榜),
"15": 中国TOP排行榜(内地榜)
"16": 香港电台中文歌曲龙虎榜,
"17": 华语金曲榜,
"18": 中国嘻哈榜,
"19": 法国 NRJ EuroHot 30周榜,
"20": 台湾Hito排行榜,
"21": Beatport全球电子舞曲榜,
"22": 云音乐ACG音乐榜,
"23": 云音乐嘻哈榜
```
**接口地址 :** `/top/list`
......@@ -1517,6 +1823,7 @@ type='1009' 获取其 id, 如`/search?keywords= 代码时间 &type=1009`
**必选参数 :** `rid`: 电台 的 id
**可选参数 :**
`limit` : 返回数量 , 默认为 30
`offset` : 偏移数量,用于分页 , 如
......
// 红心与取消红心歌曲
const { toBoolean } = require('../util')
module.exports = (query, request) => {
query.like = (query.like ? true : false)
const data = {
trackId: query.id,
like: query.like
}
return request(
'POST', `http://music.163.com/weapi/radio/like?alg=${query.alg || 'itembased'}&trackId=${query.id}&like=${query.like}&time=${query.time || 25}`, data,
{crypto: 'weapi', cookie: query.cookie, proxy: query.proxy}
)
}
\ No newline at end of file
query.like = query.like ? true : false
const data = {
trackId: query.id,
like: query.like
}
return request(
'POST',
`http://music.163.com/weapi/radio/like?alg=${query.alg ||
'itembased'}&trackId=${query.id}&like=${query.like}&time=${query.time ||
25}`,
data,
{ crypto: 'weapi', cookie: query.cookie, proxy: query.proxy }
)
}
{
"name": "NeteaseCloudMusicApi",
"version": "3.0.2",
"version": "3.0.3",
"description": "网易云音乐 NodeJS 版 API",
"scripts": {
"start": "node app.js",
......
module.exports = {
toBoolean(val) {
if (val === '') return val
return val === 'true' || val == '1'
}
}
......@@ -2,88 +2,103 @@ const encrypt = require('./crypto')
const request = require('request')
const queryString = require('querystring')
const chooseUserAgent = (ua) => {
const userAgentList = [
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89;GameHelper',
'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
'Mozilla/5.0 (iPad; CPU OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:46.0) Gecko/20100101 Firefox/46.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:46.0) Gecko/20100101 Firefox/46.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586'
]
let index = 0
if(typeof(ua) == 'undefined')
index = Math.floor(Math.random() * userAgentList.length)
else if(ua == 'mobile')
index = Math.floor(Math.random() * 7)
else if(ua == 'pc')
index = Math.floor(Math.random() * 5) + 8
else
return ua
return userAgentList[index]
// request.debug = true // 开启可看到更详细信息
const chooseUserAgent = ua => {
const userAgentList = [
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89;GameHelper',
'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
'Mozilla/5.0 (iPad; CPU OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:46.0) Gecko/20100101 Firefox/46.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:46.0) Gecko/20100101 Firefox/46.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586'
]
let index = 0
if (typeof ua == 'undefined')
index = Math.floor(Math.random() * userAgentList.length)
else if (ua == 'mobile') index = Math.floor(Math.random() * 7)
else if (ua == 'pc') index = Math.floor(Math.random() * 5) + 8
else return ua
return userAgentList[index]
}
const createRequest = (method, url, data, options) => {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
let headers = { 'User-Agent': chooseUserAgent(options.ua) }
if (method.toUpperCase() == 'POST')
headers['Content-Type'] = 'application/x-www-form-urlencoded'
if (url.includes('music.163.com'))
headers['Referer'] = 'http://music.163.com'
// headers['X-Real-IP'] = '118.88.88.88'
let headers = {'User-Agent': chooseUserAgent(options.ua)}
if(method.toUpperCase() == 'POST') headers['Content-Type'] = 'application/x-www-form-urlencoded'
if(url.includes('music.163.com')) headers['Referer'] = 'http://music.163.com'
// headers['X-Real-IP'] = '118.88.88.88'
if (typeof options.cookie === 'object')
headers['Cookie'] = Object.keys(options.cookie)
.map(
key =>
encodeURIComponent(key) +
'=' +
encodeURIComponent(options.cookie[key])
)
.join('; ')
else if (options.cookie) headers['Cookie'] = options.cookie
if(typeof(options.cookie) === 'object')
headers['Cookie'] = Object.keys(options.cookie).map(key => (encodeURIComponent(key) + '=' + encodeURIComponent(options.cookie[key]))).join('; ')
else if(options.cookie)
headers['Cookie'] = options.cookie
if (options.crypto == 'weapi') {
let csrfToken = (headers['Cookie'] || '').match(/_csrf=([^(;|$)]+)/)
data.csrf_token = csrfToken ? csrfToken[1] : ''
data = encrypt.weapi(data)
url = url.replace(/\w*api/, 'weapi')
} else if (options.crypto == 'linuxapi') {
data = encrypt.linuxapi({
method: method,
url: url.replace(/\w*api/, 'api'),
params: data
})
headers['User-Agent'] =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
url = 'http://music.163.com/api/linux/forward'
}
if(options.crypto == 'weapi'){
let csrfToken = (headers['Cookie'] || '').match(/_csrf=([^(;|$)]+)/)
data.csrf_token = (csrfToken ? csrfToken[1] : '')
data = encrypt.weapi(data)
url = url.replace(/\w*api/,'weapi')
}
else if(options.crypto == 'linuxapi'){
data = encrypt.linuxapi({'method': method, url: url.replace(/\w*api/,'api'), 'params': data})
headers['User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
url = 'http://music.163.com/api/linux/forward'
const answer = { status: 500, body: {}, cookie: [] }
request(
{
method: method,
url: url,
headers: headers,
body: queryString.stringify(data),
proxy: options.proxy
},
(err, res, body) => {
if (err) {
answer.status = 502
answer.body = { code: 502, msg: err.stack }
reject(answer)
} else {
answer.cookie = (res.headers['set-cookie'] || []).map(x =>
x.replace(/\s*Domain=[^(;|$)]+;*/, '')
)
try {
answer.body = JSON.parse(body)
answer.status = answer.body.code || res.statusCode
} catch (e) {
answer.body = body
answer.status = res.statusCode
}
answer.status =
100 < answer.status && answer.status < 600 ? answer.status : 400
if (answer.status == 200) resolve(answer)
else reject(answer)
}
const answer = {status: 500, body: {}, cookie: []}
request(
{method: method, url: url, headers: headers, body: queryString.stringify(data), proxy: options.proxy},
(err, res, body) => {
if(err){
answer.status = 502
answer.body = {code: 502, msg: err.stack}
reject(answer)
}
else{
answer.cookie = (res.headers['set-cookie'] || []).map(x => x.replace(/\s*Domain=[^(;|$)]+;*/, ''))
try{
answer.body = JSON.parse(body)
answer.status = answer.body.code || res.statusCode
}
catch(e){
answer.body = body
answer.status = res.statusCode
}
answer.status = (100 < answer.status && answer.status < 600) ? answer.status : 400
if(answer.status == 200)
resolve(answer)
else
reject(answer)
}
}
)
})
}
)
})
}
module.exports = createRequest
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册