2025-09-21 03:08:05 +08:00
|
|
|
|
# CeruMusic 插件开发指南
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
CeruMusic 支持两种类型的插件:
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
1. **CeruMusic 原生插件**:基于 CeruMusic API 的插件格式
|
|
|
|
|
|
2. **LX 兼容插件**:兼容 LX Music 的事件驱动插件格式
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
本文档将详细介绍如何开发这两种类型的插件。
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
## 文件要求
|
|
|
|
|
|
|
|
|
|
|
|
- **编码格式**:UTF-8
|
|
|
|
|
|
- **编程语言**:JavaScript (支持 ES6+ 语法)
|
|
|
|
|
|
- **文件扩展名**:`.js`
|
|
|
|
|
|
|
|
|
|
|
|
## 插件信息注释
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
所有插件文件的开头必须包含以下注释格式:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @name 插件名称
|
|
|
|
|
|
* @description 插件描述
|
|
|
|
|
|
* @version 1.0.0
|
|
|
|
|
|
* @author 作者名称
|
|
|
|
|
|
* @homepage https://example.com
|
|
|
|
|
|
*/
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 注释字段说明
|
|
|
|
|
|
|
|
|
|
|
|
- `@name`:插件名称,建议不超过 24 个字符
|
|
|
|
|
|
- `@description`:插件描述,建议不超过 36 个字符(可选)
|
|
|
|
|
|
- `@version`:版本号(可选)
|
|
|
|
|
|
- `@author`:作者名称(可选)
|
|
|
|
|
|
- `@homepage`:主页地址(可选)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## CeruMusic 原生插件开发
|
|
|
|
|
|
|
|
|
|
|
|
首先 `澜音` 插件是面向 方法的 这意味着你直接导出方法即可为播放器提供音源
|
|
|
|
|
|
|
|
|
|
|
|
### 基本结构
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
/**
|
2025-09-21 03:08:05 +08:00
|
|
|
|
* @name 示例音乐源
|
|
|
|
|
|
* @description CeruMusic 原生插件示例
|
2025-08-19 19:51:37 +08:00
|
|
|
|
* @version 1.0.0
|
2025-09-21 03:08:05 +08:00
|
|
|
|
* @author CeruMusic Team
|
2025-08-19 19:51:37 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 插件信息
|
2025-08-19 19:51:37 +08:00
|
|
|
|
const pluginInfo = {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
name: "示例音乐源",
|
|
|
|
|
|
version: "1.0.0",
|
|
|
|
|
|
author: "CeruMusic Team",
|
|
|
|
|
|
description: "这是一个示例插件"
|
|
|
|
|
|
};
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 支持的音源配置
|
2025-08-19 19:51:37 +08:00
|
|
|
|
const sources = {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
kw:{
|
|
|
|
|
|
name: "酷我音乐",
|
2025-09-30 13:46:27 +08:00
|
|
|
|
qualitys: ['128k', '320k', 'flac', 'flac24bit']
|
2025-08-19 19:51:37 +08:00
|
|
|
|
},
|
2025-09-21 03:08:05 +08:00
|
|
|
|
tx:{
|
2025-09-25 19:56:45 +08:00
|
|
|
|
name: "QQ音乐",
|
2025-09-30 13:46:27 +08:00
|
|
|
|
qualitys: ['128k', '320k', 'flac']
|
2025-08-19 19:51:37 +08:00
|
|
|
|
}
|
2025-09-21 03:08:05 +08:00
|
|
|
|
};
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 获取音乐链接的主要方法
|
2025-08-19 19:51:37 +08:00
|
|
|
|
async function musicUrl(source, musicInfo, quality) {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 使用 cerumusic API 发送 HTTP 请求
|
|
|
|
|
|
const result = await cerumusic.request('https://api.example.com/music', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
...你的其他参数 可以 是密钥或者其他...
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
id: musicInfo.id,
|
2025-09-30 13:46:27 +08:00
|
|
|
|
qualitys: quality
|
2025-09-21 03:08:05 +08:00
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (result.statusCode === 200 && result.body.url) {
|
|
|
|
|
|
return result.body.url;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('获取音乐链接失败');
|
2025-08-19 21:33:43 +08:00
|
|
|
|
}
|
2025-09-21 03:08:05 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取音乐链接时发生错误:', error);
|
|
|
|
|
|
throw error;
|
2025-08-19 19:51:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 获取歌曲封面(可选)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
async function getPic(source, musicInfo) {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const result = await cerumusic.request(`https://api.example.com/pic/${musicInfo.id}`);
|
|
|
|
|
|
return result.body.picUrl;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error('获取封面失败: ' + error.message);
|
|
|
|
|
|
}
|
2025-08-19 19:51:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 获取歌词(可选)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
async function getLyric(source, musicInfo) {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const result = await cerumusic.request(`https://api.example.com/lyric/${musicInfo.id}`);
|
|
|
|
|
|
return result.body.lyric;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error('获取歌词失败: ' + error.message);
|
|
|
|
|
|
}
|
2025-08-19 19:51:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出插件
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
pluginInfo,
|
|
|
|
|
|
sources,
|
|
|
|
|
|
musicUrl,
|
2025-09-21 03:08:05 +08:00
|
|
|
|
getPic, // 可选
|
|
|
|
|
|
getLyric // 可选
|
|
|
|
|
|
};
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
> #### PS:
|
|
|
|
|
|
>
|
|
|
|
|
|
> - `sources key` 取值
|
|
|
|
|
|
> - wy 网易云音乐 |
|
2025-09-25 19:56:45 +08:00
|
|
|
|
> - tx QQ音乐 |
|
|
|
|
|
|
> - kg 酷狗音乐 |
|
|
|
|
|
|
> - mg 咪咕音乐 |
|
2025-09-21 03:08:05 +08:00
|
|
|
|
> - kw 酷我音乐
|
|
|
|
|
|
> - 导出
|
|
|
|
|
|
>
|
|
|
|
|
|
> ```javascript
|
|
|
|
|
|
> module.exports = {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
> sources // 你的音源支持
|
|
|
|
|
|
> }
|
2025-09-21 03:08:05 +08:00
|
|
|
|
> ```
|
|
|
|
|
|
>
|
2025-09-30 13:46:27 +08:00
|
|
|
|
> - 支持的音质 ` sources.qualitys: ['128k', '320k', 'flac']`
|
2025-09-21 03:08:05 +08:00
|
|
|
|
> - `128k`: 128kbps
|
|
|
|
|
|
> - `320k`: 320kbps
|
|
|
|
|
|
> - `flac`: FLAC 无损
|
|
|
|
|
|
> - `flac24bit`: 24bit FLAC
|
|
|
|
|
|
> - `hires`: Hi-Res 高解析度
|
|
|
|
|
|
> - `atmos`: 杜比全景声
|
|
|
|
|
|
> - `master`: 母带音质
|
|
|
|
|
|
|
|
|
|
|
|
### CeruMusic API 参考
|
|
|
|
|
|
|
|
|
|
|
|
#### cerumusic.request(url, options)
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 请求方法,返回 Promise。
|
|
|
|
|
|
|
|
|
|
|
|
**参数:**
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
- `url` (string): 请求地址
|
|
|
|
|
|
- `options` (object): 请求选项
|
|
|
|
|
|
- `method`: 请求方法 (GET, POST, PUT, DELETE 等)
|
|
|
|
|
|
- `headers`: 请求头对象
|
|
|
|
|
|
- `body`: 请求体
|
|
|
|
|
|
- `timeout`: 超时时间(毫秒)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
**返回值:**
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
{
|
|
|
|
|
|
statusCode: 200,
|
|
|
|
|
|
headers: {...},
|
|
|
|
|
|
body: {...} // 自动解析的响应体
|
2025-08-19 21:33:43 +08:00
|
|
|
|
}
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
#### cerumusic.utils
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
工具方法集合:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// Buffer 操作
|
|
|
|
|
|
cerumusic.utils.buffer.from(data, encoding)
|
|
|
|
|
|
cerumusic.utils.buffer.bufToString(buffer, encoding)
|
|
|
|
|
|
|
|
|
|
|
|
// 加密工具
|
|
|
|
|
|
cerumusic.utils.crypto.md5(str)
|
|
|
|
|
|
cerumusic.utils.crypto.randomBytes(size)
|
|
|
|
|
|
cerumusic.utils.crypto.aesEncrypt(data, mode, key, iv)
|
|
|
|
|
|
cerumusic.utils.crypto.rsaEncrypt(data, key)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
#### cerumusic.NoticeCenter(type, data)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
发送通知到用户界面:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
cerumusic.NoticeCenter('info', {
|
|
|
|
|
|
title: '通知标题',
|
|
|
|
|
|
content: '通知内容',
|
2025-09-25 19:56:45 +08:00
|
|
|
|
url: 'https://example.com', // 可选 当通知为update 版本跟新可传
|
2025-09-21 03:08:05 +08:00
|
|
|
|
version: '版本号', // 当通知为update 版本跟新可传
|
|
|
|
|
|
pluginInfo: {
|
|
|
|
|
|
name: '插件名称',
|
2025-09-25 19:56:45 +08:00
|
|
|
|
type: 'cr' // 固定唯一标识
|
|
|
|
|
|
} // 当通知为update 版本跟新可传
|
|
|
|
|
|
})
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
**通知类型:**
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
- `'info'`: 信息通知
|
|
|
|
|
|
- `'success'`: 成功通知
|
|
|
|
|
|
- `'warn'`: 警告通知
|
|
|
|
|
|
- `'error'`: 错误通知
|
|
|
|
|
|
- `'update'`: 更新通知
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## LX 兼容插件开发 引用于落雪官网改编
|
|
|
|
|
|
|
|
|
|
|
|
CeruMusic 完全兼容 LX Music 的插件格式,支持事件驱动的开发模式。
|
|
|
|
|
|
|
|
|
|
|
|
### 基本结构
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @name 测试音乐源
|
|
|
|
|
|
* @description 我只是一个测试音乐源哦
|
|
|
|
|
|
* @version 1.0.0
|
|
|
|
|
|
* @author xxx
|
|
|
|
|
|
* @homepage http://xxx
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const { EVENT_NAMES, request, on, send } = globalThis.lx
|
|
|
|
|
|
|
|
|
|
|
|
// 音质配置
|
|
|
|
|
|
const qualitys = {
|
|
|
|
|
|
kw: {
|
|
|
|
|
|
'128k': '128',
|
|
|
|
|
|
'320k': '320',
|
|
|
|
|
|
flac: 'flac',
|
2025-09-25 19:56:45 +08:00
|
|
|
|
flac24bit: 'flac24bit'
|
2025-09-21 03:08:05 +08:00
|
|
|
|
},
|
2025-09-25 19:56:45 +08:00
|
|
|
|
local: {}
|
2025-08-19 21:33:43 +08:00
|
|
|
|
}
|
2025-09-21 03:08:05 +08:00
|
|
|
|
|
|
|
|
|
|
// HTTP 请求封装
|
2025-09-25 19:56:45 +08:00
|
|
|
|
const httpRequest = (url, options) =>
|
|
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
|
|
request(url, options, (err, resp) => {
|
|
|
|
|
|
if (err) return reject(err)
|
|
|
|
|
|
resolve(resp.body)
|
|
|
|
|
|
})
|
2025-09-21 03:08:05 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// API 实现
|
|
|
|
|
|
const apis = {
|
|
|
|
|
|
kw: {
|
|
|
|
|
|
musicUrl({ songmid }, quality) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return httpRequest('http://xxx').then((data) => {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
return data.url
|
|
|
|
|
|
})
|
2025-09-25 19:56:45 +08:00
|
|
|
|
}
|
2025-09-21 03:08:05 +08:00
|
|
|
|
},
|
|
|
|
|
|
local: {
|
|
|
|
|
|
musicUrl(info) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return httpRequest('http://xxx').then((data) => {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
return data.url
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
pic(info) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return httpRequest('http://xxx').then((data) => {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
return data.url
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
lyric(info) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return httpRequest('http://xxx').then((data) => {
|
2025-09-21 03:08:05 +08:00
|
|
|
|
return {
|
|
|
|
|
|
lyric: '...', // 歌曲歌词
|
|
|
|
|
|
tlyric: '...', // 翻译歌词,没有可为 null
|
|
|
|
|
|
rlyric: '...', // 罗马音歌词,没有可为 null
|
2025-09-25 19:56:45 +08:00
|
|
|
|
lxlyric: '...' // lx 逐字歌词,没有可为 null
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 注册 API 请求事件
|
|
|
|
|
|
on(EVENT_NAMES.request, ({ source, action, info }) => {
|
|
|
|
|
|
switch (action) {
|
|
|
|
|
|
case 'musicUrl':
|
|
|
|
|
|
return apis[source].musicUrl(info.musicInfo, qualitys[source][info.type])
|
|
|
|
|
|
case 'lyric':
|
|
|
|
|
|
return apis[source].lyric(info.musicInfo)
|
|
|
|
|
|
case 'pic':
|
|
|
|
|
|
return apis[source].pic(info.musicInfo)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 发送初始化完成事件
|
|
|
|
|
|
send(EVENT_NAMES.inited, {
|
|
|
|
|
|
openDevTools: false, // 是否打开开发者工具
|
|
|
|
|
|
sources: {
|
|
|
|
|
|
kw: {
|
|
|
|
|
|
name: '酷我音乐',
|
|
|
|
|
|
type: 'music',
|
|
|
|
|
|
actions: ['musicUrl'],
|
2025-09-25 19:56:45 +08:00
|
|
|
|
qualitys: ['128k', '320k', 'flac', 'flac24bit']
|
2025-09-21 03:08:05 +08:00
|
|
|
|
},
|
|
|
|
|
|
local: {
|
|
|
|
|
|
name: '本地音乐',
|
|
|
|
|
|
type: 'music',
|
|
|
|
|
|
actions: ['musicUrl', 'lyric', 'pic'],
|
2025-09-25 19:56:45 +08:00
|
|
|
|
qualitys: []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-21 03:08:05 +08:00
|
|
|
|
})
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### LX API 参考
|
|
|
|
|
|
|
|
|
|
|
|
#### globalThis.lx.EVENT_NAMES
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
事件名称常量:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
- `inited`: 初始化完成事件
|
|
|
|
|
|
- `request`: API 请求事件
|
|
|
|
|
|
- `updateAlert`: 更新提示事件
|
|
|
|
|
|
|
|
|
|
|
|
#### globalThis.lx.on(eventName, handler)
|
|
|
|
|
|
|
|
|
|
|
|
注册事件监听器:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
lx.on(lx.EVENT_NAMES.request, ({ source, action, info }) => {
|
|
|
|
|
|
// 必须返回 Promise
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return Promise.resolve(result)
|
|
|
|
|
|
})
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
#### globalThis.lx.send(eventName, data)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
发送事件:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 发送初始化事件
|
|
|
|
|
|
lx.send(lx.EVENT_NAMES.inited, {
|
|
|
|
|
|
openDevTools: false,
|
|
|
|
|
|
sources: {...}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 发送更新提示
|
|
|
|
|
|
lx.send(lx.EVENT_NAMES.updateAlert, {
|
|
|
|
|
|
log: '更新日志\n修复了一些问题',
|
|
|
|
|
|
updateUrl: 'https://example.com/update'
|
|
|
|
|
|
});
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
#### globalThis.lx.request(url, options, callback)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
HTTP 请求方法:
|
2025-08-19 21:33:43 +08:00
|
|
|
|
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```javascript
|
2025-09-25 19:56:45 +08:00
|
|
|
|
lx.request(
|
|
|
|
|
|
'https://api.example.com',
|
|
|
|
|
|
{
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
|
|
timeout: 10000
|
|
|
|
|
|
},
|
|
|
|
|
|
(err, resp) => {
|
|
|
|
|
|
if (err) {
|
|
|
|
|
|
console.error('请求失败:', err)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('响应:', resp.body)
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
2025-09-25 19:56:45 +08:00
|
|
|
|
)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
#### globalThis.lx.utils
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
工具方法:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// Buffer 操作
|
2025-09-21 03:08:05 +08:00
|
|
|
|
lx.utils.buffer.from(data, encoding)
|
|
|
|
|
|
lx.utils.buffer.bufToString(buffer, encoding)
|
|
|
|
|
|
|
|
|
|
|
|
// 加密工具
|
|
|
|
|
|
lx.utils.crypto.md5(str)
|
|
|
|
|
|
lx.utils.crypto.aesEncrypt(buffer, mode, key, iv)
|
|
|
|
|
|
lx.utils.crypto.randomBytes(size)
|
|
|
|
|
|
lx.utils.crypto.rsaEncrypt(buffer, key)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 音源配置
|
|
|
|
|
|
|
|
|
|
|
|
### 支持的音源 ID
|
|
|
|
|
|
|
|
|
|
|
|
- `kw`: 酷我音乐
|
|
|
|
|
|
- `kg`: 酷狗音乐
|
|
|
|
|
|
- `tx`: QQ音乐
|
|
|
|
|
|
- `wy`: 网易云音乐
|
|
|
|
|
|
- `mg`: 咪咕音乐
|
|
|
|
|
|
- `local`: 本地音乐
|
|
|
|
|
|
|
|
|
|
|
|
### 支持的音质
|
|
|
|
|
|
|
|
|
|
|
|
- `128k`: 128kbps
|
|
|
|
|
|
- `320k`: 320kbps
|
|
|
|
|
|
- `flac`: FLAC 无损
|
|
|
|
|
|
- `flac24bit`: 24bit FLAC
|
|
|
|
|
|
- `hires`: Hi-Res 高解析度
|
|
|
|
|
|
- `atmos`: 杜比全景声
|
|
|
|
|
|
- `master`: 母带音质
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2025-08-19 19:51:37 +08:00
|
|
|
|
## 错误处理
|
|
|
|
|
|
|
|
|
|
|
|
### 最佳实践
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
async function musicUrl(source, musicInfo, quality) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 参数验证
|
|
|
|
|
|
if (!musicInfo || !musicInfo.id) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
throw new Error('音乐信息不完整')
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// API 调用
|
2025-09-25 19:56:45 +08:00
|
|
|
|
const result = await cerumusic.request(url, options)
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 结果验证
|
|
|
|
|
|
if (!result || result.statusCode !== 200) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
throw new Error(`API 请求失败: ${result?.statusCode || 'Unknown'}`)
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
2025-08-19 21:33:43 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
if (!result.body || !result.body.url) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
throw new Error('返回数据格式错误')
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return result.body.url
|
2025-09-21 03:08:05 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 记录错误日志
|
2025-09-25 19:56:45 +08:00
|
|
|
|
console.error(`[${source}] 获取音乐链接失败:`, error.message)
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 重新抛出错误供上层处理
|
2025-09-25 19:56:45 +08:00
|
|
|
|
throw new Error(`获取 ${source} 音乐链接失败: ${error.message}`)
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
### 常见错误类型
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
1. **网络错误**: 请求超时、连接失败
|
|
|
|
|
|
2. **API 错误**: 接口返回错误状态码
|
|
|
|
|
|
3. **数据错误**: 返回数据格式不正确
|
|
|
|
|
|
4. **参数错误**: 传入参数不完整或格式错误
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
---
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
## 调试技巧
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 使用 console.log
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-25 19:56:45 +08:00
|
|
|
|
console.log('[插件名] 调试信息:', data)
|
|
|
|
|
|
console.warn('[插件名] 警告信息:', warning)
|
|
|
|
|
|
console.error('[插件名] 错误信息:', error)
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 2. LX 插件开发者工具
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
send(EVENT_NAMES.inited, {
|
|
|
|
|
|
openDevTools: true, // 开启开发者工具
|
|
|
|
|
|
sources: {...}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 3. 错误捕获
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
process.on('unhandledRejection', (reason, promise) => {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
console.error('未处理的 Promise 拒绝:', reason)
|
|
|
|
|
|
})
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 性能优化
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 请求缓存
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-25 19:56:45 +08:00
|
|
|
|
const cache = new Map()
|
2025-09-21 03:08:05 +08:00
|
|
|
|
|
|
|
|
|
|
async function getCachedData(key, fetcher, ttl = 300000) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
const cached = cache.get(key)
|
2025-09-21 03:08:05 +08:00
|
|
|
|
if (cached && Date.now() - cached.timestamp < ttl) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return cached.data
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
|
|
|
|
|
const data = await fetcher()
|
|
|
|
|
|
cache.set(key, { data, timestamp: Date.now() })
|
|
|
|
|
|
return data
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 2. 请求超时控制
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
const result = await cerumusic.request(url, {
|
|
|
|
|
|
timeout: 10000 // 10秒超时
|
2025-09-25 19:56:45 +08:00
|
|
|
|
})
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 并发控制
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2025-09-21 03:08:05 +08:00
|
|
|
|
// 限制并发请求数量
|
2025-09-25 19:56:45 +08:00
|
|
|
|
const semaphore = new Semaphore(3) // 最多3个并发请求
|
2025-09-21 03:08:05 +08:00
|
|
|
|
|
|
|
|
|
|
async function limitedRequest(url, options) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
await semaphore.acquire()
|
2025-09-21 03:08:05 +08:00
|
|
|
|
try {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return await cerumusic.request(url, options)
|
2025-09-21 03:08:05 +08:00
|
|
|
|
} finally {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
semaphore.release()
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 安全注意事项
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 1. 输入验证
|
2025-08-19 21:33:43 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
function validateMusicInfo(musicInfo) {
|
|
|
|
|
|
if (!musicInfo || typeof musicInfo !== 'object') {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
throw new Error('音乐信息格式错误')
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
if (!musicInfo.id || typeof musicInfo.id !== 'string') {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
throw new Error('音乐 ID 无效')
|
2025-08-19 21:33:43 +08:00
|
|
|
|
}
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
|
|
|
|
|
return true
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. URL 验证
|
2025-08-19 21:33:43 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
function isValidUrl(url) {
|
2025-08-19 19:51:37 +08:00
|
|
|
|
try {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
const urlObj = new URL(url)
|
|
|
|
|
|
return urlObj.protocol === 'http:' || urlObj.protocol === 'https:'
|
2025-09-21 03:08:05 +08:00
|
|
|
|
} catch {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
return false
|
2025-08-19 19:51:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 3. 敏感信息保护
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 不要在日志中输出敏感信息
|
|
|
|
|
|
console.log('请求参数:', {
|
|
|
|
|
|
...params,
|
|
|
|
|
|
token: '***', // 隐藏敏感信息
|
|
|
|
|
|
password: '***'
|
2025-09-25 19:56:45 +08:00
|
|
|
|
})
|
2025-08-19 19:51:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
---
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
## 插件发布
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 1. 代码检查清单
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] 插件信息注释完整
|
|
|
|
|
|
- [ ] 错误处理完善
|
|
|
|
|
|
- [ ] 性能优化合理
|
|
|
|
|
|
- [ ] 安全验证到位
|
|
|
|
|
|
- [ ] 测试覆盖充分
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 测试建议
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
// 单元测试示例
|
|
|
|
|
|
async function testMusicUrl() {
|
|
|
|
|
|
const testMusicInfo = {
|
|
|
|
|
|
id: 'test123',
|
|
|
|
|
|
name: '测试歌曲',
|
|
|
|
|
|
artist: '测试歌手'
|
2025-09-25 19:56:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
try {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
const url = await musicUrl('kw', testMusicInfo, '320k')
|
|
|
|
|
|
console.log('测试通过:', url)
|
2025-09-21 03:08:05 +08:00
|
|
|
|
} catch (error) {
|
2025-09-25 19:56:45 +08:00
|
|
|
|
console.error('测试失败:', error)
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### 3. 版本管理
|
2025-08-19 21:33:43 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
使用语义化版本号:
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
- `1.0.0`: 主版本.次版本.修订版本
|
2025-08-19 19:51:37 +08:00
|
|
|
|
- 主版本:不兼容的 API 修改
|
|
|
|
|
|
- 次版本:向下兼容的功能性新增
|
|
|
|
|
|
- 修订版本:向下兼容的问题修正
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
---
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 常见问题
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### Q: 插件加载失败怎么办?
|
|
|
|
|
|
|
|
|
|
|
|
A: 检查以下几点:
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
1. 文件编码是否为 UTF-8
|
|
|
|
|
|
2. 插件信息注释格式是否正确
|
|
|
|
|
|
3. JavaScript 语法是否有错误
|
|
|
|
|
|
4. 是否正确导出了必需的方法
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### Q: 如何处理跨域请求?
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
A: CeruMusic 的请求方法不受浏览器跨域限制,可以直接请求任何域名的 API。
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### Q: 插件如何更新?
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
A: 使用 `cerumusic.NoticeCenter` 事件通知用户更新:
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
```javascript
|
2025-09-25 19:56:45 +08:00
|
|
|
|
cerumusic.NoticeCenter('update', {
|
|
|
|
|
|
title: '新版本更新',
|
|
|
|
|
|
content: 'xxxx',
|
2025-09-21 03:08:05 +08:00
|
|
|
|
version: 'v1.0.3',
|
2025-09-25 19:56:45 +08:00
|
|
|
|
url: 'https://shiqianjiang.cn',
|
|
|
|
|
|
pluginInfo: {
|
|
|
|
|
|
type: 'cr'
|
2025-09-21 03:08:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
### Q: 如何调试插件?
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-25 19:56:45 +08:00
|
|
|
|
A:
|
|
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
1. 使用 `console.log` 输出调试信息 可在设置—>插件管理—>日志 查看调试
|
|
|
|
|
|
2. LX 插件可以设置 `openDevTools: true` 打开开发者工具
|
|
|
|
|
|
3. 查看 CeruMusic 的插件日志
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
---
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
## 技术支持
|
2025-08-19 19:51:37 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
如有问题或建议,请通过以下方式联系:
|
2025-09-25 19:56:45 +08:00
|
|
|
|
|
2025-09-21 03:08:05 +08:00
|
|
|
|
- GitHub Issues: [CeruMusic Issues](https://github.com/timeshiftsauce/CeruMusic/issues)
|
|
|
|
|
|
- Blog (最好登录,否则需要审核): [CeruMusic Blog](https://shiqianjiang.cn/blog/4966904626407280640)
|