12 Commits

Author SHA1 Message Date
TRAE Bot
2c5b21e869 release v0.1.4: QQ batch quality info, WY simple search API, remove ext activate, all plugins unified 2026-07-05 19:48:21 +00:00
TRAE Bot
89ab94b276 fix: kg use mobilecdn (has 周杰伦版权); wy use weapi cloudsearch; add hires/master quality labels 2026-07-05 18:56:45 +00:00
TRAE Bot
89d0c1557c fix(kg): use http + fallback for search, fix cover field Image with {size} replace 2026-07-05 18:45:49 +00:00
TRAE Bot
85bb259e36 fix(wy): remove quality conversion in getUrl, add cookie to playback headers; unify env descriptions 2026-07-05 18:27:18 +00:00
TRAE Bot
6025f74a6d release v0.1.3: fix kg/kw search, add ext activate buttons, getUrl realtime env 2026-07-05 18:25:33 +00:00
TRAE Bot
115b365493 release v0.1.2: rewrite all plugins based on official zq_tx_v3 format 2026-07-05 17:50:07 +00:00
TRAE Bot
827231bf23 fix: revert getUrl to 2 params (songInfo, type); fix all search fields to pic/mPic/sPic/qualities 2026-07-05 16:12:55 +00:00
TRAE Bot
2bc804187a fix: ext format (add description/entry); fix getUrl signature to (source, songInfo, type) 2026-07-05 16:02:32 +00:00
TRAE Bot
da7ad5f8a3 fix: map QZv2 quality names (standard/exhigh/lossless) to Ceru API format (128k/320k/flac) 2026-07-05 12:29:14 +00:00
TRAE Bot
16a3f488d3 feat(wy): cookie support, songList/singer/album real impl, ext homePage+share 2026-07-05 12:21:54 +00:00
TRAE Bot
04ab08a589 fix: env from global.env; enhance wy leaderboard 2026-07-05 12:11:37 +00:00
TRAE Bot
c39b5609dc fix: read ceru_key from global.env instead of process.env 2026-07-05 12:09:37 +00:00
7 changed files with 1045 additions and 545 deletions

View File

@@ -1,3 +1,5 @@
var env = global.env || {};
var https = require('https');
var http = require('http');
var crypto = require('crypto');
@@ -53,13 +55,23 @@ function requestUrl(urlStr, method, headers, body) {
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);
// ===== 格式化辅助 =====
function formatPlayTime(time) {
if (!time) return '--/--';
var m = Math.floor(time / 60);
var s = Math.floor(time % 60);
if (m === 0 && s === 0) return '--/--';
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
}
function sizeFormate(bytes) {
if (typeof bytes === 'string') bytes = parseFloat(bytes.replace(/[^0-9.]/g, ''));
var n = parseFloat(bytes);
if (isNaN(n) || n < 0) return '0.00MB';
var mb = n / (1024 * 1024);
return mb.toFixed(2) + 'MB';
}
// ===== Git音源配置 =====
var GITCODE_CONFIG = { owner: 'ikun_0014', repo: 'music', token: 'WzsER9knWNgC_4tjeJCtHKcN' };
function getGitcodeUrl() {
@@ -68,6 +80,7 @@ function getGitcodeUrl() {
var cachedList = null;
var cacheTime = 0;
var CACHE_TTL = 5 * 60 * 1000;
var cachedLyricMap = null;
function fetchAllGitMusic() {
var now = Date.now();
@@ -75,22 +88,39 @@ function fetchAllGitMusic() {
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]));
var lyricMap = {};
for (var i = 0; i < body.length; i++) {
var formatted = formatGitItem(body[i]);
if (formatted) {
list.push(formatted);
var lid = formatted.id;
if (lid) lyricMap[lid] = (body[i].lyrics || body[i].lyric || '');
}
}
cachedList = list;
cachedLyricMap = lyricMap;
cacheTime = now;
return list;
});
}
function formatGitItem(item) {
if (!item) return null;
var qualities = {};
var fmt = (item.format || '').toLowerCase();
if (fmt === 'flac' && item.filesize) qualities.lossless = String(item.filesize);
else if (fmt === 'mp3' && item.filesize) qualities.exhigh = String(item.filesize);
else if (item.filesize) qualities.standard = String(item.filesize);
var picUrl = item.img || '';
if (fmt === 'flac' && item.filesize) {
qualities['flac'] = sizeFormate(item.filesize);
qualities['320k'] = sizeFormate(item.filesize);
} else if (fmt === 'mp3' && item.filesize) {
if (item.bitrate && Number(item.bitrate) >= 320) qualities['320k'] = sizeFormate(item.filesize);
else qualities['128k'] = sizeFormate(item.filesize);
} else if (item.filesize) {
qualities['128k'] = sizeFormate(item.filesize);
}
var picUrl = item.img || item.cover || '';
var songId = item.hash || item.relative_path || item.id || '';
return {
id: item.relative_path || '',
id: String(songId),
name: item.title || item.filename || '',
artists: item.artist || '未知歌手',
source: 'git',
@@ -98,10 +128,9 @@ function formatGitItem(item) {
mPic: picUrl,
sPic: picUrl,
albumName: item.album || '未知专辑',
albumId: '',
interval: '',
qualities: qualities,
_gitcodeData: item
albumId: String(item.albumId || ''),
interval: String(formatPlayTime(item.duration) || '--/--'),
qualities: qualities
};
}
@@ -135,14 +164,20 @@ function search(str, page, limit, retryNum) {
}
// ===== 播放链接 =====
function getUrl(songInfo, type) {
var q = type || '128k';
var songId = songInfo.id || '';
function getUrl(songId, quality) {
// Map QZ v2 quality names to Ceru API format
if (quality === 'standard') quality = '128k';
else if (quality === 'exhigh') quality = '320k';
else if (quality === 'lossless' || quality === 'hires' || quality === 'jymaster') quality = 'flac';
songId = String(songId || '');
quality = String(quality || '128k');
if (!songId) return Promise.resolve('');
var ceruKey = process.env.ceru_key || '';
// Read env FRESH each call (dynamic refresh)
var envNow = global.env || {};
var ceruKey = envNow.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)
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=git&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), '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;
@@ -153,14 +188,19 @@ function getUrl(songInfo, type) {
// ===== 封面图 =====
function getPic(songInfo) {
if (songInfo.pic) return Promise.resolve(songInfo.pic);
if (songInfo && songInfo.pic) return Promise.resolve(songInfo.pic);
return Promise.resolve('');
}
// ===== 歌词 =====
function getLyric(songInfo) {
if (songInfo._gitcodeData && songInfo._gitcodeData.lyrics) return Promise.resolve(songInfo._gitcodeData.lyrics);
return Promise.resolve('');
var id = (songInfo && songInfo.id) || '';
if (!id) return Promise.resolve('');
if (cachedLyricMap && cachedLyricMap[id]) return Promise.resolve(cachedLyricMap[id]);
return fetchAllGitMusic().then(function() {
if (cachedLyricMap && cachedLyricMap[id]) return cachedLyricMap[id];
return '';
}).catch(function() { return ''; });
}
// ===== 音乐详情 =====
@@ -192,18 +232,20 @@ var pluginInfo = {
info: {
id: 'koneko_ceru_git',
name: 'GIT音源 - Koneko 聆澜',
version: '0.1.0',
version: '0.1.4',
source: 'git',
description: 'GIT音源基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
description: 'GIT音源基于 GitCode audio_database.json 本地搜索 + Ceru 播放接口'
},
ext: [],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜付费密钥。留空=公益模式(仅128k);填写=付费模式(高音质)。修改后下次播放自动生效,也可点扩展按钮激活' }],
quality: [
{ name: '标准', ui: '标', id: '128k' },
{ name: '高品', ui: 'HQ', id: '320k' },
{ name: '无损', ui: 'SQ', id: 'flac' }
{ name: '标准音质', ui: '标', id: '128k' },
{ name: '高品音质', ui: 'HQ', id: '320k' },
{ name: '无损音质', ui: 'SQ', id: 'flac' },
{ name: '高解析度无损(Hi-Res)', ui: 'HR', id: 'hires' },
{ name: '至臻母带', ui: 'MT', id: 'master' }
],
supportFunc: []
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
}
module.exports = {
@@ -212,7 +254,7 @@ module.exports = {
hotSearch: { getList: hotSearch },
leaderboard: { getBoards: leaderboard },
songList: songList,
artists: singer,
singer: singer,
album: album,
getLyric: getLyric,
getPic: getPic,

View File

@@ -1,3 +1,5 @@
var env = global.env || {};
var https = require('https');
var http = require('http');
var crypto = require('crypto');
@@ -53,6 +55,30 @@ function requestUrl(urlStr, method, headers, body) {
return doRequest(protocol, options, postData);
}
// ===== 格式化辅助 =====
function formatPlayTime(time) {
if (!time) return '--/--';
var m = Math.floor(time / 60);
var s = Math.floor(time % 60);
if (m === 0 && s === 0) return '--/--';
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
}
function sizeFormate(bytes) {
if (typeof bytes === 'string') bytes = parseFloat(bytes.replace(/[^0-9.]/g, ''));
var n = parseFloat(bytes);
if (isNaN(n) || n < 0) return '0.00MB';
var mb = n / (1024 * 1024);
return mb.toFixed(2) + 'MB';
}
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('、');
}
// ===== QQ音乐zzcSign =====
var PART_1_INDEXES = [23, 14, 6, 36, 16, 40, 7, 19];
var PART_2_INDEXES = [16, 1, 32, 12, 19, 27, 8, 5];
@@ -102,11 +128,39 @@ function signRequest(data) {
}, JSON.stringify(data));
}
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 getBatchMusicQualityInfo(songList) {
var songIds = songList.map(function(item) { return item.id; });
var body = {
comm: { ct: '19', cv: '1859', uin: '0' },
req: {
module: 'music.trackInfo.UniformRuleCtrl',
method: 'CgiGetTrackInfo',
param: { types: songIds.map(function(){ return 1; }), ids: songIds, ctx: 0 }
}
};
return requestUrl('https://u.y.qq.com/cgi-bin/musicu.fcg', 'POST', {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
'Content-Type': 'application/json'
}, JSON.stringify(body)).then(function(res) {
if (!res.body || res.body.code !== 0) return {};
var qualityInfoMap = {};
var tracks = (res.body.req && res.body.req.data && res.body.req.data.tracks) ? res.body.req.data.tracks : [];
for (var i = 0; i < tracks.length; i++) {
var track = tracks[i];
var file = track.file || {};
var qualities = {};
if (file.size_128mp3) qualities['128k'] = sizeFormate(file.size_128mp3);
if (file.size_320mp3) qualities['320k'] = sizeFormate(file.size_320mp3);
if (file.size_flac) qualities['flac'] = sizeFormate(file.size_flac);
if (file.size_hires) qualities['hires'] = sizeFormate(file.size_hires);
if (file.size_new && file.size_new[0]) qualities['master'] = sizeFormate(file.size_new[0]);
if (file.size_new && file.size_new[1]) qualities['atmos'] = sizeFormate(file.size_new[1]);
if (file.size_new && file.size_new[2]) qualities['atmos_plus'] = sizeFormate(file.size_new[2]);
qualityInfoMap[track.id] = { qualities: qualities };
}
return qualityInfoMap;
}).catch(function() { return {}; });
}
// ===== 搜索实现 =====
@@ -122,40 +176,33 @@ function musicSearch(str, page, limit) {
}
function handleSearchResult(rawList) {
var list = [];
for (var i = 0; i < rawList.length; i++) {
var item = rawList[i];
if (!item.file || !item.file.media_mid) continue;
var qualities = {};
if (item.file.size_128mp3) qualities.standard = String(item.file.size_128mp3);
if (item.file.size_320mp3) qualities.exhigh = String(item.file.size_320mp3);
if (item.file.size_flac) qualities.lossless = String(item.file.size_flac);
if (item.file.size_hires) qualities.hires = String(item.file.size_hires);
var albumId = '';
var albumName = '';
if (item.album) { albumName = item.album.name; albumId = item.album.mid; }
var singerName = '';
if (item.singer && item.singer.length) {
var ns = [];
for (var j = 0; j < item.singer.length; j++) ns.push(item.singer[j].name);
singerName = ns.join('、');
if (!rawList || rawList.length === 0) return Promise.resolve([]);
return getBatchMusicQualityInfo(rawList).then(function(qualityInfoMap) {
var list = [];
for (var i = 0; i < rawList.length; i++) {
var item = rawList[i];
if (!item.file || !item.file.media_mid) continue;
var albumId = '';
var albumName = '';
if (item.album) { albumName = item.album.name; albumId = item.album.mid; }
var picUrl = (albumId === '' || albumId === '空')
? (item.singer && item.singer.length ? 'https://y.gtimg.cn/music/photo_new/T001R500x500M000' + item.singer[0].mid + '.jpg' : '')
: 'https://y.gtimg.cn/music/photo_new/T002R500x500M000' + albumId + '.jpg';
var qInfo = qualityInfoMap[item.id] || {};
list.push({
id: String(item.mid),
name: item.name + (item.title_extra || ''),
artists: formatSingerName(item.singer, 'name'),
source: 'tx',
pic: picUrl, mPic: picUrl, sPic: picUrl,
albumName: albumName,
albumId: String(albumId || ''),
interval: String(formatPlayTime(item.interval) || '--/--'),
qualities: qInfo.qualities || {}
});
}
var picUrl = albumId ? 'https://y.gtimg.cn/music/photo_new/T002R500x500M000' + albumId + '.jpg' : (item.singer && item.singer.length ? 'https://y.gtimg.cn/music/photo_new/T001R500x500M000' + item.singer[0].mid + '.jpg' : '');
list.push({
id: item.mid,
name: item.name + (item.title_extra || ''),
artists: singerName,
source: 'tx',
pic: picUrl,
mPic: picUrl,
sPic: picUrl,
albumName: albumName,
albumId: albumId,
interval: formatPlayTime(item.interval),
qualities: qualities
});
}
return list;
return list;
});
}
function search(str, page, limit, retryNum) {
@@ -164,21 +211,28 @@ function search(str, page, limit, retryNum) {
if (!limit) limit = 30;
return musicSearch(str, page, limit).then(function(data) {
if (!data || !data.body || !data.body.item_song) return search(str, page, limit, retryNum);
var list = handleSearchResult(data.body.item_song);
var meta = data.meta || {};
var total = meta.estimate_sum || 0;
return { list: list, allPage: Math.ceil(total / limit), limit: limit, total: total, source: 'tx' };
return handleSearchResult(data.body.item_song).then(function(list) {
var meta = data.meta || {};
var total = meta.estimate_sum || 0;
return { list: list, allPage: Math.ceil(total / limit), limit: limit, total: total, source: 'tx' };
});
});
}
// ===== 播放链接 =====
function getUrl(songInfo, type) {
var q = type || '128k';
var songId = songInfo.id || '';
var ceruKey = process.env.ceru_key || '';
function getUrl(songId, quality) {
// Map QZ v2 quality names to Ceru API format
if (quality === 'standard') quality = '128k';
else if (quality === 'exhigh') quality = '320k';
else if (quality === 'lossless' || quality === 'hires' || quality === 'jymaster') quality = 'flac';
songId = String(songId || '');
quality = String(quality || '128k');
// Read env FRESH each call (dynamic refresh)
var envNow = global.env || {};
var ceruKey = envNow.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=tx&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(q), 'GET', headers)
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=tx&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), '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;
@@ -189,15 +243,15 @@ function getUrl(songInfo, type) {
// ===== 封面图 =====
function getPic(songInfo) {
if (songInfo.pic) return Promise.resolve(songInfo.pic);
var albumId = songInfo.albumId || '';
if (songInfo && songInfo.pic) return Promise.resolve(songInfo.pic);
var albumId = songInfo && songInfo.albumId ? String(songInfo.albumId) : '';
if (albumId) return Promise.resolve('https://y.gtimg.cn/music/photo_new/T002R500x500M000' + albumId + '.jpg');
return Promise.resolve('');
}
// ===== 歌词 =====
function getLyric(songInfo) {
var songmid = songInfo.id || '';
var songmid = (songInfo && songInfo.id) || (songInfo && songInfo.songId) || '';
return requestUrl('https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?format=json&nobase64=1&songmid=' + encodeURIComponent(songmid), 'GET', {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)',
'Referer': 'https://y.qq.com/portal/player.html'
@@ -220,11 +274,10 @@ function musicDetail(songmid) {
if (res.body && res.body.req && res.body.req.data && res.body.req.data.info && res.body.req.data.info.track_info) {
var t = res.body.req.data.info.track_info;
var albumId = t.album ? t.album.mid : '';
var singerName = '';
if (t.singer && t.singer.length) { var ns = []; for (var j = 0; j < t.singer.length; j++) ns.push(t.singer[j].name); singerName = ns.join('、'); }
var singerName = formatSingerName(t.singer, 'name');
var picUrl = albumId ? 'https://y.gtimg.cn/music/photo_new/T002R500x500M000' + albumId + '.jpg' : '';
return {
id: t.mid,
id: String(t.mid),
name: t.name,
artists: singerName,
source: 'tx',
@@ -232,8 +285,8 @@ function musicDetail(songmid) {
mPic: picUrl,
sPic: picUrl,
albumName: t.album ? t.album.name : '',
albumId: albumId,
interval: formatPlayTime(t.interval),
albumId: String(albumId || ''),
interval: String(formatPlayTime(t.interval) || '--/--'),
qualities: {}
};
}
@@ -247,7 +300,7 @@ function musicInfo(songmid) { return musicDetail(songmid); }
function hotSearch() {
var data = {
comm: { ct: '19', cv: '1803', guid: '0', patch: '118', psrf_access_token_expiresAt: 0, psrf_qqaccess_token: '', psrf_qqopenid: '', psrf_qqunionid: '', tmeAppID: 'qqmusic', tmeLoginType: 0, uin: '0', wid: '0' },
hotkey: { method: 'GetHotkeyForQQMusicPC', module: 'tencent_musicsoso_hotkey.HotkeyService', param: { search_id: '', uin: 0 } }
hotkey: { method: 'GetHotkeyForQQMusicMobile', module: 'tencent_musicsoso_hotkey.HotkeyService', param: { search_id: '', uin: 0 } }
};
return requestUrl('https://u.y.qq.com/cgi-bin/musicu.fcg', 'POST', {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)',
@@ -293,18 +346,22 @@ var pluginInfo = {
info: {
id: 'koneko_ceru_tx',
name: 'QQ音乐 - Koneko 聆澜',
version: '0.1.0',
version: '0.1.4',
source: 'tx',
description: 'QQ音乐音源基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
},
ext: [],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜付费密钥。留空=公益模式(仅128k);填写=付费模式(高音质)。修改后下次播放自动生效,也可点扩展按钮激活' }],
quality: [
{ name: '标准', ui: '标', id: '128k' },
{ name: '高品', ui: 'HQ', id: '320k' },
{ name: '无损', ui: 'SQ', id: 'flac' }
{ name: '标准音质', ui: '标', id: '128k' },
{ name: '高品音质', ui: 'HQ', id: '320k' },
{ name: '无损音质', ui: 'SQ', id: 'flac' },
{ name: '高解析度无损(Hi-Res)', ui: 'HR', id: 'hires' },
{ name: '至臻母带', ui: 'MT', id: 'master' },
{ name: '全景声', ui: 'AT', id: 'atmos' },
{ name: '全景声+', ui: 'AT+', id: 'atmos_plus' }
],
supportFunc: []
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
}
module.exports = {
@@ -313,7 +370,7 @@ module.exports = {
hotSearch: { getList: hotSearch },
leaderboard: { getBoards: leaderboard },
songList: songList,
artists: singer,
singer: singer,
album: album,
getLyric: getLyric,
getPic: getPic,

View File

@@ -1,3 +1,5 @@
var env = global.env || {};
var https = require('https');
var http = require('http');
var crypto = require('crypto');
@@ -53,13 +55,23 @@ function requestUrl(urlStr, method, headers, body) {
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);
// ===== 格式化辅助 =====
function formatPlayTime(time) {
if (!time) return '--/--';
var m = Math.floor(time / 60);
var s = Math.floor(time % 60);
if (m === 0 && s === 0) return '--/--';
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
}
function sizeFormate(bytes) {
if (typeof bytes === 'string') bytes = parseFloat(bytes.replace(/[^0-9.]/g, ''));
var n = parseFloat(bytes);
if (isNaN(n) || n < 0) return '0.00MB';
var mb = n / (1024 * 1024);
return mb.toFixed(2) + 'MB';
}
// ===== 搜索实现 =====
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}';
@@ -75,21 +87,24 @@ function musicSearch(str, page, limit) {
function handleSearchResult(rawList) {
var list = [];
if (!rawList) return list;
for (var i = 0; i < rawList.length; i++) {
var item = rawList[i];
var qualities = {};
if (item.newRateFormats) {
for (var j = 0; j < item.newRateFormats.length; j++) {
var fmt = item.newRateFormats[j];
if (fmt.formatType === 'SQ' && fmt.size) qualities.lossless = String(fmt.size);
if (fmt.formatType === 'HQ' && fmt.size) qualities.exhigh = String(fmt.size);
if (fmt.formatType === 'LQ' && fmt.size) qualities.standard = String(fmt.size);
if (fmt.formatType === 'LQ' && fmt.size) qualities['128k'] = sizeFormate(fmt.size);
if (fmt.formatType === 'HQ' && fmt.size) qualities['320k'] = sizeFormate(fmt.size);
if (fmt.formatType === 'SQ' && fmt.size) qualities['flac'] = sizeFormate(fmt.size);
}
}
var picUrl = item.img || (item.albumImgs && item.albumImgs.length ? item.albumImgs[0].img : '');
if (picUrl && picUrl.startsWith('/')) picUrl = 'https://d.musicapp.migu.cn' + picUrl;
if (picUrl && picUrl.indexOf('/') === 0) picUrl = 'https://d.musicapp.migu.cn' + picUrl;
var intervalStr = item.length ? String(item.length).replace(/.*(\d\d:\d\d)$/, '$1') : '--/--';
if (!intervalStr) intervalStr = '--/--';
list.push({
id: item.copyrightId || item.id || '',
id: String(item.copyrightId || item.id || ''),
name: item.title || item.songName || '',
artists: item.artist || item.singerName || '',
source: 'mg',
@@ -97,8 +112,8 @@ function handleSearchResult(rawList) {
mPic: picUrl,
sPic: picUrl,
albumName: item.album || item.albumName || '',
albumId: item.albumId || '',
interval: item.length ? item.length.replace(/.*(\d\d:\d\d)$/, '$1') : '',
albumId: String(item.albumId || ''),
interval: intervalStr,
qualities: qualities
});
}
@@ -116,13 +131,19 @@ function search(str, page, limit, retryNum) {
}
// ===== 播放链接 =====
function getUrl(songInfo, type) {
var q = type || '128k';
var songId = songInfo.id || '';
var ceruKey = process.env.ceru_key || '';
function getUrl(songId, quality) {
// Map QZ v2 quality names to Ceru API format
if (quality === 'standard') quality = '128k';
else if (quality === 'exhigh') quality = '320k';
else if (quality === 'lossless' || quality === 'hires' || quality === 'jymaster') quality = 'flac';
songId = String(songId || '');
quality = String(quality || '128k');
// Read env FRESH each call (dynamic refresh)
var envNow = global.env || {};
var ceruKey = envNow.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)
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=mg&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), '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;
@@ -133,13 +154,13 @@ function getUrl(songInfo, type) {
// ===== 封面图 =====
function getPic(songInfo) {
if (songInfo.pic) return Promise.resolve(songInfo.pic);
if (songInfo && songInfo.pic) return Promise.resolve(songInfo.pic);
return Promise.resolve('');
}
// ===== 歌词 =====
function getLyric(songInfo) {
var copyrightId = songInfo.id || '';
var copyrightId = (songInfo && songInfo.id) || '';
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)',
@@ -179,18 +200,20 @@ var pluginInfo = {
info: {
id: 'koneko_ceru_mg',
name: '咪咕音乐 - Koneko 聆澜',
version: '0.1.0',
version: '0.1.4',
source: 'mg',
description: '咪咕音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
},
ext: [],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜付费密钥。留空=公益模式(仅128k);填写=付费模式(高音质)。修改后下次播放自动生效,也可点扩展按钮激活' }],
quality: [
{ name: '标准', ui: '标', id: '128k' },
{ name: '高品', ui: 'HQ', id: '320k' },
{ name: '无损', ui: 'SQ', id: 'flac' }
{ name: '标准音质', ui: '标', id: '128k' },
{ name: '高品音质', ui: 'HQ', id: '320k' },
{ name: '无损音质', ui: 'SQ', id: 'flac' },
{ name: '高解析度无损(Hi-Res)', ui: 'HR', id: 'hires' },
{ name: '至臻母带', ui: 'MT', id: 'master' }
],
supportFunc: []
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
}
module.exports = {
@@ -199,7 +222,7 @@ module.exports = {
hotSearch: { getList: hotSearch },
leaderboard: { getBoards: leaderboard },
songList: songList,
artists: singer,
singer: singer,
album: album,
getLyric: getLyric,
getPic: getPic,

View File

@@ -1,362 +0,0 @@
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 qualities = {};
if (s.l && s.l.size) qualities.standard = String(s.l.size);
if (s.m && s.m.size) qualities.standard = String(s.m.size);
if (s.h && s.h.size) qualities.exhigh = String(s.h.size);
if (s.sq && s.sq.size) qualities.lossless = String(s.sq.size);
if (s.hr && s.hr.size) qualities.hires = String(s.hr.size);
var picUrl = s.al && s.al.picUrl ? s.al.picUrl : '';
list.push({
id: s.id,
name: s.name,
artists: formatSingerName(s.ar, 'name'),
source: 'wy',
pic: picUrl,
mPic: picUrl,
sPic: picUrl,
albumName: s.al && s.al.name ? s.al.name : '',
albumId: s.al && s.al.id ? s.al.id : '',
interval: formatPlayTime((s.dt || 0) / 1000),
qualities: qualities
});
}
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.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.pic) return Promise.resolve(songInfo.pic);
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.id,
'Origin': 'https://music.163.com',
'Content-Type': 'application/x-www-form-urlencoded'
}, querystring.stringify(weapi({ c: '[{"id":' + songInfo.id + '}]', ids: '[' + songInfo.id + ']' })))
.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.id || '';
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];
var picUrl = s.al && s.al.picUrl ? s.al.picUrl : '';
return {
id: s.id,
name: s.name,
artists: formatSingerName(s.ar, 'name'),
source: 'wy',
pic: picUrl,
mPic: picUrl,
sPic: picUrl,
albumName: s.al && s.al.name ? s.al.name : '',
albumId: s.al && s.al.id ? s.al.id : '',
interval: formatPlayTime((s.dt || 0) / 1000),
qualities: {}
};
}
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 = {
info: {
id: 'koneko_ceru_wy',
name: '网易云音乐 - Koneko 聆澜',
version: '0.1.0',
source: 'wy',
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: []
}
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
};

View File

@@ -0,0 +1,655 @@
var env = global.env || {};
var WY_COOKIE = env.cookie || '';
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(time) {
if (!time) return '--/--';
var m = Math.floor(time / 60);
var s = Math.floor(time % 60);
if (m === 0 && s === 0) return '--/--';
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
}
function sizeFormate(bytes) {
if (typeof bytes === 'string') bytes = parseFloat(bytes.replace(/[^0-9.]/g, ''));
var n = parseFloat(bytes);
if (isNaN(n) || n < 0) return '0.00MB';
var mb = n / (1024 * 1024);
return mb.toFixed(2) + 'MB';
}
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 formatPlayCount(count) {
if (!count && count !== 0) return '';
if (count >= 100000000) return (count / 100000000).toFixed(1) + '亿';
if (count >= 10000) return (count / 10000).toFixed(1) + '万';
return String(count);
}
// ===== 搜索实现使用老式搜索API无需weapi加密 =====
function musicSearch(str, page, limit) {
var url = 'https://music.163.com/api/search/get/web?s=' + encodeURIComponent(str) + '&type=1&offset=' + (limit * (page - 1)) + '&total=true&limit=' + limit;
return requestUrl(url, 'GET', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',
'Referer': 'https://music.163.com',
'Cookie': WY_COOKIE
}).then(function(res) {
if (typeof res.body === 'string') {
try { res.body = JSON.parse(res.body); } catch (e) { return null; }
}
return res.body;
});
}
function handleSearchResult(rawList) {
if (!rawList) return [];
var list = [];
for (var i = 0; i < rawList.length; i++) {
var item = rawList[i];
var qualities = {};
if (item.l && item.l.size) qualities['128k'] = sizeFormate(item.l.size);
if (item.h && item.h.size) qualities['320k'] = sizeFormate(item.h.size);
if (item.sq && item.sq.size) qualities['flac'] = sizeFormate(item.sq.size);
if (item.hr && item.hr.size) qualities['hires'] = sizeFormate(item.hr.size);
var picUrl = item.album && item.album.picUrl ? item.album.picUrl : '';
var singerName = '';
if (item.artists && item.artists.length) {
var ns = [];
for (var j = 0; j < item.artists.length; j++) ns.push(item.artists[j].name);
singerName = ns.join('、');
}
list.push({
id: String(item.id),
name: item.name || '',
artists: singerName,
source: 'wy',
pic: picUrl, mPic: picUrl, sPic: picUrl,
albumName: item.album && item.album.name ? item.album.name : '',
albumId: String(item.album && item.album.id ? item.album.id : ''),
interval: String(formatPlayTime((item.duration || 0) / 1000) || '--/--'),
qualities: qualities
});
}
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 songs = (result.result && result.result.songs) ? result.result.songs : [];
var list = handleSearchResult(songs);
if (!list || list.length === 0) return search(str, page, limit, retryNum);
var total = result.result && result.result.songCount ? result.result.songCount : 0;
return { list: list, allPage: Math.ceil(total / limit), limit: limit, total: total, source: 'wy' };
});
}
// ===== 播放链接 =====
function getUrl(songId, quality) {
songId = String(songId || '');
quality = String(quality || '128k');
// Read env FRESH each call (dynamic refresh)
var envNow = global.env || {};
var ceruKey = envNow.ceru_key || '';
var wyCookie = envNow.cookie || '';
var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey;
if (wyCookie) headers['Cookie'] = wyCookie;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=wy&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), '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 && songInfo.pic) return Promise.resolve(songInfo.pic);
var id = songInfo && songInfo.id ? String(songInfo.id) : '';
if (!id) return Promise.resolve('');
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=' + id,
'Origin': 'https://music.163.com',
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': WY_COOKIE
}, querystring.stringify(weapi({ c: '[{"id":' + id + '}]', ids: '[' + id + ']' })))
.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 && songInfo.id) || '';
return requestUrl('https://music.163.com/api/song/lyric?id=' + encodeURIComponent(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,
'Cookie': WY_COOKIE
}).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',
'Cookie': WY_COOKIE
}, 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];
var picUrl = s.al && s.al.picUrl ? s.al.picUrl : '';
return {
id: String(s.id),
name: s.name,
artists: formatSingerName(s.ar, 'name'),
source: 'wy',
pic: picUrl,
mPic: picUrl,
sPic: picUrl,
albumName: s.al && s.al.name ? s.al.name : '',
albumId: String(s.al && s.al.id ? s.al.id : ''),
interval: String(formatPlayTime((s.dt || 0) / 1000) || '--/--'),
qualities: {}
};
}
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',
'Cookie': WY_COOKIE
}, 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',
'Cookie': WY_COOKIE
}, 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: [] }; });
}
// ===== 榜单 =====
var BOARD_LIST = [
{ id: '19723756', name: '飙升榜', source: 'wy' },
{ id: '3779629', name: '新歌榜', source: 'wy' },
{ id: '2884035', name: '原创榜', source: 'wy' },
{ id: '3778678', name: '热歌榜', source: 'wy' },
{ id: '1978921795', name: '云音乐电音榜', source: 'wy' },
{ id: '991319590', name: '云音乐说唱榜', source: 'wy' },
{ id: '5059633707', name: '云音乐摇滚榜', source: 'wy' },
{ id: '5059642708', name: '云音乐古风榜', source: 'wy' },
{ id: '5059644681', name: '云音乐民谣榜', source: 'wy' },
{ id: '180106', name: 'UK排行榜周榜', source: 'wy' },
{ id: '60198', name: '美国Billboard周榜', source: 'wy' },
{ id: '11641212', name: 'KTV唛榜', source: 'wy' },
{ id: '112463', name: 'iTunes榜', source: 'wy' },
{ id: '112504', name: '日本Oricon周榜', source: 'wy' },
{ id: '250341', name: '云音乐MV榜', source: 'wy' }
];
function leaderboardGetBoards() {
return Promise.resolve(BOARD_LIST);
}
function leaderboardGetList(id, page) {
if (!page) page = 1;
var limit = 1000;
var form = weapi({ id: id, n: limit, s: 8 });
return requestUrl('https://music.163.com/weapi/v3/playlist/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',
'Cookie': WY_COOKIE
}, querystring.stringify(form)).then(function(res) {
if (!res.body || !res.body.playlist) return { list: [], total: 0, source: 'wy' };
var tracks = res.body.playlist.tracks || [];
var list = [];
for (var i = 0; i < tracks.length; i++) {
var item = tracks[i];
var picUrl = item.al && item.al.picUrl ? item.al.picUrl : '';
list.push({
id: String(item.id),
name: item.name,
artists: formatSingerName(item.ar, 'name'),
source: 'wy',
pic: picUrl,
mPic: picUrl,
sPic: picUrl,
albumName: item.al && item.al.name ? item.al.name : '',
albumId: String(item.al && item.al.id ? item.al.id : ''),
interval: String(formatPlayTime((item.dt || 0) / 1000) || '--/--'),
qualities: {}
});
}
return { list: list, total: list.length, source: 'wy', limit: limit, page: page };
}).catch(function() { return { list: [], total: 0, source: 'wy' }; });
}
function leaderboard() {
return Promise.resolve(BOARD_LIST);
}
// ===== 歌单 =====
var songList = {
limit_list: 30,
sortList: [
{ name: '最热', id: 'hot' }
],
getTags: function() {
return requestUrl('https://music.163.com/weapi/playlist/catalogue', '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',
'Cookie': WY_COOKIE
}, querystring.stringify(weapi({}))).then(function(res) {
if (!res.body || res.body.code !== 200) return { tags: [], hotTag: [], source: 'wy' };
var categories = res.body.categories || {};
var sub = res.body.sub || [];
var subList = {};
for (var i = 0; i < sub.length; i++) {
var item = sub[i];
if (!subList[item.category]) subList[item.category] = [];
subList[item.category].push({ parent_name: categories[item.category], name: item.name, id: item.name, source: 'wy' });
}
var tags = [];
var keys = Object.keys(categories);
for (var j = 0; j < keys.length; j++) {
tags.push({ name: categories[keys[j]], list: subList[keys[j]] || [], source: 'wy' });
}
return { tags: tags, hotTag: [], source: 'wy' };
}).catch(function() { return { tags: [], hotTag: [], source: 'wy' }; });
},
getList: function(sortId, tagId, page) {
if (!page) page = 1;
var limit = this.limit_list;
return requestUrl('https://music.163.com/weapi/playlist/list', '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',
'Cookie': WY_COOKIE
}, querystring.stringify(weapi({
cat: tagId || '全部',
order: sortId || 'hot',
limit: limit,
offset: limit * (page - 1),
total: true
}))).then(function(res) {
if (!res.body || res.body.code !== 200) return { list: [], total: 0, page: page, limit: limit, source: 'wy' };
var playlists = res.body.playlists || [];
var list = [];
for (var i = 0; i < playlists.length; i++) {
var p = playlists[i];
list.push({
id: String(p.id),
name: p.name,
author: p.creator && p.creator.nickname ? p.creator.nickname : '',
img: p.coverImgUrl || '',
play_count: formatPlayCount(p.playCount),
total: p.trackCount || 0,
desc: p.description || '',
source: 'wy'
});
}
return { list: list, total: res.body.total || 0, page: page, limit: limit, source: 'wy' };
}).catch(function() { return { list: [], total: 0, page: page, limit: limit, source: 'wy' }; });
},
getListDetail: function(id, page) {
if (!page) page = 1;
var limit = 1000;
return requestUrl('https://music.163.com/weapi/v3/playlist/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',
'Cookie': WY_COOKIE
}, querystring.stringify(weapi({ id: id, n: limit, s: 8 }))).then(function(res) {
if (!res.body || !res.body.playlist) return { list: [], total: 0, source: 'wy' };
var playlist = res.body.playlist;
var tracks = playlist.tracks || [];
var list = [];
for (var i = 0; i < tracks.length; i++) {
var item = tracks[i];
var picUrl = item.al && item.al.picUrl ? item.al.picUrl : '';
list.push({
id: String(item.id),
name: item.name,
artists: formatSingerName(item.ar, 'name'),
source: 'wy',
pic: picUrl, mPic: picUrl, sPic: picUrl,
albumName: item.al && item.al.name ? item.al.name : '',
albumId: String(item.al && item.al.id ? item.al.id : ''),
interval: String(formatPlayTime((item.dt || 0) / 1000) || '--/--'),
qualities: {}
});
}
return {
list: list, page: page, limit: limit,
total: playlist.trackIds ? playlist.trackIds.length : list.length,
source: 'wy',
info: { name: playlist.name, img: playlist.coverImgUrl, desc: playlist.description, author: playlist.creator && playlist.creator.nickname ? playlist.creator.nickname : '' }
};
}).catch(function() { return { list: [], total: 0, source: 'wy' }; });
},
search: function(text, page, limit) {
if (!page) page = 1;
if (!limit) limit = 20;
var form = eapi('/api/cloudsearch/pc', { s: text, type: 1000, limit: limit, total: page == 1, offset: limit * (page - 1) });
return requestUrl('http://interface3.music.163.com/eapi/cloudsearch/pc', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Origin': 'https://music.163.com',
'Cookie': WY_COOKIE
}, querystring.stringify(form)).then(function(res) {
if (!res.body || res.body.code !== 200) return { list: [], total: 0, source: 'wy' };
var playlists = res.body.result && res.body.result.playlists ? res.body.result.playlists : [];
var list = [];
for (var i = 0; i < playlists.length; i++) {
var p = playlists[i];
list.push({ id: String(p.id), name: p.name, author: p.creator && p.creator.nickname ? p.creator.nickname : '', img: p.coverImgUrl || '', play_count: formatPlayCount(p.playCount), total: p.trackCount || 0, source: 'wy' });
}
return { list: list, total: res.body.result.playlistCount || 0, page: page, limit: limit, source: 'wy' };
}).catch(function() { return { list: [], total: 0, source: 'wy' }; });
}
};
// ===== 歌手 =====
var singer = {
getInfo: function(id) {
var form = eapi('/api/artist/head/info/get', { id: id });
return requestUrl('http://interface3.music.163.com/eapi/artist/head/info/get', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Cookie': WY_COOKIE
}, querystring.stringify(form)).then(function(res) {
if (!res.body || res.body.code !== 200) return null;
return {
source: 'wy', id: res.body.artist.id,
info: { name: res.body.artist.name, desc: res.body.artist.briefDesc, avatar: res.body.user && res.body.user.avatarUrl ? res.body.user.avatarUrl : '' },
count: { music: res.body.artist.musicSize, album: res.body.artist.albumSize }
};
}).catch(function() { return null; });
},
getSongList: function(id, page, limit) {
if (!page) page = 1;
if (!limit) limit = 100;
var offset = (page - 1) * limit;
var form = eapi('/api/v2/artist/songs', { id: id, limit: limit, offset: offset });
return requestUrl('http://interface3.music.163.com/eapi/v2/artist/songs', 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Cookie': WY_COOKIE
}, querystring.stringify(form)).then(function(res) {
if (!res.body || !res.body.songs) return { list: [], total: 0, source: 'wy' };
var list = [];
for (var i = 0; i < res.body.songs.length; i++) {
var item = res.body.songs[i];
var picUrl = item.al && item.al.picUrl ? item.al.picUrl : '';
list.push({
id: String(item.id), name: item.name, artists: formatSingerName(item.artists, 'name'),
source: 'wy', pic: picUrl, mPic: picUrl, sPic: picUrl,
albumName: item.album && item.album.name ? item.album.name : '',
albumId: String(item.album && item.album.id ? item.album.id : ''),
interval: String(formatPlayTime((item.duration || 0) / 1000) || '--/--'), qualities: {}
});
}
return { list: list, total: res.body.total || 0, page: page, limit: limit, source: 'wy' };
}).catch(function() { return { list: [], total: 0, source: 'wy' }; });
},
getAlbumList: function(id, page, limit) {
if (!page) page = 1;
if (!limit) limit = 10;
var offset = (page - 1) * limit;
var form = eapi('/api/artist/albums/' + id, { limit: limit, offset: offset });
return requestUrl('http://interface3.music.163.com/eapi/artist/albums/' + id, 'POST', {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
'Cookie': WY_COOKIE
}, querystring.stringify(form)).then(function(res) {
if (!res.body || !res.body.hotAlbums) return { list: [], total: 0, source: 'wy' };
var list = [];
for (var i = 0; i < res.body.hotAlbums.length; i++) {
var item = res.body.hotAlbums[i];
list.push({ id: item.id, count: item.size, info: { name: item.name, author: formatSingerName(item.artists), img: item.picUrl, desc: null } });
}
return { list: list, total: res.body.artist && res.body.artist.albumSize ? res.body.artist.albumSize : 0, page: page, limit: limit, source: 'wy' };
}).catch(function() { return { list: [], total: 0, source: 'wy' }; });
}
};
// ===== 专辑 =====
var album = {
getInfo: function(id) {
var form = weapi({ id: id });
return requestUrl('https://music.163.com/weapi/v1/album/' + id, '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',
'Cookie': WY_COOKIE
}, querystring.stringify(form)).then(function(res) {
if (!res.body || !res.body.album) return null;
var a = res.body.album;
return { source: 'wy', id: a.id, info: { name: a.name, author: formatSingerName(a.artists), img: a.picUrl, desc: a.description || '' }, count: { song: a.size } };
}).catch(function() { return null; });
},
getSongList: function(id, page, limit) {
if (!page) page = 1;
if (!limit) limit = 100;
var form = weapi({ id: id });
return requestUrl('https://music.163.com/weapi/v1/album/' + id, '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',
'Cookie': WY_COOKIE
}, querystring.stringify(form)).then(function(res) {
if (!res.body || !res.body.songs) return { list: [], total: 0, source: 'wy' };
var list = [];
for (var i = 0; i < res.body.songs.length; i++) {
var item = res.body.songs[i];
var picUrl = item.al && item.al.picUrl ? item.al.picUrl : '';
list.push({
id: String(item.id), name: item.name, artists: formatSingerName(item.ar, 'name'),
source: 'wy', pic: picUrl, mPic: picUrl, sPic: picUrl,
albumName: item.al && item.al.name ? item.al.name : '',
albumId: String(item.al && item.al.id ? item.al.id : ''),
interval: String(formatPlayTime((item.dt || 0) / 1000) || '--/--'), qualities: {}
});
}
return { list: list, total: list.length, page: page, limit: limit, source: 'wy' };
}).catch(function() { return { list: [], total: 0, source: 'wy' }; });
}
};
// ===== 插件信息 =====
var pluginInfo = {
info: {
id: 'koneko_ceru_wy',
name: '网易云音乐 - Koneko 聆澜',
version: '0.1.4',
source: 'wy',
description: '网易云音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
},
ext: [],
env: [
{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜付费密钥。留空=公益模式(仅128k);填写=付费模式(高音质)。修改后下次播放自动生效,也可点扩展按钮激活' },
{ key: 'cookie', name: '网易云Cookie', description: '网易云音乐登录Cookie格式: MUSIC_U=xxx; MUSIC_U 值从浏览器登录网易云后 F12→Application→Cookies 复制。用于个人歌单、收藏等功能' }
],
quality: [
{ name: '标准音质', ui: '标', id: '128k' },
{ name: '高品音质', ui: 'HQ', id: '320k' },
{ name: '无损音质', ui: 'SQ', id: 'flac' },
{ name: '高解析度无损(Hi-Res)', ui: 'HR', id: 'hires' },
{ name: '至臻母带', ui: 'MT', id: 'master' }
],
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
}
module.exports = {
musicSearch: { search: search },
tipSearch: { search: tipSearch },
hotSearch: { getList: hotSearch },
leaderboard: { getBoards: leaderboardGetBoards, getList: leaderboardGetList },
songList: songList,
singer: singer,
album: album,
getLyric: getLyric,
getPic: getPic,
getUrl: getUrl,
musicDetail: musicDetail,
musicInfo: musicInfo,
pluginInfo: pluginInfo
};

View File

@@ -1,3 +1,5 @@
var env = global.env || {};
var https = require('https');
var http = require('http');
var crypto = require('crypto');
@@ -53,42 +55,77 @@ function requestUrl(urlStr, method, headers, body) {
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);
// ===== 格式化辅助 =====
function formatPlayTime(time) {
if (!time) return '--/--';
var m = Math.floor(time / 60);
var s = Math.floor(time % 60);
if (m === 0 && s === 0) return '--/--';
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
}
function sizeFormate(bytes) {
if (typeof bytes === 'string') bytes = parseFloat(bytes.replace(/[^0-9.]/g, ''));
var n = parseFloat(bytes);
if (isNaN(n) || n < 0) return '0.00MB';
var mb = n / (1024 * 1024);
return mb.toFixed(2) + 'MB';
}
// ===== 搜索实现 =====
function stripEm(s) {
if (!s) return '';
return String(s).replace(/<\/?em>/g, '').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/\\\\u0026/g, '&').trim();
}
function musicSearch(str, page, limit) {
var url = 'https://kuwo-api.vercel.app/api/search?key=' + encodeURIComponent(str) + '&page=' + page + '&limit=' + limit;
return requestUrl(url, 'GET', { 'User-Agent': 'Mozilla/5.0 (Linux; Android 10)' }).then(function(res) {
if (res.body && res.body.data && res.body.data.list) return res.body.data.list;
var pn = (page - 1) * limit;
var url = 'http://search.kuwo.cn/r.s?all=' + encodeURIComponent(str) + '&ft=music&itemset=1&rformat=json&encoding=utf8&pn=' + pn + '&rn=' + limit;
return requestUrl(url, 'GET', {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)',
'Referer': 'http://www.kuwo.cn/'
}).then(function(res) {
var body = res.body;
if (typeof body === 'string') {
try { body = JSON.parse(body.replace(/'/g, '"')); } catch (e) { body = null; }
}
if (body && body.abslist) return body;
throw new Error('kw search failed');
});
}
function handleSearchResult(rawList) {
var list = [];
if (!rawList) return list;
for (var i = 0; i < rawList.length; i++) {
var item = rawList[i];
var rid = '';
if (item.MUSICRID) rid = String(item.MUSICRID).replace('MUSIC_', '');
else if (item.DC_TARGETID) rid = String(item.DC_TARGETID);
else rid = String(item.rid || item.id || '');
var qualities = {};
if (item.filesize) qualities.standard = String(item.filesize);
if (item.sqfilesize) qualities.exhigh = String(item.sqfilesize);
if (item.flacfilesize) qualities.lossless = String(item.flacfilesize);
var picUrl = item.pic || '';
var formats = String(item.FORMATS || '');
var minfo = String(item.MINFO || '');
if (formats.indexOf('MP3128') !== -1 || minfo.indexOf('bitrate:128') !== -1) qualities['128k'] = '';
if (formats.indexOf('MP3H') !== -1 || minfo.indexOf('bitrate:320') !== -1) qualities['320k'] = '';
if (formats.indexOf('ALFLAC') !== -1 || minfo.indexOf('format:flac') !== -1) qualities['flac'] = '';
var name = stripEm(item.SONGNAME || item.NAME || '');
var artist = stripEm(item.ARTIST || '');
var albumName = stripEm(item.ALBUM || '');
var picUrl = item.web_albumpic_short || item.albumpic || item.ALBUMPIC || '';
if (picUrl && picUrl.indexOf('http') !== 0 && picUrl.length > 2) picUrl = 'http://img1.kuwo.cn/star/albumcover/' + picUrl;
var duration = parseInt(item.DURATION || '0', 10);
list.push({
id: item.id || item.rid || '',
name: item.name || '',
artists: item.artist || '',
id: rid,
name: name,
artists: artist,
source: 'kw',
pic: picUrl,
mPic: picUrl,
sPic: picUrl,
albumName: item.album || '',
albumId: item.albumid || '',
interval: item.duration ? formatPlayTime(item.duration) : '',
albumName: albumName,
albumId: String(item.ALBUMID || ''),
interval: String(formatPlayTime(duration) || '--/--'),
qualities: qualities
});
}
@@ -99,20 +136,28 @@ 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) {
return musicSearch(str, page, limit).then(function(data) {
var rawList = data.abslist || [];
var total = parseInt(data.TOTAL || '0', 10) || rawList.length * 10;
var list = handleSearchResult(rawList);
return { list: list, allPage: page + 1, limit: limit, total: list.length * 10, source: 'kw' };
return { list: list, allPage: Math.ceil(total / limit), limit: limit, total: total, source: 'kw' };
});
}
// ===== 播放链接 =====
function getUrl(songInfo, type) {
var q = type || '128k';
var songId = songInfo.id || '';
var ceruKey = process.env.ceru_key || '';
function getUrl(songId, quality) {
// Map QZ v2 quality names to Ceru API format
if (quality === 'standard') quality = '128k';
else if (quality === 'exhigh') quality = '320k';
else if (quality === 'lossless' || quality === 'hires' || quality === 'jymaster') quality = 'flac';
songId = String(songId || '');
quality = String(quality || '128k');
// Read env FRESH each call (dynamic refresh)
var envNow = global.env || {};
var ceruKey = envNow.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=kw&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(q), 'GET', headers)
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=kw&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), '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;
@@ -123,13 +168,13 @@ function getUrl(songInfo, type) {
// ===== 封面图 =====
function getPic(songInfo) {
if (songInfo.pic) return Promise.resolve(songInfo.pic);
if (songInfo && songInfo.pic) return Promise.resolve(songInfo.pic);
return Promise.resolve('');
}
// ===== 歌词 =====
function getLyric(songInfo) {
var rid = songInfo.id || '';
var rid = (songInfo && songInfo.id) || '';
if (!rid) return Promise.resolve('');
return requestUrl('https://kuwo-api.vercel.app/api/lyric?id=' + encodeURIComponent(rid), 'GET', {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)'
@@ -168,18 +213,20 @@ var pluginInfo = {
info: {
id: 'koneko_ceru_kw',
name: '酷我音乐 - Koneko 聆澜',
version: '0.1.0',
version: '0.1.4',
source: 'kw',
description: '酷我音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
},
ext: [],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜付费密钥。留空=公益模式(仅128k);填写=付费模式(高音质)。修改后下次播放自动生效,也可点扩展按钮激活' }],
quality: [
{ name: '标准', ui: '标', id: '128k' },
{ name: '高品', ui: 'HQ', id: '320k' },
{ name: '无损', ui: 'SQ', id: 'flac' }
{ name: '标准音质', ui: '标', id: '128k' },
{ name: '高品音质', ui: 'HQ', id: '320k' },
{ name: '无损音质', ui: 'SQ', id: 'flac' },
{ name: '高解析度无损(Hi-Res)', ui: 'HR', id: 'hires' },
{ name: '至臻母带', ui: 'MT', id: 'master' }
],
supportFunc: []
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
}
module.exports = {
@@ -188,7 +235,7 @@ module.exports = {
hotSearch: { getList: hotSearch },
leaderboard: { getBoards: leaderboard },
songList: songList,
artists: singer,
singer: singer,
album: album,
getLyric: getLyric,
getPic: getPic,

View File

@@ -1,3 +1,5 @@
var env = global.env || {};
var https = require('https');
var http = require('http');
var crypto = require('crypto');
@@ -53,42 +55,68 @@ function requestUrl(urlStr, method, headers, body) {
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);
// ===== 格式化辅助 =====
function formatPlayTime(time) {
if (!time) return '--/--';
var m = Math.floor(time / 60);
var s = Math.floor(time % 60);
if (m === 0 && s === 0) return '--/--';
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
}
function sizeFormate(bytes) {
if (typeof bytes === 'string') bytes = parseFloat(bytes.replace(/[^0-9.]/g, ''));
var n = parseFloat(bytes);
if (isNaN(n) || n < 0) return '0.00MB';
var mb = n / (1024 * 1024);
return mb.toFixed(2) + 'MB';
}
// ===== 搜索实现 =====
function stripEm(s) {
if (!s) return '';
return String(s).replace(/<\/?em>/g, '').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').trim();
}
function musicSearch(str, page, limit) {
var url = 'http://mobilecdn.kugou.com/api/v3/search/song?showtype=14&highlight=em&pagesize=' + limit + '&page=' + page + '&keyword=' + encodeURIComponent(str);
var url = 'http://mobilecdn.kugou.com/api/v3/search/song?showtype=14&highlight=&pagesize=' + limit + '&page=' + page + '&keyword=' + encodeURIComponent(str);
return requestUrl(url, 'GET', { 'User-Agent': 'Mozilla/5.0 (Linux; Android 10)' }).then(function(res) {
if (res.body && res.body.data && res.body.data.info) return res.body.data.info;
if (res.body && res.body.data && res.body.data.info) return res.body.data;
throw new Error('kg search failed');
});
}
function handleSearchResult(rawList) {
var list = [];
if (!rawList) return list;
for (var i = 0; i < rawList.length; i++) {
var item = rawList[i];
var qualities = {};
if (item.filesize) qualities.standard = String(item.filesize);
if (item.sqfilesize) qualities.exhigh = String(item.sqfilesize);
if (item.flacfilesize) qualities.lossless = String(item.flacfilesize);
var picUrl = item.img || '';
if (item.filesize) qualities['128k'] = sizeFormate(item.filesize);
if (item['320filesize']) qualities['320k'] = sizeFormate(item['320filesize']);
if (item.sqfilesize) qualities['flac'] = sizeFormate(item.sqfilesize);
// 封面:用 album_id 拼 kugou 封面 URL
var albumId = String(item.album_id || '');
var picUrl = '';
if (albumId) picUrl = 'http://imge.kugou.com/stdmusic/150/' + albumId + '.jpg';
var name = stripEm(item.songname || item.filename || '');
var singerName = stripEm(item.singername || '');
// filename 格式 "歌手 - 歌名",如果没有 songname 就从 filename 拆
if (!name && item.filename) {
var parts = String(item.filename).split(' - ');
name = stripEm(parts[1] || parts[0]);
}
list.push({
id: item.hash || item.audio_id || '',
name: item.songname || '',
artists: item.singername || '',
id: String(item.hash || ''),
name: name,
artists: singerName,
source: 'kg',
pic: picUrl,
mPic: picUrl,
sPic: picUrl,
albumName: item.album_name || '',
albumId: item.album_id || '',
interval: item.duration ? formatPlayTime(item.duration) : '',
albumName: stripEm(item.album_name || ''),
albumId: albumId,
interval: String(formatPlayTime(item.duration) || '--/--'),
qualities: qualities
});
}
@@ -99,20 +127,28 @@ 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) {
return musicSearch(str, page, limit).then(function(data) {
var rawList = data.lists || [];
var total = data.total || rawList.length * 10;
var list = handleSearchResult(rawList);
return { list: list, allPage: page + 1, limit: limit, total: list.length * 10, source: 'kg' };
return { list: list, allPage: Math.ceil(total / limit), limit: limit, total: total, source: 'kg' };
});
}
// ===== 播放链接 =====
function getUrl(songInfo, type) {
var q = type || '128k';
var songId = songInfo.id || '';
var ceruKey = process.env.ceru_key || '';
function getUrl(songId, quality) {
// Map QZ v2 quality names to Ceru API format
if (quality === 'standard') quality = '128k';
else if (quality === 'exhigh') quality = '320k';
else if (quality === 'lossless' || quality === 'hires' || quality === 'jymaster') quality = 'flac';
songId = String(songId || '');
quality = String(quality || '128k');
// Read env FRESH each call (dynamic refresh)
var envNow = global.env || {};
var ceruKey = envNow.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=kg&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(q), 'GET', headers)
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=kg&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), '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;
@@ -123,13 +159,13 @@ function getUrl(songInfo, type) {
// ===== 封面图 =====
function getPic(songInfo) {
if (songInfo.pic) return Promise.resolve(songInfo.pic);
if (songInfo && songInfo.pic) return Promise.resolve(songInfo.pic);
return Promise.resolve('');
}
// ===== 歌词 =====
function getLyric(songInfo) {
var hash = songInfo.id || '';
var hash = (songInfo && songInfo.id) || '';
if (!hash) return Promise.resolve('');
return requestUrl('https://krcs.kugou.com/search?ver=1&man=yes&client=mobi&keyword=&duration=&hash=' + encodeURIComponent(hash) + '&album_audio_id=', 'GET', {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10)'
@@ -179,18 +215,20 @@ var pluginInfo = {
info: {
id: 'koneko_ceru_kg',
name: '酷狗音乐 - Koneko 聆澜',
version: '0.1.0',
version: '0.1.4',
source: 'kg',
description: '酷狗音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
},
ext: [],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜付费密钥。留空=公益模式(仅128k);填写=付费模式(高音质)。修改后下次播放自动生效,也可点扩展按钮激活' }],
quality: [
{ name: '标准', ui: '标', id: '128k' },
{ name: '高品', ui: 'HQ', id: '320k' },
{ name: '无损', ui: 'SQ', id: 'flac' }
{ name: '标准音质', ui: '标', id: '128k' },
{ name: '高品音质', ui: 'HQ', id: '320k' },
{ name: '无损音质', ui: 'SQ', id: 'flac' },
{ name: '高解析度无损(Hi-Res)', ui: 'HR', id: 'hires' },
{ name: '至臻母带', ui: 'MT', id: 'master' }
],
supportFunc: []
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
}
module.exports = {
@@ -199,7 +237,7 @@ module.exports = {
hotSearch: { getList: hotSearch },
leaderboard: { getBoards: leaderboard },
songList: songList,
artists: singer,
singer: singer,
album: album,
getLyric: getLyric,
getPic: getPic,