├── .gitignore ├── README.md ├── bird-auth.js ├── index.js ├── lib ├── baidu │ ├── passport.js │ └── uuap.js ├── httpClient.js └── netease │ ├── crypto.js │ └── music.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ 3 | .idea/ 4 | /code.png 5 | 6 | package-lock.json 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bird-auth 2 | 3 | [![NPM Version][npm-image]][npm-url] 4 | ![NODE Version][node-image] 5 | ![OSX Build][osx-image] 6 | ![LINUX Build][liunx-image] 7 | 8 | 解决 [birdv1](https://github.com/weger/bird) 版本手动取cookie问题, 支持网易云音乐、百度认证。 9 | 10 | ## Install 11 | 12 | `npm install --save-dev bird-auth` 13 | 14 | ## API 15 | 16 | ### birdAuth 17 | 18 | | name | api | instance function | 19 | | :----- | :----- | :----- | 20 | | `client` | `client[fn]` | not support | 21 | | `baidu.uuap` | `birdAuth.baidu.uuap(options[, callback])` | `retry`, `getCookie` | 22 | | `baidu.passport`| `birdAuth.baidu.passport(options[, callback])` | `getCookie` | 23 | | `netease.music`| `birdAuth.netease.music(options[, callback])` | `retry`, `getCookie`, `aesEncrypt` | 24 | 25 | 26 | ### client 27 | 28 | | method | demo | detail | 29 | | :----- | :-- | :----- | 30 | | `url_get` | `client.url_get(url, callback)` | get method(no cookie) | 31 | | `get` | `client.get(url, callback)` | get method(with cookie) | 32 | | `post` | `client.post(options, callback)` | post method(with cookie) | 33 | | `get_cookies_string` | `client.get_cookies_string()` | get all cookies | 34 | | `update_cookies` | `client.update_cookies(cookies, true)` | update new cookies | 35 | | `set_cookies` | `client.set_cookies(cookies, true)` | set new cookies | 36 | | `clear_cookies` | `client.clear_cookies()` | remove all cookies | 37 | 38 | 39 | ## Command Line 40 | 41 | usage: `bird -u xxx -p xxx -t netseae_music` 42 | 43 | ```bash 44 | Options: 45 | -h, --help Show help [boolean] 46 | -t, --type baidu_uuap, baidu_passport, netease_music [default: "baidu_uuap"] 47 | -u, --username username [required] 48 | -p, --password password [required] 49 | -s, --server server(baidu_uuap need it), if you don't know this, you can logout you system and get url. 50 | ``` 51 | 52 | ## Examples 53 | 54 | #### baidu uuap auth 55 | 56 | ```javascript 57 | const birdAuth = require('bird-auth') 58 | const uuap = new birdAuth.baidu.uuap({ 59 | username: 'xxx', 60 | password: 'xxx', 61 | type: 3, // default 1 is username and password; 3 is username and verification code. 62 | uuapServer: 'http://xxx.baidu.com/login', // CAS auth url 63 | service: 'http://xxx.baidu.com/' // service address, if you don't know this url, you can logout you system, and get `service` parameters 64 | }, function(cookie) { 65 | console.log(cookie) 66 | }); 67 | 68 | uuap.retry({ 69 | username: 'xxx', 70 | password: 'xxx', 71 | uuapServer: 'http://xxx.baidu.com/login', 72 | service: 'http://xxx.baidu.com/' 73 | }); 74 | ``` 75 | 76 | #### baidu passport auth 77 | 78 | ```javascript 79 | const birdAuth = require('bird-auth') 80 | const passport = new birdAuth.baidu.passport({ 81 | username: 'xxx', 82 | password: 'xxx', 83 | service: 'https://passport.baidu.com/v2/?login' //default passport.baidu.com 84 | }, function(cookie) { 85 | console.log(cookie) 86 | }); 87 | ``` 88 | 89 | #### netease music auth 90 | 91 | ```javascript 92 | const birdAuth = require('bird-auth') 93 | const music = new birdAuth.netease.music({ 94 | username: 'xxx', // phone number or mail 95 | password: 'xxx' 96 | }, function(cookie) { 97 | console.log(cookie) 98 | }); 99 | ``` 100 | 101 | ## History 102 | 103 | - [2.5.0] remove `querystring` package. 104 | - [2.4.1] fix url check method. 105 | - [2.4.0] switch login to authorize. 106 | - [2.3.1] add type option to auth parameter. 107 | - [2.3.0] fix token verification mechanism. 108 | - [2.2.1] modify parameter naming. 109 | - [2.2.0] change login to authorize. 110 | - [2.1.0] remove service params. 111 | - [2.0.0] refactor & update auth. 112 | - [1.2.8] add auth rsa check. 113 | - [1.2.7] add `client.set_cookies & client.clear_cookies` method. 114 | - [1.2.5] add `client.update_cookies` method. 115 | - [1.2.4] fix passport test(qatest/rdtest) auth bug. 116 | - [1.2.0] group auth and add netease music auth. 117 | - [1.1.10] add httpClient Content-Type adjust. 118 | - [1.1.9] fix passport agent. 119 | - [1.1.6] Fixup 302 response location is not a normal url 😂 120 | - [1.1.3] Custom agent to fix https authorized bug. :( 121 | - [1.1.0] Add `bird-auth` command, you can use `bird-auth -h` to see more :) 122 | - [1.0.6] Fixed get_cookies_string bug 123 | - [1.0.5] Fixed Syntax Error 124 | - [1.0.4] Fixed passport auth bugfix 125 | - [1.0.3] Project init 126 | 127 | [npm-image]: https://img.shields.io/badge/npm-v5.3.6-blue.svg 128 | [npm-url]: https://npmjs.org/package/bird-auth 129 | [node-image]: https://img.shields.io/badge/node-v10.0.0%2B-yellow.svg 130 | [osx-image]: https://img.shields.io/badge/OSX-passing-brightgreen.svg 131 | [liunx-image]: https://img.shields.io/badge/Liunx-passing-brightgreen.svg 132 | 133 | ## Future 134 | - refactor with typescript. 135 | - optimize function, remove useless code. 136 | - Change account and get cookie afresh 137 | - Support Https 138 | - Support online Passport auth 139 | - Set rejectUnauthorized false and fix uuap auth bug. [detail](http://stackoverflow.com/questions/20082893/unable-to-verify-leaf-signature) 140 | - Add bprouting support 141 | - statusCode === 302 judgment -------------------------------------------------------------------------------- /bird-auth.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const argv = require('yargs') 4 | .help('h') 5 | .alias('h', 'help') 6 | .alias('t', 'type') 7 | .default('t', 'baidu_uuap') 8 | .describe('t', 'baidu_uuap, baidu_passport, netease_music') 9 | .alias('u', 'username') 10 | .demand('u', true) 11 | .describe('u', 'username') 12 | .alias('p', 'password') 13 | .demand('p', true) 14 | .describe('p', 'password') 15 | .alias('s', 'server') 16 | .describe('s', "server(baidu_uuap need it), if you don't know this, you can logout you system and get url.") 17 | .argv; 18 | 19 | const baiduUuap = require('./lib/baidu/uuap.js'); 20 | const baiduPassport = require('./lib/baidu/passport.js'); 21 | const neteaseMusic = require('./lib/netease/music.js'); 22 | 23 | const CALLBACK = function (cookie) { 24 | console.log(cookie); 25 | return cookie; 26 | }; 27 | 28 | switch (argv.t) { 29 | case 'baidu_uuap': 30 | baiduUuap({ 31 | username: argv.u, 32 | password: argv.p, 33 | server: argv.s 34 | }, CALLBACK); 35 | break; 36 | case 'baidu_passport': 37 | baiduPassport({ 38 | username: argv.u, 39 | password: argv.p, 40 | server: argv.s 41 | }, CALLBACK); 42 | break; 43 | case 'netease_music': 44 | neteaseMusic({ 45 | username: argv.u, 46 | password: argv.p 47 | }, CALLBACK); 48 | break; 49 | default: 50 | break; 51 | } 52 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file bird-auth > index 3 | */ 4 | 5 | const uuap = require('./lib/baidu/uuap.js'); 6 | const passport = require('./lib/baidu/passport.js'); 7 | const birdNetease = require('./lib/netease/music.js'); 8 | const client = require('./lib/httpClient.js'); 9 | 10 | module.exports = { 11 | uuap, 12 | passport, 13 | baidu: { 14 | uuap, 15 | passport 16 | }, 17 | netease: { 18 | music: birdNetease 19 | }, 20 | client 21 | }; 22 | -------------------------------------------------------------------------------- /lib/baidu/passport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * baidu passport auth 3 | * get token 4 | * first post 5 | * if need to verify, to get verify code and repost 6 | */ 7 | 8 | 'use strict' 9 | 10 | var fs = require('fs'); 11 | var URL = require('url'); 12 | var readline = require('readline'); 13 | 14 | var client = require('./../httpClient'); 15 | 16 | var rl; 17 | 18 | var options = { 19 | // sign_url: options.server + '/v2/api/?login' 20 | }; 21 | 22 | var birdPassport = module.exports = function (params, cb) { 23 | if (typeof params !== 'object') { 24 | throw new Error('Where are you params?'); 25 | } 26 | options.callback = cb; 27 | options.username = params.username; 28 | options.password = params.password; 29 | options.service = params.service || 'https://passport.baidu.com/v2/?login'; 30 | 31 | if (!params.server) { 32 | var tmp = URL.parse(decodeURIComponent(options.service)); 33 | options.server = tmp.protocol + '//' + tmp.hostname + (~~tmp.port ? ':' + tmp.port : ''); 34 | } 35 | else { 36 | options.server = params.server; 37 | } 38 | options.sign_url = options.server + '/v2/api/?login'; 39 | 40 | // GET BAIDUID 41 | client.url_get(options.server + '/v2/?login', function (res, data) { 42 | client.get(options.server + '/v2/api/?getapi&tpl=pp&apiver=v3&tt=' + Date.now() + '&class=login&logintype=dialogLogin&callback=bd__cbs__w5lrwn', function (res, data) { 43 | // console.log(data) 44 | options.token = data.match(/"token" : "(.*?)"/i)[1]; 45 | Login(); 46 | }) 47 | }); 48 | }; 49 | 50 | function Login(data) { 51 | var form = data 52 | if (!data) { 53 | form = { 54 | 'username': options.username, 55 | 'password': options.password, 56 | 'u': 'https://www.baidu.com/', 57 | 'tpl': 'pp', 58 | 'token': options.token, 59 | 'codestring': '', 60 | 'verifycode': '', 61 | 'staticpage': options.server + '/static/passpc-account/html/v3Jump.html', 62 | 'isPhone': 'false', 63 | 'mem_pass': 'on' 64 | } 65 | } 66 | var tmp = URL.parse(options.sign_url) 67 | client.post({ 68 | protocol: tmp.protocol, 69 | host: tmp.hostname, 70 | port: tmp.port || (tmp.protocol == 'https:' ? 443 : 80), 71 | path: tmp.path, 72 | method: 'POST', 73 | headers: { 74 | 'Refer': 'https://www.baidu.com/' 75 | } 76 | }, form, function (res, data) { 77 | // console.log(data) 78 | if (data.match(/error=(\d+)/i)) { 79 | if (+data.match(/error=(\d+)/i)[1] === 257) { 80 | options.codestring = data.match(/codestring=(.+?)&username/i)[1]; 81 | getVerifyCode(); 82 | } 83 | else if (+data.match(/error=(\d+)/i)[1] === 0) { 84 | options.callback && options.callback(client.get_cookies_string()); 85 | } 86 | } 87 | else if (data.match(/err_no=(\d+)&callback/i)) { 88 | if (+data.match(/err_no=(\d+)&callback/i)[1] === 0) { 89 | options.callback && options.callback(client.get_cookies_string()); 90 | } 91 | else if (+data.match(/err_no=(\d+)&callback/i)[1] === 6) { 92 | options.codestring = data.match(/codestring=(.+?)&username/i)[1]; 93 | getVerifyCode(); 94 | } 95 | } 96 | }) 97 | } 98 | 99 | function getVerifyCode() { 100 | var url = options.server + "/cgi-bin/genimage?" + options.codestring; 101 | client.url_get(url, function (res, data) { 102 | if (url.match(/rdtest|qatest/)) { 103 | Login({ 104 | 'apiver': 'v3', 105 | 'codestring': options.codestring, 106 | 'isPhone': '', 107 | 'logLoginType': ' pc_loginDialog', 108 | 'loginmerge': 'true', 109 | 'logintype': 'dialogLogin', 110 | 'mem_pass': 'on', 111 | 'password': options.password, 112 | 'ppui_logintime': '5452', 113 | 'quick_user': '0', 114 | 'safeflg': '0', 115 | 'splogin': 'newuser', 116 | 'staticpage': 'https://www.baidu.com/cache/user/html/v3Jump.html', 117 | 'token': options.token, 118 | 'tpl': 'mn', 119 | 'tt': Date.now(), 120 | 'u': options.server + '/', 121 | 'username': options.username, 122 | 'verifycode': 'aaaa' 123 | }); 124 | return 125 | } 126 | fs.writeFile('./code.png', data, 'binary', function (err) { 127 | if (err) { 128 | console.log(err); 129 | } 130 | else { 131 | rl = readline.createInterface({ 132 | input: process.stdin, 133 | output: process.stdout 134 | }); 135 | require('child_process').exec('open ./code.png'); 136 | rl.question('Please input the verify code : ', function (code) { 137 | options.code = code; 138 | rl.close(); 139 | require('child_process').exec('rm -rf ./code.png'); 140 | Login({ 141 | 'apiver': 'v3', 142 | 'codestring': options.codestring, 143 | 'isPhone': '', 144 | 'logLoginType': ' pc_loginDialog', 145 | 'loginmerge': 'true', 146 | 'logintype': 'dialogLogin', 147 | 'mem_pass': 'on', 148 | 'password': options.password, 149 | 'ppui_logintime': '5452', 150 | 'quick_user': '0', 151 | 'safeflg': '0', 152 | 'splogin': 'newuser', 153 | 'staticpage': 'https://www.baidu.com/cache/user/html/v3Jump.html', 154 | 'token': options.token, 155 | 'tpl': 'mn', 156 | 'tt': Date.now(), 157 | 'u': options.server + '/', 158 | 'username': options.username, 159 | 'verifycode': options.code 160 | }); 161 | }); 162 | } 163 | }); 164 | }, function (res) { 165 | res.setEncoding('binary'); 166 | }); 167 | } -------------------------------------------------------------------------------- /lib/baidu/uuap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file baidu > uuap > auth 3 | */ 4 | 5 | const JSEncrypt = require('node-jsencrypt'); 6 | const {URL} = require('url'); 7 | const client = require('./../httpClient'); 8 | 9 | const UUAP_SERVER = 'https://uuap.baidu.com/login'; 10 | let loginUrl = ''; 11 | 12 | const baiduUuap = module.exports = function (params, cb) { 13 | if (typeof params !== 'object') { 14 | throw new Error('Where are you params?'); 15 | } 16 | 17 | const self = this; 18 | self._options = params; 19 | self._cb = cb || function () {}; 20 | 21 | const authorizeUrl = self._options.server || UUAP_SERVER; 22 | 23 | const METHOD = 'login'; 24 | loginUrl = authorizeUrl.replace('authorize', METHOD); 25 | 26 | client.url_get(authorizeUrl, {}, function (response) { 27 | if (response.statusCode === 200) { 28 | client.url_get(loginUrl, { 29 | headers: { 30 | 'originalUrl': authorizeUrl 31 | } 32 | }, function (res, data) { 33 | const {result} = JSON.parse(data); 34 | const {lt, execution, rsaPublicKey, _eventId, loginPath} = result; 35 | const loginOptions = { 36 | lt, 37 | execution, 38 | rsaPublicKey, 39 | _eventId, 40 | loginPath 41 | }; 42 | 43 | _login(self._options, loginOptions, function (cookie) { 44 | self._cookie = cookie; 45 | self._cb(cookie); 46 | }) 47 | }); 48 | } else { 49 | throw new Error('UUAP Server Errors.'); 50 | } 51 | }); 52 | }; 53 | 54 | /** 55 | * 获取cookie 56 | * @type {get_cookies_string} 57 | */ 58 | baiduUuap.prototype.getCookie = client.get_cookies_string; 59 | 60 | /** 61 | * 重试 62 | */ 63 | baiduUuap.prototype.retry = function (options) { 64 | baiduUuap(options || this._options, this._cb); 65 | }; 66 | 67 | /** 68 | * Post登录 69 | * @param options 70 | * @param loginOptions 71 | * @param callback 72 | * @private 73 | */ 74 | function _login(options, loginOptions, callback) { 75 | const {username, password, server, type} = options; 76 | const {lt, execution, rsaPublicKey, _eventId, loginPath} = loginOptions; 77 | const form = { 78 | username, 79 | password, 80 | rememberMe: true, 81 | lt, 82 | execution, 83 | _eventId, 84 | type: type || 1 85 | }; 86 | 87 | if (rsaPublicKey) { 88 | const timestamp = new Date().getTime(); 89 | const arr = [lt, timestamp, password]; 90 | 91 | const encrypt = new JSEncrypt(); 92 | encrypt.setPublicKey(rsaPublicKey); 93 | const encryptedPassword = encrypt.encrypt(arr.join(',')); 94 | 95 | form.password = encryptedPassword; 96 | } 97 | 98 | const tmpUrl = loginUrl.replace('/login', loginPath); 99 | const tmpObj = new URL(tmpUrl); 100 | 101 | const {protocol, hostname, port, pathname, search} = tmpObj; 102 | 103 | client.post({ 104 | protocol, 105 | host: hostname, 106 | port: port || (protocol == 'https:' ? 443 : 80), 107 | path: pathname + search, 108 | method: 'POST', 109 | headers: { 110 | 'Referer': server || UUAP_SERVER, 111 | } 112 | }, form, function (res, data) { 113 | const {code, result} = JSON.parse(data); 114 | if (code === 302 && result.redirectUrl) { 115 | client.get(result.redirectUrl, function (res, data) { 116 | const tokenMatch = data.match(/window.location.replace\("(.*)"\)/); 117 | const sTokenUrl = tokenMatch ? tokenMatch[1] : ''; 118 | if (sTokenUrl) { 119 | client.get(sTokenUrl, function(res, data) { 120 | callback(client.get_cookies_string()); 121 | }); 122 | } else { 123 | callback(client.get_cookies_string()); 124 | } 125 | }); 126 | } else if (server) { 127 | client.get(loginUrl, function (res, data) { 128 | callback(client.get_cookies_string()); 129 | }); 130 | } else { 131 | callback(client.get_cookies_string()); 132 | } 133 | }); 134 | }; 135 | -------------------------------------------------------------------------------- /lib/httpClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http client 3 | */ 4 | 5 | const http = require('http'); 6 | const https = require('https'); 7 | const {URL} = require('url'); 8 | const querystring = require('querystring'); 9 | const union = require('lodash.union'); 10 | 11 | const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36'; 12 | 13 | let all_cookies = []; 14 | 15 | let agent; 16 | 17 | const _getNet = url => { 18 | let protocol = http; 19 | 20 | if (url.indexOf('https:') === 0) { 21 | protocol = https; 22 | } 23 | return protocol; 24 | } 25 | 26 | const get_cookies_string = function () { 27 | const cookie_map = {}; 28 | 29 | all_cookies.forEach(function (ck) { 30 | const v = ck.split(';'); 31 | v.forEach(function (item) { 32 | if (!item.match('path')) { 33 | const kv = item.trim().split('='); 34 | if (kv[1]) { 35 | cookie_map[kv[0]] = kv[1]; 36 | } 37 | } 38 | }) 39 | }); 40 | 41 | const cks = []; 42 | for (let k in cookie_map) { 43 | cks.push(k + '=' + cookie_map[k]); 44 | } 45 | 46 | return cks.join(';') 47 | } 48 | 49 | const update_cookies = function (cks) { 50 | if (cks) { 51 | all_cookies = union(all_cookies, cks); 52 | } 53 | } 54 | 55 | const set_cookies = function (cks, single) { 56 | if (cks) { 57 | all_cookies = union(all_cookies, single ? cks : cks.split(' ')); 58 | } 59 | } 60 | 61 | const clear_cookies = function() { 62 | all_cookies = []; 63 | } 64 | 65 | // No Cookie Get 66 | const url_get = function (url, options, callback) { 67 | const http_or_https = _getNet(url); 68 | 69 | if (url.match('passport')) { 70 | options.rejectUnauthorized = false; 71 | options.port = options.port || (options.protocol == 'https:' ? 443 : 80); 72 | agent = new https.Agent(options); 73 | options.agent = agent; 74 | } 75 | 76 | http_or_https.get(url, options, function (res) { 77 | all_cookies = []; 78 | set_cookies(res.headers['set-cookie'], true); 79 | 80 | let body = ''; 81 | res.on('data', function (chunk) { 82 | return body += chunk; 83 | }) 84 | res.on('end', function () { 85 | return callback(res, body); 86 | }) 87 | }).on('error', function (e) { 88 | return console.error(e); 89 | }); 90 | } 91 | 92 | // Cookie Post 93 | const post = function (options, data, callback) { 94 | const http_or_https = _getNet(options.protocol); 95 | 96 | if (options.protocol === 'https:') { 97 | options.agent = agent; 98 | } 99 | 100 | if (typeof options.headers !== 'object') { 101 | options.headers = {}; 102 | } 103 | 104 | const postData = options.headers['Content-Type'] ? JSON.stringify(data) : querystring.stringify(data); 105 | 106 | options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/x-www-form-urlencoded'; 107 | options.headers['Content-Length'] = Buffer.byteLength(postData); 108 | options.headers['Cookie'] = get_cookies_string(); 109 | options.headers['User-Agent'] = USER_AGENT; 110 | 111 | const req = http_or_https.request(options, function (resp) { 112 | const res = resp; 113 | let body = ''; 114 | resp.on('data', function (chunk) { 115 | body += chunk; 116 | }) 117 | 118 | set_cookies(resp.headers['set-cookie'], true); 119 | 120 | resp.on('end', function (argument) { 121 | callback(res, body); 122 | }) 123 | }).on('error', function (e) { 124 | return console.error(e); 125 | }); 126 | 127 | req.write(postData); 128 | 129 | return req.end(); 130 | } 131 | 132 | // Cookie Get 133 | const get = function (url, callback) { 134 | const http_or_https = _getNet(url); 135 | 136 | const {protocol, hostname, port, pathname, search} = new URL(url); 137 | 138 | const options = { 139 | protocol, 140 | host: hostname, 141 | port: port || (protocol == 'https:' ? 443 : 80), 142 | path: pathname + search, 143 | method: 'GET', 144 | rejectUnauthorized: false, 145 | headers: {} 146 | } 147 | 148 | options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 149 | options.headers['Cookie'] = get_cookies_string(); 150 | options.headers['User-Agent'] = USER_AGENT; 151 | 152 | const req = http_or_https.request(options, function (res, data) { 153 | let body = ''; 154 | res.on('data', function (chunk) { 155 | body += chunk; 156 | }); 157 | 158 | set_cookies(res.headers['set-cookie'], true); 159 | 160 | return res.on('end', function (argument) { 161 | if (res.statusCode === 302 && res.headers.location.match(/^http/)) { 162 | get(res.headers.location, callback); 163 | } else { 164 | callback(res, body); 165 | } 166 | }) 167 | }).on('error', function (e) { 168 | console.error(e, options); 169 | }); 170 | 171 | return req.end(); 172 | } 173 | 174 | module.exports = { 175 | url_get, 176 | get, 177 | post, 178 | get_cookies_string, 179 | update_cookies, 180 | set_cookies, 181 | clear_cookies 182 | }; 183 | -------------------------------------------------------------------------------- /lib/netease/crypto.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var bigInt = require('big-integer'); 3 | 4 | function addPadding(encText, modulus) { 5 | var ml = modulus.length; 6 | for (i = 0; ml > 0 && modulus[i] == '0'; i++)ml--; 7 | var num = ml - encText.length, prefix = ''; 8 | for (var i = 0; i < num; i++) { 9 | prefix += '0'; 10 | } 11 | return prefix + encText; 12 | } 13 | 14 | 15 | function aesEncrypt(text, secKey) { 16 | var cipher = crypto.createCipheriv('AES-128-CBC', secKey, '0102030405060708'); 17 | return cipher.update(text, 'utf-8', 'base64') + cipher.final('base64'); 18 | } 19 | 20 | /** 21 | * RSA Encryption algorithm. 22 | * @param text {string} - raw data to encrypt 23 | * @param exponent {string} - public exponent 24 | * @param modulus {string} - modulus 25 | * @returns {string} - encrypted data: reverseText^pubKey%modulus 26 | */ 27 | function rsaEncrypt(text, exponent, modulus) { 28 | var rText = '', radix = 16; 29 | for (var i = text.length - 1; i >= 0; i--) rText += text[i];//reverse text 30 | var biText = bigInt(new Buffer(rText).toString('hex'), radix), 31 | biEx = bigInt(exponent, radix), 32 | biMod = bigInt(modulus, radix), 33 | biRet = biText.modPow(biEx, biMod); 34 | return addPadding(biRet.toString(radix), modulus); 35 | } 36 | 37 | function createSecretKey(size) { 38 | var keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 39 | var key = ""; 40 | for (var i = 0; i < size; i++) { 41 | var pos = Math.random() * keys.length; 42 | pos = Math.floor(pos); 43 | key = key + keys.charAt(pos) 44 | } 45 | return key; 46 | } 47 | var modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'; 48 | var nonce = '0CoJUm6Qyw8W8jud'; 49 | var pubKey = '010001'; 50 | var Crypto = { 51 | MD5: function (text) { 52 | return crypto.createHash('md5').update(text).digest('hex'); 53 | }, 54 | aesEncrypt: function(text) { 55 | var secKey = createSecretKey(16); 56 | return aesEncrypt(aesEncrypt(text, nonce), secKey); 57 | }, 58 | aesRsaEncrypt: function (text) { 59 | var secKey = createSecretKey(16); 60 | return { 61 | params: aesEncrypt(aesEncrypt(text, nonce), secKey), 62 | encSecKey: rsaEncrypt(secKey, pubKey, modulus) 63 | } 64 | } 65 | }; 66 | module.exports = Crypto; -------------------------------------------------------------------------------- /lib/netease/music.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file netease > music > auth 3 | */ 4 | 5 | var URL = require('url'); 6 | 7 | var client = require('./../httpClient'); 8 | var crypto = require('./crypto'); 9 | 10 | var neteaseMusic = module.exports = function (params, cb) { 11 | if (typeof params !== 'object') { 12 | throw new Error('Where are you params?'); 13 | } 14 | 15 | var self = this; 16 | 17 | self._options = params; 18 | self._cb = cb || function () { 19 | }; 20 | // console.log(params); 21 | var url, 22 | pattern = /^0\d{2,3}\d{7,8}$|^1[34578]\d{9}$/, 23 | body = { 24 | password: crypto.MD5(params.password), 25 | rememberLogin: 'false' 26 | }; 27 | 28 | if (pattern.test(params.username)) { 29 | body.phone = params.username; 30 | url = 'http://music.163.com/weapi/login/cellphone/'; 31 | } else { 32 | body.username = params.username; 33 | url = 'http://music.163.com/weapi/login'; 34 | } 35 | 36 | _login(url, crypto.aesRsaEncrypt(JSON.stringify(body)), function (cookie) { 37 | self._cookie = cookie; 38 | self._cb(cookie); 39 | }); 40 | }; 41 | 42 | /** 43 | * 获取cookie 44 | * @type {get_cookies_string} 45 | */ 46 | neteaseMusic.prototype.getCookie = client.get_cookies_string; 47 | 48 | /** 49 | * 加密参数 50 | * @type {Crypto.aesEncrypt} 51 | */ 52 | neteaseMusic.prototype.aesEncrypt = crypto.aesEncrypt; 53 | 54 | /** 55 | * 重试 56 | */ 57 | neteaseMusic.prototype.retry = function (options) { 58 | neteaseMusic(options || this._options, this._cb); 59 | }; 60 | 61 | function _login(url, encBody, callback) { 62 | 63 | var tmp = URL.parse(url); 64 | 65 | client.post({ 66 | protocol: tmp.protocol, 67 | host: tmp.hostname, 68 | port: tmp.port || (tmp.protocol == 'https:' ? 443 : 80), 69 | path: tmp.path, 70 | method: 'POST', 71 | headers: { 72 | 'Referer': 'http://music.163.com/' 73 | } 74 | }, encBody, function (res, data) { 75 | callback(client.get_cookies_string()); 76 | }); 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bird-auth", 3 | "version": "2.5.1", 4 | "description": "get cookie and then you can use it with birdv1", 5 | "main": "index.js", 6 | "dependencies": { 7 | "big-integer": "^1.6.22", 8 | "lodash.union": "^4.6.0", 9 | "node-jsencrypt": "^1.0.0", 10 | "yargs": "^4.8.1" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "bin": { 16 | "bird-auth": "bird-auth.js" 17 | }, 18 | "author": "gismanli@163.com", 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10.0.0", 22 | "npm": ">= 5.6.0" 23 | } 24 | } 25 | --------------------------------------------------------------------------------