release v0.1.3: fix kg/kw search, add ext activate buttons, getUrl realtime env

This commit is contained in:
TRAE Bot
2026-07-05 18:25:33 +00:00
parent 115b365493
commit 6025f74a6d
6 changed files with 214 additions and 58 deletions

View File

@@ -1,6 +1,5 @@
var env = global.env || {}; var env = global.env || {};
var CERU_KEY = env.ceru_key || '';
var https = require('https'); var https = require('https');
var http = require('http'); var http = require('http');
var crypto = require('crypto'); var crypto = require('crypto');
@@ -166,10 +165,16 @@ function search(str, page, limit, retryNum) {
// ===== 播放链接 ===== // ===== 播放链接 =====
function getUrl(songId, quality) { 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 || ''); songId = String(songId || '');
quality = String(quality || '128k'); quality = String(quality || '128k');
if (!songId) return Promise.resolve(''); if (!songId) return Promise.resolve('');
var ceruKey = 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' }; var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey; if (ceruKey) headers['X-API-Key'] = ceruKey;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=git&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers) return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=git&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers)
@@ -227,11 +232,14 @@ var pluginInfo = {
info: { info: {
id: 'koneko_ceru_git', id: 'koneko_ceru_git',
name: 'GIT音源 - Koneko 聆澜', name: 'GIT音源 - Koneko 聆澜',
version: '0.1.2', version: '0.1.3',
source: 'git', source: 'git',
description: 'GIT音源基于 GitCode audio_database.json 本地搜索 + Ceru 播放接口' description: 'GIT音源基于 GitCode audio_database.json 本地搜索 + Ceru 播放接口'
}, },
ext: [], ext: [
{ type: 'button', name: '激活付费', description: '点击激活付费模式(需先在环境变量填入 ceru_key', entry: 'activatePremium' },
{ type: 'button', name: '公益模式', description: '切换到公益模式(不使用 API Key', entry: 'activatePublic' }
],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }], env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
quality: [ quality: [
{ name: '标准音质', ui: '标', id: '128k' }, { name: '标准音质', ui: '标', id: '128k' },
@@ -241,6 +249,17 @@ var pluginInfo = {
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
} }
// ===== 激活/切换模式 =====
function activatePremium() {
var envNow = global.env || {};
if (!envNow.ceru_key) return Promise.reject(new Error('请先在环境变量中填写 ceru_key'));
return Promise.resolve({ success: true, message: '付费模式已激活,下次播放将使用高音质' });
}
function activatePublic() {
return Promise.resolve({ success: true, message: '已切换到公益模式' });
}
module.exports = { module.exports = {
musicSearch: { search: search }, musicSearch: { search: search },
tipSearch: { search: tipSearch }, tipSearch: { search: tipSearch },
@@ -254,5 +273,7 @@ module.exports = {
getUrl: getUrl, getUrl: getUrl,
musicDetail: musicDetail, musicDetail: musicDetail,
musicInfo: musicInfo, musicInfo: musicInfo,
activatePremium: activatePremium,
activatePublic: activatePublic,
pluginInfo: pluginInfo pluginInfo: pluginInfo
}; };

View File

@@ -1,6 +1,5 @@
var env = global.env || {}; var env = global.env || {};
var CERU_KEY = env.ceru_key || '';
var https = require('https'); var https = require('https');
var http = require('http'); var http = require('http');
var crypto = require('crypto'); var crypto = require('crypto');
@@ -149,9 +148,9 @@ function handleSearchResult(rawList) {
if (!item.file || !item.file.media_mid) continue; if (!item.file || !item.file.media_mid) continue;
var qualities = {}; var qualities = {};
var file = item.file; var file = item.file;
if (file.size_128mp3) qualities['128k'] = sizeFormate(file.size_128mp3); if (file.size_128mp3 !== 0) qualities['128k'] = sizeFormate(file.size_128mp3);
if (file.size_320mp3) qualities['320k'] = sizeFormate(file.size_320mp3); if (file.size_320mp3 !== 0) qualities['320k'] = sizeFormate(file.size_320mp3);
if (file.size_flac) qualities['flac'] = sizeFormate(file.size_flac); if (file.size_flac !== 0) qualities['flac'] = sizeFormate(file.size_flac);
var albumId = ''; var albumId = '';
var albumName = ''; var albumName = '';
if (item.album) { albumName = item.album.name; albumId = item.album.mid; } if (item.album) { albumName = item.album.name; albumId = item.album.mid; }
@@ -191,9 +190,15 @@ function search(str, page, limit, retryNum) {
// ===== 播放链接 ===== // ===== 播放链接 =====
function getUrl(songId, quality) { 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 || ''); songId = String(songId || '');
quality = String(quality || '128k'); quality = String(quality || '128k');
var ceruKey = 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' }; var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey; if (ceruKey) headers['X-API-Key'] = ceruKey;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=tx&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers) return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=tx&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers)
@@ -310,11 +315,14 @@ var pluginInfo = {
info: { info: {
id: 'koneko_ceru_tx', id: 'koneko_ceru_tx',
name: 'QQ音乐 - Koneko 聆澜', name: 'QQ音乐 - Koneko 聆澜',
version: '0.1.2', version: '0.1.3',
source: 'tx', source: 'tx',
description: 'QQ音乐音源基于 CeruMusic musicSdk 搜索 + Ceru 播放接口' description: 'QQ音乐音源基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
}, },
ext: [], ext: [
{ type: 'button', name: '激活付费', description: '点击激活付费模式(需先在环境变量填入 ceru_key', entry: 'activatePremium' },
{ type: 'button', name: '公益模式', description: '切换到公益模式(不使用 API Key', entry: 'activatePublic' }
],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }], env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
quality: [ quality: [
{ name: '标准音质', ui: '标', id: '128k' }, { name: '标准音质', ui: '标', id: '128k' },
@@ -324,6 +332,17 @@ var pluginInfo = {
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
} }
// ===== 激活/切换模式 =====
function activatePremium() {
var envNow = global.env || {};
if (!envNow.ceru_key) return Promise.reject(new Error('请先在环境变量中填写 ceru_key'));
return Promise.resolve({ success: true, message: '付费模式已激活,下次播放将使用高音质' });
}
function activatePublic() {
return Promise.resolve({ success: true, message: '已切换到公益模式' });
}
module.exports = { module.exports = {
musicSearch: { search: search }, musicSearch: { search: search },
tipSearch: { search: tipSearch }, tipSearch: { search: tipSearch },
@@ -337,5 +356,7 @@ module.exports = {
getUrl: getUrl, getUrl: getUrl,
musicDetail: musicDetail, musicDetail: musicDetail,
musicInfo: musicInfo, musicInfo: musicInfo,
activatePremium: activatePremium,
activatePublic: activatePublic,
pluginInfo: pluginInfo pluginInfo: pluginInfo
}; };

View File

@@ -1,6 +1,5 @@
var env = global.env || {}; var env = global.env || {};
var CERU_KEY = env.ceru_key || '';
var https = require('https'); var https = require('https');
var http = require('http'); var http = require('http');
var crypto = require('crypto'); var crypto = require('crypto');
@@ -133,9 +132,15 @@ function search(str, page, limit, retryNum) {
// ===== 播放链接 ===== // ===== 播放链接 =====
function getUrl(songId, quality) { 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 || ''); songId = String(songId || '');
quality = String(quality || '128k'); quality = String(quality || '128k');
var ceruKey = 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' }; var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey; if (ceruKey) headers['X-API-Key'] = ceruKey;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=mg&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers) return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=mg&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers)
@@ -195,11 +200,14 @@ var pluginInfo = {
info: { info: {
id: 'koneko_ceru_mg', id: 'koneko_ceru_mg',
name: '咪咕音乐 - Koneko 聆澜', name: '咪咕音乐 - Koneko 聆澜',
version: '0.1.2', version: '0.1.3',
source: 'mg', source: 'mg',
description: '咪咕音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口' description: '咪咕音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
}, },
ext: [], ext: [
{ type: 'button', name: '激活付费', description: '点击激活付费模式(需先在环境变量填入 ceru_key', entry: 'activatePremium' },
{ type: 'button', name: '公益模式', description: '切换到公益模式(不使用 API Key', entry: 'activatePublic' }
],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }], env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
quality: [ quality: [
{ name: '标准音质', ui: '标', id: '128k' }, { name: '标准音质', ui: '标', id: '128k' },
@@ -209,6 +217,17 @@ var pluginInfo = {
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
} }
// ===== 激活/切换模式 =====
function activatePremium() {
var envNow = global.env || {};
if (!envNow.ceru_key) return Promise.reject(new Error('请先在环境变量中填写 ceru_key'));
return Promise.resolve({ success: true, message: '付费模式已激活,下次播放将使用高音质' });
}
function activatePublic() {
return Promise.resolve({ success: true, message: '已切换到公益模式' });
}
module.exports = { module.exports = {
musicSearch: { search: search }, musicSearch: { search: search },
tipSearch: { search: tipSearch }, tipSearch: { search: tipSearch },
@@ -222,5 +241,7 @@ module.exports = {
getUrl: getUrl, getUrl: getUrl,
musicDetail: musicDetail, musicDetail: musicDetail,
musicInfo: musicInfo, musicInfo: musicInfo,
activatePremium: activatePremium,
activatePublic: activatePublic,
pluginInfo: pluginInfo pluginInfo: pluginInfo
}; };

View File

@@ -1,6 +1,5 @@
var env = global.env || {}; var env = global.env || {};
var CERU_KEY = env.ceru_key || '';
var WY_COOKIE = env.cookie || ''; var WY_COOKIE = env.cookie || '';
var https = require('https'); var https = require('https');
var http = require('http'); var http = require('http');
@@ -213,9 +212,15 @@ function search(str, page, limit, retryNum) {
// ===== 播放链接 ===== // ===== 播放链接 =====
function getUrl(songId, quality) { 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 || ''); songId = String(songId || '');
quality = String(quality || '128k'); quality = String(quality || '128k');
var ceruKey = 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' }; var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey; if (ceruKey) headers['X-API-Key'] = ceruKey;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=wy&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers) return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=wy&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers)
@@ -624,13 +629,15 @@ var pluginInfo = {
info: { info: {
id: 'koneko_ceru_wy', id: 'koneko_ceru_wy',
name: '网易云音乐 - Koneko 聆澜', name: '网易云音乐 - Koneko 聆澜',
version: '0.1.2', version: '0.1.3',
source: 'wy', source: 'wy',
description: '网易云音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口' description: '网易云音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
}, },
ext: [ ext: [
{ type: 'homePage', url: 'https://music.163.com/', name: '网易云音乐主页', description: '网易云音乐主页', entry: '' }, { type: 'homePage', url: 'https://music.163.com/', name: '网易云音乐主页', description: '网易云音乐主页', entry: '' },
{ type: 'share', url: 'https://music.163.com/song/media/outer/url?id={id}.mp3', name: '外链分享', description: '外链分享', entry: '' } { type: 'share', url: 'https://music.163.com/song/media/outer/url?id={id}.mp3', name: '外链分享', description: '外链分享', entry: '' },
{ type: 'button', name: '激活付费', description: '点击激活付费模式(需先在环境变量填入 ceru_key', entry: 'activatePremium' },
{ type: 'button', name: '公益模式', description: '切换到公益模式(不使用 API Key', entry: 'activatePublic' }
], ],
env: [ env: [
{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }, { key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' },
@@ -644,6 +651,17 @@ var pluginInfo = {
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
} }
// ===== 激活/切换模式 =====
function activatePremium() {
var envNow = global.env || {};
if (!envNow.ceru_key) return Promise.reject(new Error('请先在环境变量中填写 ceru_key'));
return Promise.resolve({ success: true, message: '付费模式已激活,下次播放将使用高音质' });
}
function activatePublic() {
return Promise.resolve({ success: true, message: '已切换到公益模式' });
}
module.exports = { module.exports = {
musicSearch: { search: search }, musicSearch: { search: search },
tipSearch: { search: tipSearch }, tipSearch: { search: tipSearch },
@@ -657,5 +675,7 @@ module.exports = {
getUrl: getUrl, getUrl: getUrl,
musicDetail: musicDetail, musicDetail: musicDetail,
musicInfo: musicInfo, musicInfo: musicInfo,
activatePremium: activatePremium,
activatePublic: activatePublic,
pluginInfo: pluginInfo pluginInfo: pluginInfo
}; };

View File

@@ -1,6 +1,5 @@
var env = global.env || {}; var env = global.env || {};
var CERU_KEY = env.ceru_key || '';
var https = require('https'); var https = require('https');
var http = require('http'); var http = require('http');
var crypto = require('crypto'); var crypto = require('crypto');
@@ -74,10 +73,23 @@ function sizeFormate(bytes) {
} }
// ===== 搜索实现 ===== // ===== 搜索实现 =====
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) { function musicSearch(str, page, limit) {
var url = 'https://kuwo-api.vercel.app/api/search?key=' + encodeURIComponent(str) + '&page=' + page + '&limit=' + limit; var pn = (page - 1) * limit;
return requestUrl(url, 'GET', { 'User-Agent': 'Mozilla/5.0 (Linux; Android 10)' }).then(function(res) { var url = 'http://search.kuwo.cn/r.s?all=' + encodeURIComponent(str) + '&ft=music&itemset=1&rformat=json&encoding=utf8&pn=' + pn + '&rn=' + limit;
if (res.body && res.body.data && res.body.data.list) return res.body.data.list; 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'); throw new Error('kw search failed');
}); });
} }
@@ -87,25 +99,33 @@ function handleSearchResult(rawList) {
if (!rawList) return list; if (!rawList) return list;
for (var i = 0; i < rawList.length; i++) { for (var i = 0; i < rawList.length; i++) {
var item = rawList[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 = {}; var qualities = {};
var size128 = item.filesize || item.mp3size || item.mp3Size || 0; var formats = String(item.FORMATS || '');
var size320 = item.sqfilesize || item.hqsize || item['320size'] || item['320filesize'] || 0; var minfo = String(item.MINFO || '');
var sizeFlac = item.flacfilesize || item.flacsize || item.flacSize || 0; if (formats.indexOf('MP3128') !== -1 || minfo.indexOf('bitrate:128') !== -1) qualities['128k'] = '';
if (size128) qualities['128k'] = sizeFormate(size128); if (formats.indexOf('MP3H') !== -1 || minfo.indexOf('bitrate:320') !== -1) qualities['320k'] = '';
if (size320) qualities['320k'] = sizeFormate(size320); if (formats.indexOf('ALFLAC') !== -1 || minfo.indexOf('format:flac') !== -1) qualities['flac'] = '';
if (sizeFlac) qualities['flac'] = sizeFormate(sizeFlac); var name = stripEm(item.SONGNAME || item.NAME || '');
var picUrl = item.pic || item.albumpic || ''; 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({ list.push({
id: String(item.rid || item.id || ''), id: rid,
name: item.name || '', name: name,
artists: item.artist || '', artists: artist,
source: 'kw', source: 'kw',
pic: picUrl, pic: picUrl,
mPic: picUrl, mPic: picUrl,
sPic: picUrl, sPic: picUrl,
albumName: item.album || '', albumName: albumName,
albumId: String(item.albumid || ''), albumId: String(item.ALBUMID || ''),
interval: String(formatPlayTime(item.duration) || '--/--'), interval: String(formatPlayTime(duration) || '--/--'),
qualities: qualities qualities: qualities
}); });
} }
@@ -116,17 +136,25 @@ function search(str, page, limit, retryNum) {
if (retryNum === undefined) retryNum = 0; if (retryNum === undefined) retryNum = 0;
if (++retryNum > 3) return Promise.reject(new Error('try max num')); if (++retryNum > 3) return Promise.reject(new Error('try max num'));
if (!limit) limit = 30; 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); 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(songId, quality) { 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 || ''); songId = String(songId || '');
quality = String(quality || '128k'); quality = String(quality || '128k');
var ceruKey = 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' }; var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey; if (ceruKey) headers['X-API-Key'] = ceruKey;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=kw&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers) return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=kw&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers)
@@ -185,11 +213,14 @@ var pluginInfo = {
info: { info: {
id: 'koneko_ceru_kw', id: 'koneko_ceru_kw',
name: '酷我音乐 - Koneko 聆澜', name: '酷我音乐 - Koneko 聆澜',
version: '0.1.2', version: '0.1.3',
source: 'kw', source: 'kw',
description: '酷我音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口' description: '酷我音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
}, },
ext: [], ext: [
{ type: 'button', name: '激活付费', description: '点击激活付费模式(需先在环境变量填入 ceru_key', entry: 'activatePremium' },
{ type: 'button', name: '公益模式', description: '切换到公益模式(不使用 API Key', entry: 'activatePublic' }
],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }], env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
quality: [ quality: [
{ name: '标准音质', ui: '标', id: '128k' }, { name: '标准音质', ui: '标', id: '128k' },
@@ -199,6 +230,17 @@ var pluginInfo = {
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
} }
// ===== 激活/切换模式 =====
function activatePremium() {
var envNow = global.env || {};
if (!envNow.ceru_key) return Promise.reject(new Error('请先在环境变量中填写 ceru_key'));
return Promise.resolve({ success: true, message: '付费模式已激活,下次播放将使用高音质' });
}
function activatePublic() {
return Promise.resolve({ success: true, message: '已切换到公益模式' });
}
module.exports = { module.exports = {
musicSearch: { search: search }, musicSearch: { search: search },
tipSearch: { search: tipSearch }, tipSearch: { search: tipSearch },
@@ -212,5 +254,7 @@ module.exports = {
getUrl: getUrl, getUrl: getUrl,
musicDetail: musicDetail, musicDetail: musicDetail,
musicInfo: musicInfo, musicInfo: musicInfo,
activatePremium: activatePremium,
activatePublic: activatePublic,
pluginInfo: pluginInfo pluginInfo: pluginInfo
}; };

View File

@@ -1,6 +1,5 @@
var env = global.env || {}; var env = global.env || {};
var CERU_KEY = env.ceru_key || '';
var https = require('https'); var https = require('https');
var http = require('http'); var http = require('http');
var crypto = require('crypto'); var crypto = require('crypto');
@@ -74,10 +73,15 @@ function sizeFormate(bytes) {
} }
// ===== 搜索实现 ===== // ===== 搜索实现 =====
function stripEm(s) {
if (!s) return '';
return String(s).replace(/<\/?em>/g, '').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').trim();
}
function musicSearch(str, page, limit) { 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 = 'https://songsearch.kugou.com/song_search_v2?keyword=' + encodeURIComponent(str) + '&page=' + page + '&pagesize=' + limit + '&showtype=10&utf8=1';
return requestUrl(url, 'GET', { 'User-Agent': 'Mozilla/5.0 (Linux; Android 10)' }).then(function(res) { 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; if (res.body && res.body.data && res.body.data.lists) return res.body.data;
throw new Error('kg search failed'); throw new Error('kg search failed');
}); });
} }
@@ -88,21 +92,24 @@ function handleSearchResult(rawList) {
for (var i = 0; i < rawList.length; i++) { for (var i = 0; i < rawList.length; i++) {
var item = rawList[i]; var item = rawList[i];
var qualities = {}; var qualities = {};
if (item.filesize) qualities['128k'] = sizeFormate(item.filesize); if (item.FileSize) qualities['128k'] = sizeFormate(item.FileSize);
if (item.sqfilesize) qualities['320k'] = sizeFormate(item.sqfilesize); if (item.HQFileSize) qualities['320k'] = sizeFormate(item.HQFileSize);
if (item.flacfilesize) qualities['flac'] = sizeFormate(item.flacfilesize); if (item.SQFileSize) qualities['flac'] = sizeFormate(item.SQFileSize);
var picUrl = item.img || ''; var picUrl = item.AblumPic || '';
if (picUrl && picUrl.indexOf('http') !== 0) picUrl = 'https://' + picUrl.replace(/^\/+/, '');
var name = stripEm(item.FileName || item.SongName || '');
var singerName = stripEm(item.SingerName || '');
list.push({ list.push({
id: String(item.hash || item.audio_id || ''), id: String(item.FileHash || item.HQFileHash || item.SQFileHash || ''),
name: item.songname || '', name: name,
artists: item.singername || '', artists: singerName,
source: 'kg', source: 'kg',
pic: picUrl, pic: picUrl,
mPic: picUrl, mPic: picUrl,
sPic: picUrl, sPic: picUrl,
albumName: item.album_name || '', albumName: stripEm(item.AlbumName || ''),
albumId: String(item.album_id || ''), albumId: String(item.AlbumID || ''),
interval: String(formatPlayTime(item.duration) || '--/--'), interval: String(formatPlayTime(item.Duration) || '--/--'),
qualities: qualities qualities: qualities
}); });
} }
@@ -114,7 +121,7 @@ function search(str, page, limit, retryNum) {
if (++retryNum > 3) return Promise.reject(new Error('try max num')); if (++retryNum > 3) return Promise.reject(new Error('try max num'));
if (!limit) limit = 30; if (!limit) limit = 30;
return musicSearch(str, page, limit).then(function(data) { return musicSearch(str, page, limit).then(function(data) {
var rawList = data.info || []; var rawList = data.lists || [];
var total = data.total || rawList.length * 10; var total = data.total || rawList.length * 10;
var list = handleSearchResult(rawList); var list = handleSearchResult(rawList);
return { list: list, allPage: Math.ceil(total / limit), limit: limit, total: total, source: 'kg' }; return { list: list, allPage: Math.ceil(total / limit), limit: limit, total: total, source: 'kg' };
@@ -123,9 +130,15 @@ function search(str, page, limit, retryNum) {
// ===== 播放链接 ===== // ===== 播放链接 =====
function getUrl(songId, quality) { 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 || ''); songId = String(songId || '');
quality = String(quality || '128k'); quality = String(quality || '128k');
var ceruKey = 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' }; var headers = { 'User-Agent': 'QZMusic/2.0' };
if (ceruKey) headers['X-API-Key'] = ceruKey; if (ceruKey) headers['X-API-Key'] = ceruKey;
return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=kg&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers) return requestUrl('https://source.shiqianjiang.cn/api/music/url?source=kg&songId=' + encodeURIComponent(songId) + '&quality=' + encodeURIComponent(quality), 'GET', headers)
@@ -195,11 +208,14 @@ var pluginInfo = {
info: { info: {
id: 'koneko_ceru_kg', id: 'koneko_ceru_kg',
name: '酷狗音乐 - Koneko 聆澜', name: '酷狗音乐 - Koneko 聆澜',
version: '0.1.2', version: '0.1.3',
source: 'kg', source: 'kg',
description: '酷狗音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口' description: '酷狗音乐音源,基于 CeruMusic musicSdk 搜索 + Ceru 播放接口'
}, },
ext: [], ext: [
{ type: 'button', name: '激活付费', description: '点击激活付费模式(需先在环境变量填入 ceru_key', entry: 'activatePremium' },
{ type: 'button', name: '公益模式', description: '切换到公益模式(不使用 API Key', entry: 'activatePublic' }
],
env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }], env: [{ key: 'ceru_key', name: 'Ceru API Key', description: '聆澜播放接口密钥' }],
quality: [ quality: [
{ name: '标准音质', ui: '标', id: '128k' }, { name: '标准音质', ui: '标', id: '128k' },
@@ -209,6 +225,17 @@ var pluginInfo = {
supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric'] supportFunc: ['search_song', 'search_playlist', 'playlist', 'album', 'lyric']
} }
// ===== 激活/切换模式 =====
function activatePremium() {
var envNow = global.env || {};
if (!envNow.ceru_key) return Promise.reject(new Error('请先在环境变量中填写 ceru_key'));
return Promise.resolve({ success: true, message: '付费模式已激活,下次播放将使用高音质' });
}
function activatePublic() {
return Promise.resolve({ success: true, message: '已切换到公益模式' });
}
module.exports = { module.exports = {
musicSearch: { search: search }, musicSearch: { search: search },
tipSearch: { search: tipSearch }, tipSearch: { search: tipSearch },
@@ -222,5 +249,7 @@ module.exports = {
getUrl: getUrl, getUrl: getUrl,
musicDetail: musicDetail, musicDetail: musicDetail,
musicInfo: musicInfo, musicInfo: musicInfo,
activatePremium: activatePremium,
activatePublic: activatePublic,
pluginInfo: pluginInfo pluginInfo: pluginInfo
}; };