举个栗子
开发方式
为了在 app 内使用特定功能,我们需要按照插件协议实现插件定义的函数。实现这些函数通常有 3 种方式。
第一种是不涉及网络请求的情况,直接按照一些特定逻辑进行拼接转化为插件可以识别的格式即可。
第二种是直接请求接口,比如可以自己开启一个用于提供音乐服务的 web server,然后便可以通过插件中的 axios 库进行网络请求,并将结果转化为插件可以识别的格式。
第三种是类似于爬虫的原理,先发起网络请求,获取原本的 html 文件,然后再用 cheerio 对 html 进行解析,得到目标内容,并做进一步处理,转化为插件可以识别的格式。
插件示例
我们以一个第三方音乐网站 freesound 为例,做一个支持搜索、播放、导入单曲功能的插件。freesound 的页面如下图所示:

我们搜索任意内容,即可看到搜索结果。搜索结果也可以在 dom 结构直接看到。搜索页面遵循如下规则:https://freesound.org/search/?q=关键字,我们直接解析 dom 结构即可拿到关于歌曲的完整信息。


根据上述分析,我们可以按照方式三开发。为了实现插件的搜索功能,我们可以参考 插件协议的搜索函数 来进行具体实现。
步骤一:插件框架
我们先来定义一下插件的大概结构:
module.exports = {
platform: "FreeSound", // 插件名
version: "0.0.0", // 版本号
cacheControl: "no-store", // 我们可以直接解析出musicItem的结构,因此选取no-store就好了,当然也可以不写这个字段
async search(query, page, type) {
// TODO: 在这里实现搜索函数的功能
},
};module.exports = {
platform: "FreeSound", // 插件名
version: "0.0.0", // 版本号
cacheControl: "no-store", // 我们可以直接解析出musicItem的结构,因此选取no-store就好了,当然也可以不写这个字段
async search(query, page, type) {
// TODO: 在这里实现搜索函数的功能
},
};步骤二:实现搜索函数
我们首先需要获取搜索页面对应的 html 文件。请求网络可以用 axios 库,这是一个前端很常用的网络请求库。
我们写出如下 search 函数,它首先能完成请求对应的网址:
const axios = require("axios");
module.exports = {
platform: "FreeSound", // 插件名
version: "0.0.0", // 版本号
cacheControl: "no-store", // 我们可以直接解析出musicItem的结构,因此选取no-store就好了,当然也可以不写这个字段
async search(query, page, type) {
if (type === "music") {
// 我们能搜索的只有音乐,因此判断下类型
// 获取网站的html
const rawHtml = (
await axios.get("https://freesound.org/search", {
q: query,
page,
})
).data;
// TODO: 接下来解析html
}
},
};const axios = require("axios");
module.exports = {
platform: "FreeSound", // 插件名
version: "0.0.0", // 版本号
cacheControl: "no-store", // 我们可以直接解析出musicItem的结构,因此选取no-store就好了,当然也可以不写这个字段
async search(query, page, type) {
if (type === "music") {
// 我们能搜索的只有音乐,因此判断下类型
// 获取网站的html
const rawHtml = (
await axios.get("https://freesound.org/search", {
q: query,
page,
})
).data;
// TODO: 接下来解析html
}
},
};接下来我们需要解析 html 文件,并把它转化为插件可以识别的 IMusicItem 类型。解析 html 可以使用 cheerio 库,它可以用类似 jquery 的语法快速解析 html 元素。根据 dom 结构,我们写出如下代码:
const axios = require("axios");
const cheerio = require('cheerio');
module.exports = {
platform: "FreeSound", // 插件名
version: "0.0.0", // 版本号
cacheControl: "no-store", // 我们可以直接解析出musicItem的结构,因此选取no-store就好了,当然也可以不写这个字段
async search(query, page, type) {
if (type === "music") {
// 我们能搜索的只有音乐,因此判断下类型
// 获取网站的html
const rawHtml = (
await axios.get("https://freesound.org/search", {
q: query,
page,
})
).data;
// 接下来解析html
const $ = cheerio.load(rawHtml);
// 存储搜索结果
const searchResults = [];
// 获取所有的结果
const resultElements = $('.bw-search__result');
// 解析每一个结果
resultElements.each((index, element) => {
const playerElement = $(element).find('.bw-player');
// id
const id = playerElement.data('sound-id');
// 音频名
const title = playerElement.data('title');
// 作者
const artist = $(element).find('.col-12.col-lg-12.middle a').text();
// 专辑封面
const artwork = playerElement.data('waveform');
// 音源
const url = playerElement.data('mp3');
// 专辑名,这里就随便写个了,不写也没事
const album = '来自FreeSound的音频';
searchResults.push({
// 一定要有一个 id 字段
id,
title,
artist,
artwork,
album,
url
})
});
return {
isEnd: true,
data: searchResults
}
}
},
};const axios = require("axios");
const cheerio = require('cheerio');
module.exports = {
platform: "FreeSound", // 插件名
version: "0.0.0", // 版本号
cacheControl: "no-store", // 我们可以直接解析出musicItem的结构,因此选取no-store就好了,当然也可以不写这个字段
async search(query, page, type) {
if (type === "music") {
// 我们能搜索的只有音乐,因此判断下类型
// 获取网站的html
const rawHtml = (
await axios.get("https://freesound.org/search", {
q: query,
page,
})
).data;
// 接下来解析html
const $ = cheerio.load(rawHtml);
// 存储搜索结果
const searchResults = [];
// 获取所有的结果
const resultElements = $('.bw-search__result');
// 解析每一个结果
resultElements.each((index, element) => {
const playerElement = $(element).find('.bw-player');
// id
const id = playerElement.data('sound-id');
// 音频名
const title = playerElement.data('title');
// 作者
const artist = $(element).find('.col-12.col-lg-12.middle a').text();
// 专辑封面
const artwork = playerElement.data('waveform');
// 音源
const url = playerElement.data('mp3');
// 专辑名,这里就随便写个了,不写也没事
const album = '来自FreeSound的音频';
searchResults.push({
// 一定要有一个 id 字段
id,
title,
artist,
artwork,
album,
url
})
});
return {
isEnd: true,
data: searchResults
}
}
},
};此时,一个插件就开发完成了。上面的插件并没有判断是否还有下一页(返回的 isEnd 始终是 true),你也可以尝试完善一下,让插件拥有分页功能。
插件安装后,在 PC 版本上表现如下:

尝试搜索一下:


在 安卓 版本上表现如下:

尝试搜索一下:


完整代码
你跟着上述步骤开发,并直接通过【安装本地插件】导入插件,也可以尝试从 URL 安装插件:https://gitee.com/maotoumao/MusicFreePlugins/raw/master/example/freesound.js。
插件模板
如果你觉得这种开发方式过于原始,MusicFree 也提供了插件开发模板:Github Gitee ,按照 Readme 使用即可。
MusicFree