upgrade version 2.0,add login api,upgrade api verison,add document and unit test

This commit is contained in:
binaryify 2017-04-01 18:42:51 +08:00
parent 35f37afd77
commit a2e4d26401
46 changed files with 714 additions and 614 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,3 +0,0 @@
{
"presets": ["es2015"]
}

View File

@ -1,20 +0,0 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -1,6 +1,4 @@
language: node_js
node_js:
- 4.0
- 6.2

21
LICENSE
View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2016 Binaryify
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,5 +1,5 @@
# NeteaseCloudMusicApi
一个调用网易云音乐 API 的 node 模块
网易云音乐 nodejs 接口
<p>
<a href="https://www.npmjs.com/package/NeteaseCloudMusicApi"><img src="https://img.shields.io/npm/v/NeteaseCloudMusicApi.svg" alt="Version"></a>
<a href="https://www.npmjs.com/package/NeteaseCloudMusicApi"><img src="https://img.shields.io/npm/l/NeteaseCloudMusicApi.svg" alt="License"></a>
@ -9,64 +9,35 @@
<a href="https://travis-ci.org/Binaryify/NeteaseCloudMusicApi"><img src="https://api.travis-ci.org/Binaryify/NeteaseCloudMusicApi.svg?branch=master" /></a>
</p>
![](http://binaryify.github.io/images/api.jpg)
## Start
## 版本新特性
增加使用文档,完成项目重构,增加更完善的单元测试,版本升级到2.0,升级 api 到 v2+,支持登录并获取用户信息和创建的歌单,可通过获取音乐 url 接口获取用户歌单里的的音乐,获取每日推荐歌单和每日推荐音乐
## 环境要求
需要 NodeJS 6.0+ 环境
## 安装
``` shell
npm install NeteaseCloudMusicApi
$ git clone git@github.com:Binaryify/NeteaseCloudMusicApi.git
$ npm install
```
## 运行
``` shell
$ node app.js
```
## Usage
``` javascript
var api = require('NeteaseCloudMusicApi').api
api.search('年度之歌',function(data){
console.log(data)
})
```
or
``` javascript
import {api} from 'NeteaseCloudMusicApi'
api.search('年度之歌',data => {
console.log(data)
})
## 使用文档
[文档地址](https://binaryify.github.io/NeteaseCloudMusicApi)
## 单元测试
``` shell
$ npm test
```
## API
### search
``` javascript
api.search(name:String,[callback:function,onlySong:Boolean default:true,limit:Number default:3, offset:Number default:0])
```
说明:onlySong默认为true,如果为false,则返回一个对象,包含songs和mvs,songs和mvs均为数组
### lrc
``` javascript
api.lrc(id:Number,[callback:function,lv:Number default:-1])
```
### song
``` javascript
api.song(id:Number,[callback:function])
```
### getArtistAlbums
``` javascript
api.getArtistAlbums(id:Number,[callback:function,limit:Number default:3, offset:Number default:0])
```
## getAlbums
``` javascript
api.getAlbums(id:Number,[callback:function])
```
## getPlaylists
``` javascript
api.Playlists(id:Number,[callback:function])
```
![单元测试](https://raw.githubusercontent.com/Binaryify/NeteaseCloudMusicApi/master/static/screenshot1.png)
![单元测试](https://raw.githubusercontent.com/Binaryify/NeteaseCloudMusicApi/master/static/screenshot2.png)
## License
[The MIT License (MIT)](LICENSE)

39
app.js Normal file
View File

@ -0,0 +1,39 @@
const express = require('express')
const http = require('http')
const app = express()
//手机登录
app.use('/login/cellphone', require('./router/loginCellphone'))
app.use('/login', require('./router/login'))
// 获取每日推荐歌曲
app.use('/recommend/songs', require('./router/recommendSongs'))
// 获取每日推荐歌单
app.use('/recommend/resource', require('./router/recommendResource'))
app.use('/lyric', require('./router/lyric'))
app.use('/user/playlist', require('./router/userPlaylist'))
app.use('/playlist/detail', require('./router/playlistDetail'))
app.use('/playlist/tracks', require('./router/playlistTracks'))
// 获取音乐 url
app.use('/music/url', require('./router/musicUrl'))
// 搜歌
app.use('/search', require('.//router/search'))
app.use('/log/web', require('./router/logWeb'))
process.on('SIGHUP', () => {
console.log('server: bye bye')
process.exit()
})
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`server running @${port}`)
})
module.exports = app

View File

@ -1,28 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.api = undefined;
var _search = require('./component/search');
var _song = require('./component/song');
var _lrc = require('./component/lrc');
var _getArtistAlbums = require('./component/getArtistAlbums');
var _getAlbums = require('./component/getAlbums');
var _getPlaylists = require('./component/getPlaylists');
var api = {
search: _search.search,
song: _song.song,
lrc: _lrc.lrc,
getArtistAlbums: _getArtistAlbums.getArtistAlbums,
getAlbums: _getAlbums.getAlbums,
getPlaylists: _getPlaylists.getPlaylists
};
exports.api = api;

View File

@ -1,32 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getAlbums = undefined;
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _config = require('../config');
var _util = require('../util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var getAlbums = function getAlbums(id, callback) {
var option = (0, _util.deepCopy)(_config.globalOption);
var url = _config.origin + '/api/album/' + id;
var method = 'get';
Object.assign(option, { url: url, method: method });
(0, _request2.default)(option, function (err, res, body) {
if (!err && res.statusCode == 200) {
var info = JSON.parse(body);
callback && callback(JSON.stringify(info, '', 2));
} else {
console.error(err);
}
});
};
exports.getAlbums = getAlbums;

View File

@ -1,35 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getArtistAlbums = undefined;
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _config = require('../config');
var _util = require('../util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var getArtistAlbums = function getArtistAlbums(id, callback) {
var limit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 3;
var offset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var option = (0, _util.deepCopy)(_config.globalOption);
var url = _config.origin + '/api/artist/albums/' + id + '?offset=' + offset + '&limit=' + limit;
var method = 'GET';
Object.assign(option, { url: url, method: method });
(0, _request2.default)(option, function (err, res, body) {
if (!err && res.statusCode == 200) {
var info = JSON.parse(body);
callback && callback(JSON.stringify(info, '', 2));
} else {
console.error(err);
}
});
};
exports.getArtistAlbums = getArtistAlbums;

View File

@ -1,32 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getAlbums = undefined;
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _config = require('../config');
var _util = require('../util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var getPlaylists = function getPlaylists(id, callback) {
var option = (0, _util.deepCopy)(_config.globalOption);
var url = _config.origin + '/api/playlist/detail?id=' + id;
var method = 'get';
Object.assign(option, { url: url, method: method });
(0, _request2.default)(option, function (err, res, body) {
if (!err && res.statusCode == 200) {
var info = JSON.parse(body);
callback && callback(JSON.stringify(info, '', 2));
} else {
console.error(err);
}
});
};
exports.getPlaylists = getPlaylists;

View File

@ -1,32 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getPlaylists = undefined;
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _config = require('../config');
var _util = require('../util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var getPlaylists = function getPlaylists(id, callback) {
var option = (0, _util.deepCopy)(_config.globalOption);
var url = _config.origin + '/api/playlist/detail?id=' + id;
var method = 'get';
Object.assign(option, { url: url, method: method });
(0, _request2.default)(option, function (err, res, body) {
if (!err && res.statusCode == 200) {
var info = JSON.parse(body);
callback && callback(JSON.stringify(info, '', 2));
} else {
console.error(err);
}
});
};
exports.getPlaylists = getPlaylists;

View File

@ -1,35 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.lrc = undefined;
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _config = require('../config');
var _util = require('../util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var lrc = function lrc(id) {
var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var lv = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : -1;
var option = (0, _util.deepCopy)(_config.globalOption);
var url = _config.origin + '/api/song/lyric?lv=' + lv + '&id=' + id;
var method = 'GET';
Object.assign(option, { url: url, method: method });
(0, _request2.default)(option, function (err, res, body) {
if (!err && res.statusCode == 200) {
var info = JSON.parse(body);
callback && callback(JSON.stringify(info, '', 2));
} else {
console.error(err);
}
});
};
exports.lrc = lrc;

View File

@ -1,51 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.search = undefined;
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _config = require('../config');
var _util = require('../util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var search = function search() {
var name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var onlySong = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
var limit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 3;
var offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
var option = (0, _util.deepCopy)(_config.globalOption);
var url = _config.origin + '/api/search/get';
var form = {
s: name,
limit: limit,
type: 1,
offset: offset
};
var method = 'POST';
Object.assign(option, { url: url, form: form, method: method });
(0, _request2.default)(option, function (err, res, body) {
if (!err && res.statusCode == 200) {
var info = JSON.parse(body);
var data = void 0;
if (onlySong) {
data = info.result.songs;
} else {
data = { songs: info.result.songs, mvs: info.result.mvs };
}
callback && callback(JSON.stringify(data, '', 2));
} else {
console.error(err);
}
});
};
exports.search = search;

View File

@ -1,34 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.song = undefined;
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _config = require('../config');
var _util = require('../util');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var song = function song(id) {
var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var option = (0, _util.deepCopy)(_config.globalOption);
var url = _config.origin + '/api/song/detail?ids=%5B' + id + '%5d';
var method = 'GET';
Object.assign(option, { url: url, method: method });
(0, _request2.default)(option, function (err, res, body) {
if (!err && res.statusCode == 200) {
var info = JSON.parse(body);
callback && callback(JSON.stringify(info, '', 2));
} else {
console.error(err);
}
});
};
exports.song = song;

View File

@ -1,18 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var origin = 'http://music.163.com';
var globalOption = {
headers: {
'Origin': origin,
'Referer': origin,
'Content-Type': 'application/x-www-form-urlencoded'
},
proxy: false
};
exports.origin = origin;
exports.globalOption = globalOption;

View File

@ -1,10 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var deepCopy = function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
};
exports.deepCopy = deepCopy;

View File

@ -7,17 +7,18 @@
## 安装
``` shell
npm install NeteaseCloudMusicApi
$ git clone git@github.com:Binaryify/NeteaseCloudMusicApi.git
$ npm install
```
## 使用
## 运行
``` shell
node app.js
$ node app.js
```
服务器启动,默认端口为3000
## 接口
## 接口文档
### 登录
登录有两个接口
@ -89,6 +90,7 @@ node app.js
`/recommend/resource`
返回数据如下图:
![搜索音乐](https://raw.githubusercontent.com/Binaryify/NeteaseCloudMusicApi/master/static/%E6%8E%A8%E8%8D%90%E6%AD%8C%E5%8D%95.png)
### 获取每日推荐歌曲
说明:调用此接口,可获得每日推荐歌曲(需要登录)
接口地址:

View File

@ -1,20 +1,21 @@
{
"name": "NeteaseCloudMusicApi",
"version": "1.5.2",
"description": "网易云音乐nodejs版接口模块",
"main": "build/app.js",
"version": "2.0.0",
"description": "",
"scripts": {
"test": "node test/test.js",
"build": "babel src/ -d build/"
"start": "node app.js",
"test": "mocha -r intelli-espower-loader -t 20000 test"
},
"keywords": ["NeteaseCloudMusic","网易云音乐","网易云"],
"author": "traveller",
"license": "MIT",
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"request": "^2.72.0"
"big-integer": "^1.6.17",
"express": "^4.15.2"
},
"devDependencies": {
"babel-core": "^6.9.1",
"babel-preset-es2015": "^6.9.0"
"intelli-espower-loader": "^1.0.1",
"mocha": "^3.2.0",
"power-assert": "^1.4.2"
}
}
}

26
router/logWeb.js Normal file
View File

@ -0,0 +1,26 @@
const express = require("express")
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
const data = {
"action": req.query.action,
"json": req.query.json,
"csrf_token": "",
}
createWebAPIRequest(
'music.163.com',
'/weapi/log/web',
'POST',
data,
cookie,
music_req => res.send(music_req),
err => res.status(502).send('fetch error')
)
})
module.exports = router

34
router/login.js Normal file
View File

@ -0,0 +1,34 @@
const express = require("express")
const crypto = require('crypto')
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const email = req.query.email
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
const md5sum = crypto.createHash('md5')
md5sum.update(req.query.password)
const data = {
'username': email,
'password': md5sum.digest('hex'),
'rememberLogin': 'true'
}
createWebAPIRequest(
'music.163.com',
'/weapi/login',
'POST',
data,
cookie,
(music_req, cookie) => {
console.log(music_req)
res.set({
'Set-Cookie': cookie,
})
res.send(music_req)
},
err => res.status(502).send('fetch error')
)
})
module.exports = router

34
router/loginCellphone.js Normal file
View File

@ -0,0 +1,34 @@
const express = require("express")
const crypto = require('crypto')
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const phone = req.query.phone
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
const md5sum = crypto.createHash('md5')
md5sum.update(req.query.password)
const data = {
'phone': phone,
'password': md5sum.digest('hex'),
'rememberLogin': 'true'
}
createWebAPIRequest(
'music.163.com',
'/weapi/login/cellphone',
'POST',
data,
cookie,
(music_req, cookie) => {
console.log(music_req)
res.set({
'Set-Cookie': cookie,
})
res.send(music_req)
},
err => res.status(502).send('fetch error')
)
})
module.exports = router

18
router/lyric.js Normal file
View File

@ -0,0 +1,18 @@
const express = require("express")
const router = express()
const { createRequest } = require("../util/util")
router.get("/", (req, res) => {
const id = req.query.id
createRequest('/api/song/lyric?os=osx&id=' + id + '&lv=-1&kv=-1&tv=-1', 'GET', null)
.then(result => {
res.setHeader("Content-Type", "application/json")
res.send(result)
})
.catch(err => {
res.status(502).send('fetch error')
})
})
module.exports = router

31
router/musicUrl.js Normal file
View File

@ -0,0 +1,31 @@
const express = require("express")
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const id = parseInt(req.query.id)
const br = parseInt(req.query.br || 999000)
const data = {
"ids": [id],
"br": br,
"csrf_token": ""
}
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
createWebAPIRequest(
'music.163.com',
'/weapi/song/enhance/player/url',
'POST',
data,
cookie,
music_req => {
res.setHeader("Content-Type", "application/json")
res.send(music_req)
},
err => {
res.status(502).send('fetch error')
}
)
})
module.exports = router

68
router/playlistDetail.js Normal file
View File

@ -0,0 +1,68 @@
const http = require('http')
const express = require("express")
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
let detail, imgurl
const data = {
"id": req.query.id,
"offset": 0,
"total": true,
"limit": 1000,
"n": 1000,
"csrf_token": ""
}
createWebAPIRequest(
'music.163.com',
'/weapi/v3/playlist/detail',
'POST',
data,
cookie,
music_req=> {
console.log(music_req)
detail = music_req
mergeRes()
},
err =>{
res.status(502).send('fetch error')
}
)
// FIXME:i dont know the api to get coverimgurl
// so i get it by parsing html
const http_client = http.get({
hostname: 'music.163.com',
path: '/playlist?id=' + req.query.id,
headers: {
'Referer': 'http://music.163.com',
},
}, function (res) {
res.setEncoding('utf8')
let html = ''
res.on('data', function (chunk) {
html += chunk
})
res.on('end', function () {
console.log('end', html)
const regImgCover = /\<img src=\"(.*)\" class="j-img"/ig
imgurl = regImgCover.exec(html)[1]
mergeRes()
})
})
function mergeRes() {
if (imgurl != undefined && detail != undefined) {
detail = JSON.parse(detail)
detail.playlist.picUrl = imgurl
res.send(detail)
}
}
})
module.exports = router

30
router/playlistTracks.js Normal file
View File

@ -0,0 +1,30 @@
const express = require("express")
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const op = req.query.op
const pid = req.query.pid
const tracks = req.query.tracks
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
// console.log('COOKIESS', cookie)
const data = {
"op": op,
"pid": pid,
"tracks": tracks,
"trackIds": JSON.stringify([tracks]),
"csrf_token": "",
}
createWebAPIRequest(
'music.163.com',
'/weapi/playlist/manipulate/tracks',
'POST',
data,
cookie,
music_req => res.send(music_req),
err => res.status(502).send('fetch error')
)
})
module.exports = router

View File

@ -0,0 +1,24 @@
const express = require("express")
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
const data = {
"csrf_token": ""
}
createWebAPIRequest(
'music.163.com',
'/weapi/v1/discovery/recommend/resource',
'POST',
data,
cookie,
music_req => res.send(music_req),
err => res.status(502).send('fetch error')
)
})
module.exports = router

27
router/recommendSongs.js Normal file
View File

@ -0,0 +1,27 @@
const express = require("express")
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/",(req,res)=>{
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
const data = {
"offset": 0,
"total": true,
"limit": 20,
"csrf_token": ""
}
createWebAPIRequest(
'music.163.com',
'/weapi/v1/discovery/recommend/songs',
'POST',
data,
cookie,
music_req => res.send(music_req),
err => res.status(502).send('fetch error')
)
})
module.exports=router

20
router/search.js Normal file
View File

@ -0,0 +1,20 @@
const express = require("express")
const router = express()
const { createRequest } = require("../util/util")
router.get("/", (req, res) => {
const keywords = req.query.keywords
const type = req.query.type || 1
const limit = req.query.limit || 30
const data = 's=' + keywords + '&limit=' + limit + '&type=' + type + '&offset=0'
createRequest('/api/search/pc/', 'POST', data)
.then(result => {
res.setHeader("Content-Type", "application/json")
res.send(result)
})
.catch(err => {
res.status(502).send('fetch error')
})
})
module.exports = router

24
router/userPlaylist.js Normal file
View File

@ -0,0 +1,24 @@
const express = require("express")
const router = express()
const { createWebAPIRequest } = require("../util/util")
router.get("/", (req, res) => {
const cookie = req.get('Cookie') ? req.get('Cookie') : ''
const data = {
"offset": 0,
"uid": req.query.uid,
"limit": 1000,
"csrf_token": ""
}
createWebAPIRequest(
'music.163.com',
'/weapi/user/playlist',
'POST',
data,
cookie,
music_req => res.send(music_req),
err => res.status(502).send('fetch error')
)
})
module.exports = router

View File

@ -1,16 +0,0 @@
import { search } from './component/search'
import { song } from './component/song'
import { lrc } from './component/lrc'
import { getArtistAlbums } from './component/getArtistAlbums'
import { getAlbums } from './component/getAlbums'
import { getPlaylists } from './component/getPlaylists'
let api = {
search: search,
song: song,
lrc: lrc,
getArtistAlbums: getArtistAlbums,
getAlbums: getAlbums,
getPlaylists: getPlaylists
}
export {api}

View File

@ -1,19 +0,0 @@
import request from 'request'
import { origin, globalOption } from '../config'
import { deepCopy } from '../util'
const getAlbums = (id, callback) => {
const option = deepCopy(globalOption)
const url = `${origin}/api/album/${id}`
const method = 'get'
Object.assign(option, {url, method})
request(option, (err, res, body) => {
if(!err && res.statusCode == 200) {
let info = JSON.parse(body)
callback && callback(JSON.stringify(info, '', 2))
} else {
console.error(err)
}
})
}
export { getAlbums }

View File

@ -1,19 +0,0 @@
import request from 'request'
import { origin, globalOption } from '../config'
import { deepCopy } from '../util'
const getArtistAlbums = (id, callback, limit = 3, offset = 0) => {
const option = deepCopy(globalOption)
const url = `${origin}/api/artist/albums/${id}?offset=${offset}&limit=${limit}`
const method = 'GET'
Object.assign(option, {url, method})
request(option, (err, res, body) => {
if(!err && res.statusCode == 200) {
let info = JSON.parse(body)
callback && callback(JSON.stringify(info, '', 2))
} else {
console.error(err)
}
})
}
export { getArtistAlbums }

View File

@ -1,19 +0,0 @@
import request from 'request'
import { origin, globalOption } from '../config'
import { deepCopy } from '../util'
const getPlaylists = (id, callback) => {
const option = deepCopy(globalOption)
const url = `${origin}/api/playlist/detail?id=${id}`
const method = 'get'
Object.assign(option, {url, method})
request(option, (err, res, body) => {
if(!err && res.statusCode == 200) {
let info = JSON.parse(body)
callback && callback(JSON.stringify(info, '', 2))
} else {
console.error(err)
}
})
}
export { getPlaylists }

View File

@ -1,19 +0,0 @@
import request from 'request'
import { origin, globalOption } from '../config'
import { deepCopy } from '../util'
const lrc = (id, callback = null, lv = -1) => {
const option = deepCopy(globalOption)
const url = `${origin}/api/song/lyric?lv=${lv}&id=${id}`
const method = 'GET'
Object.assign(option, { url, method })
request(option, (err, res, body) => {
if (!err && res.statusCode == 200) {
let info = JSON.parse(body)
callback && callback(JSON.stringify(info, '', 2))
} else {
console.error(err)
}
})
}
export { lrc }

View File

@ -1,32 +0,0 @@
import request from 'request'
import { origin, globalOption } from '../config'
import { deepCopy } from '../util'
const search = (name = null, callback = null,onlySong=true, limit = 3, offset = 0) => {
const option = deepCopy(globalOption)
const url = `${origin}/api/search/get`
const form = {
s: name,
limit,
type: 1,
offset
}
const method = 'POST'
Object.assign(option, { url, form, method })
request(option, (err, res, body) => {
if (!err && res.statusCode == 200) {
let info = JSON.parse(body)
let data
if(onlySong){
data=info.result.songs
}else{
data={songs:info.result.songs,mvs:info.result.mvs}
}
callback&&callback(JSON.stringify(data,'',2))
} else {
console.error(err)
}
})
}
export { search }

View File

@ -1,19 +0,0 @@
import request from 'request'
import { origin, globalOption } from '../config'
import { deepCopy } from '../util'
const song = (id, callback = null) => {
const option = deepCopy(globalOption)
const url = `${origin}/api/song/detail?ids=%5B${id}%5d`
const method = 'GET'
Object.assign(option, { url, method })
request(option, (err, res, body) => {
if (!err && res.statusCode == 200) {
let info = JSON.parse(body);
callback && callback(JSON.stringify(info, '', 2))
} else {
console.error(err)
}
})
}
export { song }

View File

@ -1,12 +0,0 @@
const origin = 'http://music.163.com'
const globalOption = {
headers: {
'Origin': origin,
'Referer': origin,
'Content-Type': 'application/x-www-form-urlencoded'
},
proxy:false
}
export { origin, globalOption }

View File

@ -1,3 +0,0 @@
const deepCopy = obj => JSON.parse(JSON.stringify(obj))
export { deepCopy }

70
test/login.test.js Normal file
View File

@ -0,0 +1,70 @@
const assert = require('assert')
const crypto = require('crypto')
const { createWebAPIRequest } = require("../util/util")
describe('测试登录是否正常', () => {
it('手机登录 code 应该等于200', done => {
const phone = "换成你的手机号"
const password = "换成你的密码"
let cookie = ''
const md5sum = crypto.createHash('md5')
md5sum.update(password)
const data = {
'phone': phone,
'password': md5sum.digest('hex'),
'rememberLogin': 'true'
}
createWebAPIRequest(
'music.163.com',
'/weapi/login/cellphone',
'POST',
data,
cookie,
(music_req, cookie) => {
const result = JSON.parse(music_req)
console.log({
loginType: result.loginType,
code: result.code,
account: result.account
})
assert(result.code === 200)
done()
},
err => done(err)
)
})
it('邮箱登录 code 应该等于200', done => {
const email = "换成你的163网易邮箱"
const password = "换成你的密码"
const cookie = ''
const md5sum = crypto.createHash('md5')
md5sum.update(password)
const data = {
'username': email,
'password': md5sum.digest('hex'),
'rememberLogin': 'true'
}
createWebAPIRequest(
'music.163.com',
'/weapi/login',
'POST',
data,
cookie,
(music_req, cookie) => {
const result = JSON.parse(music_req)
console.log({
loginType: result.loginType,
code: result.code,
account: result.account
})
assert(result.code === 200)
done()
},
err => done(err)
)
})
})

20
test/lyric.test.js Normal file
View File

@ -0,0 +1,20 @@
const assert = require('assert')
const crypto = require('crypto')
const { createRequest } = require("../util/util")
const phone = "换成你的账号"
const password = "换成你的密码"
describe('测试获取歌词是否正常', () => {
it('数据应该有 lrc 字段', done => {
const id = 347230
createRequest('/api/song/lyric?os=osx&id=' + id + '&lv=-1&kv=-1&tv=-1', 'GET', null)
.then(result => {
console.log(JSON.parse(result).lrc)
assert(typeof JSON.parse(result).lrc!=='undefined')
done()
})
.catch(err => {
done(err)
})
})
})

34
test/musicUrl.test.js Normal file
View File

@ -0,0 +1,34 @@
const assert = require('assert')
const crypto = require('crypto')
const { createWebAPIRequest } = require("../util/util")
const phone = "换成你的账号"
const password = "换成你的密码"
describe('测试获取歌曲是否正常', () => {
it('歌曲的 url 不应该为空', done => {
const id = 347230
const br = 999000
const data = {
"ids": [id],
"br": br,
"csrf_token": ""
}
const cookie = ''
createWebAPIRequest(
'music.163.com',
'/weapi/song/enhance/player/url',
'POST',
data,
cookie,
music_req => {
console.log(JSON.parse(music_req).data[0].url)
assert(!!JSON.parse(music_req).data[0].url)
done()
},
err => {
done(err)
}
)
})
})

23
test/search.test.js Normal file
View File

@ -0,0 +1,23 @@
const assert = require('assert')
const crypto = require('crypto')
const { createRequest } = require("../util/util")
const phone = "换成你的账号"
const password = "换成你的密码"
describe('测试搜索是否正常', () => {
it('获取到的数据的 name 应该和搜索关键词一致', done => {
const keywords = "海阔天空"
const type = 1
const limit = 30
const data = 's=' + keywords + '&limit=' + limit + '&type=' + type + '&offset=0'
createRequest('/api/search/pc/', 'POST', data)
.then(result => {
console.log(JSON.parse(result).result.songs[0].mp3Url)
assert(JSON.parse(result).result.songs[0].name === '海阔天空')
done()
})
.catch(err => {
done(err)
})
})
})

View File

@ -1,34 +0,0 @@
// import { api } from '../src/app.js'
// const { api } =require('../src/app.js')
var api=require('../build/app.js').api
api.search("年度之歌",data => {
console.log("################Search API#################")
console.log(data)
})
api.song('308169',data => {
console.log("################Song API#################")
console.log(data)
})
api.lrc('5243023',data => {
console.log("################Lrc API#################")
console.log(data)
})
api.getArtistAlbums('9952', data => {
console.log('####################Artist Albums##############')
console.log(data)
})
api.getAlbums('32311', data => {
console.log("####################Albums####################")
console.log(data)
})
api.getPlaylists('311785002', data => {
console.log("####################Playlists####################")
console.log(data)
})

66
util/crypto.js Normal file
View File

@ -0,0 +1,66 @@
// 参考 https://github.com/darknessomi/musicbox/wiki/
'use strict'
const crypto = require('crypto')
const bigInt = require('big-integer')
const modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
const nonce = '0CoJUm6Qyw8W8jud'
const pubKey = '010001'
String.prototype.hexEncode = function(){
let hex, i
let result = ""
for (i=0; i<this.length; i++) {
hex = this.charCodeAt(i).toString(16)
result += (""+hex).slice(-4)
}
return result
}
function createSecretKey(size) {
const keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let key = ""
for (let i = 0; i < size; i++) {
let pos = Math.random() * keys.length
pos = Math.floor(pos)
key = key + keys.charAt(pos)
}
return key
}
function aesEncrypt(text, secKey) {
const _text = text
const lv = new Buffer('0102030405060708', "binary")
const _secKey = new Buffer(secKey, "binary")
const cipher = crypto.createCipheriv('AES-128-CBC', _secKey, lv)
let encrypted = cipher.update(_text, 'utf8', 'base64')
encrypted += cipher.final('base64')
return encrypted
}
function zfill(str, size) {
while (str.length < size) str = "0" + str
return str
}
function rsaEncrypt(text, pubKey, modulus) {
const _text = text.split('').reverse().join('')
const biText = bigInt(new Buffer(_text).toString('hex'), 16),
biEx = bigInt(pubKey, 16),
biMod = bigInt(modulus, 16),
biRet = biText.modPow(biEx, biMod)
return zfill(biRet.toString(16), 256)
}
function Encrypt(obj) {
const text = JSON.stringify(obj)
const secKey = createSecretKey(16)
const encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
const encSecKey = rsaEncrypt(secKey, pubKey, modulus)
return {
params: encText,
encSecKey: encSecKey
}
}
module.exports = Encrypt

83
util/util.js Normal file
View File

@ -0,0 +1,83 @@
const Encrypt = require('./crypto.js')
const http = require('http')
function createWebAPIRequest(host, path, method, data, cookie, callback, errorcallback) {
let music_req = ''
const cryptoreq = Encrypt(data)
const http_client = http.request({
hostname: host,
method: method,
path: path,
headers: {
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': 'http://music.163.com',
'Host': 'music.163.com',
'Cookie': cookie,
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36',
},
}, function (res) {
res.on('error', function (err) {
errorcallback(err)
})
res.setEncoding('utf8')
if (res.statusCode != 200) {
createWebAPIRequest(host, path, method, data, cookie, callback)
return
} else {
res.on('data', function (chunk) {
music_req += chunk
})
res.on('end', function () {
if (music_req == '') {
createWebAPIRequest(host, path, method, data, cookie, callback)
return
}
if (res.headers['set-cookie']) {
callback(music_req, res.headers['set-cookie'])
} else {
callback(music_req)
}
})
}
})
http_client.write('params=' + cryptoreq.params + '&encSecKey=' + cryptoreq.encSecKey)
http_client.end()
}
function createRequest(path, method, data, callback, errorcallback) {
return new Promise((resolve, reject) => {
let ne_req = ''
const http_client = http.request({
hostname: 'music.163.com',
method: method,
path: path,
headers: {
'Referer': 'http://music.163.com',
'Cookie': 'appver=2.0.2',
'Content-Type': 'application/x-www-form-urlencoded',
},
}, res => {
res.setEncoding('utf8')
res.on('error', err => {
reject(err)
})
res.on('data', chunk => {
ne_req += chunk
})
res.on('end', () => {
resolve(ne_req)
})
})
if (method == 'POST') {
http_client.write(data)
}
http_client.end()
})
}
module.exports = {
createWebAPIRequest,
createRequest
}