Add files via upload

This commit is contained in:
hy5528
2026-05-09 09:52:32 +08:00
committed by GitHub
parent 06a1412484
commit 2c635b7f37
3 changed files with 567 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
/**
* @name 统一音乐源
* @description 基于GD音乐台(music.gdstudio.xyz)的通用音乐源
* @version 1.0.0
* @author 脚本作者:7878gyc API提供者:GDSTUDIO
*/
console.log('脚本开始执行');
// 检查lx对象
if (typeof globalThis.lx === 'undefined') {
console.log('错误: lx对象不存在');
} else {
console.log('lx版本:', globalThis.lx.version);
console.log('运行环境:', globalThis.lx.env);
// 源映射
var sourceMap = {
'kw': 'kuwo',
'wy': 'netease'
};
// 音质映射 - 添加flac支持
var qualityMap = {
'128k': '128',
'192k': '192',
'320k': '320',
'flac': '740', // 16bit flac
'flac24bit': '999' // 24bit flac(酷我可能不支持,但先加上)
};
// 各源支持的音质
var sourceQualitys = {
'kw': ['128k', '192k', '320k', 'flac'], // 酷我支持16bit flac
'wy': ['128k', '320k', 'flac'] // 网易云支持flac
};
// HTTP请求函数
function httpRequest(url) {
return new Promise(function(resolve, reject) {
console.log('发送HTTP请求:', url);
var options = {
timeout: 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'application/json'
}
};
globalThis.lx.request(url, options, function(err, resp) {
if (err) {
console.log('HTTP请求错误:', err.message || err);
reject(new Error('网络请求失败'));
return;
}
console.log('HTTP响应状态码:', resp.statusCode);
console.log('响应体类型:', typeof resp.body);
// 记录响应头中的content-type
if (resp.headers && resp.headers['content-type']) {
console.log('Content-Type:', resp.headers['content-type']);
}
// 如果响应体是对象,直接记录
if (resp.body && typeof resp.body === 'object') {
console.log('响应体是对象,键:', Object.keys(resp.body));
console.log('响应体内容:', JSON.stringify(resp.body).substring(0, 200));
} else if (typeof resp.body === 'string') {
console.log('响应体是字符串,长度:', resp.body.length);
console.log('响应体前200字符:', resp.body.substring(0, Math.min(200, resp.body.length)));
}
resolve(resp);
});
});
}
// 从响应中提取URL
function extractUrlFromResponse(resp) {
if (!resp) {
console.log('响应为空');
return null;
}
var body = resp.body;
console.log('提取URLbody类型:', typeof body);
// 如果body已经是对象
if (body && typeof body === 'object') {
console.log('body是对象,直接提取URL');
// 根据API文档,可能的返回格式:
// 1. {url: "音乐链接", br: "音质", size: "文件大小"}
// 2. {data: {url: "音乐链接", br: "音质", size: "文件大小"}}
if (body.url) {
console.log('从body.url获取URL');
return body.url;
}
if (body.data && body.data.url) {
console.log('从body.data.url获取URL');
return body.data.url;
}
// 尝试查找任何包含URL的字段
for (var key in body) {
var value = body[key];
if (typeof value === 'string' && value.startsWith('http')) {
console.log('从字段', key, '获取URL');
return value;
}
if (value && typeof value === 'object' && value.url && typeof value.url === 'string' && value.url.startsWith('http')) {
console.log('从嵌套对象', key, '.url获取URL');
return value.url;
}
}
console.log('无法从对象中提取URL,对象内容:', JSON.stringify(body).substring(0, 300));
return null;
}
// 如果body是字符串,尝试解析为JSON
if (typeof body === 'string') {
console.log('body是字符串,尝试解析为JSON');
try {
var data = JSON.parse(body);
if (data.url) return data.url;
if (data.data && data.data.url) return data.data.url;
} catch (e) {
console.log('JSON解析失败:', e.message);
}
// 如果不是JSON,尝试正则匹配URL
var urlMatch = body.match(/https?:\/\/[^\s<>"']+/);
if (urlMatch) {
console.log('从字符串中正则匹配到URL');
return urlMatch[0];
}
}
console.log('无法提取URL');
return null;
}
// 音质降级策略
function getQualityFallbackChain(quality) {
var chain = [];
switch(quality) {
case 'flac24bit':
chain = ['flac24bit', 'flac', '320k', '192k', '128k'];
break;
case 'flac':
chain = ['flac', '320k', '192k', '128k'];
break;
case '320k':
chain = ['320k', '192k', '128k'];
break;
case '192k':
chain = ['192k', '128k'];
break;
case '128k':
chain = ['128k'];
break;
default:
chain = ['320k', '128k'];
}
return chain;
}
// 获取音乐URL(支持音质降级)
function getMusicUrl(musicInfo, quality) {
console.log('开始获取音乐URL:', {
source: musicInfo.source,
songmid: musicInfo.songmid,
id: musicInfo.id,
quality: quality
});
return new Promise(function(resolve, reject) {
try {
var source = musicInfo.source;
var apiSource = sourceMap[source];
if (!apiSource) {
console.log('不支持的音源:', source);
reject(new Error('暂不支持此音源'));
return;
}
var songId = musicInfo.songmid || musicInfo.id;
if (!songId) {
console.log('缺少歌曲ID');
reject(new Error('缺少歌曲ID'));
return;
}
// 获取该源支持的音质列表
var supportedQualitys = sourceQualitys[source] || ['128k', '320k'];
var qualityChain = getQualityFallbackChain(quality);
// 过滤掉不支持的音质
qualityChain = qualityChain.filter(function(q) {
return supportedQualitys.includes(q);
});
console.log('音质尝试链:', qualityChain);
// 递归尝试不同音质
function tryQualityChain(index) {
if (index >= qualityChain.length) {
reject(new Error('所有音质尝试均失败'));
return;
}
var currentQuality = qualityChain[index];
var br = qualityMap[currentQuality] || '320';
console.log('尝试音质:', currentQuality, '-> br:', br);
var url = 'https://music-api.gdstudio.xyz/api.php?types=url&source=' + apiSource + '&id=' + songId + '&br=' + br;
httpRequest(url).then(function(resp) {
if (resp.statusCode !== 200) {
console.log('音质', currentQuality, '请求失败,状态码:', resp.statusCode);
tryQualityChain(index + 1);
return;
}
var musicUrl = extractUrlFromResponse(resp);
if (musicUrl) {
console.log('成功获取', currentQuality, '音质URL');
resolve(musicUrl);
} else {
console.log('音质', currentQuality, '无法提取URL');
tryQualityChain(index + 1);
}
}).catch(function(err) {
console.log('音质', currentQuality, '请求出错:', err.message);
tryQualityChain(index + 1);
});
}
// 开始尝试
tryQualityChain(0);
} catch (error) {
console.log('获取音乐URL过程中发生异常:', error);
reject(error);
}
});
}
// 注册事件处理器
console.log('注册事件处理器');
globalThis.lx.on(globalThis.lx.EVENT_NAMES.request, function(data) {
console.log('收到请求事件, action:', data.action);
if (data.action === 'musicUrl') {
return getMusicUrl(data.info.musicInfo, data.info.type);
}
return Promise.reject(new Error('不支持的action: ' + data.action));
});
// 初始化
console.log('准备初始化');
setTimeout(function() {
try {
console.log('发送初始化事件');
var config = {
sources: {
kw: {
name: '酷我音乐',
type: 'music',
actions: ['musicUrl'],
qualitys: ['128k', '192k', '320k', 'flac'] // 添加flac支持
},
wy: {
name: '网易云音乐',
type: 'music',
actions: ['musicUrl'],
qualitys: ['128k', '320k', 'flac'] // 添加flac支持
}
}
};
globalThis.lx.send(globalThis.lx.EVENT_NAMES.inited, config);
console.log('初始化完成');
} catch (error) {
console.log('初始化失败:', error);
}
}, 100);
}
console.log('脚本加载完成');
+7
View File
@@ -0,0 +1,7 @@
/*!
* @name 聚合API接口 (CF)
* @description v3
* @version 3
* @author lerd
*/
let{stringify:t,parse:a}=JSON;let x=(r)=>{throw new Error(r)};let{EVENT_NAMES:n,request:b,on,send:y,version:v}=globalThis.lx;let A='https://api.music.lerd.dpdns.org';let h=(u,o={method:'GET'})=>new Promise((s,j)=>{b(u,o,(e,r)=>{if(e)return j(e);s(r)})});h(A+'/init.conf').then(r=>{if(r.body.code!==200)x("脚本初始化失败");let U=r.body.data;if(U.update.version>v)y(n.updateAlert,U.update);y(n.inited,U.init);}).catch(e=>x(e));on(n.request,async({action,source,info})=>{let r=await h(`${A}/${source}`,{method:'POST',body:t(info),headers:{'Content-Type':'application/json'}});let B=r.body;if(B.code===200)return B.data.url;else if(B.code===303){let S=a(t(B.data));let D=S.request;let F=S.response;try{let z=await h(encodeURI(D.url),D.options);if(F.check.key.reduce((a,c)=>a&&a[c],z)==F.check.value){let u=F.url.reduce((a,c)=>a&&a[c],z);if(u.startsWith("http"))return u;}}catch(e){x(e)}}else x(B.msg);});
+255
View File
@@ -0,0 +1,255 @@
/*!
* @name 肥猫不肥
* @description 肥猫不肥
* @version 肥猫不肥
* @author 肥猫不肥
* @repository 肥猫不肥
*/
// 是否开启开发模式
const DEV_ENABLE = false
// 服务端地址
const API_URL = "http://music.xn--z7x900a.live"
// 服务端配置的请求key
const API_KEY = `114514`
// 音质配置(key为音源名称,不要乱填.如果你账号为VIP可以填写到hires)
// 全部的支持值: ['128k', '320k', 'flac', 'flac24bit']
const MUSIC_QUALITY = JSON.parse('{"kw":["128k","320k","flac"],"kg":["128k"],"tx":["128k"],"wy":["128k"],"mg":["128k"]}')
// 音源配置(默认为自动生成,可以修改为手动)
const MUSIC_SOURCE = Object.keys(MUSIC_QUALITY)
MUSIC_SOURCE.push('local')
/**
* 下面的东西就不要修改了
*/
const { EVENT_NAMES, request, on, send, utils, env, version } = globalThis.lx
/**
* URL请求
*
* @param {string} url - 请求的地址
* @param {object} options - 请求的配置文件
* @return {Promise} 携带响应体的Promise对象
*/
const httpFetch = (url, options = { method: 'GET' }) => {
return new Promise((resolve, reject) => {
console.log('--- start --- ' + url)
request(url, options, (err, resp) => {
if (err) return reject(err)
console.log('API Response: ', resp)
resolve(resp)
})
})
}
/**
* Encodes the given data to base64.
*
* @param {type} data - the data to be encoded
* @return {string} the base64 encoded string
*/
const handleBase64Encode = (data) => {
var data = utils.buffer.from(data, 'utf-8')
return utils.buffer.bufToString(data, 'base64')
}
/**
*
* @param {string} source - 音源
* @param {object} musicInfo - 歌曲信息
* @param {string} quality - 音质
* @returns {Promise<string>} 歌曲播放链接
* @throws {Error} - 错误消息
*/
const handleGetMusicUrl = async (source, musicInfo, quality) => {
if (source == 'local') {
if (!musicInfo.songmid.startsWith('server_')) throw new Error('upsupported local file')
const songId = musicInfo.songmid
const requestBody = {
p: songId.replace('server_', ''),
}
var t = 'c'
var b = handleBase64Encode(JSON.stringify(requestBody)) /* url safe*/.replace(/\+/g, '-').replace(/\//g, '_')
const targetUrl = `${API_URL}/local/${t}?q=${b}`
const request = await httpFetch(targetUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}`,
'X-Request-Key': API_KEY,
},
follow_max: 5,
})
const { body } = request
if (body.code == 0 && body.data && body.data.file) {
var t = 'u'
var b = handleBase64Encode(JSON.stringify(requestBody)) /* url safe*/.replace(/\+/g, '-').replace(/\//g, '_')
return `${API_URL}/local/${t}?q=${b}`
}
throw new Error('404 Not Found')
}
const songId = musicInfo.hash ?? musicInfo.songmid
const request = await httpFetch(`${API_URL}/url/${source}/${songId}/${quality}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}`,
'X-Request-Key': API_KEY,
},
follow_max: 5,
})
const { body } = request
if (!body || isNaN(Number(body.code))) throw new Error('unknow error')
if (env != 'mobile') console.groupEnd()
switch (body.code) {
case 0:
console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) success, URL: ${body.data}`)
return body.data
case 1:
console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed: ip被封禁`)
throw new Error('block ip')
case 2:
console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, ${body.msg}`)
throw new Error('get music url failed')
case 4:
console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, 远程服务器错误`)
throw new Error('internal server error')
case 5:
console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, 请求过于频繁,请休息一下吧`)
throw new Error('too many requests')
case 6:
console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, 请求参数错误`)
throw new Error('param error')
default:
console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, ${body.msg ? body.msg : 'unknow error'}`)
throw new Error(body.msg ?? 'unknow error')
}
}
const handleGetMusicPic = async (source, musicInfo) => {
switch (source) {
case 'local':
// 先从服务器检查是否有对应的类型,再响应链接
if (!musicInfo.songmid.startsWith('server_')) throw new Error('upsupported local file')
const songId = musicInfo.songmid
const requestBody = {
p: songId.replace('server_', ''),
}
var t = 'c'
var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_')
const targetUrl = `${API_URL}/local/${t}?q=${b}`
const request = await httpFetch(targetUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}`
},
follow_max: 5,
})
const { body } = request
if (body.code === 0 && body.data.cover) {
var t = 'p'
var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_')
return `${API_URL}/local/${t}?q=${b}`
}
throw new Error('get music pic failed')
default:
throw new Error('action(pic) does not support source(' + source + ')')
}
}
const handleGetMusicLyric = async (source, musicInfo) => {
switch (source) {
case 'local':
if (!musicInfo.songmid.startsWith('server_')) throw new Error('upsupported local file')
const songId = musicInfo.songmid
const requestBody = {
p: songId.replace('server_', ''),
}
var t = 'c'
var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_')
const targetUrl = `${API_URL}/local/${t}?q=${b}`
const request = await httpFetch(targetUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}`
},
follow_max: 5,
})
const { body } = request
if (body.code === 0 && body.data.lyric) {
var t = 'l'
var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_')
const request2 = await httpFetch(`${API_URL}/local/${t}?q=${b}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}`
},
follow_max: 5,
})
if (request2.body.code === 0) {
return {
lyric: request2.body.data ?? "",
tlyric: "",
rlyric: "",
lxlyric: ""
}
}
throw new Error('get music lyric failed')
}
throw new Error('get music lyric failed')
default:
throw new Error('action(lyric) does not support source(' + source + ')')
}
}
// 生成歌曲信息
const musicSources = {}
MUSIC_SOURCE.forEach(item => {
musicSources[item] = {
name: item,
type: 'music',
actions: (item == 'local') ? ['musicUrl', 'pic', 'lyric'] : ['musicUrl'],
qualitys: (item == 'local') ? [] : MUSIC_QUALITY[item],
}
})
// 监听 LX Music 请求事件
on(EVENT_NAMES.request, ({ action, source, info }) => {
switch (action) {
case 'musicUrl':
if (env != 'mobile') {
console.group(`Handle Action(musicUrl)`)
console.log('source', source)
console.log('quality', info.type)
console.log('musicInfo', info.musicInfo)
} else {
console.log(`Handle Action(musicUrl)`)
console.log('source', source)
console.log('quality', info.type)
console.log('musicInfo', info.musicInfo)
}
return handleGetMusicUrl(source, info.musicInfo, info.type)
.then(data => Promise.resolve(data))
.catch(err => Promise.reject(err))
case 'pic':
return handleGetMusicPic(source, info.musicInfo)
.then(data => Promise.resolve(data))
.catch(err => Promise.reject(err))
case 'lyric':
return handleGetMusicLyric(source, info.musicInfo)
.then(data => Promise.resolve(data))
.catch(err => Promise.reject(err))
default:
console.error(`action(${action}) not support`)
return Promise.reject('action not support')
}
})
// 向 LX Music 发送初始化成功事件
send(EVENT_NAMES.inited, { status: true, openDevTools: DEV_ENABLE, sources: musicSources })