diff --git a/app.js b/app.js index 4b47bf8d8a166cc7bc28f25ba4863fabdb088f8d..f2f5cdc54415dc7f671788877b9036a44f72cc00 100644 --- a/app.js +++ b/app.js @@ -45,11 +45,20 @@ app.use(express.static(path.resolve(__dirname, 'public'))) app.use(function(req, res, next) { const proxy = req.query.proxy if (proxy) { - req.headers.cookie = req.headers.cookie + `__proxy__${proxy}` + req.headers.cookie += `__proxy__${proxy}` } next() }) +// 补全缺失的cookie +const { completeCookie } = require('./util/init') +app.use(function(req, res, next) { + let cookie = completeCookie(req.headers.cookie) + req.headers.cookie = cookie.map(x => x[0]).concat(req.headers.cookie || []).join('; ') + res.append('Set-Cookie', cookie.map(x => (x.concat('Path=/').join('; ')))) + next() +}) + // 因为这几个文件对外所注册的路由 和 其他文件对外注册的路由规则不一样, 所以专门写个MAP对这些文件做特殊处理 const UnusualRouteFileMap = { // key 为文件名, value 为对外注册的路由 @@ -80,9 +89,6 @@ fs.readdirSync(path.resolve(__dirname, 'router')) file .replace(/\.js$/i, '') .replace(/_/g, '/') - .replace(/[A-Z]/g, a => { - return '/' + a.toLowerCase() - }) } app.use(route, Wrap(require('./router/' + file))) @@ -90,7 +96,7 @@ fs.readdirSync(path.resolve(__dirname, 'router')) const port = process.env.PORT || 3000 -app.listen(port, () => { +app.server = app.listen(port, () => { console.log(`server running @ http://localhost:${port}`) }) diff --git a/app.test.js b/app.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2aa54093c1320c3228d69976d39bb5444f052184 --- /dev/null +++ b/app.test.js @@ -0,0 +1,16 @@ +const fs = require('fs') +const path = require('path') + +let app +before(() => { + app = require('./app.js') + global.host = 'http://localhost:' + app.server.address().port +}) +after((done) => { + app.server.close(done) +}) + +fs.readdirSync(path.resolve(__dirname, 'test')) +.forEach(file => { + require('./test/' + file) +}) \ No newline at end of file diff --git a/package.json b/package.json index 63bcdd3d872b91147ddb01f459c4eb9edfc0d890..f97631f6d6cdb8e7a9c1133c1914d5b529efbbea 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "网易云音乐 NodeJS 版 API", "scripts": { "start": "node app.js", - "test": "mocha -r intelli-espower-loader -t 20000 test" + "test": "mocha -r intelli-espower-loader -t 20000 app.test.js --exit" }, "keywords": [ "网易云音乐", diff --git a/router/banner.js b/router/banner.js index 3f6c7fd15159bc61293357f129ff4b266cfd047d..d54c39d67f5e06f6ac0f3897bbc9c337e2cc7b7f 100644 --- a/router/banner.js +++ b/router/banner.js @@ -3,9 +3,8 @@ module.exports = (req, res, createWebAPIRequest, request) => { url: "http://music.163.com/discover", method: "GET", headers: { - Referer: "http://music.163.com", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3380.0 Safari/537.36" + "Referer": "http://music.163.com", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3380.0 Safari/537.36" } }; request(options, (error, response, body) => { @@ -13,8 +12,7 @@ module.exports = (req, res, createWebAPIRequest, request) => { res.status(502).send("fetch error"); } else { try { - const pattern = /]*>\s*window\.Gbanners\s*=\s*([^;]+?);\s*<\/script>/g; - const banners = pattern.exec(body)[1]; + const banners = /Gbanners\s*=\s*([^;]+);/.exec(body)[1]; res.send(JSON.stringify(eval(`({code:200,banners:${banners}})`))); } catch (error) { res.status(502).send("fetch error"); diff --git a/router/check_music.js b/router/check_music.js index e3d5c4caf6e713df7f6f5404871f91685a1069f2..76e4c3f4bf57ab6f8c30960c4409515299188bbb 100644 --- a/router/check_music.js +++ b/router/check_music.js @@ -15,8 +15,11 @@ module.exports = (req, res, createWebAPIRequest, request) => { data, cookie, music_req => { - if (JSON.parse(music_req).code == 200) { - return res.send({ success: true, message: 'ok' }) + music_req = JSON.parse(music_req) + if (music_req.code == 200) { + if (music_req.data[0].code == 200){ + return res.send({ success: true, message: 'ok' }) + } } return res.send({ success: false, message: '亲爱的,暂无版权' }) }, diff --git a/router/login.js b/router/login.js index ac914ef90b1b092495ce6c0c45dcfee0e9289b65..3fea72ac4ceca0f70cd3a937fcf68a4373e1db28 100644 --- a/router/login.js +++ b/router/login.js @@ -21,10 +21,7 @@ module.exports = (req, res, createWebAPIRequest, request) => { cookie, (music_req, cookie) => { // console.log(music_req) - cookie = cookie && cookie.map(x => x.replace('Domain=.music.163.com', '')) - res.set({ - 'Set-Cookie': cookie - }) + res.append("Set-Cookie", cookie) res.send(music_req) }, err => res.status(502).send('fetch error') diff --git a/router/loginCellphone.js b/router/login_cellphone.js similarity index 63% rename from router/loginCellphone.js rename to router/login_cellphone.js index ab6dffb4dbb149ded3fe5dac17f234349c8654ce..89729c6bef0e026a30ebcdcb7d1caa88c1aa6c5b 100644 --- a/router/loginCellphone.js +++ b/router/login_cellphone.js @@ -17,15 +17,8 @@ module.exports = (req, res, createWebAPIRequest, request) => { "POST", data, cookie, - (music_req, cookie = []) => { - const cookieStr = - "appver=1.5.9;os=osx; channel=netease;osver=%E7%89%88%E6%9C%AC%2010.13.2%EF%BC%88%E7%89%88%E5%8F%B7%2017C88%EF%BC%89"; - cookieStr.split(";").forEach(item => { - cookie.push(item + ";Path=/"); - }); - res.set({ - "Set-Cookie": cookie - }); + (music_req, cookie) => { + res.append("Set-Cookie", cookie); res.send(music_req); }, err => res.status(502).send("fetch error") diff --git a/router/login_refresh.js b/router/login_refresh.js index 3e71cfeae50d7621aab8c117925da1588e7002d2..4aac6970a959fa1f17aa55fd7ecf668a29d6974c 100644 --- a/router/login_refresh.js +++ b/router/login_refresh.js @@ -11,9 +11,7 @@ module.exports = (req, res, createWebAPIRequest, request) => { data, cookie, (music_req, cookie) => { - res.set({ - "Set-Cookie": cookie - }); + res.append("Set-Cookie", cookie); res.send(music_req); }, err => res.status(502).send("fetch error") diff --git a/router/login_status.js b/router/login_status.js index 4ad4919b00925e7d001e456187f932c4f61cc306..7c46e618f0d14de92bac250178c0e213458138c4 100644 --- a/router/login_status.js +++ b/router/login_status.js @@ -7,14 +7,13 @@ module.exports = (req, res, createWebAPIRequest, request) => { "GET", {}, cookie, - (music_req, cookie) => { + music_req => { try { - var userInfo = (/var GUser=([^;]+);/g).exec(music_req)[1]; - var bindInfo = (/var GBinds=([^;]+);/g).exec(music_req)[1]; - userInfo = eval(`(${userInfo})`); - userInfo.userBind = eval(`(${bindInfo})`); - userInfo.userBind.forEach((item) => {item.tokenJsonStr = JSON.parse(item.tokenJsonStr)}); - res.send(userInfo); + var profile = /GUser\s*=\s*([^;]+);/.exec(music_req)[1]; + var bindings = /GBinds\s*=\s*([^;]+);/.exec(music_req)[1]; + profile = eval(`(${profile})`); + bindings = eval(`(${bindings})`); + res.send({code: 200, profile: profile, bindings: bindings}); } catch (error) { res.status(502).send("fetch error"); } diff --git a/router/logout.js b/router/logout.js new file mode 100644 index 0000000000000000000000000000000000000000..b5e5a748e8057be2a8975140a66aad4ab7dce2a4 --- /dev/null +++ b/router/logout.js @@ -0,0 +1,16 @@ +//登出 +module.exports = (req, res, createWebAPIRequest, request) => { + const cookie = req.get('Cookie') ? req.get('Cookie') : '' + createWebAPIRequest( + 'music.163.com', + '/weapi/logout', + 'POST', + {}, + cookie, + (music_req, cookie) => { + res.append("Set-Cookie", cookie) + res.send(music_req) + }, + err => res.status(502).send('fetch error') + ) +} diff --git a/router/musicUrl.js b/router/music_url.js similarity index 100% rename from router/musicUrl.js rename to router/music_url.js diff --git a/router/logWeb.js b/router/weblog.js similarity index 100% rename from router/logWeb.js rename to router/weblog.js diff --git a/test/album.test.js b/test/album.test.js index 8a811e036d0a756224d1cd58d28363ee1ea252af..b2253fe23f970dea1c30580f7e0d08179313e2c2 100644 --- a/test/album.test.js +++ b/test/album.test.js @@ -1,17 +1,22 @@ const assert = require('assert') -const crypto = require('crypto') -const { createRequest } = require('../util/util') +const request = require('request') +host = global.host || 'http://localhost:3000' describe('测试获取歌手专辑列表是否正常', () => { it('数据的 code 应该为200', done => { - const id = 32311 - createRequest(`/api/album/${id}`, 'GET', null) - .then(result => { - const code = JSON.parse(result).code - console.log('code:' + code) - assert(code === 200) + const qs = { + id: 32311 + } + + request.get({url: `${host}/album`,qs: qs}, (err, res, body) => { + if (!err && res.statusCode == 200) { + body = JSON.parse(body) + assert(body.code === 200) done() - }) - .catch(err => done(err)) + } + else{ + done(err) + } + }) }) }) diff --git a/test/comment.test.js b/test/comment.test.js index 39e586ded15b74d1f464f305b0156d13d32da1c5..bbf4ba77aa13e1147859a06865d8e92a3a19b423 100644 --- a/test/comment.test.js +++ b/test/comment.test.js @@ -1,31 +1,22 @@ const assert = require('assert') -const crypto = require('crypto') -const { createWebAPIRequest } = require('../util/util') +const request = require('request') +host = global.host || 'http://localhost:3000' describe('测试获取评论是否正常', () => { it('数据的 code 应该为200', done => { - const rid = 32311 - const cookie = '' - const data = { - offset: 0, - rid: rid, - limit: 20, - csrf_token: '' + const qs = { + id: 32311 } - createWebAPIRequest( - 'music.163.com', - `/weapi/v1/resource/comments/R_SO_4_${rid}/?csrf_token=`, - 'POST', - data, - cookie, - music_req => { - console.log({ - code: JSON.parse(music_req).code - }) - assert(JSON.parse(music_req).code === 200) + + request.get({url: `${host}/comment/album`,qs: qs}, (err, res, body) => { + if (!err && res.statusCode == 200) { + body = JSON.parse(body) + assert(body.code === 200) done() - }, - err => done(err) - ) + } + else{ + done(err) + } + }) }) }) diff --git a/test/login.test.js b/test/login.test.js index a001af6e0b7c5953d182b0b19226bd33dbdeb9e5..349b9e339115fad17b0fbe4468a35dad8953e18d 100644 --- a/test/login.test.js +++ b/test/login.test.js @@ -1,39 +1,25 @@ -const assert = require("assert"); -const crypto = require("crypto"); -const { createWebAPIRequest } = require("../util/util"); +const assert = require('assert') +const request = require('request') +host = global.host || 'http://localhost:3000' -console.log("注意:测试登陆需要替换这里的账号密码!!!"); +console.log("注意: 测试登录需在 test/login.test.js 中填写账号密码!!!"); describe("测试登录是否正常", () => { it("手机登录 code 应该等于200", done => { - const phone = "换成你的手机号"; - const password = "换成你的密码"; - let cookie = ""; - const md5sum = crypto.createHash("md5"); - md5sum.update(password); - const data = { + const qs = { phone: phone, - password: md5sum.digest("hex"), - rememberLogin: "true" - }; + password: password + } - 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) - ); - }); -}); + request.get({url: `${host}/login/cellphone`,qs: qs}, (err, res, body) => { + if (!err && res.statusCode == 200) { + body = JSON.parse(body) + assert(body.code === 200) + done() + } + else{ + done(err) + } + }) + }) +}) diff --git a/test/lyric.test.js b/test/lyric.test.js index 128d86b788c64440b0cbf9dbb6d904fdc60967e1..491113e2fb2b862e6427245527314172c25e5af4 100644 --- a/test/lyric.test.js +++ b/test/lyric.test.js @@ -1,25 +1,22 @@ -const assert = require("assert"); -const crypto = require("crypto"); -const { createWebAPIRequest } = require("../util/util"); +const assert = require('assert') +const request = require('request') +host = global.host || 'http://localhost:3000' describe("测试获取歌词是否正常", () => { it("数据应该有 lrc 字段", done => { - const cookie = ""; - const data = {}; - const id = 347230; - createWebAPIRequest( - "music.163.com", - "/weapi/song/lyric?os=osx&id=" + id + "&lv=-1&kv=-1&tv=-1", - "POST", - data, - cookie, - music_req => { - console.log(music_req); - assert(typeof JSON.parse(music_req).lrc !== "undefined"); - done(); - // res.send(music_req) - }, - err => res.status(502).send("fetch error") - ); - }); -}); + const qs = { + id: 347230 + } + + request.get({url: `${host}/lyric`,qs: qs}, (err, res, body) => { + if (!err && res.statusCode == 200) { + body = JSON.parse(body) + assert(typeof body.lrc !== "undefined") + done() + } + else{ + done(err) + } + }) + }) +}) diff --git a/test/musicUrl.test.js b/test/musicUrl.test.js deleted file mode 100644 index bd4528657dd53a8a72bfef41cb0828638d8f31c9..0000000000000000000000000000000000000000 --- a/test/musicUrl.test.js +++ /dev/null @@ -1,33 +0,0 @@ -const assert = require("assert"); -const crypto = require("crypto"); -const { createWebAPIRequest } = require("../util/util"); - -describe("测试获取歌曲是否正常", () => { - it("歌曲的 url 不应该为空", done => { - const id = 462791935; - 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(music_req); - console.log(JSON.parse(music_req).data[0].url); - assert(!!JSON.parse(music_req).data[0].url); - done(); - }, - err => { - done(err); - } - ); - }); -}); diff --git a/test/music_url.test.js b/test/music_url.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c0fccb4e83c27138c28525e9364549a52701fb65 --- /dev/null +++ b/test/music_url.test.js @@ -0,0 +1,23 @@ +const assert = require('assert') +const request = require('request') +host = global.host || 'http://localhost:3000' + +describe("测试获取歌曲是否正常", () => { + it("歌曲的 url 不应该为空", done => { + const qs = { + id: 462791935, + br: 999000 + } + + request.get({url: `${host}/music/url`,qs: qs}, (err, res, body) => { + if (!err && res.statusCode == 200) { + body = JSON.parse(body) + assert(!!body.data[0].url) + done() + } + else{ + done(err) + } + }) + }); +}); diff --git a/test/search.test.js b/test/search.test.js index b9c280c6185fa86a10c23b331c11c0243e07d659..4f92b5ad8c292ecf868e0fdf1a90596f36ae1246 100644 --- a/test/search.test.js +++ b/test/search.test.js @@ -1,22 +1,22 @@ const assert = require('assert') -const crypto = require('crypto') -const { createRequest } = require('../util/util') +const request = require('request') +host = global.host || 'http://localhost:3000' 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 === '海阔天空') + const qs = { + keywords: '海阔天空', + type: 1 + } + request.get({url: `${host}/search`,qs: qs}, (err, res, body) => { + if (!err && res.statusCode == 200) { + body = JSON.parse(body) + assert(body.result.songs[0].name === '海阔天空') done() - }) - .catch(err => { + } + else{ done(err) - }) + } + }) }) }) diff --git a/util/init.js b/util/init.js index 48c145984dc02d8f39df910e7f169216fd9fab94..bf36199f418408176eaec4ac5ecb4ac4002a7229 100644 --- a/util/init.js +++ b/util/init.js @@ -1,8 +1,30 @@ function randomString(pattern, length){ - return Array.apply(null, {length: length}).map(() => (pattern[Math.floor(Math.random() * pattern.length)])).join(''); + return Array.apply(null, {length: length}).map(() => (pattern[Math.floor(Math.random() * pattern.length)])).join('') } -const jsessionid = randomString('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ\\/+',176) + ':' + (new Date).getTime(); -const nuid = randomString('0123456789abcdefghijklmnopqrstuvwxyz',32); +function completeCookie(cookie){ + let origin = (cookie || '').split(/;\s*/).map(element => (element.split('=')[0])), extra = [] + let now = (new Date).getTime() -module.exports = `JSESSIONID-WYYY=${jsessionid}; _iuqxldmzr_=32; _ntes_nnid=${nuid},${(new Date).getTime()}; _ntes_nuid=${nuid}`; \ No newline at end of file + if(!origin.includes('JSESSIONID-WYYY')){ + let expire = new Date(now + 1800000) //30 minutes + let jessionid = randomString('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ\\/+',176) + ':' + expire.getTime() + extra.push(['JSESSIONID-WYYY=' + jessionid, 'Expires=' + expire.toGMTString()]) + } + if(!origin.includes('_iuqxldmzr_')){ + let expire = new Date(now + 157680000000) //5 years + extra.push(['_iuqxldmzr_=32', 'Expires=' + expire.toGMTString()]) + } + if((!origin.includes('_ntes_nnid'))||(!origin.includes('_ntes_nuid'))){ + let expire = new Date(now + 3153600000000) //100 years + let nnid = randomString('0123456789abcdefghijklmnopqrstuvwxyz',32) + ',' + now + extra.push(['_ntes_nnid=' + nnid, 'Expires=' + expire.toGMTString()]) + extra.push(['_ntes_nuid=' + nnid.slice(0,32), 'Expires=' + expire.toGMTString()]) + } + + return extra +} + +module.exports = { + completeCookie +} \ No newline at end of file diff --git a/util/util.js b/util/util.js index a789bf39d1bb81ad9ca596a2ff691817d099da68..65243d342d03615a59d4dd2f17cbd0ac27c82c94 100644 --- a/util/util.js +++ b/util/util.js @@ -1,7 +1,6 @@ const Encrypt = require("./crypto.js"); const request = require("request"); -const querystring = require("querystring"); -const baseCookie = require("./init.js"); +const queryString = require("querystring"); request.debug = true; @@ -38,31 +37,34 @@ function createWebAPIRequest( data, cookie, callback, - errorcallback + errorCallback ) { - // console.log(cookie); - if (cookie.match(/_csrf=[^(;|$)]+/g)) - data.csrf_token = cookie.match(/_csrf=[^(;|$)]+/g)[0].slice(6); - else data.csrf_token = ""; + const csrfToken = cookie.match(/_csrf=([^(;|$)]+)/); + if (csrfToken) + data.csrf_token = csrfToken[1]; + else + data.csrf_token = ""; + const proxy = cookie.split("__proxy__")[1]; cookie = cookie.split("__proxy__")[0]; - const cryptoreq = Encrypt(data); + + const encryptedData = Encrypt(data); const options = { url: `http://${host}${path}`, method: method, headers: { - Accept: "*/*", + "Accept": "*/*", "Accept-Language": "zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4", - Connection: "keep-alive", + "Connection": "keep-alive", "Content-Type": "application/x-www-form-urlencoded", - Referer: "http://music.163.com", - Host: "music.163.com", - Cookie: baseCookie + (cookie ? "; " : "") + cookie, + "Referer": "http://music.163.com", + "Host": "music.163.com", + "Cookie": cookie, "User-Agent": randomUserAgent() }, - body: querystring.stringify({ - params: cryptoreq.params, - encSecKey: cryptoreq.encSecKey + body: queryString.stringify({ + params: encryptedData.params, + encSecKey: encryptedData.encSecKey }), proxy: proxy }; @@ -73,49 +75,16 @@ function createWebAPIRequest( request(options, function(error, res, body) { if (error) { console.error(error); - errorcallback(error); + errorCallback(error); } else { - //解决 网易云 cookie 添加 .music.163.com 域设置。 - //如: Domain=.music.163.com - let cookie = res.headers["set-cookie"]; - if (Array.isArray(cookie)) { - cookie = cookie - .map(x => x.replace(/.music.163.com/g, "")) - .sort((a, b) => a.length - b.length); - } + let cookie = res.headers["set-cookie"] || []; + cookie = cookie.map(x => x.replace(/\s*Domain=[^(;|$)]+;*/, "")); callback(body, cookie); } }); } -function createRequest(path, method, data) { - return new Promise((resolve, reject) => { - const options = { - url: `http://music.163.com${path}`, - method: method, - headers: { - Referer: "http://music.163.com", - Cookie: "appver=1.5.2", - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": randomUserAgent() - } - }; - - if (method.toLowerCase() === "post") { - options.body = data; - } - - request(options, function(error, res, body) { - if (error) { - reject(error); - } else { - resolve(body); - } - }); - }); -} module.exports = { request, - createWebAPIRequest, - createRequest + createWebAPIRequest };