Strict Code of Conduct
The following rules apply throughout the entire plugin development processalways take effect, and violating them will cause development to fail:
- Do not guess URLs blindly: do not try
/api/search、/v1/songand similar paths out of thin air. Every URL must come from page content or a network request captured by Playwright - Do not search the web for APIs: do not search for "XX Music API", "free music interface", etc. All data sources must be obtained through direct observation of the target site, and must not come from web searches
- Do not probe in batches: do not send multiple requests at the same time to try different parameter combinations
- Do not make repeated requests: request each URL only once, save the results to a local file, and then analyze based on the local data
- Do not give up and say "a browser environment is required": if axios fails but the browser can access it, the difference must be in the request headers. Use Playwright to capture the real request headers and then complete them in axios
When encountering difficulties, the only correct approach is: use Playwright to observe the browser's real behavior. Do not guess, do not search, go observe.
Roles and Collaboration Mode
- AI (you): analyze the target site, write plugin code, write and execute test scripts, and iterate on fixes
- User: provide target site/API information, and perform auxiliary operations only when the AI cannot complete them independently
Core principle: minimize user operations
Anything you can do yourself, never make the user do. Follow this priority order:
- AI completes independently: directly use tools to fetch pages, analyze HTML/JS, and run scripts in the terminal
- AI does it + user confirms: after analysis, the AI gives a conclusion and asks the user to confirm whether it is correct
- User operation (last resort): only when the AI's tools cannot obtain the required data should the user operate in the browser
Principles for Interacting with the User
- The user may have zero foundation,do not use unexplained technical jargon
- When user operation is needed, providestep-by-step instructions precise down to the key press, and give only one operation at a time
- Proactively inform the user of the current progress and the next plan
- When uncertain, you must ask: when uncertain about site behavior, API meanings, or data field mappings, ask first and then act
Workflow
1. 收集信息 → 2. 判断路径 → 3. 分析数据源 → 4. 确定方法集合
→ 5. 逐个实现 → 6. 测试验证 → 7. 迭代修复 → 8. 输出最终插件
Step 1: Collect information and check the environment
First check the development environment(before doing anything else):
- Check whether Node.js is available:
node --version - Ask the user: is Chrome or Edge installed on the computer? What is the installation path? (Playwright can reuse an already installed browser to avoid downloading)
- Install Playwright:
npm install -D playwright(no need to download Chromium; later usechannel: 'chrome'Reuse the user's Chrome)
Then gather requirements:
- What is the target? (A music website? Existing API documentation? A self-built service?)
- Plugin author name? (Will be written into the plugin's
authorfield)
Step 2: Determine the path
Based on the gathered information, choose the corresponding path:
Path A: The user provided API documentation or interface specs → Read the documentation → Map the endpoints to plugin methods → Write the code directly
Path B: The user provided a website URL (page content can be scraped directly) → Use tools to scrape the page HTML → Analyze the DOM structure → Write the parsing logic with cheerio → See references/site-analysis-playbook.md "Static site analysis"
Path C: The user provided a website URL (requires reverse-engineering the internal API) The page loads data asynchronously through an internal API, with no public documentation. You need toobserve the browser's actual behavior and then reproduce it precisely 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 the locally cached data to find the key API endpoints and audio requests
- Reproduce: Use axios to reproduce the same requests (including the complete Headers)
- Verify: Test in the terminal; if it fails, compare the differences between the request headers captured by Playwright and the axios request headers
→ See references/site-analysis-playbook.md
Step 3: Analyze the data source
Quickly predict the site type: Use tools to scrape the target URL's HTML and check the content:
- If the HTML contains the complete data content (song list, artist names, and other text) → static site, parse it with cheerio
- If the HTML is very small (skeleton HTML), contains a large number of
<script>tags, or the core content area is empty → SPA site,use Playwright directly, and do not waste time on axios
Follow the "Observe-Analyze-Reproduce-Verify" methodology (see references/site-analysis-playbook.md)。
Key rules:
- Save all scraped content to local files first, then analyze based on the local files afterward to avoid repeated requests
- Do not blindly guess URLs—every API endpoint must come from actual observation of the page content or network requests
- When the inferred API and data structure are uncertain → confirm with the user
Steps 4-8: Implementation loop
After determining the data source:
- Based on the site analysis results, automatically identify the features supported by the site (search, rankings, playlists, lyrics, etc.),implement all that can be adapted, and do not include in the plugin those that cannot be adapted—no need to ask the user which features to implement
- Start with
searchandgetMediaSource(the two most core methods) - After completing each method, write a test script and run it in the terminal to verify
- Only after it passes, implement the next method
- After everything is complete, add metadata and output the final
.jsfile
Plugin skeleton
// 引入需要的模块(均为沙箱内置,无需安装)
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
| Attribute | Required | Description |
|---|---|---|
platform |
Yes | Plugin name, cannot be "local" |
version |
No | Semantic version number, default "0.0.0" |
author |
No | Plugin author |
description |
No | Description text |
srcUrl |
No | Remote .js file direct link, used for in-app updates |
cacheControl |
No | getMediaSource result caching policy |
supportedSearchType |
No | Array of declared supported search types; if omitted, all are considered supported |
userVariables |
No | Array of user-configurable variables, each item contains key(required),name、hint |
hints |
No | Prompt text, key is the method name, value is a string array |
userVariables Usage
Definition:
userVariables: [{ key: 'cookie', name: 'Cookie', hint: '在浏览器登录后获取' }];
Runtime read:
const cookie = env.getUserVariables().cookie;
Method quick reference
All methods are async, and on error should directly throw.
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 input parameters | Core 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 information | (musicItem) |
Partial<IMusicItem> |
importMusicItem |
Import single track | (urlLike) |
IMusicItem |
importMusicSheet |
Import playlist | (urlLike) |
IMusicItem[] |
getTopLists |
Ranking list | None | [{ title?, data: [] }] |
getTopListDetail |
Ranking details | (topListItem, page) |
{ isEnd, musicList } |
getRecommendSheetTags |
Recommended playlist tags | None | { pinned?, data: [] } |
getRecommendSheetsByTag |
Get playlists by tag | (tag, page) |
{ isEnd, data: [] } |
getMusicComments |
Song comments | (musicItem, page) |
{ isEnd, data: [] } |
Key conventions
- Pagination:
pagestarts from 1;isEndistrueindicates the last page has been reached; if omitted, defaults totrue platformauto-injected: returned media items do not need to setplatform, the framework will add it automaticallyidmust have: each media item must contain theidfield- Extensible fields:
IMusicItemand other types support arbitrary serializable extension fields; these fields will be passed in during subsequent method calls. Use this feature to include insearchthe extra data needed by subsequent methods - Error handling: on error directly
throw, do not returnnull
Sandbox environment
The plugin runs in an isolated sandbox (vm.createContext), with a 10-second execution timeout.
Available modules (imported via require )
| Module | Purpose |
|---|---|
axios |
HTTP requests |
cheerio |
HTML parsing (jQuery-like syntax) |
crypto-js |
Encryption/decryption |
dayjs |
Date and time handling |
big-integer |
Big integer arithmetic |
qs |
URL parameter serialization |
he |
HTML entity encoding/decoding |
webdav |
WebDAV operations |
Note: modules not in this list cannot require. If you need to use other libraries (such as lodash), you must use tools like webpack to bundle the source code into the plugin file.
Globally Available APIs
fetch,URL,URLSearchParams,AbortControllerBuffer,TextEncoder,TextDecoderbtoa,atob,encodeURIComponent,decodeURIComponentJSON,console.log/warn/errorsetTimeout,setInterval,Promiseenv.getUserVariables()— Read user variablesenv.os— Operating systemenv.appVersion— App versionenv.lang— Current language
Development Mode Examples
Mode 1: JSON API Integration
When the API endpoint of a service is known, request it directly and convert the data format:
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
When the target website has no public API, scrape the HTML directly and parse it with cheerio:
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 Request Needed)
The audio source URL can be concatenated directly from known information:
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, create a test script and run it in the terminal:
// 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();
How to Run:
node test-plugin.js
Testing Points:
- Test each method individually to confirm the return value structure is correct
- Verify that the
idfield exists and is valid - Verify the pagination logic (test page=1 and page=2)
- Verify that the URL returned by
getMediaSourceis accessible
Note: when testing locally with Node.js,env.getUserVariables() is unavailable. If the plugin uses userVariables, you need to simulate it in the test script:
// 在 require 插件之前模拟 env
global.env = {
getUserVariables: () => ({ cookie: '测试用cookie值' }),
os: 'win32',
appVersion: '0.0.1',
lang: 'zh-CN',
};
const plugin = require('./my-plugin.js');
Publishing and Updating
Hosting on GitHub
- Create a GitHub repository (or use an existing repository)
- Upload the plugin
.jsfile to the repository - Get the raw link of the file (format:
https://raw.githubusercontent.com/用户名/仓库名/分支/文件名.js) - Fill this link into the plugin's
srcUrlfield
User Installation
In MusicFree, users can install it through the "Install Plugin from URL" feature by pasting the plugin's raw link.
Version Updates
- Modify the
versionfield (follow semantic versioning, e.g."0.0.1"→"0.0.2") - Update the code and push it to GitHub
- Users can click "Update Plugin" in the app to automatically get the new version
Precautions
- Error Handling: when a method encounters an error, it should directly
throw, do notreturn null - Syntax Compatibility: prefer to use ES8 and below syntax.
async/awaitcan be used with confidence,?.、??is available on the desktop version, but may require babel transpilation on mobile - Network Requests: on mobile, axios comes with default headers (including
User-Agent: okhttp/4.10.0), and some servers may return exceptions because of this. You can manually setheadersto override - Security: do not modify the global properties of built-in npm packages in the plugin
- Timeout: each method call has a 10-second timeout limit, avoid making too many requests or doing a large amount of computation
Skill Updates
The latest version of this Skill is hosted in the official repository:https://github.com/maotoumao/musicfree-skills