223 lines
7.5 KiB
JavaScript
223 lines
7.5 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);
|
||
}
|
||
|
||
// ===== Git音源配置 =====
|
||
var GITCODE_CONFIG = { owner: 'ikun_0014', repo: 'music', token: 'WzsER9knWNgC_4tjeJCtHKcN' };
|
||
function getGitcodeUrl() {
|
||
return 'https://api.gitcode.com/api/v5/repos/' + GITCODE_CONFIG.owner + '/' + GITCODE_CONFIG.repo + '/raw/audio_database.json?access_token=' + GITCODE_CONFIG.token;
|
||
}
|
||
var cachedList = null;
|
||
var cacheTime = 0;
|
||
var CACHE_TTL = 5 * 60 * 1000;
|
||
|
||
function fetchAllGitMusic() {
|
||
var now = Date.now();
|
||
if (cachedList && now - cacheTime < CACHE_TTL) return Promise.resolve(cachedList);
|
||
return requestUrl(getGitcodeUrl(), 'GET', { 'User-Agent': 'Mozilla/5.0' }).then(function(res) {
|
||
var body = Array.isArray(res.body) ? res.body : [];
|
||
var list = [];
|
||
for (var i = 0; i < body.length; i++) list.push(formatGitItem(body[i]));
|
||
cachedList = list;
|
||
cacheTime = now;
|
||
return list;
|
||
});
|
||
}
|
||
|
||
function formatGitItem(item) {
|
||
var types = [];
|
||
var _types = {};
|
||
var fmt = (item.format || '').toLowerCase();
|
||
if (fmt === 'flac') { types.push({ type: 'flac', size: item.filesize || '' }); _types.flac = { size: item.filesize || '' }; }
|
||
else if (fmt === 'mp3') { types.push({ type: '320k', size: item.filesize || '' }); _types['320k'] = { size: item.filesize || '' }; }
|
||
else { types.push({ type: '128k', size: item.filesize || '' }); _types['128k'] = { size: item.filesize || '' }; }
|
||
return {
|
||
artists: item.artist || '未知歌手',
|
||
name: item.title || item.filename || '',
|
||
albumName: item.album || '未知专辑',
|
||
albumId: '',
|
||
id: item.relative_path || '',
|
||
source: 'git',
|
||
interval: '',
|
||
img: item.img || '',
|
||
lrc: item.lyrics || null,
|
||
types: types, _types: _types, typeUrl: {},
|
||
_gitcodeData: item
|
||
};
|
||
}
|
||
|
||
// ===== 搜索实现 =====
|
||
function musicSearch(str, page, limit) {
|
||
return fetchAllGitMusic().then(function(list) {
|
||
var regexStr = str.split('').map(function(c) { return c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }).join('.*');
|
||
var regex = new RegExp(regexStr, 'i');
|
||
var filtered = [];
|
||
for (var i = 0; i < list.length; i++) {
|
||
var item = list[i];
|
||
if (regex.test(item.artists) || regex.test(item.name) || regex.test(item.albumName)) filtered.push(item);
|
||
}
|
||
var start = (page - 1) * limit;
|
||
return { data: filtered.slice(start, start + limit), total: filtered.length };
|
||
});
|
||
}
|
||
|
||
function handleSearchResult(result) {
|
||
return result.data || [];
|
||
}
|
||
|
||
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) {
|
||
var list = handleSearchResult(result);
|
||
return { list: list, allPage: Math.ceil(result.total / limit), limit: limit, total: result.total, source: 'git' };
|
||
});
|
||
}
|
||
|
||
// ===== 播放链接 =====
|
||
function getUrl(songInfo, type) {
|
||
var q = type || '128k';
|
||
var songId = songInfo.id || '';
|
||
if (!songId) return Promise.resolve('');
|
||
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=git&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) {
|
||
if (songInfo.lrc) return Promise.resolve(songInfo.lrc);
|
||
return Promise.resolve('');
|
||
}
|
||
|
||
// ===== 音乐详情 =====
|
||
function musicDetail(songmid) {
|
||
return Promise.resolve(null);
|
||
}
|
||
function musicInfo(songmid) { return musicDetail(songmid); }
|
||
|
||
// ===== 热搜 =====
|
||
function hotSearch() { return Promise.resolve({ source: 'git', 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: 'git' }); }
|
||
|
||
// ===== 歌手 =====
|
||
function singer() { return Promise.resolve({ source: 'git', list: [], total: 0 }); }
|
||
|
||
// ===== 专辑 =====
|
||
function album() { return Promise.resolve({ source: 'git', list: [], total: 0 }); }
|
||
|
||
// ===== 插件信息 =====
|
||
var pluginInfo = {
|
||
info: {
|
||
id: 'koneko_ceru_git',
|
||
name: 'GIT音源 - Koneko 聆澜',
|
||
version: '0.0.3',
|
||
source: 'git',
|
||
description: 'GIT音源,基于 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: []
|
||
}
|
||
|
||
module.exports = {
|
||
musicSearch: { search: search },
|
||
tipSearch: { search: tipSearch },
|
||
hotSearch: { getList: hotSearch },
|
||
leaderboard: { getBoards: leaderboard },
|
||
songList: songList,
|
||
artists: singer,
|
||
album: album,
|
||
getLyric: getLyric,
|
||
getPic: getPic,
|
||
getUrl: getUrl,
|
||
musicDetail: musicDetail,
|
||
musicInfo: musicInfo,
|
||
pluginInfo: pluginInfo
|
||
};
|