Skip to content

🎧 QZ Music v2 插件开发指南

📜 概述

QZ Music v2 的插件系统旨在提供灵活的音乐数据获取和功能扩展能力。插件分为以下两种类型:

  1. 基础插件(Base Plugin)
    • 用途:用于提供完整的核心接口平台,以获取歌单、歌词、歌曲信息等数据。
    • 不包括:音频 URL/数据和其他拓展功能。
  2. 拓展插件(Extension Plugin)
    • 用途:嵌入基础插件,实现多种拓展功能,如接入音源、拉取用户歌单列表等。
    • 特点:用途多样,拓展性强。

本文档将详细介绍如何开发这两种类型的插件。


💻 文件要求与运行环境

要求类型规范备注
编码格式UTF-8
编程语言JavaScript (NodeJS)仅支持 CommonJS 语法(因 Javet 兼容性原因)
文件扩展名拓展插件: .js / 基础插件: .zip
运行环境V8 + Node运行时实现为编译后的 libnode-xxx.so

📦 基础插件开发

基础插件开发难度较高,其核心在于提供信息获取通道,不内置音频源。音频源/拓展功能开发请参考下方的拓展插件。

📁 zip 内文件示例结构

一个基础插件最终需要打包成一个 .zip 文件,其内部结构示例如下:

Plugin.zip
├── utils/
│   └── http.js         // 例如: 封装的请求库
├── node_modules/       // NodeJS 依赖库(必须包含,例如 npm i axios 后的产物)
├── index.js            // 导出主入口文件
├── package.json        // NodeJS 自带的配置文件
├── package-lock.json   // NodeJS 自带的依赖声明文件
└── plugin.json         // QZ Music v2 插件信息声明文件 (核心)

📝 index.js 主入口

index.js 必须实现并按名称导出所有支持的基础功能。

javascript
// 示例 index.js
const leaderboard = require('./utils/musicSdk/wy/leaderboard.js');
// ... 引入其他基础功能模块
const { getUrl } = require('./ext.js'); // 拓展性插件函数(可选)
// ...

const moduleObj = {
    tipSearch,       // 搜索建议
    leaderboard,     // 排行榜
    musicSearch,     // 音乐搜索
    songList,        // 歌单
    hotSearch,       // 热门搜索
    singer,          // 歌手信息
    getLyric,        // 获取歌词
    // 基础插件提供获取图片链接的方法
    getPic(songId) {
        const requestObj = getMusicInfo(songId)
        // 确保返回的是 Promise
        return requestObj.promise.then((info) => info.al.picUrl) 
    },
    getUrl,          // 音频 URL 获取(通常由拓展插件实现并引入)
    musicDetail,     // 歌曲详情
}

module.exports = moduleObj

📌 PS:

所有导出的方法都必须返回一个 Promise 具体的函数参数和返回格式请参考群文件中的任意基础插件示例。

⚙️ plugin.json 插件信息声明

plugin.json 用于声明插件的基本信息、支持的功能以及用户可配置的变量。

字段类型说明示例值
infoObject插件基本信息。
info.idString插件唯一 ID,关乎数据获取插件。"wy"
info.nameString插件名称。"小芸音乐插件"
info.versionString插件版本号。"1.0.0"
env_varArray<Object>用户可配置的环境变量列表。
env_var[].keyString变量的键名,用于代码中引用。"playlist_url"
env_var[].nameString变量在 UI 中显示的名称。"个人主页链接"
env_var[].nullableBoolean是否可为空。true
supportArray<String>基础插件支持的核心功能列表,对应 index.js 导出的方法名。["tipSearch", "getLyric", "getUrl"]
ext_funArray<Object>拓展功能列表,用于 UI 显示。
ext_fun[].nameString拓展功能在 UI 中显示的名称。"个人歌单"
ext_fun[].entryString拓展插件中实现此功能的函数名(应在拓展插件的 module.exports 中)。
ext_fun[].var_mapArray<Object>拓展函数所需的参数映射。
var_map[].key_idString映射到 env_var 中定义的键名。"playlist_url"
var_map[].arg_idString映射到拓展函数中的参数名。"url"

📌 示例 plugin.json 结构:

json
{
  "info": { ... },
  "env_var": [ ... ],
  "support": ["tipSearch", "leaderBoard", "musicSearch", "songList", 
  "hotSearch", "getLyric", "getPic", "getUrl"],
  "ext_fun": [
    {
      "name": "个人歌单",
      "entry": "playlist", // 假设拓展插件导出了 playlist 函数
      "var_map": [
        {
          "key_id": "playlist_url",
          "arg_id": "url"
        }
      ]
    }
  ]
}

🔗 拓展插件开发

拓展插件通常用于嵌入基础插件,实现额外的拓展功能,如获取音频 URL(音源)或用户歌单列表。

📂 基本结构 (ext.js)

拓展插件是一个独立的 .js 文件,通过 module.exports 导出功能函数。

javascript
// 示例 ext.js
const crypto = require('crypto') // 支持引入内置库
const axios = require('axios')

// 实现核心功能:获取音频 URL
function getUrl(songId, quality){
    return axios.get(`https://api.xxxxxx.cn/music/${songId}/${quality}`).then((response) => {
        if(!response.data.url || !response.data.url.startsWith('http')) {
            throw new Error("No url found.")
        }
        return response.data.data.url // 返回音频 URL
    })
}

// 实现拓展功能:获取用户歌单列表
async function playlist(url){
    return axios.get(`https://music-api.lgfzer.com/music/extra?source=wy&url=${url}`).then((response) => {
        if(!response.data.success) {
            throw new Error("Unexpected response.");
        }
        const data = response.data.data;
        return {
            "type":"playlists",
            "data":data // 返回规范化的歌单数据
        }
    })
}

module.exports = {
    playlist, // 对应 plugin.json 中的 ext_fun 
    getUrl,   // 对应 plugin.json 中的 support 
}

🔊 支持的音质 Quality

拓展插件在实现 getUrl(songId, quality) 时,需要处理以下常见的音质参数:

参数说明
128k128kbps 标准音质
320k320kbps 高音质
flacFLAC 无损音质
flac24bit24bit FLAC
hiresHi-Res 高解析度
atmos杜比全景声
master母带音质

提示:本 APP 支持通过基础插件提供自定义音质表,具体实现请参考需要嵌入的基础插件。


🚨 错误处理

良好的错误处理是插件稳定运行的关键。所有方法都应返回 Promise,并在其中捕获和抛出错误。

🌟 最佳实践

javascript
async function musicUrl(source, musicInfo, quality) {
  try {
    // 1. 参数验证
    if (!musicInfo || !musicInfo.id) {
      throw new Error('音乐信息不完整')
    }

    // 2. API 调用
    const result = await axios.get(url,options)

    // 3. 结果验证
    if (!result || result.statusCode !== 200) {
      throw new Error(`API 请求失败: ${result?.statusCode || 'Unknown'}`)
    }

    if (!result.body || !result.body.url) {
      throw new Error('返回数据格式错误')
    }

    return result.body.url
  } catch (error) {
    // 4. 记录错误日志
    console.error(`[${source}] 获取音乐链接失败:`, error.message)

    // 5. 重新抛出错误供上层处理
    throw new Error(`获取 ${source} 音乐链接失败: ${error.message}`)
  }
}

🧩 常见错误类型

  1. 网络错误: 请求超时、连接失败等。
  2. API 错误: 接口返回错误状态码(如 403, 404, 500)。
  3. 数据错误: 返回数据结构不正确或缺少关键字段。
  4. 参数错误: 传入参数不完整或格式错误。

🛠️ 调试技巧

1. 使用 console.log 进行调试

插件的运行环境支持标准的 console 方法:

javascript
console.log('[插件名] 调试信息:', data)
console.warn('[插件名] 警告信息:', warning)
console.error('[插件名] 错误信息:', error)

2. 错误捕获

建议全局捕获未处理的 Promise 拒绝:

javascript
process.on('unhandledRejection', (reason, promise) => {
    console.error('未处理的 Promise 拒绝:', reason)
})

🚀 性能优化

1. 请求缓存 (已在 APP 中实现)

开发者无需再实现缓存逻辑,应用已内置支持。

2. 并发控制

如果插件需要向同一接口发起大量请求,可以考虑使用信号量(Semaphore)进行并发控制,防止请求过多导致 API 限制。

javascript
// 注: 需自行 npm i async-mutex 并 require 导入
const semaphore = new Semaphore(3) // 最多3个并发请求

async function limitedRequest(url, options) {
    await semaphore.acquire()
    try {
        return await axios.get(url, options)
    } finally {
        semaphore.release()
    }
}

🔒 安全注意事项

1. 输入验证

始终对用户输入或外部传入的参数进行验证:

javascript
function validateMusicInfo(musicInfo) {
    if (!musicInfo || typeof musicInfo !== 'object') {
        throw new Error('音乐信息格式错误')
    }

    if (!musicInfo.id || typeof musicInfo.id !== 'string') {
        throw new Error('音乐 ID 无效')
    }

    return true
}

2. URL 验证

在返回或使用 URL 时,验证其有效性和协议:

javascript
function isValidUrl(url) {
    try {
        const urlObj = new URL(url)
        return urlObj.protocol === 'http:' || urlObj.protocol === 'https:'
    } catch {
        return false
    }
}

3. 敏感信息保护

不要在日志中输出敏感信息,如密钥、Token 或密码。

javascript
console.log('请求参数:', {
    ...params,
    token: '***', // 隐藏敏感信息
    password: '***'
})

📤 插件发布

1. 代码检查清单

发布前请确保满足以下要求:

  • [ ] 插件信息 (plugin.json) 注释完整
  • [ ] 错误处理(try...catch)完善
  • [ ] 性能优化(如并发控制)合理
  • [ ] 输入和 URL 安全验证到位
  • [ ] 核心功能测试覆盖充分

2. 版本管理

请使用语义化版本号 (Major.Minor.Patch):

  • 主版本 (Major):进行了不兼容的 API 修改。
  • 次版本 (Minor):新增了向下兼容的功能。
  • 修订版本 (Patch):进行了向下兼容的问题修正。

❓ 常见问题 (FAQ)

Q: 插件加载失败怎么办?

A: 检查以下几点:

  1. 文件编码是否为 UTF-8
  2. 插件信息注释 (plugin.json) 格式是否正确
  3. JavaScript 语法是否有错误,是否使用了 CommonJS 语法
  4. 是否正确导出了必需的方法(在 index.js 或拓展插件中)

Q: 如何处理跨域请求?

A: QZ Music v2 的插件运行环境不受浏览器跨域限制,可以直接请求任何域名的 API。

Q: 插件如何更新?

A: 暂未支持自动更新,需要手动替换插件文件。


🤝 技术支持

如有问题或建议,请通过 Q 群私信 联系。