UNPKG

163-music

Version:
152 lines (149 loc) 4.28 kB
const URI = require('url'); const http = require('http'); const pack = require('./pack'); const crypto = require('crypto'); const EventEmitter = require('events'); const querystring = require('querystring'); class NeteaseMusic extends EventEmitter { constructor(options) { super(); Object.assign(this, { api: 'http://music.163.com/', secret: '7246674226682325323F5E6544673A51', cookie: 'os=pc; osver=Microsoft-Windows-10-Professional-build-10586-64bit; appver=2.0.3.131777; channel=netease; __remember_me=true', userAgent: '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' }, options); } encrypt(body) { const { secret } = this; const password = pack('H*', secret) const cipher = crypto.createCipheriv('aes-128-ecb', password, '') body = JSON.stringify(body); body = cipher.update(body, 'utf8', 'base64') + cipher.final('base64') const hex = Buffer.from(body, 'base64').toString('hex') return hex.toUpperCase(); } request(path, params) { const { method, body, headers = {} } = params; headers['Content-Type'] = 'application/x-www-form-urlencoded'; headers['Content-Length'] = Buffer.byteLength(body); const { hostname } = URI.parse(this.api); return new Promise((resolve, reject) => { const req = http.request({ hostname, path, method, headers }, (res, buffer = []) => { res .on('error', reject) .on('data', chunk => buffer.push(chunk)) .on('end', () => { res.data = Buffer.concat(buffer) res.text = () => Promise.resolve(res.data.toString()); res.json = () => res.text().then(JSON.parse); resolve(res); }); }); req.write(body); req.end(); }); } invoke(method, path, params) { const { userAgent, cookie, api } = this; return this.request('/api/linux/forward', { method: 'POST', headers: { cookie, referer: api, 'user-agent': userAgent, }, body: querystring.stringify({ eparams: this.encrypt({ method, params, url: api + path, }) }) }) .then(res => res.json()) .then(res => { if (res.code === 200) return res; const err = new Error(res.msg); err.code = res.code; err.data = res; throw err; }) } search(keyword, offset = 0, limit = 3) { return this.invoke('POST', '/api/cloudsearch/pc', { s: keyword, type: 1, limit, offset, total: true, }); } artist(id, limit = 50) { return this.invoke('GET', `/api/v1/artist/${id}`, { id, ext: true, top: limit }); } playlist(id) { return this.invoke('POST', '/api/v3/playlist/detail', { id }); } album(id) { return this.invoke('GET', `/api/v1/album/${id}`, { id }); } song(id) { return this.invoke('POST', '/api/v3/song/detail', { c: `[{id: ${id}}]` }); } url(id, br = 320) { return this.invoke('POST', '/api/song/enhance/player/url', { ids: [id], br: br * 1000 }); } lyric(id) { return this.invoke('POST', '/api/song/lyric', { id, os: 'linux', lv: -1, kv: -1, tv: -1, }); } picture(id, size = 300) { const md5 = data => { const buf = Buffer.from(data) const str = buf.toString('binary') return crypto.createHash("md5").update(str).digest('hex') } const pickey = id => { const magic = '3go8&$8*3*3h0k(2)2'.split('') const song_id = id.split('').map((item, index) => { return String.fromCharCode(item.charCodeAt() ^ (magic[index % magic.length]).charCodeAt()) }) const md5Code = md5(song_id.join('')) const base64Code = Buffer.from(md5Code, 'hex').toString('base64') return base64Code.replace(/\//g, '_').replace(/\+/g, '-') } return Promise.resolve({ code: 200, url: `https://p3.music.126.net/${pickey(id)}/${id}.jpg?param=${size}y${size}` }); } } module.exports = NeteaseMusic;