Bondage Studio Bondage Studio
☰ 文档

插件 API

在模组或插件中调用 window.bmm.api 读取与控制 BMM —— 模组、registry、设置、UI 与事件。

BMM 在 window.bmm.api 上暴露了一套稳定、带命名空间的 API,供同页运行的模组、 插件与第三方代码使用。你可以用它读取和控制模组、registry、设置与 UI,并订阅生命 周期事件。

如果你要把 BMM 嵌入到某个宿主中(浏览器外壳、Electron 客户端或反代客户端)并拦截 其存储 / 网络,请看平台集成

就绪检测

window.bmm.api 在 BMM 的应用外壳挂载后才安装,因此启动初期可能短暂不存在。请做好 判空,若你加载较早可轮询等待:

function withBmm(fn) {
  if (window.bmm?.api) return fn(window.bmm.api);
  const timer = setInterval(() => {
    if (window.bmm?.api) {
      clearInterval(timer);
      fn(window.bmm.api);
    }
  }, 50);
}

withBmm((api) => {
  console.log("BMM", api.version, "on", api.platform.name);
});

api.version 是 API/桥接协议版本(数字)。api.platform 标识 BMM 的运行环境:

interface BmmPlatformInfo {
  id: string;            // "browser"、"studio-bondage-club"、"electron-foo" 等
  name: string;
  version?: string;
  capabilities: string[];
  embedded: boolean;     // 在宿主中运行时为 true(非普通标签页)
}

模组

const api = window.bmm.api;

api.mods.list();                                // 已安装的模组(BmmModInfo[])
api.mods.available();                           // 所有可安装的模组
api.mods.get(modId, registryId);               // BmmModInfo | null
api.mods.isInstalled(modId, registryId);
api.mods.isEnabled(modId, registryId);
api.mods.isLoaded(modId, registryId);

api.mods.install(modId, registryId, "stable"); // 安装并启用(版本可选)
api.mods.enable(modId, registryId);
api.mods.disable(modId, registryId);           // 需刷新页面才能完全卸载
api.mods.remove(modId, registryId);
api.mods.setVersion(modId, registryId, "beta");

api.mods.progress();                            // ModLoadProgress 快照

一个模组由 (modId, registryId) 这对值定位。每条记录形如:

interface BmmModInfo {
  modId: string;
  registryId: string;
  name: string;
  author: string;
  enabled: boolean;
  loaded: boolean;
  selectedVersion: string;
  availableVersions: string[];
  tags?: string[];
  type?: string;
  sourceUrl?: string;
}

禁用或移除一个已加载的模组需刷新页面才能完全生效 —— 大多数 BC 模组执行后无法被 卸载。BMM 会在模组管理器关闭时处理刷新(或交由宿主处理,见 平台集成)。

Registry

api.registries.list();
api.registries.add("https://example.com/manifest.json"); // type 默认为 "fusam"
api.registries.remove(registryId);
await api.registries.refresh();                           // 重新拉取所有 manifest

设置

api.settings.getAll();
api.settings.get("modCacheEnabled");
api.settings.set("modCacheEnabled", true);
api.settings.isLocked("modCacheEnabled");  // 被宿主锁定时为 true

当 BMM 运行在锁定了某项设置的宿主中时,set() 对该键无效,且 isLocked() 返回 true。在向用户提供开关前先检查它。

UI 控制

api.ui.open("mod-manager");        // 打开页面(默认 "mod-manager")
api.ui.close();                    // 关闭回到启动器
api.ui.current();                  // 当前页面名,或 null
api.ui.setLauncherVisible(true);   // 强制显示/隐藏启动器;null = 自动管理

页面名:"mod-manager""registry-manager""log-viewer""settings"

事件

api.events.on(type, listener) 返回一个取消订阅函数:

const off = api.events.on("loadProgress", (p) => {
  console.log(`${p.loaded}/${p.total} 已加载,${p.errored} 失败`);
});
// 之后:off();
事件载荷触发时机
readyBmmPlatformInfoAPI 已安装。
loadProgressModLoadProgress某模组的加载生命周期变化。
modsChangedModConfig[]安装 / 启用 / 改版本 / 移除模组。
settingsChangedAppSettings某项设置变化。
pageChanged{ page }活动页面变化(null = 已关闭)。
reloadRequested{ reason }BMM 需要刷新但刷新由宿主接管。

Mod SDK

BMM 拥有并初始化 BC Mod SDKwindow.bcModSdk)。api.sdk 把权威实例交给你 —— 即每个模组注册时所用的同一个 实例,因此所有 hook 与 patch 共享同一条链。

const sdk = api.sdk.get();          // ModSDKGlobalAPI | null
if (sdk) {
  const mod = sdk.registerMod({ name: "MyMod", fullName: "My Mod", version: "1.0.0" });
  mod.hookFunction("ChatRoomSendChat", 1, (args, next) => next(args));
}

// 便捷封装(返回每个模组的 API,SDK 未就绪时返回 null):
const mod = api.sdk.registerMod({ name: "MyMod", fullName: "My Mod", version: "1.0.0" });

api.sdk.isHijacked();  // 若 BC 自带的 SDK 先于 BMM 加载则为 true

优先使用 api.sdk.get() 而非直接读取 window.bcModSdk:BMM 保证在 API 就绪时该实例 就是权威实例,并已解决与 BC 自带 SDK 的竞争。isHijacked() 报告少见的情况 —— BC 的 SDK 先初始化且无法被替换;此时 hook 仍可用,但 BMM 的崩溃诊断能力会受限。

调试报告

注册一个出现在 BMM 调试报告与日志查看器中的命名段(与 FUSAM 的 FUSAM.registerDebugMethod 兼容):

api.log.registerDebugMethod("My Plugin", () => {
  return `state: ${JSON.stringify(myPluginState)}`;
});

注意

  • 模组和插件与 BMM 共享页面;该 API 不是安全边界。
  • <script src> 形式的模组加载由浏览器直接发起,不受宿主 fetch 覆盖影响。