202 lines
7.7 KiB
JavaScript
202 lines
7.7 KiB
JavaScript
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);
|
|
}
|
|
|
|
function formatPlayTime(seconds) {
|
|
if (!seconds) return '00:00';
|
|
var m = Math.floor(seconds / 60);
|
|
var s = Math.floor(seconds % 60);
|
|
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
|
|
}
|
|
|
|
// ===== 搜索实现 =====
|
|
function musicSearch(str, page, limit) {
|
|
var url = 'https://jadeite.migu.cn/music_search/v3/search?text=' + encodeURIComponent(str) + '&pageNo=' + page + '&pageSize=' + limit + '&searchSwitch={"song":1}';
|
|
return requestUrl(url, 'GET', {
|
|
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)',
|
|
'Referer': 'https://jadeite.migu.cn/music_search.html?keyword=' + encodeURIComponent(str),
|
|
'By': '7242a87651f953c07d759b1d08c62b74'
|
|
}).then(function(res) {
|
|
if (res.body && res.body.musics) return res.body.musics;
|
|
throw new Error('mg search failed');
|
|
});
|
|
}
|
|
|
|
function handleSearchResult(rawList) {
|
|
var list = [];
|
|
for (var i = 0; i < rawList.length; i++) {
|
|
var item = rawList[i];
|
|
var types = [];
|
|
var _types = {};
|
|
types.push({ type: '128k', size: '' }); _types['128k'] = { size: '' };
|
|
if (item.newRateFormats) {
|
|
for (var j = 0; j < item.newRateFormats.length; j++) {
|
|
var fmt = item.newRateFormats[j];
|
|
if (fmt.formatType === 'SQ') { types.push({ type: 'flac', size: '' }); _types.flac = { size: '' }; }
|
|
if (fmt.formatType === 'HQ') { types.push({ type: '320k', size: '' }); _types['320k'] = { size: '' }; }
|
|
}
|
|
}
|
|
var img = item.img || (item.albumImgs && item.albumImgs.length ? item.albumImgs[0].img : '');
|
|
if (img && img.startsWith('/')) img = 'https://d.musicapp.migu.cn' + img;
|
|
list.push({
|
|
singer: item.artist || item.singerName || '',
|
|
name: item.title || item.songName || '',
|
|
albumName: item.album || item.albumName || '',
|
|
albumId: item.albumId || '',
|
|
source: 'mg',
|
|
interval: item.length ? item.length.replace(/.*(\d\d:\d\d)$/, '$1') : '',
|
|
songmid: item.copyrightId || item.id || '',
|
|
songId: item.copyrightId || item.id || '',
|
|
img: img,
|
|
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(rawList) {
|
|
var list = handleSearchResult(rawList);
|
|
return { list: list, allPage: page + 1, limit: limit, total: list.length * 10, source: 'mg' };
|
|
});
|
|
}
|
|
|
|
// ===== 播放链接 =====
|
|
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=mg&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 Promise.resolve('');
|
|
}
|
|
|
|
// ===== 歌词 =====
|
|
function getLyric(songInfo) {
|
|
var copyrightId = songInfo.songmid || '';
|
|
if (!copyrightId) return Promise.resolve('');
|
|
return requestUrl('http://music.migu.cn/v3/api/music/audioPlayer/getLyric?copyrightId=' + encodeURIComponent(copyrightId), 'GET', {
|
|
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)',
|
|
'Referer': 'http://music.migu.cn/v3/music/player/audio?from=migu'
|
|
}).then(function(res) {
|
|
if (res.body && res.body.lyric) return res.body.lyric;
|
|
return '';
|
|
}).catch(function() { return ''; });
|
|
}
|
|
|
|
// ===== 音乐详情 =====
|
|
function musicDetail(songmid) {
|
|
return Promise.resolve(null);
|
|
}
|
|
function musicInfo(songmid) { return musicDetail(songmid); }
|
|
|
|
// ===== 热搜 =====
|
|
function hotSearch() { return Promise.resolve({ source: 'mg', list: [] }); }
|
|
|
|
// ===== 搜索建议 =====
|
|
function tipSearch(str) { return Promise.resolve({ order: [], songs: [], artists: [], albums: [], playlists: [] }); }
|
|
|
|
// ===== 榜单 =====
|
|
function leaderboard() { return Promise.resolve([]); }
|
|
|
|
// ===== 歌单 =====
|
|
function songList() { return Promise.resolve({ list: [], total: 0, page: 1, source: 'mg' }); }
|
|
|
|
// ===== 歌手 =====
|
|
function singer() { return Promise.resolve({ source: 'mg', list: [], total: 0 }); }
|
|
|
|
// ===== 专辑 =====
|
|
function album() { return Promise.resolve({ source: 'mg', list: [], total: 0 }); }
|
|
|
|
// ===== 插件信息 =====
|
|
module.exports = {
|
|
info: { id: 'koneko_ceru_mg', name: '咪咕音乐 - Koneko 聆澜', version: '0.0.3', source: 'mg', description: '咪咕音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口' },
|
|
ext: [],
|
|
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
|
|
quality: [
|
|
{ name: '标准', ui: '标', id: '128k' },
|
|
{ name: '高品', ui: 'HQ', id: '320k' },
|
|
{ name: '无损', ui: 'SQ', id: 'flac' }
|
|
],
|
|
supportFunc: ['musicSearch', 'tipSearch', 'hotSearch', 'leaderboard', 'songList', 'singer', 'album', 'getLyric', 'getPic', 'getUrl', 'musicDetail', 'musicInfo'],
|
|
musicSearch: { search: search },
|
|
tipSearch: { search: tipSearch },
|
|
hotSearch: { getList: hotSearch },
|
|
leaderboard: { getBoards: leaderboard },
|
|
songList: { search: songList, getTags: function() { return Promise.resolve({ tags: [], hotTag: [], source: 'mg' }); }, getList: songList },
|
|
singer: { getInfo: singer, getSongList: singer, getAlbumList: singer },
|
|
album: { getInfo: album, getSongList: album },
|
|
getLyric: getLyric,
|
|
getPic: getPic,
|
|
getUrl: getUrl,
|
|
musicDetail: musicDetail,
|
|
musicInfo: musicInfo
|
|
};
|