Files
Koneko_api_for_QZ-Music/Koneko_聆澜_网易云音乐_v0.0.3_QZv2.js

354 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
var https = require('https');
var http = require('http');
var crypto = require('crypto');
var querystring = require('querystring');
// ===== 通用HTTP请求 =====
function doRequest(protocol, options, postData) {
return new Promise(function(resolve, reject) {
var client = protocol === 'https' ? https : http;
var req = client.request(options, function(res) {
var chunks = [];
res.on('data', function(chunk) { chunks.push(chunk); });
res.on('end', function() {
var buf = Buffer.concat(chunks);
var body;
try {
body = JSON.parse(buf.toString('utf8'));
} catch (e) {
body = buf.toString('utf8');
}
resolve({ body: body, statusCode: res.statusCode, headers: res.headers });
});
});
req.on('error', function(err) { reject(err); });
req.setTimeout(15000, function() { req.destroy(); reject(new Error('timeout')); });
if (postData) req.write(postData);
req.end();
});
}
function requestUrl(urlStr, method, headers, body) {
var parsed = new URL(urlStr);
var protocol = parsed.protocol.replace(':', '');
var options = {
hostname: parsed.hostname,
port: parsed.port || (protocol === 'https' ? 443 : 80),
path: parsed.pathname + parsed.search,
method: method || 'GET',
headers: headers || {}
};
var postData = null;
if (body) {
if (typeof body === 'string') {
postData = Buffer.from(body, 'utf8');
} else if (Buffer.isBuffer(body)) {
postData = body;
} else {
postData = Buffer.from(JSON.stringify(body), 'utf8');
if (!options.headers['Content-Type']) options.headers['Content-Type'] = 'application/json';
}
if (postData) options.headers['Content-Length'] = postData.length;
}
return doRequest(protocol, options, postData);
}
// ===== 网易云eapi加密 =====
var EAPI_KEY = 'e82ckenh8dichen8';
function aesEncryptEapi(text) {
var cipher = crypto.createCipheriv('aes-128-ecb', EAPI_KEY, '');
var encrypted = cipher.update(text, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
}
function eapi(url, params) {
var payload = JSON.stringify({
method: 'POST',
url: url,
params: params
});
return { params: aesEncryptEapi(payload) };
}
// ===== weapi加密简化版用于歌词等 =====
var WEAPI_IV = '0102030405060708';
var WEAPI_PRESET_KEY = '0CoJUm6Qyw8W8jud';
var WEAPI_RSA_PUBKEY = '010001';
var WEAPI_RSA_MODULUS = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7';
function aesEncryptWeapi(text, key) {
var cipher = crypto.createCipheriv('aes-128-cbc', key, WEAPI_IV);
var encrypted = cipher.update(text, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
}
function rsaEncryptWeapi(text) {
var reversed = text.split('').reverse().join('');
var input = Buffer.from(reversed, 'utf8').toString('hex');
var bigInput = BigInt('0x' + input);
var bigExp = BigInt('0x' + WEAPI_RSA_PUBKEY);
var bigMod = BigInt('0x' + WEAPI_RSA_MODULUS);
var bigResult = 1n;
var e = bigExp;
var b = bigInput;
while (e > 0n) {
if (e % 2n === 1n) bigResult = (bigResult * b) % bigMod;
b = (b * b) % bigMod;
e = e / 2n;
}
var result = bigResult.toString(16).padStart(256, '0');
return result;
}
function weapi(params) {
var text = JSON.stringify(params);
var secretKey = '';
for (var i = 0; i < 16; i++) secretKey += String.fromCharCode(97 + Math.floor(Math.random() * 25));
var encrypted = aesEncryptWeapi(aesEncryptWeapi(text, WEAPI_PRESET_KEY), secretKey);
var encSecKey = rsaEncryptWeapi(secretKey);
return { params: encrypted, encSecKey: encSecKey };
}
// ===== 格式化辅助 =====
function formatPlayTime(seconds) {
var m = Math.floor(seconds / 60);
var s = Math.floor(seconds % 60);
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
}
function formatSingerName(arr, key) {
if (!arr || !arr.length) return '';
var names = [];
for (var i = 0; i < arr.length; i++) names.push(arr[i][key] || arr[i]);
return names.join('、');
}
// ===== 搜索实现 =====
function musicSearch(str, page, limit) {
var form = eapi('/api/search/song/list/page', {
keyword: str,
needCorrect: '1',
channel: 'typing',
offset: limit * (page - 1),
scene: 'normal',
total: page == 1,
limit: limit
});
return requestUrl('http://interface3.music.163.com/eapi/search/song/list/page', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
'Origin': 'https://music.163.com',
'Content-Type': 'application/x-www-form-urlencoded'
}, querystring.stringify(form)).then(function(res) {
return res.body;
});
}
function handleSearchResult(rawList) {
if (!rawList) return [];
var list = [];
for (var i = 0; i < rawList.length; i++) {
var item = rawList[i];
if (!item.baseInfo || !item.baseInfo.simpleSongData) continue;
var s = item.baseInfo.simpleSongData;
var types = [];
var _types = {};
if (s.h && s.h.br >= 320000) { types.push({ type: '320k', size: '' }); _types['320k'] = { size: '' }; }
else if (s.m && s.m.br >= 192000) { types.push({ type: '192k', size: '' }); _types['192k'] = { size: '' }; }
else if (s.l) { types.push({ type: '128k', size: '' }); _types['128k'] = { size: '' }; }
if (s.sq) { types.push({ type: 'flac', size: '' }); _types.flac = { size: '' }; }
if (s.hr) { types.push({ type: 'hires', size: '' }); _types.hires = { size: '' }; }
list.push({
singer: formatSingerName(s.ar, 'name'),
name: s.name,
albumName: s.al && s.al.name ? s.al.name : '',
albumId: s.al && s.al.id ? s.al.id : '',
source: 'wy',
interval: formatPlayTime((s.dt || 0) / 1000),
songmid: s.id,
img: s.al && s.al.picUrl ? s.al.picUrl : '',
lrc: null,
types: types,
_types: _types,
typeUrl: {}
});
}
return list;
}
function search(str, page, limit, retryNum) {
if (retryNum === undefined) retryNum = 0;
if (++retryNum > 3) return Promise.reject(new Error('try max num'));
if (!limit) limit = 30;
return musicSearch(str, page, limit).then(function(result) {
if (!result || result.code !== 200) return search(str, page, limit, retryNum);
var list = handleSearchResult(result.data && result.data.resources ? result.data.resources : []);
if (!list || list.length === 0) return search(str, page, limit, retryNum);
var total = result.data && result.data.totalCount ? result.data.totalCount : 0;
return {
list: list,
allPage: Math.ceil(total / limit),
limit: limit,
total: total,
source: 'wy'
};
});
}
// ===== 播放链接 =====
function getUrl(songInfo, type) {
var q = type || '128k';
var songId = songInfo.songmid || songInfo.id || '';
var ceruKey = process.env.ceru_key || '';
var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=wy&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(q), 'GET', headers)
.then(function(res) {
if (res.body && typeof res.body === 'object' && res.body.url) return res.body.url;
if (res.body && typeof res.body === 'object' && res.body.data && res.body.data.url) return res.body.data.url;
if (typeof res.body === 'string' && res.body.indexOf('http') === 0) return res.body;
return '';
})
.catch(function() { return ''; });
}
// ===== 封面图 =====
function getPic(songInfo) {
if (songInfo.img) return Promise.resolve(songInfo.img);
return requestUrl('https://music.163.com/weapi/v3/song/detail', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Referer': 'https://music.163.com/song?id=' + songInfo.songmid,
'Origin': 'https://music.163.com',
'Content-Type': 'application/x-www-form-urlencoded'
}, querystring.stringify(weapi({ c: '[{"id":' + songInfo.songmid + '}]', ids: '[' + songInfo.songmid + ']' })))
.then(function(res) {
if (res.body && res.body.songs && res.body.songs.length) return res.body.songs[0].al.picUrl;
return '';
})
.catch(function() { return ''; });
}
// ===== 歌词 =====
function getLyric(songInfo) {
var songmid = songInfo.songmid || '';
return requestUrl('https://music.163.com/api/song/lyric?id=' + songmid + '&lv=1&kv=1&tv=-1', 'GET', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Referer': 'https://music.163.com/song?id=' + songmid
}).then(function(res) {
if (res.body && res.body.lrc && res.body.lrc.lyric) return res.body.lrc.lyric;
return '';
}).catch(function() { return ''; });
}
// ===== 音乐详情 =====
function musicDetail(songmid) {
return requestUrl('https://music.163.com/weapi/v3/song/detail', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Origin': 'https://music.163.com',
'Content-Type': 'application/x-www-form-urlencoded'
}, querystring.stringify(weapi({ c: '[{"id":' + songmid + '}]', ids: '[' + songmid + ']' })))
.then(function(res) {
if (res.body && res.body.songs && res.body.songs.length) {
var s = res.body.songs[0];
return {
singer: formatSingerName(s.ar, 'name'),
name: s.name,
albumName: s.al && s.al.name ? s.al.name : '',
albumId: s.al && s.al.id ? s.al.id : '',
source: 'wy',
interval: formatPlayTime((s.dt || 0) / 1000),
songmid: s.id,
img: s.al && s.al.picUrl ? s.al.picUrl : '',
lrc: null,
types: [],
_types: {},
typeUrl: {}
};
}
return null;
})
.catch(function() { return null; });
}
function musicInfo(songmid) {
return musicDetail(songmid);
}
// ===== 热搜 =====
function hotSearch() {
return requestUrl('https://music.163.com/weapi/search/hot', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Origin': 'https://music.163.com',
'Content-Type': 'application/x-www-form-urlencoded'
}, querystring.stringify(weapi({ type: 1111 })))
.then(function(res) {
var list = [];
if (res.body && res.body.result && res.body.result.hots) {
for (var i = 0; i < res.body.result.hots.length; i++) list.push(res.body.result.hots[i].first);
}
return { source: 'wy', list: list };
})
.catch(function() { return { source: 'wy', list: [] }; });
}
// ===== 搜索建议 =====
function tipSearch(str) {
return requestUrl('https://music.163.com/weapi/search/suggest/web', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Origin': 'https://music.163.com',
'Content-Type': 'application/x-www-form-urlencoded'
}, querystring.stringify(weapi({ s: str, limit: 10 })))
.then(function(res) {
var result = { order: [], songs: [], artists: [], albums: [], playlists: [] };
if (res.body && res.body.result) {
var r = res.body.result;
if (r.songs) { result.order.push('songs'); result.songs = r.songs.map(function(s) { return { name: s.name, id: s.id, source: 'wy' }; }); }
if (r.artists) { result.order.push('artists'); result.artists = r.artists.map(function(a) { return { name: a.name, id: a.id, source: 'wy' }; }); }
}
return result;
})
.catch(function() { return { order: [], songs: [], artists: [], albums: [], playlists: [] }; });
}
// ===== 榜单 =====
function leaderboard() {
return Promise.resolve([]);
}
// ===== 歌单 =====
function songList() {
return Promise.resolve({ list: [], total: 0, page: 1, source: 'wy' });
}
// ===== 歌手 =====
function singer() {
return Promise.resolve({ source: 'wy', list: [], total: 0 });
}
// ===== 专辑 =====
function album() {
return Promise.resolve({ source: 'wy', list: [], total: 0 });
}
// ===== 插件信息 =====
var pluginInfo = {
id: 'koneko_ceru_wy',
name: '网易云音乐 - Koneko 聆澜',
version: '0.0.3',
source: 'wy',
description: '网易云音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
};
module.exports = {
pluginInfo: pluginInfo,
musicSearch: { search: search },
tipSearch: { search: tipSearch },
hotSearch: { getList: hotSearch },
leaderboard: { getBoards: leaderboard },
songList: { search: songList, getTags: function() { return Promise.resolve({ tags: [], hotTag: [], source: 'wy' }); }, getList: songList },
singer: { getInfo: singer, getSongList: singer, getAlbumList: singer },
album: { getInfo: album, getSongList: album },
getLyric: getLyric,
getPic: getPic,
getUrl: getUrl,
musicDetail: musicDetail,
musicInfo: musicInfo
};