详细用法
This content is not available in your language yet.
本页深入讲解 Go 扩展的 sdk 用法、返回类型、完整示例、Scriggo 限制与可导入的包。入口函数的声明见 快速开始;两种运行方式见 原生 Go 运行 与 Scriggo 运行。
返回类型速查
Section titled “返回类型速查”sdk 包直接导出以下类型(均为 miru-core 内部类型的别名,跨 Scriggo/宿主边界可安全传递):
| 类型 | 用途 |
|---|---|
sdk.ExtensionListItem |
列表项:Title / URL / Cover / Update / Image / Type / Headers |
sdk.ExtensionDetail |
详情:Title / URL / Cover / Desc / Description / Chapters []ExtensionEpisodeGroup / Headers(Desc 与 Description 二选一即可) |
sdk.ExtensionEpisodeGroup |
详情内的章节分组:Title + URLs []string |
sdk.ExtensionWatch |
Watch 通用返回:分组镜像列表 Groups []ExtensionMirrorGroup |
sdk.ExtensionMirrorGroup / sdk.ExtensionMirror |
镜像分组 / 单个镜像:Name / URL / Headers |
sdk.ExtensionAllMirror |
@type all 的 Watch 返回: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.HLS、sdk.MP4、sdk.Magnet(注意没有 sdk.Torrent 常量——torrent 的内容类型字符串是 "torrent",URL 直接携带原始 .torrent 链接或 magnet: URI)。
网络请求与缓存
Section titled “网络请求与缓存”// 发起 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")TLS 指纹伪装与代理
Section titled “TLS 指纹伪装与代理”许多视频流媒体网站使用 Cloudflare 或类似的反爬虫服务,会拒绝 Go 默认的 TLS 指纹。 miru-core 使用 tls-client 解决此问题——这是一个能模仿真实浏览器 TLS 握手 (JA3、HTTP/2 设置等)的库。Go 扩展的所有网络请求都经过后端——Flutter 客户端永远不会直接连接上游 CDN。
Go 的 net/http 会产生一个独特的 TLS 指纹,Cloudflare 和其他 CDN 能立即识别并封锁。
爬取这些网站的扩展需要一种方式,使请求看起来像是来自真实的 Chrome 浏览器。
使用 TLSConfig
Section titled “使用 TLSConfig”TLSConfig 可用于两种不同的场景——它们不是可互换的替代方案,而是各自服务于不同的目的:
| 场景 | 用途 | 运作方式 |
|---|---|---|
设置在镜像上(ExtensionBangumiWatchMirror.TLSConfig) |
代理视频播放流 | 后端将镜像中所有 URL(主 URL + 字幕 URL)改写为 localhost 代理 URL。Flutter 播放器通过后端获取,后端使用 tls-client 连接上游。 |
搭配 sdk.Fetch |
在 Detail() / Search() 中发起 HTTP 请求 |
后端执行一次带有指定 TLS 指纹的服务器端请求,直接将响应体返回给你的代码。 |
代理 URL 的运作方式
Section titled “代理 URL 的运作方式”当后端改写 URL(通过镜像上的 TLSConfig)时,它会产生一个
主机相对的代理 URL,例如:
http://127.0.0.1:3000/proxy/video.m3u8?__u=<base64-encoded-raw-url>&__h=<base64-encoded-headers>Flutter 视频播放器请求这个 localhost URL。后端:
- 从查询参数解码原始 URL 和 headers
- 使用带有指定指纹的 tls-client 抓取上游资源
- 将响应流式传回播放器
这意味着 Flutter 客户端永远不会直接与上游 CDN 通信——所有 TLS 指纹伪装、CORS 和防盗链问题都在服务器端处理。
何时不使用 TLS 指纹伪装
Section titled “何时不使用 TLS 指纹伪装”- Torrent / Magnet URL:对于
type: "torrent"或type: "magnet", URL 承载的是原始.torrent链接或magnet:URI。不要在这些镜像上设置TLSConfig——Torrent 解析由前端或后端独立端点处理,而非镜像代理。
在 Scriggo 中使用 sdk 包(实战)
Section titled “在 Scriggo 中使用 sdk 包(实战)”下面把上面列出的 sdk API 放进真实的入口函数里。sdk 包在 Scriggo VM 启动时已注入,直接 import 后调用即可。
Fetch + TLSConfig(详情页抓取)
Section titled “Fetch + TLSConfig(详情页抓取)”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}Scriggo 的限制
Section titled “Scriggo 的限制”Miru 的 Go 扩展由 Scriggo 引擎编译运行,它并非完整的 Go 编译器,而是为「在 Go 中嵌入脚本」设计的解释器。编写扩展时需要注意以下限制(完整列表见 Scriggo 官方文档):
尚未支持、仍在开发中的特性
Section titled “尚未支持、仍在开发中的特性”- 方法声明(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),长时间运行的宿主中应注意不要无限累积类型。
性能取向的硬性限制
Section titled “性能取向的硬性限制”为提升解释执行性能,Scriggo 对每个函数施加了上限(超出会编译失败):
| 限制项 | 上限 |
|---|---|
| 单函数的整数 / 浮点 / 字符串 / 通用寄存器 | 各 127 个 |
| 函数字面量声明 + 唯一函数调用 | 256 个 |
| 可用类型数 | 256 个 |
| 唯一预定义函数数 | 256 个 |
| 整数 / 浮点常量值 | 各 16384 个 |
| 字符串常量值 | 256 个 |
| 通用值 | 256 个 |
对扩展编写的实操建议
Section titled “对扩展编写的实操建议”- 只用包级函数与结构体:不要尝试定义方法或接口类型;把逻辑写成普通
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 的包。
当前导出的包主要包含:
- 标准库:
fmt、strings、strconv、regexp、encoding/json、encoding/xml、encoding/base64、net/http、net/url、crypto/*、math、time、io、os、sync、reflect、sort、context、bufio、bytes、path/filepath、html、image等(完整列表以源码为准)。 - 内置的 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等。