From 2c635b7f3722c19b430845d589385ce1c92823a2 Mon Sep 17 00:00:00 2001 From: hy5528 <95360525+hy5528@users.noreply.github.com> Date: Sat, 9 May 2026 09:52:32 +0800 Subject: [PATCH] Add files via upload --- jar/统一音乐源.js | 305 ++++++++++++++++++++++++++++++++++++++++ jar/聚合API接口 (CF).js | 7 + jar/肥猫不肥.js | 255 +++++++++++++++++++++++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 jar/统一音乐源.js create mode 100644 jar/聚合API接口 (CF).js create mode 100644 jar/肥猫不肥.js diff --git a/jar/统一音乐源.js b/jar/统一音乐源.js new file mode 100644 index 0000000..df44509 --- /dev/null +++ b/jar/统一音乐源.js @@ -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('提取URL,body类型:', 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('脚本加载完成'); diff --git a/jar/聚合API接口 (CF).js b/jar/聚合API接口 (CF).js new file mode 100644 index 0000000..da25829 --- /dev/null +++ b/jar/聚合API接口 (CF).js @@ -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);}); \ No newline at end of file diff --git a/jar/肥猫不肥.js b/jar/肥猫不肥.js new file mode 100644 index 0000000..81f8bd6 --- /dev/null +++ b/jar/肥猫不肥.js @@ -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} 歌曲播放链接 + * @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 })