转到内容

快速开始(V2)

Miru Alpha 的 JavaScript 扩展由纯 Go 实现的 goja 引擎运行,扩展文件后缀为 .js。本页介绍 V2(推荐) 的完整 JS 扩展写法;通用元数据与数据返回格式见 开始开发数据格式

在扩展加载目录中创建一个与包名同名的 .js 文件(例如 my.extension.js)。

// V2:直接在模块顶层声明这些函数,无需类
var latest = (page) => { /* 最近更新 */ };
var search = (kw, page, filter) => { /* 搜索(filter 为可选筛选项) */ };
var detail = (url) => { /* 详情 */ };
var watch = (url) => { /* 返回镜像列表 */ };
var mirror = (url) => { /* 解析最终播放地址 */ };
var createFilter = (filter) => { /* 返回筛选项描述(可选,默认未实现) */ };
var checkUpdate = () => { /* 检查更新(可选) */ };
async function load() { /* 扩展加载时执行一次 */ }

请求通过全局 Miru 对象发起:

// request:把 url 拼到扩展 @webSite 之后(相对地址),并自动尝试 JSON.parse 响应
const json = await Miru.request("/api/list?page=1");
// rawRequest:使用完整地址,不会自动以 @webSite 拼接
const html = await Miru.rawRequest("https://example.com/page");
// 跨调用保存状态(goja VM 每次执行后都会销毁,状态必须走缓存)。值一律以字符串存储
Miru.saveCache("token", "abc123");
const token = Miru.getCache("token");
// Miru 上还暴露了扩展自身的元信息
Miru.pkg; Miru.name; Miru.website;

watch() 在 V2 中返回镜像列表(而非最终地址),用户选择某个镜像后,前端再调用 mirror(url) 解析出最终播放链接:

var watch = (url) => {
const groups = {
"Server 1": [
{ name: "Mirror 1", url: "https://m1.example.com/a.m3u8", headers: {} },
{ name: "Mirror 2", url: "https://m2.example.com/a.m3u8", headers: {} },
],
};
return { groups: Object.keys(groups).map((t) => ({ title: t, mirrors: groups[t] })) };
};
var mirror = (url) => ({
type: "hls", // hls | mp4 | torrent | magnet(内容类型,不是扩展类型)
url,
headers: { "User-Agent": "Mozilla/5.0" },
// tlsConfig: { profile: "chrome_133" }, // 仅 Go 扩展可用:后端自动代理并伪装 TLS 指纹
});

goja 运行环境内置了 nodejs 风格的 require,扩展可以直接加载宿主预置的模块,无需自己打包依赖:

const { parseHTML } = require("linkedom"); // 轻量 DOM 解析
const CryptoJS = require("crypto-js"); // CryptoJS.MD5 / AES / ...
const md5 = require("md5"); // blueimp JavaScript-MD5:md5("str")
const JSEncrypt = require("jsencrypt"); // RSA 加密:new JSEncrypt()
const zlib = require("zlib"); // 压缩/解压(见下表)

可用模块一览:

模块 用途 / 导出
linkedom parseHTML(html) 返回 DOM(document/querySelector 等),用于抓取页面。
crypto-js CryptoJS(MD5、SHA、AES、DES、Base64、Hmac 等,与浏览器端 crypto-js 用法一致)。
md5 blueimp 的 md5(string) 函数。
jsencrypt JSEncrypt 类(new JSEncrypt() + setPublicKey + encrypt)。
zlib 同步压缩/解压:gzipSync / gunzipSync / deflateSync / inflateSync / brotliCompressSync / brotliDecompressSync / zstdCompressSync / zstdDecompressSync,返回 Uint8ArraybytesFromBase64(str) 把 base64 解码为保留二进制的 Uint8Array
url nodejs url 模块(new URL()、解析等)。
console 调试输出(console.log / warn / error),会转发到 Miru 的开发者日志。

此外运行环境中还有全局对象 cryptocrypto.getRandomValues,单次最多 65536 字节)。

// ==MiruExtension==
// @name Demo Bangumi
// @package demo.bangumi
// @author Demo
// @license MIT
// @lang zh-cn
// @icon https://example.com/icon.png
// @webSite https://example.com/
// @type bangumi
// @apiVersion 2
// ==/MiruExtension==
var latest = async (page) => {
const json = await Miru.request(`/api/latest?page=${page}`);
return json.list.map((it) => ({ title: it.title, url: it.id, cover: it.cover, update: it.update }));
};
var search = async (kw, page) => {
const json = await Miru.request(`/api/search?kw=${kw}&page=${page}`);
return json.list.map((it) => ({ title: it.title, url: it.id, cover: it.cover }));
};
var detail = async (url) => {
const json = await Miru.request(`/api/detail/${url}`);
return {
title: json.title,
cover: json.cover,
desc: json.desc,
chapters: json.episodes.map((ep) => ({ title: ep.name, urls: ep.sources.map((s) => s.url) })),
};
};
var watch = (url) => ({
groups: [{ title: "默认", mirrors: [{ name: "", url, headers: {} }] }],
});
var mirror = (url) => ({ type: "mp4", url, headers: {} });

请通过 PR 将扩展提交到 Miru 扩展仓库,PR 中需包含扩展源文件,不需要 index.json