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 { singer: item.artist || '未知歌手', name: item.title || item.filename || '', albumName: item.album || '未知专辑', albumId: '', songmid: 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.singer) || 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.songmid || ''; 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 = { id: 'koneko_ceru_git', name: 'GIT音源 - Koneko 聆澜', version: '0.0.3', source: 'git', description: 'GIT音源,基于 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: 'git' }); }, getList: songList }, singer: { getInfo: singer, getSongList: singer, getAlbumList: singer }, album: { getInfo: album, getSongList: album }, getLyric: getLyric, getPic: getPic, getUrl: getUrl, musicDetail: musicDetail, musicInfo: musicInfo };