├── LICENSE ├── README.md └── lx-music-source-example.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 LX Music 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![lx-music-api-server-python](https://socialify.git.ci/lxmusics/lx-music-api-server/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pulls=1&stargazers=1&theme=Auto) 4 | 5 | **本项目已经迁移,地址见下面链接** 6 | 7 |
8 | 9 | ## 仓库地址 10 | 11 | [Python 版](https://github.com/lxmusics/lx-music-api-server-python) 12 | 13 | 一个他人维护的[Golang版本](https://github.com/ZxwyWebSite/lx-source) 14 | 15 | ## 交流群 16 | 17 | ### QQ 18 | 19 | 群号: 206995059 20 | 21 | [点击添加](https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=117h8X7TPBWMLwK0Nec_TkdFpqsSs7LJ&group_code=206995059) 22 | 23 | ### Telegram 24 | 25 | [点击添加](https://t.me/+zBJAaMgr6yZmYWI9) 26 | 27 | ## 使用音源 28 | 1. 搭建服务器,并按照要求进行配置 29 | 2. 下载项目目录下的`lx-music-source-example.js`文件 30 | 3. 打开你下载的`lx-music-source-example.js`,修改脚本中的API_URL和API_KEY为你自己搭建的或者你从别处获取的 31 | 4. 保存文件,将这个JS文件导入LX Music中 32 | 5. 享受畅快的听歌之旅 33 | 34 | ## 项目协议 35 | 36 | 本项目基于 [MIT](https://github.com/lxmusics/lx-music-api-server/blob/main/LICENSE) 许可证发行,以下协议是对于 MIT 原协议的补充,如有冲突,以以下协议为准。 37 | 38 | 词语约定:本协议中的“本项目”指本音源项目;“使用者”指签署本协议的使用者;“官方音乐平台”指对本项目内置的包括酷我、酷狗、咪咕等音乐源的官方平台统称;“版权数据”指包括但不限于图像、音频、名字等在内的他人拥有所属版权的数据。 39 | 40 | 1. 本项目的数据来源原理是从各官方音乐平台的公开服务器中拉取数据,经过对数据简单地筛选与合并后进行展示,因此本项目不对数据的准确性负责。 41 | 2. 使用本项目的过程中可能会产生版权数据,对于这些版权数据,本项目不拥有它们的所有权,为了避免造成侵权,使用者务必在**24 小时**内清除使用本项目的过程中所产生的版权数据。 42 | 3. 由于使用本项目产生的包括由于本协议或由于使用或无法使用本项目而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。 43 | 4. 本项目完全免费,且开源发布于 GitHub 面向全世界人用作对技术的学习交流,本项目不对项目内的技术可能存在违反当地法律法规的行为作保证,**禁止在违反当地法律法规的情况下使用本项目**,对于使用者在明知或不知当地法律法规不允许的情况下使用本项目所造成的任何违法违规行为由使用者承担,本项目不承担由此造成的任何直接、间接、特殊、偶然或结果性责任。 44 | 45 | 若你使用了本项目,将代表你接受以上协议。 46 | 47 | 音乐平台不易,请尊重版权,支持正版。 48 | 本项目仅用于对技术可行性的探索及研究,不接受任何商业(包括但不限于广告等)合作及捐赠。 49 | 若对此有疑问请 mail to: 50 | helloplhm-qwq+outlook.com 51 | (请将`+`替换成`@`) 52 | -------------------------------------------------------------------------------- /lx-music-source-example.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @name 替换为你的音乐源名称 3 | * @description 替换为你的音乐源介绍 4 | * @version v2.0.0 5 | * @author Folltoshe & helloplhm-qwq 6 | * @repository https://github.com/lxmusics/lx-music-api-server 7 | */ 8 | 9 | // 是否开启开发模式 10 | const DEV_ENABLE = true 11 | // 服务端地址 12 | const API_URL = 'http://127.0.0.1:9763' 13 | // 服务端配置的请求key 14 | const API_KEY = '' 15 | // 音质配置(key为音源名称,不要乱填.如果你账号为VIP可以填写到hires) 16 | // 全部的支持值: ['128k', '320k', 'flac', 'flac24bit'] 17 | const MUSIC_QUALITY = { 18 | kw: ['128k'], 19 | kg: ['128k'], 20 | tx: ['128k'], 21 | wy: ['128k'], 22 | mg: ['128k'], 23 | } 24 | // 音源配置(默认为自动生成,可以修改为手动) 25 | const MUSIC_SOURCE = Object.keys(MUSIC_QUALITY) 26 | MUSIC_SOURCE.push('local') 27 | 28 | /** 29 | * 下面的东西就不要修改了 30 | */ 31 | const { EVENT_NAMES, request, on, send, utils, env, version } = globalThis.lx 32 | 33 | /** 34 | * URL请求 35 | * 36 | * @param {string} url - 请求的地址 37 | * @param {object} options - 请求的配置文件 38 | * @return {Promise} 携带响应体的Promise对象 39 | */ 40 | const httpFetch = (url, options = { method: 'GET' }) => { 41 | return new Promise((resolve, reject) => { 42 | console.log('--- start --- ' + url) 43 | request(url, options, (err, resp) => { 44 | if (err) return reject(err) 45 | console.log('API Response: ', resp) 46 | resolve(resp) 47 | }) 48 | }) 49 | } 50 | 51 | /** 52 | * Encodes the given data to base64. 53 | * 54 | * @param {type} data - the data to be encoded 55 | * @return {string} the base64 encoded string 56 | */ 57 | const handleBase64Encode = (data) => { 58 | var data = utils.buffer.from(data, 'utf-8') 59 | return utils.buffer.bufToString(data, 'base64') 60 | } 61 | 62 | /** 63 | * 64 | * @param {string} source - 音源 65 | * @param {object} musicInfo - 歌曲信息 66 | * @param {string} quality - 音质 67 | * @returns {Promise} 歌曲播放链接 68 | * @throws {Error} - 错误消息 69 | */ 70 | const handleGetMusicUrl = async (source, musicInfo, quality) => { 71 | if (source == 'local') { 72 | if (!musicInfo.songmid.startsWith('server_')) throw new Error('upsupported local file') 73 | const songId = musicInfo.songmid 74 | const requestBody = { 75 | p: songId.replace('server_', ''), 76 | } 77 | var t = 'c' 78 | var b = handleBase64Encode(JSON.stringify(requestBody)) /* url safe*/.replace(/\+/g, '-').replace(/\//g, '_') 79 | const targetUrl = `${API_URL}/local/${t}?q=${b}` 80 | const request = await httpFetch(targetUrl, { 81 | method: 'GET', 82 | headers: { 83 | 'Content-Type': 'application/json', 84 | 'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}`, 85 | 'X-Request-Key': API_KEY, 86 | }, 87 | follow_max: 5, 88 | }) 89 | const { body } = request 90 | if (body.code == 0 && body.data && body.data.file) { 91 | var t = 'u' 92 | var b = handleBase64Encode(JSON.stringify(requestBody)) /* url safe*/.replace(/\+/g, '-').replace(/\//g, '_') 93 | return `${API_URL}/local/${t}?q=${b}` 94 | } 95 | throw new Error('404 Not Found') 96 | } 97 | 98 | const songId = musicInfo.hash ?? musicInfo.songmid 99 | 100 | const request = await httpFetch(`${API_URL}/url/${source}/${songId}/${quality}`, { 101 | method: 'GET', 102 | headers: { 103 | 'Content-Type': 'application/json', 104 | 'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}`, 105 | 'X-Request-Key': API_KEY, 106 | }, 107 | follow_max: 5, 108 | }) 109 | const { body } = request 110 | 111 | if (!body || isNaN(Number(body.code))) throw new Error('unknow error') 112 | if (env != 'mobile') console.groupEnd() 113 | switch (body.code) { 114 | case 0: 115 | console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) success, URL: ${body.data}`) 116 | return body.data 117 | case 1: 118 | console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed: ip被封禁`) 119 | throw new Error('block ip') 120 | case 2: 121 | console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, ${body.msg}`) 122 | throw new Error('get music url failed') 123 | case 4: 124 | console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, 远程服务器错误`) 125 | throw new Error('internal server error') 126 | case 5: 127 | console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, 请求过于频繁,请休息一下吧`) 128 | throw new Error('too many requests') 129 | case 6: 130 | console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, 请求参数错误`) 131 | throw new Error('param error') 132 | default: 133 | console.log(`handleGetMusicUrl(${source}_${musicInfo.songmid}, ${quality}) failed, ${body.msg ? body.msg : 'unknow error'}`) 134 | throw new Error(body.msg ?? 'unknow error') 135 | } 136 | } 137 | 138 | const handleGetMusicPic = async (source, musicInfo) => { 139 | switch (source) { 140 | case 'local': 141 | // 先从服务器检查是否有对应的类型,再响应链接 142 | if (!musicInfo.songmid.startsWith('server_')) throw new Error('upsupported local file') 143 | const songId = musicInfo.songmid 144 | const requestBody = { 145 | p: songId.replace('server_', ''), 146 | } 147 | var t = 'c' 148 | var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_') 149 | const targetUrl = `${API_URL}/local/${t}?q=${b}` 150 | const request = await httpFetch(targetUrl, { 151 | method: 'GET', 152 | headers: { 153 | 'Content-Type': 'application/json', 154 | 'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}` 155 | }, 156 | follow_max: 5, 157 | }) 158 | const { body } = request 159 | if (body.code === 0 && body.data.cover) { 160 | var t = 'p' 161 | var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_') 162 | return `${API_URL}/local/${t}?q=${b}` 163 | } 164 | throw new Error('get music pic failed') 165 | default: 166 | throw new Error('action(pic) does not support source(' + source + ')') 167 | } 168 | } 169 | 170 | const handleGetMusicLyric = async (source, musicInfo) => { 171 | switch (source) { 172 | case 'local': 173 | if (!musicInfo.songmid.startsWith('server_')) throw new Error('upsupported local file') 174 | const songId = musicInfo.songmid 175 | const requestBody = { 176 | p: songId.replace('server_', ''), 177 | } 178 | var t = 'c' 179 | var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_') 180 | const targetUrl = `${API_URL}/local/${t}?q=${b}` 181 | const request = await httpFetch(targetUrl, { 182 | method: 'GET', 183 | headers: { 184 | 'Content-Type': 'application/json', 185 | 'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}` 186 | }, 187 | follow_max: 5, 188 | }) 189 | const { body } = request 190 | if (body.code === 0 && body.data.lyric) { 191 | var t = 'l' 192 | var b = handleBase64Encode(JSON.stringify(requestBody))/* url safe*/.replace(/\+/g, '-').replace(/\//g, '_') 193 | const request2 = await httpFetch(`${API_URL}/local/${t}?q=${b}`, { 194 | method: 'GET', 195 | headers: { 196 | 'Content-Type': 'application/json', 197 | 'User-Agent': `${env ? `lx-music-${env}/${version}` : `lx-music-request/${version}`}` 198 | }, 199 | follow_max: 5, 200 | }) 201 | if (request2.body.code === 0) { 202 | return { 203 | lyric: request2.body.data ?? "", 204 | tlyric: "", 205 | rlyric: "", 206 | lxlyric: "" 207 | } 208 | } 209 | throw new Error('get music lyric failed') 210 | } 211 | throw new Error('get music lyric failed') 212 | default: 213 | throw new Error('action(lyric) does not support source(' + source + ')') 214 | } 215 | } 216 | 217 | // 生成歌曲信息 218 | const musicSources = {} 219 | MUSIC_SOURCE.forEach(item => { 220 | musicSources[item] = { 221 | name: item, 222 | type: 'music', 223 | actions: (item == 'local') ? ['musicUrl', 'pic', 'lyric'] : ['musicUrl'], 224 | qualitys: (item == 'local') ? [] : MUSIC_QUALITY[item], 225 | } 226 | }) 227 | 228 | // 监听 LX Music 请求事件 229 | on(EVENT_NAMES.request, ({ action, source, info }) => { 230 | switch (action) { 231 | case 'musicUrl': 232 | if (env != 'mobile') { 233 | console.group(`Handle Action(musicUrl)`) 234 | console.log('source', source) 235 | console.log('quality', info.type) 236 | console.log('musicInfo', info.musicInfo) 237 | } else { 238 | console.log(`Handle Action(musicUrl)`) 239 | console.log('source', source) 240 | console.log('quality', info.type) 241 | console.log('musicInfo', info.musicInfo) 242 | } 243 | return handleGetMusicUrl(source, info.musicInfo, info.type) 244 | .then(data => Promise.resolve(data)) 245 | .catch(err => Promise.reject(err)) 246 | case 'pic': 247 | return handleGetMusicPic(source, info.musicInfo) 248 | .then(data => Promise.resolve(data)) 249 | .catch(err => Promise.reject(err)) 250 | case 'lyric': 251 | return handleGetMusicLyric(source, info.musicInfo) 252 | .then(data => Promise.resolve(data)) 253 | .catch(err => Promise.reject(err)) 254 | default: 255 | console.error(`action(${action}) not support`) 256 | return Promise.reject('action not support') 257 | } 258 | }) 259 | 260 | // 向 LX Music 发送初始化成功事件 261 | send(EVENT_NAMES.inited, { status: true, openDevTools: DEV_ENABLE, sources: musicSources }) 262 | --------------------------------------------------------------------------------