MusicFree Plugin Development
Strict Code of Conduct
The following rules are in effect at all times during the entire plugin development process; violations will result in development failure:
- Forbid guessing URLs blindly: Do not try paths like or out of thin air. Every URL must come from page content or network requests captured by Playwright
- 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
- Forbid batch probing: Do not send multiple requests simultaneously to test different parameter combinations
- Forbid duplicate requests: Each URL is requested only once; after saving the results to a local file, analyze based on the local data
- 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 (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
Do everything you can on your own, never ask the user to do it. Follow the following priority:
- AI completes independently: Directly use tools to crawl pages, analyze HTML/JS, run scripts in the terminal
- AI does + user confirms: AI gives conclusions after analysis, asks the user to confirm if they are correct
- 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
- 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. 输出最终插件
Step 1: Collect Information and Check Environment
Check the development environment first (before doing anything else):
- Check if Node.js is available:
- 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)
- Install Playwright:
npm install -D playwright
(No need to download Chromium; use 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 field of the plugin)
Step 2: Determine the Path
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:
- Observe: Use Playwright to load the page, capture all network requests, and save them locally
- Analyze: Perform offline analysis based on locally cached data to find key API endpoints and audio requests
- Reproduce: Reproduce the same request with axios (including complete Headers)
- 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
Step 3: Analyze Data Sources
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 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
Steps 4-8: Implementation Loop
After determining the data source:
- 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
- Start with and (the two most core methods)
- After completing each method, write a test script and execute it in the terminal for verification
- Implement the next method after passing the test
- After all are completed, add metadata and output the final 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) {
/* ... */
},
// ... 更多方法
};
Property Quick Reference
| Property | Required | Description |
|---|
| Yes | Plugin name, cannot be "Local" |
| No | Semantic version number, default |
| No | Plugin author |
| No | Plugin description |
| No | Remote file direct link, used for in-app updates |
| No | Cache strategy for results |
| No | Array of declared supported search types; if not filled, all are considered supported |
| No | Array of user-configurable variables, each containing (required), , |
| No | Prompt text, keys are method names, values are string arrays |
Usage
Definition:
javascript
userVariables: [{ key: 'cookie', name: 'Cookie', hint: '在浏览器登录后获取' }];
Read at runtime:
javascript
const cookie = env.getUserVariables().cookie;
Method Quick Reference
All methods are
; 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.
| Method | Function | Core Parameters | Core Return Value |
|---|
| Search | | |
| Get playback link | | |
| Get lyrics | | { rawLrc?, translation? }
|
| Album details | | { isEnd, musicList, albumItem? }
|
| Playlist details | | { isEnd, musicList, sheetItem? }
|
| Artist works | | |
| Complete song info | | |
| Import single song | | |
| Import playlist | | |
| Leaderboard list | None | |
| Leaderboard details | | |
| Recommended playlist tags | None | |
| Get playlists by tag | | |
| Song comments | | |
Key Conventions
- Pagination: starts from 1; being means reaching the last page, default is if not filled
- auto-injection: No need to set for returned media items; the framework will add it automatically
- is required: Each media item must contain the field
- Extensible fields: Types like 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
- Error handling: Throw errors directly when encountering them, do not return
Sandbox Environment
Plugins run in an isolated sandbox (
) with a 10-second execution timeout.
Available Modules (Import via )
| Module | Purpose |
|---|
| HTTP requests |
| HTML parsing (jQuery-like syntax) |
| Encryption and decryption |
| Date and time handling |
| Big integer operations |
| URL parameter serialization |
| HTML entity encoding/decoding |
| WebDAV operations |
Note: Modules not in this list cannot be
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.
Globally Available APIs
- , , ,
- , ,
- , , ,
- ,
- , ,
- — Read user variables
- — Operating system
- — App version
- — Current language
Development Mode Examples
Mode 1: JSON API Integration
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 };
},
};
Mode 2: HTML Page Parsing
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)
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
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:
Testing Points:
- Test each method individually to confirm the return value structure is correct
- Verify that the field exists and is valid
- Verify pagination logic (test page=1 and page=2)
- Verify that the URL returned by is accessible
Note: When testing locally with Node.js,
is not available. If the plugin uses
, 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
Host on GitHub
- Create a GitHub repository (or use an existing one)
- Upload the plugin file to the repository
- Get the raw link of the file (format:
https://raw.githubusercontent.com/username/repository/branch/filename.js
)
- Fill this link into the field of the plugin
User Installation
Users can paste the raw link of the plugin into the "Install Plugin from URL" feature in MusicFree to install it.
Version Updates
- Modify the field (follow semantic versioning, e.g., → )
- Update the code and push to GitHub
- Users can click "Update Plugin" in the app to automatically get the new version
Notes
- Error Handling: Throw errors directly when methods encounter them, do not return
- Syntax Compatibility: Prioritize using ES8 or lower syntax. 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
- 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 Updates
The latest version of this Skill is hosted in the official repository:
https://github.com/maotoumao/musicfree-skills