musicfree-plugin-dev

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MusicFree 插件开发

MusicFree Plugin Development

严格行为准则

Strict Code of Conduct

以下规则在整个插件开发过程中始终生效,违反将导致开发失败:
  1. 禁止盲猜 URL:不要凭空试
    /api/search
    /v1/song
    等路径。每个 URL 必须来自页面内容或 Playwright 捕获的网络请求
  2. 禁止搜索网络寻找 API:不要搜索"XX音乐 API"、"免费音乐接口"等。所有数据来源必须通过对目标站点的直接观察获得,不能来自网络搜索
  3. 禁止批量探测:不要同时发多个请求试不同的参数组合
  4. 禁止重复请求:每个 URL 只请求一次,结果保存到本地文件后基于本地数据分析
  5. 禁止放弃说"需要浏览器环境":axios 失败但浏览器可以访问,差别一定在请求头上,用 Playwright 捕获真实请求头后在 axios 中补全
当遇到困难时,唯一正确的做法是:用 Playwright 观察浏览器的真实行为。不要猜测,不要搜索,去观察。
The following rules are in effect at all times during the entire plugin development process; violations will result in development failure:
  1. Forbid guessing URLs blindly: Do not try paths like
    /api/search
    or
    /v1/song
    out of thin air. Every URL must come from page content or network requests captured by Playwright
  2. Forbid searching the web for APIs: Do not search for terms like "XX Music API" or "free music interfaces". All data sources must be obtained through direct observation of the target site, not from web searches
  3. Forbid batch probing: Do not send multiple requests simultaneously to test different parameter combinations
  4. Forbid duplicate requests: Each URL is requested only once; after saving the results to a local file, analyze based on the local data
  5. Forbid giving up with "browser environment required": If axios fails but the browser can access it, the difference must be in the request headers. Capture the real request headers with Playwright and complete them in axios
When encountering difficulties, the only correct approach is: Observe the real behavior of the browser with Playwright. Do not guess, do not search, just observe.

角色与协作模式

Roles and Collaboration Mode

  • AI(你):分析目标站点、编写插件代码、编写并执行测试脚本、迭代修复
  • 用户:提供目标站点/API 信息,仅在 AI 无法独立完成时执行辅助操作
  • AI (You): Analyze the target site, write plugin code, write and execute test scripts, iterate and fix issues
  • User: Provide target site/API information, and only perform auxiliary operations when AI cannot complete independently

核心原则:最小化用户操作

Core Principle: Minimize User Operations

能自己做的,绝不让用户做。 遵循以下优先级:
  1. AI 独立完成:直接用工具抓取页面、分析 HTML/JS、在终端运行脚本
  2. AI 做 + 用户确认:AI 分析后给出结论,请用户确认是否正确
  3. 用户操作(最后手段):仅当 AI 的工具无法获取到所需数据时,才让用户在浏览器中操作
Do everything you can on your own, never ask the user to do it. Follow the following priority:
  1. AI completes independently: Directly use tools to crawl pages, analyze HTML/JS, run scripts in the terminal
  2. AI does + user confirms: AI gives conclusions after analysis, asks the user to confirm if they are correct
  3. User operation (last resort): Only ask the user to operate in the browser when AI's tools cannot obtain the required data

与用户交互原则

Interaction Principles with Users

  • 用户可能是零基础,不要使用未解释的技术术语
  • 需要用户操作时,给出精确到按键级别的逐步指令,每次只下达一个操作
  • 主动告知用户当前进度和下一步计划
  • 不确定时必须询问:当对站点行为、API 含义、数据字段映射不确定时,先问再做
  • Users may be beginners, do not use unexplained technical terms
  • When requiring user operations, provide step-by-step instructions precise to the key press level, and only give one operation at a time
  • Proactively inform the user of the current progress and next plan
  • Must ask when unsure: When unsure about site behavior, API meaning, or data field mapping, ask first before proceeding

工作流程

Workflow

1. 收集信息 → 2. 判断路径 → 3. 分析数据源 → 4. 确定方法集合
→ 5. 逐个实现 → 6. 测试验证 → 7. 迭代修复 → 8. 输出最终插件
1. 收集信息 → 2. 判断路径 → 3. 分析数据源 → 4. 确定方法集合
→ 5. 逐个实现 → 6. 测试验证 → 7. 迭代修复 → 8. 输出最终插件

步骤 1:收集信息并检查环境

Step 1: Collect Information and Check Environment

先检查开发环境(在做任何其他事之前):
  1. 检查 Node.js 是否可用:
    node --version
  2. 询问用户:电脑上是否安装了 Chrome 或 Edge 浏览器?安装路径是什么?(Playwright 可以复用已安装的浏览器,避免下载)
  3. 安装 Playwright:
    npm install -D playwright
    (不需要下载 Chromium,后续用
    channel: 'chrome'
    复用用户的 Chrome)
然后收集需求
  • 目标是什么?(某个音乐网站?已有的 API 文档?自建的服务?)
  • 插件作者名?(会写入插件的
    author
    字段)
Check the development environment first (before doing anything else):
  1. Check if Node.js is available:
    node --version
  2. Ask the user: Do you have Chrome or Edge browser installed on your computer? What is the installation path? (Playwright can reuse the installed browser to avoid downloading)
  3. Install Playwright:
    npm install -D playwright
    (No need to download Chromium; use
    channel: 'chrome'
    later to reuse the user's Chrome)
Then collect requirements:
  • What is the target? (A certain music website? Existing API documentation? Self-built service?)
  • Plugin author name? (Will be written into the
    author
    field of the plugin)

步骤 2:判断路径

Step 2: Determine the Path

根据收集到的信息,选择对应路径:
路径 A:用户提供了 API 文档或接口规格 → 阅读文档 → 将端点映射到插件方法 → 直接编写代码
路径 B:用户提供了网站 URL(页面内容直接可抓取) → 用工具抓取页面 HTML → 分析 DOM 结构 → 用 cheerio 编写解析逻辑 → 详见 references/site-analysis-playbook.md "静态站点分析"
路径 C:用户提供了网站 URL(需逆向分析内部 API) 页面通过内部 API 异步加载数据,没有公开文档。需要观察浏览器的实际行为,然后用 axios 精确复现
核心方法论——观察 → 分析 → 复现 → 验证
  1. 观察:用 Playwright 加载页面,捕获所有网络请求,保存到本地
  2. 分析:基于本地缓存的数据做离线分析,找到关键的 API 端点和音频请求
  3. 复现:用 axios 复现相同的请求(包括完整的 Headers)
  4. 验证:在终端测试,如果失败则对比 Playwright 捕获的请求头与 axios 请求头的差异
→ 详见 references/site-analysis-playbook.md
Choose the corresponding path based on the collected information:
Path A: User provides API documentation or interface specifications → Read the documentation → Map endpoints to plugin methods → Write code directly
Path B: User provides website URL (page content can be crawled directly) → Use tools to crawl page HTML → Analyze DOM structure → Write parsing logic with cheerio → See references/site-analysis-playbook.md "Static Site Analysis"
Path C: User provides website URL (needs reverse analysis of internal APIs) The page loads data asynchronously through internal APIs without public documentation. Need to observe the actual behavior of the browser, then accurately reproduce it with axios.
Core Methodology——Observe → Analyze → Reproduce → Verify:
  1. Observe: Use Playwright to load the page, capture all network requests, and save them locally
  2. Analyze: Perform offline analysis based on locally cached data to find key API endpoints and audio requests
  3. Reproduce: Reproduce the same request with axios (including complete Headers)
  4. Verify: Test in the terminal; if it fails, compare the difference between the request headers captured by Playwright and those of axios
→ See references/site-analysis-playbook.md

步骤 3:分析数据源

Step 3: Analyze Data Sources

快速预判站点类型:用工具抓取目标 URL 的 HTML,检查内容:
  • 如果 HTML 中包含完整的数据内容(歌曲列表、歌手名等文本) → 静态站点,用 cheerio 解析
  • 如果 HTML 很小(骨架 HTML)、包含大量
    <script>
    标签、或核心内容区域为空 → SPA 站点,直接使用 Playwright,不要在 axios 上浪费时间
遵循"观察-分析-复现-验证"方法论(详见 references/site-analysis-playbook.md)。
关键规则:
  • 所有抓取内容先保存到本地文件,后续基于本地文件分析,避免重复请求
  • 不要盲猜 URL——每个 API 端点必须来自页面内容或网络请求的实际观察
  • 推断出的 API 和数据结构不确定时 → 向用户确认
Quickly predict the site type: Use tools to crawl the HTML of the target URL and check the content:
  • If the HTML contains complete data content (song lists, artist names, etc. text) → Static site, parse with cheerio
  • If the HTML is small (skeleton HTML), contains a large number of
    <script>
    tags, or the core content area is empty → SPA site, use Playwright directly, do not waste time on axios
Follow the "Observe-Analyze-Reproduce-Verify" methodology (see references/site-analysis-playbook.md).
Key Rules:
  • Save all crawled content to local files first, then analyze based on local files to avoid duplicate requests
  • Do not guess URLs blindly——Every API endpoint must come from actual observation of page content or network requests
  • When unsure about the inferred API and data structure → Confirm with the user

步骤 4-8:实现循环

Steps 4-8: Implementation Loop

确定数据源后:
  1. 根据站点分析结果,自动识别站点支持的功能(搜索、排行榜、歌单、歌词等),能适配的全部实现,适配不了的不写入插件——不需要询问用户实现哪些功能
  2. search
    getMediaSource
    开始(最核心的两个方法)
  3. 每完成一个方法,编写测试脚本并在终端中执行验证
  4. 通过后再实现下一个方法
  5. 全部完成后,添加元数据,输出最终
    .js
    文件
After determining the data source:
  1. Automatically identify the functions supported by the site based on site analysis results (search, leaderboard, playlist, lyrics, etc.), implement all that can be adapted, do not write those that cannot be adapted into the plugin——No need to ask the user which functions to implement
  2. Start with
    search
    and
    getMediaSource
    (the two most core methods)
  3. After completing each method, write a test script and execute it in the terminal for verification
  4. Implement the next method after passing the test
  5. After all are completed, add metadata and output the final
    .js
    file

插件骨架

Plugin Skeleton

javascript
// 引入需要的模块(均为沙箱内置,无需安装)
const axios = require('axios');
const cheerio = require('cheerio');

module.exports = {
    // ===== 必填属性 =====
    platform: '插件名称',

    // ===== 可选属性 =====
    version: '0.0.1', // 语义化版本号
    author: '作者名',
    description: '插件说明',
    srcUrl: 'https://example.com/plugin.js', // 远程更新地址
    cacheControl: 'no-cache', // "cache" | "no-cache" | "no-store"
    supportedSearchType: ['music', 'album', 'artist', 'sheet'],
    userVariables: [{ key: 'cookie', name: 'Cookie', hint: '请输入你的 Cookie' }],
    hints: {
        importMusicSheet: ['支持以下格式的链接:...', 'https://example.com/playlist/123'],
    },

    // ===== 方法 =====
    async search(query, page, type) {
        /* ... */
    },
    async getMediaSource(musicItem, quality) {
        /* ... */
    },
    // ... 更多方法
};
javascript
// 引入需要的模块(均为沙箱内置,无需安装)
const axios = require('axios');
const cheerio = require('cheerio');

module.exports = {
    // ===== 必填属性 =====
    platform: '插件名称',

    // ===== 可选属性 =====
    version: '0.0.1', // 语义化版本号
    author: '作者名',
    description: '插件说明',
    srcUrl: 'https://example.com/plugin.js', // 远程更新地址
    cacheControl: 'no-cache', // "cache" | "no-cache" | "no-store"
    supportedSearchType: ['music', 'album', 'artist', 'sheet'],
    userVariables: [{ key: 'cookie', name: 'Cookie', hint: '请输入你的 Cookie' }],
    hints: {
        importMusicSheet: ['支持以下格式的链接:...', 'https://example.com/playlist/123'],
    },

    // ===== 方法 =====
    async search(query, page, type) {
        /* ... */
    },
    async getMediaSource(musicItem, quality) {
        /* ... */
    },
    // ... 更多方法
};

属性速查

Property Quick Reference

属性必填说明
platform
插件名称,不可为"本地"
version
语义化版本号,默认
"0.0.0"
author
插件作者
description
说明文字
srcUrl
远程
.js
文件直链,用于应用内更新
cacheControl
getMediaSource
结果的缓存策略
supportedSearchType
声明支持的搜索类型数组,不填则认为全部支持
userVariables
用户可配置变量数组,每项含
key
(必填)、
name
hint
hints
提示文案,键为方法名,值为字符串数组
PropertyRequiredDescription
platform
YesPlugin name, cannot be "Local"
version
NoSemantic version number, default
"0.0.0"
author
NoPlugin author
description
NoPlugin description
srcUrl
NoRemote
.js
file direct link, used for in-app updates
cacheControl
NoCache strategy for
getMediaSource
results
supportedSearchType
NoArray of declared supported search types; if not filled, all are considered supported
userVariables
NoArray of user-configurable variables, each containing
key
(required),
name
,
hint
hints
NoPrompt text, keys are method names, values are string arrays

userVariables
用法

userVariables
Usage

定义:
javascript
userVariables: [{ key: 'cookie', name: 'Cookie', hint: '在浏览器登录后获取' }];
运行时读取:
javascript
const cookie = env.getUserVariables().cookie;
Definition:
javascript
userVariables: [{ key: 'cookie', name: 'Cookie', hint: '在浏览器登录后获取' }];
Read at runtime:
javascript
const cookie = env.getUserVariables().cookie;

方法速查

Method Quick Reference

所有方法均为
async
,遇到错误应直接
throw
。 完整签名、参数与返回值详见 references/plugin-protocol.md。 基本媒体类型字段详见 references/media-types.md
方法功能核心入参核心返回值
search
搜索
(query, page, type)
{ isEnd, data: [] }
getMediaSource
获取播放链接
(musicItem, quality)
{ url, headers? }
getLyric
获取歌词
(musicItem)
{ rawLrc?, translation? }
getAlbumInfo
专辑详情
(albumItem, page)
{ isEnd, musicList, albumItem? }
getMusicSheetInfo
歌单详情
(sheetItem, page)
{ isEnd, musicList, sheetItem? }
getArtistWorks
歌手作品
(artistItem, page, type)
{ isEnd, data: [] }
getMusicInfo
补全歌曲信息
(musicItem)
Partial<IMusicItem>
importMusicItem
导入单曲
(urlLike)
IMusicItem
importMusicSheet
导入歌单
(urlLike)
IMusicItem[]
getTopLists
排行榜列表
[{ title?, data: [] }]
getTopListDetail
排行榜详情
(topListItem, page)
{ isEnd, musicList }
getRecommendSheetTags
推荐歌单标签
{ pinned?, data: [] }
getRecommendSheetsByTag
按标签获取歌单
(tag, page)
{ isEnd, data: [] }
getMusicComments
歌曲评论
(musicItem, page)
{ isEnd, data: [] }
All methods are
async
; throw errors directly when encountering them. For complete signatures, parameters, and return values, see references/plugin-protocol.md. For basic media type fields, see references/media-types.md.
MethodFunctionCore ParametersCore Return Value
search
Search
(query, page, type)
{ isEnd, data: [] }
getMediaSource
Get playback link
(musicItem, quality)
{ url, headers? }
getLyric
Get lyrics
(musicItem)
{ rawLrc?, translation? }
getAlbumInfo
Album details
(albumItem, page)
{ isEnd, musicList, albumItem? }
getMusicSheetInfo
Playlist details
(sheetItem, page)
{ isEnd, musicList, sheetItem? }
getArtistWorks
Artist works
(artistItem, page, type)
{ isEnd, data: [] }
getMusicInfo
Complete song info
(musicItem)
Partial<IMusicItem>
importMusicItem
Import single song
(urlLike)
IMusicItem
importMusicSheet
Import playlist
(urlLike)
IMusicItem[]
getTopLists
Leaderboard listNone
[{ title?, data: [] }]
getTopListDetail
Leaderboard details
(topListItem, page)
{ isEnd, musicList }
getRecommendSheetTags
Recommended playlist tagsNone
{ pinned?, data: [] }
getRecommendSheetsByTag
Get playlists by tag
(tag, page)
{ isEnd, data: [] }
getMusicComments
Song comments
(musicItem, page)
{ isEnd, data: [] }

关键约定

Key Conventions

  • 分页
    page
    从 1 开始;
    isEnd
    true
    表示到达最后一页,不填默认
    true
  • platform
    自动注入
    :返回的媒体项无需设置
    platform
    ,框架会自动添加
  • id
    必须有
    :每个媒体项必须包含
    id
    字段
  • 可扩展字段
    IMusicItem
    等类型支持任意可序列化的扩展字段,这些字段会在后续方法调用时被传入。利用这一特性在
    search
    中附带后续方法所需的额外数据
  • 错误处理:遇到错误直接
    throw
    ,不要返回
    null
  • Pagination:
    page
    starts from 1;
    isEnd
    being
    true
    means reaching the last page, default is
    true
    if not filled
  • platform
    auto-injection
    : No need to set
    platform
    for returned media items; the framework will add it automatically
  • id
    is required
    : Each media item must contain the
    id
    field
  • Extensible fields: Types like
    IMusicItem
    support any serializable extended fields, which will be passed in during subsequent method calls. Use this feature to attach additional data needed by subsequent methods in
    search
  • Error handling: Throw errors directly when encountering them, do not return
    null

沙箱环境

Sandbox Environment

插件运行在隔离沙箱中(
vm.createContext
),有 10 秒执行超时。
Plugins run in an isolated sandbox (
vm.createContext
) with a 10-second execution timeout.

可用模块(通过
require
引入)

Available Modules (Import via
require
)

模块用途
axios
HTTP 请求
cheerio
HTML 解析(类 jQuery 语法)
crypto-js
加解密
dayjs
日期时间处理
big-integer
大整数运算
qs
URL 参数序列化
he
HTML 实体编码/解码
webdav
WebDAV 操作
注意:不在此列表中的模块无法
require
。如需使用其它库(如 lodash),必须用 webpack 等工具将源码打包到插件文件中。
ModulePurpose
axios
HTTP requests
cheerio
HTML parsing (jQuery-like syntax)
crypto-js
Encryption and decryption
dayjs
Date and time handling
big-integer
Big integer operations
qs
URL parameter serialization
he
HTML entity encoding/decoding
webdav
WebDAV operations
Note: Modules not in this list cannot be
require
d. If you need to use other libraries (such as lodash), you must package the source code into the plugin file using tools like webpack.

全局可用 API

Globally Available APIs

  • fetch
    ,
    URL
    ,
    URLSearchParams
    ,
    AbortController
  • Buffer
    ,
    TextEncoder
    ,
    TextDecoder
  • btoa
    ,
    atob
    ,
    encodeURIComponent
    ,
    decodeURIComponent
  • JSON
    ,
    console.log/warn/error
  • setTimeout
    ,
    setInterval
    ,
    Promise
  • env.getUserVariables()
    — 读取用户变量
  • env.os
    — 操作系统
  • env.appVersion
    — 应用版本
  • env.lang
    — 当前语言
  • fetch
    ,
    URL
    ,
    URLSearchParams
    ,
    AbortController
  • Buffer
    ,
    TextEncoder
    ,
    TextDecoder
  • btoa
    ,
    atob
    ,
    encodeURIComponent
    ,
    decodeURIComponent
  • JSON
    ,
    console.log/warn/error
  • setTimeout
    ,
    setInterval
    ,
    Promise
  • env.getUserVariables()
    — Read user variables
  • env.os
    — Operating system
  • env.appVersion
    — App version
  • env.lang
    — Current language

开发模式示例

Development Mode Examples

模式一:JSON API 对接

Mode 1: JSON API Integration

已知某服务的 API 接口,直接请求并转换数据格式:
javascript
const axios = require('axios');

module.exports = {
    platform: '示例API源',
    version: '0.0.1',
    supportedSearchType: ['music'],

    async search(query, page, type) {
        if (type !== 'music') return { isEnd: true, data: [] };

        const res = await axios.get('https://api.example.com/search', {
            params: { keyword: query, page, limit: 20 },
        });

        const list = res.data.songs || [];
        return {
            isEnd: list.length < 20,
            data: list.map((item) => ({
                id: item.songId,
                title: item.songName,
                artist: item.singerName,
                album: item.albumName,
                artwork: item.coverUrl,
                duration: item.duration,
                // 扩展字段:后续 getMediaSource 会用到
                extraId: item.fileHash,
            })),
        };
    },

    async getMediaSource(musicItem, quality) {
        const res = await axios.get('https://api.example.com/play', {
            params: { hash: musicItem.extraId, quality },
        });
        if (!res.data.url) throw new Error('无法获取播放链接');
        return { url: res.data.url };
    },
};
Known API interface of a service, directly request and convert data format:
javascript
const axios = require('axios');

module.exports = {
    platform: '示例API源',
    version: '0.0.1',
    supportedSearchType: ['music'],

    async search(query, page, type) {
        if (type !== 'music') return { isEnd: true, data: [] };

        const res = await axios.get('https://api.example.com/search', {
            params: { keyword: query, page, limit: 20 },
        });

        const list = res.data.songs || [];
        return {
            isEnd: list.length < 20,
            data: list.map((item) => ({
                id: item.songId,
                title: item.songName,
                artist: item.singerName,
                album: item.albumName,
                artwork: item.coverUrl,
                duration: item.duration,
                // 扩展字段:后续 getMediaSource 会用到
                extraId: item.fileHash,
            })),
        };
    },

    async getMediaSource(musicItem, quality) {
        const res = await axios.get('https://api.example.com/play', {
            params: { hash: musicItem.extraId, quality },
        });
        if (!res.data.url) throw new Error('无法获取播放链接');
        return { url: res.data.url };
    },
};

模式二:HTML 页面解析

Mode 2: HTML Page Parsing

目标网站没有公开 API,直接抓取 HTML 并用 cheerio 解析:
javascript
const axios = require('axios');
const cheerio = require('cheerio');

module.exports = {
    platform: '示例HTML源',
    version: '0.0.1',
    supportedSearchType: ['music'],

    async search(query, page, type) {
        if (type !== 'music') return { isEnd: true, data: [] };

        const rawHtml = (
            await axios.get('https://example.com/search', {
                params: { q: query, page },
            })
        ).data;

        const $ = cheerio.load(rawHtml);
        const results = [];

        $('.search-result-item').each((i, el) => {
            results.push({
                id: $(el).attr('data-id'),
                title: $(el).find('.song-title').text().trim(),
                artist: $(el).find('.artist-name').text().trim(),
                artwork: $(el).find('img').attr('src'),
                url: $(el).find('audio').attr('src'), // 如果页面直接包含音源
            });
        });

        return {
            isEnd: $('.next-page').length === 0,
            data: results,
        };
    },
};
The target website has no public API; directly crawl HTML and parse with cheerio:
javascript
const axios = require('axios');
const cheerio = require('cheerio');

module.exports = {
    platform: '示例HTML源',
    version: '0.0.1',
    supportedSearchType: ['music'],

    async search(query, page, type) {
        if (type !== 'music') return { isEnd: true, data: [] };

        const rawHtml = (
            await axios.get('https://example.com/search', {
                params: { q: query, page },
            })
        ).data;

        const $ = cheerio.load(rawHtml);
        const results = [];

        $('.search-result-item').each((i, el) => {
            results.push({
                id: $(el).attr('data-id'),
                title: $(el).find('.song-title').text().trim(),
                artist: $(el).find('.artist-name').text().trim(),
                artwork: $(el).find('img').attr('src'),
                url: $(el).find('audio').attr('src'), // 如果页面直接包含音源
            });
        });

        return {
            isEnd: $('.next-page').length === 0,
            data: results,
        };
    },
};

模式三:直接拼接(无需网络请求)

Mode 3: Direct Concatenation (No Network Requests Needed)

音源 URL 可直接由已知信息拼接得出:
javascript
module.exports = {
    platform: '示例拼接源',
    version: '0.0.1',
    cacheControl: 'no-store',

    async getMediaSource(musicItem, quality) {
        return {
            url: 'https://cdn.example.com/audio/' + musicItem.id + '.mp3',
        };
    },
};
The audio source URL can be directly concatenated from known information:
javascript
module.exports = {
    platform: '示例拼接源',
    version: '0.0.1',
    cacheControl: 'no-store',

    async getMediaSource(musicItem, quality) {
        return {
            url: 'https://cdn.example.com/audio/' + musicItem.id + '.mp3',
        };
    },
};

测试流程

Testing Process

完成插件编写后,创建一个测试脚本在终端中运行:
javascript
// test-plugin.js
const plugin = require('./my-plugin.js');

async function test() {
    console.log('=== 测试 search ===');
    try {
        const searchResult = await plugin.search('测试关键词', 1, 'music');
        console.log('搜索结果数量:', searchResult.data.length);
        console.log('是否最后一页:', searchResult.isEnd);
        if (searchResult.data.length > 0) {
            console.log('第一条结果:', JSON.stringify(searchResult.data[0], null, 2));

            console.log('\n=== 测试 getMediaSource ===');
            const source = await plugin.getMediaSource(searchResult.data[0], 'standard');
            console.log('播放链接:', source?.url ? '✓ 获取成功' : '✗ 获取失败');
            console.log('详细信息:', JSON.stringify(source, null, 2));
        }
    } catch (e) {
        console.error('测试失败:', e.message);
    }
}

test();
执行方式:
bash
node test-plugin.js
测试要点
  • 每个方法单独测试,确认返回值结构正确
  • 验证
    id
    字段存在且有效
  • 验证分页逻辑(测试 page=1 和 page=2)
  • 验证
    getMediaSource
    返回的 URL 可访问
注意:本地 Node.js 测试时,
env.getUserVariables()
不可用。如果插件使用了
userVariables
,需要在测试脚本中模拟:
javascript
// 在 require 插件之前模拟 env
global.env = {
    getUserVariables: () => ({ cookie: '测试用cookie值' }),
    os: 'win32',
    appVersion: '0.0.1',
    lang: 'zh-CN',
};
const plugin = require('./my-plugin.js');
After completing the plugin writing, create a test script to run in the terminal:
javascript
// test-plugin.js
const plugin = require('./my-plugin.js');

async function test() {
    console.log('=== 测试 search ===');
    try {
        const searchResult = await plugin.search('测试关键词', 1, 'music');
        console.log('搜索结果数量:', searchResult.data.length);
        console.log('是否最后一页:', searchResult.isEnd);
        if (searchResult.data.length > 0) {
            console.log('第一条结果:', JSON.stringify(searchResult.data[0], null, 2));

            console.log('\n=== 测试 getMediaSource ===');
            const source = await plugin.getMediaSource(searchResult.data[0], 'standard');
            console.log('播放链接:', source?.url ? '✓ 获取成功' : '✗ 获取失败');
            console.log('详细信息:', JSON.stringify(source, null, 2));
        }
    } catch (e) {
        console.error('测试失败:', e.message);
    }
}

test();
Execution method:
bash
node test-plugin.js
Testing Points:
  • Test each method individually to confirm the return value structure is correct
  • Verify that the
    id
    field exists and is valid
  • Verify pagination logic (test page=1 and page=2)
  • Verify that the URL returned by
    getMediaSource
    is accessible
Note: When testing locally with Node.js,
env.getUserVariables()
is not available. If the plugin uses
userVariables
, simulate it in the test script:
javascript
// 在 require 插件之前模拟 env
global.env = {
    getUserVariables: () => ({ cookie: '测试用cookie值' }),
    os: 'win32',
    appVersion: '0.0.1',
    lang: 'zh-CN',
};
const plugin = require('./my-plugin.js');

发布与更新

Release and Updates

托管到 GitHub

Host on GitHub

  1. 创建一个 GitHub 仓库(或使用已有仓库)
  2. 将插件
    .js
    文件上传到仓库
  3. 获取文件的 raw 链接(格式:
    https://raw.githubusercontent.com/用户名/仓库名/分支/文件名.js
  4. 将此链接填入插件的
    srcUrl
    字段
  1. Create a GitHub repository (or use an existing one)
  2. Upload the plugin
    .js
    file to the repository
  3. Get the raw link of the file (format:
    https://raw.githubusercontent.com/username/repository/branch/filename.js
    )
  4. Fill this link into the
    srcUrl
    field of the plugin

用户安装

User Installation

用户在 MusicFree 中通过 "从 URL 安装插件" 功能,粘贴插件的 raw 链接即可安装。
Users can paste the raw link of the plugin into the "Install Plugin from URL" feature in MusicFree to install it.

版本更新

Version Updates

  1. 修改
    version
    字段(遵循语义化版本,如
    "0.0.1"
    "0.0.2"
  2. 更新代码并推送到 GitHub
  3. 用户在应用内点击"更新插件"即可自动获取新版本
  1. Modify the
    version
    field (follow semantic versioning, e.g.,
    "0.0.1"
    "0.0.2"
    )
  2. Update the code and push to GitHub
  3. Users can click "Update Plugin" in the app to automatically get the new version

注意事项

Notes

  • 错误处理:方法遇到错误应直接
    throw
    ,不要
    return null
  • 语法兼容:优先使用 ES8 及以下语法。
    async/await
    可放心使用,
    ?.
    ??
    在桌面版可用,但移动端可能需要 babel 转译
  • 网络请求:移动端 axios 会自带默认 headers(包括
    User-Agent: okhttp/4.10.0
    ),某些服务器可能因此返回异常,可手动设置
    headers
    覆盖
  • 安全:不要在插件中修改内置 npm 包的全局属性
  • 超时:每次方法调用有 10 秒超时限制,避免做过多请求或大量计算
  • Error Handling: Throw errors directly when methods encounter them, do not return
    null
  • Syntax Compatibility: Prioritize using ES8 or lower syntax.
    async/await
    can be used safely;
    ?.
    and
    ??
    are available in the desktop version but may require babel transpilation for the mobile version
  • Network Requests: Axios on the mobile version will come with default headers (including
    User-Agent: okhttp/4.10.0
    ), some servers may return exceptions due to this, which can be manually overridden by setting
    headers
  • Security: Do not modify global properties of built-in npm packages in plugins
  • Timeout: Each method call has a 10-second timeout limit; avoid making too many requests or doing heavy computations

Skill 更新

Skill Updates

本 Skill 的最新版本托管在官方仓库:https://github.com/maotoumao/musicfree-skills
The latest version of this Skill is hosted in the official repository: https://github.com/maotoumao/musicfree-skills