musicfree-plugin-dev について
MusicFree の音源プラグイン開発を、サイト分析から実装、テストまで案内するスキルです。API を推測したり検索したりせず、Playwright で実際のリクエストを観察する方針です。
主な機能
- 01サイトと実際のリクエストを分析
- 02MusicFree のプラグインメソッドを実装
- 03メソッドと返却データをテスト
- 04プラグインの設定と更新を案内
- 05リクエスト観察のルールを適用
活用シーン
- 01サイトの実際の挙動に基づくプラグイン開発
- 02文書化された API をプラグインメソッドに対応付け
- 03検索と再生 URL のメソッドをテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ maotoumao/ musicfree-skills --skill musicfree-plugin-devインストール後、Claude Code で/musicfree-plugin-devを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/media-types.mdplugin-protocol.mdsite-analysis-playbook.md詳細
MusicFree 插件开发
严格行为准则
以下规则在整个插件开发过程中始终生效,违反将导致开发失败:
- 禁止盲猜 URL:不要凭空试
/api/search、/v1/song等路径。每个 URL 必须来自页面内容或 Playwright 捕获的网络请求 - 禁止搜索网络寻找 API:不要搜索"XX音乐 API"、"免费音乐接口"等。所有数据来源必须通过对目标站点的直接观察获得,不能来自网络搜索
- 禁止批量探测:不要同时发多个请求试不同的参数组合
- 禁止重复请求:每个 URL 只请求一次,结果保存到本地文件后基于本地数据分析
- 禁止放弃说"需要浏览器环境":axios 失败但浏览器可以访问,差别一定在请求头上,用 Playwright 捕获真实请求头后在 axios 中补全
当遇到困难时,唯一正确的做法是:用 Playwright 观察浏览器的真实行为。不要猜测,不要搜索,去观察。
角色与协作模式
- AI(你):分析目标站点、编写插件代码、编写并执行测试脚本、迭代修复
- 用户:提供目标站点/API 信息,仅在 AI 无法独立完成时执行辅助操作
核心原则:最小化用户操作
能自己做的,绝不让用户做。 遵循以下优先级:
- AI 独立完成:直接用工具抓取页面、分析 HTML/JS、在终端运行脚本
- AI 做 + 用户确认:AI 分析后给出结论,请用户确认是否正确
- 用户操作(最后手段):仅当 AI 的工具无法获取到所需数据时,才让用户在浏览器中操作
与用户交互原则
- 用户可能是零基础,不要使用未解释的技术术语
- 需要用户操作时,给出精确到按键级别的逐步指令,每次只下达一个操作
- 主动告知用户当前进度和下一步计划
- 不确定时必须询问:当对站点行为、API 含义、数据字段映射不确定时,先问再做
工作流程
1. 收集信息 → 2. 判断路径 → 3. 分析数据源 → 4. 确定方法集合
→ 5. 逐个实现 → 6. 测试验证 → 7. 迭代修复 → 8. 输出最终插件
步骤 1:收集信息并检查环境
先检查开发环境(在做任何其他事之前):
- 检查 Node.js 是否可用:
node --version - 询问用户:电脑上是否安装了 Chrome 或 Edge 浏览器?安装路径是什么?(Playwright 可以复用已安装的浏览器,避免下载)
- 安装 Playwright:
npm install -D playwright(不需要下载 Chromium,后续用channel: 'chrome'复用用户的 Chrome)
然后收集需求:
- 目标是什么?(某个音乐网站?已有的 API 文档?自建的服务?)
- 插件作者名?(会写入插件的
author字段)
步骤 2:判断路径
根据收集到的信息,选择对应路径:
路径 A:用户提供了 API 文档或接口规格 → 阅读文档 → 将端点映射到插件方法 → 直接编写代码
路径 B:用户提供了网站 URL(页面内容直接可抓取) → 用工具抓取页面 HTML → 分析 DOM 结构 → 用 cheerio 编写解析逻辑 → 详见 references/site-analysis-playbook.md "静态站点分析"
路径 C:用户提供了网站 URL(需逆向分析内部 API) 页面通过内部 API 异步加载数据,没有公开文档。需要观察浏览器的实际行为,然后用 axios 精确复现。
核心方法论——观察 → 分析 → 复现 → 验证:
- 观察:用 Playwright 加载页面,捕获所有网络请求,保存到本地
- 分析:基于本地缓存的数据做离线分析,找到关键的 API 端点和音频请求
- 复现:用 axios 复现相同的请求(包括完整的 Headers)
- 验证:在终端测试,如果失败则对比 Playwright 捕获的请求头与 axios 请求头的差异
→ 详见 references/site-analysis-playbook.md
步骤 3:分析数据源
快速预判站点类型:用工具抓取目标 URL 的 HTML,检查内容:
- 如果 HTML 中包含完整的数据内容(歌曲列表、歌手名等文本) → 静态站点,用 cheerio 解析
- 如果 HTML 很小(骨架 HTML)、包含大量
<script>标签、或核心内容区域为空 → SPA 站点,直接使用 Playwright,不要在 axios 上浪费时间
遵循"观察-分析-复现-验证"方法论(详见 references/site-analysis-playbook.md)。
关键规则:
- 所有抓取内容先保存到本地文件,后续基于本地文件分析,避免重复请求
- 不要盲猜 URL——每个 API 端点必须来自页面内容或网络请求的实际观察
- 推断出的 API 和数据结构不确定时 → 向用户确认
步骤 4-8:实现循环
确定数据源后:
- 根据站点分析结果,自动识别站点支持的功能(搜索、排行榜、歌单、歌词等),能适配的全部实现,适配不了的不写入插件——不需要询问用户实现哪些功能
- 从
search和getMediaSource开始(最核心的两个方法) - 每完成一个方法,编写测试脚本并在终端中执行验证
- 通过后再实现下一个方法
- 全部完成后,添加元数据,输出最终
.js文件
插件骨架
// 引入需要的模块(均为沙箱内置,无需安装)
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) {
/* ... */
},
// ... 更多方法
};
属性速查
| 属性 | 必填 | 说明 |
|---|---|---|
platform | 是 | 插件名称,不可为"本地" |
version | 否 | 语义化版本号,默认 "0.0.0" |
author | 否 | 插件作者 |
description | 否 | 说明文字 |
srcUrl | 否 | 远程 .js 文件直链,用于应用内更新 |
cacheControl | 否 | getMediaSource 结果的缓存策略 |
supportedSearchType | 否 | 声明支持的搜索类型数组,不填则认为全部支持 |
userVariables | 否 | 用户可配置变量数组,每项含 key(必填)、name、hint |
hints | 否 | 提示文案,键为方法名,值为字符串数组 |
userVariables 用法
定义:
userVariables: [{ key: 'cookie', name: 'Cookie', hint: '在浏览器登录后获取' }];
运行时读取:
const cookie = env.getUserVariables().cookie;
メソッド早見表
すべてのメソッドは 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: [] } |
主な規約
- ページネーション:
pageは 1 から始まります。isEndがtrueの場合は最終ページです。省略時のデフォルトはtrueです platformの自動設定:返されるメディア項目にplatformを設定する必要はありません。フレームワークが自動的に追加しますidは必須:各メディア項目にはidフィールドが必要です- 拡張可能なフィールド:
IMusicItemなどの型は、シリアライズ可能な任意の拡張フィールドに対応しています。これらのフィールドは後続のメソッド呼び出し時に渡されます。この機能を利用して、searchで後続のメソッドに必要な追加データを付加してください - エラー処理:エラーが発生した場合は、そのまま
throwしてください。nullは返さないでください
サンドボックス環境
プラグインは分離されたサンドボックス(vm.createContext)で実行され、実行タイムアウトは 10 秒です。
使用可能なモジュール(require で読み込み)
| モジュール | 用途 |
|---|---|
axios | HTTP リクエスト |
cheerio | HTML 解析(jQuery 風の構文) |
crypto-js | 暗号化と復号 |
dayjs | 日付と時刻の処理 |
big-integer | 大整数演算 |
qs | URL パラメーターのシリアライズ |
he | HTML エンティティのエンコード/デコード |
webdav | WebDAV 操作 |
注意:この一覧にないモジュールは require できません。lodash など、ほかのライブラリを使用する場合は、webpack などのツールでソースコードをプラグインファイルにバンドルする必要があります。
グローバルに使用可能な API
fetch,URL,URLSearchParams,AbortControllerBuffer,TextEncoder,TextDecoderbtoa,atob,encodeURIComponent,decodeURIComponentJSON,console.log/warn/errorsetTimeout,setInterval,Promiseenv.getUserVariables()— ユーザー変数を読み取るenv.os— オペレーティングシステムenv.appVersion— アプリのバージョンenv.lang— 現在の言語
開発パターンの例
パターン 1:JSON API への接続
サービスの API エンドポイントが分かっている場合は、直接リクエストしてデータ形式を変換します。
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 };
},
};
パターン 2:HTML ページの解析
対象のウェブサイトに公開 API がない場合は、HTML を直接取得し、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,
};
},
};
パターン 3:直接組み立て(ネットワークリクエスト不要)
音源 URL は、既知の情報から直接組み立てられます。
module.exports = {
platform: '示例拼接源',
version: '0.0.1',
cacheControl: 'no-store',
async getMediaSource(musicItem, quality) {
return {
url: 'https://cdn.example.com/audio/' + musicItem.id + '.mp3',
};
},
};
テスト手順
プラグインの作成後、テストスクリプトを作成してターミナルで実行します。
// 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();
実行方法:
node test-plugin.js
テストのポイント:
- 各メソッドを個別にテストし、戻り値の構造が正しいことを確認する
idフィールドが存在し、有効であることを確認する- ページネーションのロジックを確認する(page=1 と page=2 をテストする)
getMediaSourceが返す URL にアクセスできることを確認する
注意:ローカルの Node.js でテストする場合、env.getUserVariables() は使用できません。プラグインで userVariables を使用している場合は、テストスクリプト内でモックする必要があります。
// 在 require 插件之前模拟 env
global.env = {
getUserVariables: () => ({ cookie: '测试用cookie值' }),
os: 'win32',
appVersion: '0.0.1',
lang: 'zh-CN',
};
const plugin = require('./my-plugin.js');
公開と更新
GitHub でホスティング
- GitHub リポジトリを作成します(または既存のリポジトリを使用します)
- プラグインの
.jsファイルをリポジトリにアップロードします - ファイルの raw リンクを取得します(形式:
https://raw.githubusercontent.com/用户名/仓库名/分支/文件名.js) - このリンクをプラグインの
srcUrlフィールドに入力します
ユーザーによるインストール
ユーザーは MusicFree の「URL からプラグインをインストール」機能で、プラグインの raw リンクを貼り付けてインストールできます。
バージョンの更新
versionフィールドを変更します("0.0.1"→"0.0.2"のように、セマンティックバージョニングに従います)- コードを更新して GitHub にプッシュします
- ユーザーはアプリ内で「プラグインを更新」をクリックすると、新しいバージョンを自動的に取得できます
注意事项
- 错误处理:方法遇到错误应直接
throw,不要return null - 语法兼容:优先使用 ES8 及以下语法。
async/await可放心使用,?.、??在桌面版可用,但移动端可能需要 babel 转译 - 网络请求:移动端 axios 会自带默认 headers(包括
User-Agent: okhttp/4.10.0),某些服务器可能因此返回异常,可手动设置headers覆盖 - 安全:不要在插件中修改内置 npm 包的全局属性
- 超时:每次方法调用有 10 秒超时限制,避免做过多请求或大量计算
Skill 更新
本 Skill 的最新版本托管在官方仓库:https://github.com/maotoumao/musicfree-skills