Files
CeruMusic/docs/guide/CeruMusicPluginDev.md

681 lines
14 KiB
Markdown
Raw Normal View History

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-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:{
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 网易云音乐 |
> - tx QQ音乐 |
> - kg 酷狗音乐 |
> - mg 咪咕音乐 |
2025-09-21 03:08:05 +08:00
> - kw 酷我音乐
> - 导出
>
> ```javascript
> module.exports = {
> 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-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-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: '通知内容',
url: 'https://example.com', // 可选 当通知为update 版本跟新可传
2025-09-21 03:08:05 +08:00
version: '版本号', // 当通知为update 版本跟新可传
pluginInfo: {
name: '插件名称',
type: 'cr' // 固定唯一标识
} // 当通知为update 版本跟新可传
})
2025-08-19 19:51:37 +08:00
```
2025-09-21 03:08:05 +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',
flac24bit: 'flac24bit'
2025-09-21 03:08:05 +08:00
},
local: {}
2025-08-19 21:33:43 +08:00
}
2025-09-21 03:08:05 +08:00
// HTTP 请求封装
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) {
return httpRequest('http://xxx').then((data) => {
2025-09-21 03:08:05 +08:00
return data.url
})
}
2025-09-21 03:08:05 +08:00
},
local: {
musicUrl(info) {
return httpRequest('http://xxx').then((data) => {
2025-09-21 03:08:05 +08:00
return data.url
})
},
pic(info) {
return httpRequest('http://xxx').then((data) => {
2025-09-21 03:08:05 +08:00
return data.url
})
},
lyric(info) {
return httpRequest('http://xxx').then((data) => {
2025-09-21 03:08:05 +08:00
return {
lyric: '...', // 歌曲歌词
tlyric: '...', // 翻译歌词,没有可为 null
rlyric: '...', // 罗马音歌词,没有可为 null
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'],
qualitys: ['128k', '320k', 'flac', 'flac24bit']
2025-09-21 03:08:05 +08:00
},
local: {
name: '本地音乐',
type: 'music',
actions: ['musicUrl', 'lyric', 'pic'],
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
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
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-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) {
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 调用
const result = await cerumusic.request(url, options)
2025-09-21 03:08:05 +08:00
// 结果验证
if (!result || result.statusCode !== 200) {
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) {
throw new Error('返回数据格式错误')
2025-09-21 03:08:05 +08:00
}
2025-08-19 19:51:37 +08:00
return result.body.url
2025-09-21 03:08:05 +08:00
} catch (error) {
// 记录错误日志
console.error(`[${source}] 获取音乐链接失败:`, error.message)
2025-09-21 03:08:05 +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
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) => {
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
const cache = new Map()
2025-09-21 03:08:05 +08:00
async function getCachedData(key, fetcher, ttl = 300000) {
const cached = cache.get(key)
2025-09-21 03:08:05 +08:00
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data
2025-09-21 03:08:05 +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-21 03:08:05 +08:00
```
### 3. 并发控制
2025-08-19 19:51:37 +08:00
```javascript
2025-09-21 03:08:05 +08:00
// 限制并发请求数量
const semaphore = new Semaphore(3) // 最多3个并发请求
2025-09-21 03:08:05 +08:00
async function limitedRequest(url, options) {
await semaphore.acquire()
2025-09-21 03:08:05 +08:00
try {
return await cerumusic.request(url, options)
2025-09-21 03:08:05 +08:00
} finally {
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') {
throw new Error('音乐信息格式错误')
2025-09-21 03:08:05 +08:00
}
2025-09-21 03:08:05 +08:00
if (!musicInfo.id || typeof musicInfo.id !== 'string') {
throw new Error('音乐 ID 无效')
2025-08-19 21:33:43 +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 {
const urlObj = new URL(url)
return urlObj.protocol === 'http:' || urlObj.protocol === 'https:'
2025-09-21 03:08:05 +08:00
} catch {
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-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-21 03:08:05 +08:00
try {
const url = await musicUrl('kw', testMusicInfo, '320k')
console.log('测试通过:', url)
2025-09-21 03:08:05 +08:00
} catch (error) {
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-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-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
cerumusic.NoticeCenter('update', {
title: '新版本更新',
content: 'xxxx',
2025-09-21 03:08:05 +08:00
version: 'v1.0.3',
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
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-21 03:08:05 +08:00
- GitHub Issues: [CeruMusic Issues](https://github.com/timeshiftsauce/CeruMusic/issues)
- Blog (最好登录,否则需要审核): [CeruMusic Blog](https://shiqianjiang.cn/blog/4966904626407280640)