转到内容

详细用法

此内容还没有支持您的语言版本.

本页深入讲解 Go 扩展的 sdk 用法、返回类型、完整示例、Scriggo 限制与可导入的包。入口函数的声明见 快速开始;两种运行方式见 原生 Go 运行Scriggo 运行

sdk 包直接导出以下类型(均为 miru-core 内部类型的别名,跨 Scriggo/宿主边界可安全传递):

类型 用途
sdk.ExtensionListItem 列表项:Title / URL / Cover / Update / Image / Type / Headers
sdk.ExtensionDetail 详情:Title / URL / Cover / Desc / Description / Chapters []ExtensionEpisodeGroup / HeadersDescDescription 二选一即可)
sdk.ExtensionEpisodeGroup 详情内的章节分组:Title + URLs []string
sdk.ExtensionWatch Watch 通用返回:分组镜像列表 Groups []ExtensionMirrorGroup
sdk.ExtensionMirrorGroup / sdk.ExtensionMirror 镜像分组 / 单个镜像:Name / URL / Headers
sdk.ExtensionAllMirror @type allWatch 返回:Manga / Fikushon / Bangumi 三个成员
sdk.ExtensionMangaWatchMirror 漫画:URLs []string + Headers
sdk.ExtensionFikushonWatchMirror 小说:Content []string + Title + Subtitle
sdk.ExtensionBangumiWatchMirror 动画:Type(hls/mp4/torrent/magnet)/ URL / Subtitles / Headers / AudioTrack / TLSConfig
sdk.ExtensionBangumiWatchMirrorSubtitle 字幕:Language / Title / URL
sdk.TLSConfig 浏览器指纹请求配置:Profile / UserAgent / DisableRedirect / InsecureSkipVerify

内容类型常量:sdk.HLSsdk.MP4sdk.Magnet(注意没有 sdk.Torrent 常量——torrent 的内容类型字符串是 "torrent",URL 直接携带原始 .torrent 链接或 magnet: URI)。

// 发起 HTTP 请求,返回 响应体 / 状态码 / 错误信息
body, status, errStr := sdk.Fetch("https://example.com/api", "GET", map[string]string{
"User-Agent": "Miru",
}, "", nil)
// 浏览器指纹(tls-client)请求,绕过 Cloudflare 等
body, _, _ = sdk.Fetch(url, "GET", headers, "", &sdk.TLSConfig{Profile: "chrome_133"})
// 将镜像结构体设置 `TLSConfig`,后端自动改写所有 URL 为代理地址。
// Torrent / Magnet URL 不需要 TLS 指纹伪装,直接返回原始链接即可。
// 跨函数保存状态(每个请求都会重新编译运行一个 Scriggo VM)
sdk.SaveCache("key", value)
v, ok := sdk.GetCache("key")

许多视频流媒体网站使用 Cloudflare 或类似的反爬虫服务,会拒绝 Go 默认的 TLS 指纹。 miru-core 使用 tls-client 解决此问题——这是一个能模仿真实浏览器 TLS 握手 (JA3、HTTP/2 设置等)的库。Go 扩展的所有网络请求都经过后端——Flutter 客户端永远不会直接连接上游 CDN。

Go 的 net/http 会产生一个独特的 TLS 指纹,Cloudflare 和其他 CDN 能立即识别并封锁。 爬取这些网站的扩展需要一种方式,使请求看起来像是来自真实的 Chrome 浏览器。

TLSConfig 可用于两种不同的场景——它们不是可互换的替代方案,而是各自服务于不同的目的:

场景 用途 运作方式
设置在镜像上ExtensionBangumiWatchMirror.TLSConfig 代理视频播放流 后端将镜像中所有 URL(主 URL + 字幕 URL)改写为 localhost 代理 URL。Flutter 播放器通过后端获取,后端使用 tls-client 连接上游。
搭配 sdk.Fetch Detail() / Search() 中发起 HTTP 请求 后端执行一次带有指定 TLS 指纹的服务器端请求,直接将响应体返回给你的代码。

当后端改写 URL(通过镜像上的 TLSConfig)时,它会产生一个 主机相对的代理 URL,例如:

http://127.0.0.1:3000/proxy/video.m3u8?__u=<base64-encoded-raw-url>&__h=<base64-encoded-headers>

Flutter 视频播放器请求这个 localhost URL。后端:

  1. 从查询参数解码原始 URL 和 headers
  2. 使用带有指定指纹的 tls-client 抓取上游资源
  3. 将响应流式传回播放器

这意味着 Flutter 客户端永远不会直接与上游 CDN 通信——所有 TLS 指纹伪装、CORS 和防盗链问题都在服务器端处理。

  • Torrent / Magnet URL:对于 type: "torrent"type: "magnet", URL 承载的是原始 .torrent 链接或 magnet: URI。不要在这些镜像上设置 TLSConfig——Torrent 解析由前端或后端独立端点处理,而非镜像代理。

下面把上面列出的 sdk API 放进真实的入口函数里。sdk 包在 Scriggo VM 启动时已注入,直接 import 后调用即可。

import sdk "github.com/miru-project/miru-core/pkg/extension/golang/sdk"
func Detail(pkg, url string) (*sdk.ExtensionDetail, error) {
// 普通请求:body 为响应体字符串,status 为状态码,errStr 为错误信息
body, status, errStr := sdk.Fetch(url, "GET", map[string]string{
"User-Agent": "Mozilla/5.0",
"Referer": "https://example.com/",
}, "", nil)
if errStr != "" {
return nil, fmt.Errorf("fetch failed (status %d): %s", status, errStr)
}
// 需要绕过 Cloudflare / 指纹校验时,加 TLSConfig
body, _, _ = sdk.Fetch(url, "GET", headers, "", &sdk.TLSConfig{
Profile: "chrome_133",
InsecureSkipVerify: true,
})
// 防盗链/跨域的媒体地址:直接使用 URL(带 TLSConfig 的镜像会自动代理)
return &sdk.ExtensionDetail{
Title: "示例",
Cover: "https://example.com/cover.jpg",
Desc: body,
Chapters: []sdk.ExtensionEpisodeGroup{
{Title: "第 1 话", URLs: []string{"https://cdn.example.com/a.m3u8"}},
},
}, nil
}

Torrent / Magnet 镜像(原始 URL 直接返回)

Section titled “Torrent / Magnet 镜像(原始 URL 直接返回)”

Torrent 或 magnet 内容时,扩展只需返回原始 .torrent 链接或 magnet: URI,无需解析——前端会自行处理。

func Mirror(pkg, url string) (*sdk.ExtensionBangumiWatchMirror, error) {
// Torrent 文件地址——直接返回
return &sdk.ExtensionBangumiWatchMirror{
Type: sdk.HLS, // 或原始字符串 "torrent" / "magnet"
URL: "https://example.com/files/ep1.torrent",
}, nil
// Magnet 链接
return &sdk.ExtensionBangumiWatchMirror{
Type: sdk.Magnet,
URL: "magnet:?xt=urn:btih:abc123def456&dn=One+Piece+EP1",
}, nil
}

在 Mirror 上设置 TLSConfig(自动代理 + TLS 指纹)

Section titled “在 Mirror 上设置 TLSConfig(自动代理 + TLS 指纹)”

对于需要 TLS 指纹伪装的 HLS/MP4 流(如 Cloudflare 前置的 CDN),在镜像结构体上设置 TLSConfig。后端会自动将所有 URL 改写为代理 URL:

func Mirror(pkg, url string) (*sdk.ExtensionBangumiWatchMirror, error) {
// 后端会用 chrome_133 指纹自动代理此 URL
return &sdk.ExtensionBangumiWatchMirror{
Type: sdk.HLS,
URL: "https://cdn.example.com/video.m3u8",
Headers: map[string]string{
"Referer": "https://example.com/",
},
TLSConfig: &sdk.TLSConfig{
Profile: "chrome_133", // 后端自动改写所有 URL 为 /proxy/ 路径
},
Subtitles: []sdk.ExtensionBangumiWatchMirrorSubtitle{
{Language: "en", Title: "English", URL: "https://cdn.example.com/subs/en.vtt"},
},
}, nil
}

Flutter 客户端收到的代理 URL 形如 http://127.0.0.1:3000/proxy/video.m3u8?__u=<encoded>,视频播放器只与 localhost 通信——所有代理和 TLS 指纹伪装由后端处理。

SaveCache / GetCache(跨函数保存状态)

Section titled “SaveCache / GetCache(跨函数保存状态)”
func Search(pkg, kw string, page int, filter string) ([]sdk.ExtensionListItem, error) {
token, ok := sdk.GetCache("token")
if !ok {
// 首次请求登录拿 token,存起来供后续调用复用
body, _, _ := sdk.Fetch("https://example.com/login", "POST", nil, "", nil)
token = body // 实际应解析出 token
sdk.SaveCache("token", token)
}
body, _, _ := sdk.Fetch("https://example.com/search?kw="+kw, "GET",
map[string]string{"Authorization": token}, "", nil)
// ...解析 body 为 []sdk.ExtensionListItem
return nil, nil
}
// ==MiruExtension==
// @name Example
// @version v0.1.0
// @author Miru
// @license MIT
// @lang all
// @icon https://example.com/icon.png
// @package example
// @type all
// @webSite https://example.com
// ==/MiruExtension==
package example
import sdk "github.com/miru-project/miru-core/pkg/extension/golang/sdk"
func Search(pkg, kw string, page int, filter string) ([]sdk.ExtensionListItem, error) {
return []sdk.ExtensionListItem{
{Title: "示例 1", URL: "https://example.com/1", Cover: "https://example.com/1.jpg", Type: "manga"},
}, nil
}
func Latest(pkg string, page int) ([]sdk.ExtensionListItem, error) {
return []sdk.ExtensionListItem{
{Title: "最新 1", URL: "https://example.com/latest/1", Cover: "https://example.com/latest/1.jpg", Type: "manga"},
}, nil
}
func Detail(pkg, url string) (*sdk.ExtensionDetail, error) {
return &sdk.ExtensionDetail{Title: "详情", Desc: "描述", URL: url}, nil
}
func Watch(pkg, url string) (*sdk.ExtensionAllMirror, error) {
return &sdk.ExtensionAllMirror{
Manga: &sdk.ExtensionMangaWatchMirror{URLs: []string{"https://example.com/1.jpg"}},
Fikushon: &sdk.ExtensionFikushonWatchMirror{
Title: "第 1 章",
Content: []string{"第一段。", "第二段。"},
},
Bangumi: &sdk.ExtensionBangumiWatchMirror{Type: sdk.HLS, URL: url},
}, nil
}
// Mirror 解析用户所选的镜像,返回最终的按类型播放结构
func Mirror(pkg, url string) (*sdk.ExtensionBangumiWatchMirror, error) {
return &sdk.ExtensionBangumiWatchMirror{Type: sdk.HLS, URL: url}, nil
}

Miru 的 Go 扩展由 Scriggo 引擎编译运行,它并非完整的 Go 编译器,而是为「在 Go 中嵌入脚本」设计的解释器。编写扩展时需要注意以下限制(完整列表见 Scriggo 官方文档):

  • 方法声明(method declarations):无法为类型定义方法,只能写包级函数。
  • 接口类型定义(interface types):不能 type X interface { ... } 自行定义接口。
  • for range 中赋值给非变量:例如 for i, (&s).field = range ... 不被支持。
  • 导入 unsafe / runtime:Scriggo 中无法 import "unsafe"import "runtime"
  • 带标签的 continue / break:不支持 label: 形式的跳转。
  • 未导入就编译非 main 包:一个扩展文件通常是一个独立的包,无法直接 import 并编译另一个扩展包。

与 Go 官方编译器 gc 互操作带来的限制

Section titled “与 Go 官方编译器 gc 互操作带来的限制”
  • reflect 无法正确识别 Scriggo 内定义的类型:例如 fmt.Printf("%T", v)v 是 Scriggo 定义的类型时,打印出的会是它被包装前的底层类型名,而非你定义的类型名。
  • Scriggo 定义的结构体中的非导出字段仍可被原生包的 reflect 访问:这些字段带有特殊前缀以避免被意外访问,但无法通过 reflect 修改。
  • 结构体嵌入:除接口外,若嵌入的类型带有方法,则它必须是结构体的第一个字段(受 reflect.StructOf 限制)。
  • select 最多 65536 个 case
  • 原生包必须在 packages.go 中导出才能导入:只有 miru-core 后端已经使用并导出的包可用,无法在扩展里随意引入任意第三方包。
  • 类型不会被垃圾回收(见 golang/go#28783),长时间运行的宿主中应注意不要无限累积类型。

为提升解释执行性能,Scriggo 对每个函数施加了上限(超出会编译失败):

限制项 上限
单函数的整数 / 浮点 / 字符串 / 通用寄存器 各 127 个
函数字面量声明 + 唯一函数调用 256 个
可用类型数 256 个
唯一预定义函数数 256 个
整数 / 浮点常量值 各 16384 个
字符串常量值 256 个
通用值 256 个
  • 只用包级函数与结构体:不要尝试定义方法或接口类型;把逻辑写成普通 func
  • 避免依赖 reflect / fmt %T:读取返回结构时直接用 SDK 提供的类型,不要用反射去识别自定义类型。
  • 控制单文件复杂度:不要在一个函数里塞入巨量常量、类型或闭包,必要时拆分成多个入口函数。
  • 不要依赖 unsafe / runtime:网络与代理能力请统一走 sdk.Fetch / 镜像上的 TLSConfig,而非底层包。
  • 状态用 sdk.SaveCache 而非全局变量:每个请求都会重新编译运行一个 Scriggo VM,包级全局变量不会在多次调用间保留。

Scriggo 扩展只能 import miru-core 后端已经使用并导出pkg/extension/golang/packages.go 中的包。这是因为 Scriggo 需要原生 Go 包被编译进宿主二进制文件——扩展无法引入不属于 miru-core 的包。

当前导出的包主要包含:

  • 标准库:fmtstringsstrconvregexpencoding/jsonencoding/xmlencoding/base64net/httpnet/urlcrypto/*mathtimeioossyncreflectsortcontextbufiobytespath/filepathhtmlimage 等(完整列表以源码为准)。
  • 内置的 miru 包:
    • github.com/miru-project/miru-core/pkg/extension/golang/sdk —— 扩展开发 SDK(请求、缓存等)。
    • github.com/miru-project/miru-core/pkg/extension/golang/runtime —— 运行时辅助。
  • miru-core 已依赖的第三方包:github.com/bogdanfinn/tls-client(浏览器指纹请求)、github.com/PuerkitoBio/goquery(HTML 解析)、structs 等。