サイトアダプター開発ガイド

🔌 アダプター開発ガイド

ForcedSkin アダプターは 宣言型 JSON formulaforcedskin-adapter-formula/v1 )として作成され、対象 hostname と着色する selector layer を記述します。サーバーは JSON のみを保存し、拡張機能は 固定インタープリター を同梱してペイントルールを適用します — 任意の JavaScript は実行しません。

仕組み

コアエンジンは CSS 変数 + インラインオーバーレイでベースラインの再配色を行います。

hostname が一致すると、アダプターは priority の昇順で実行されます — 数値が小さいほど先に実行されます(サイトアダプターは 100 を推奨)。

各アダプターは公開された engineApi ヘルパーで対象ノードを調整します。半透明オーバーレイ/プレイヤー領域は Best practices の注意に従ってスキップしてください。

ランタイムは DOM の変化を監視し、スロットル付きでアダプターを再適用します — MutationObserver のボイラープレートは不要です。

formula は GET /api/pub/extension-adapters から取得され、各レコードの JSON blob はローカルにキャッシュされます。ポータルの内容を更新すると、ユーザーがアダプターを更新するかブラウザを再起動すれば反映されます — 再インストールは不要です。

最小例

{
  "schema": "forcedskin-adapter-formula/v1",
  "id": "example-site",
  "priority": 100,
  "match": {
    "hostname": [
      { "op": "suffixDomain", "value": "example.com" }
    ]
  },
  "layers": [
    {
      "kind": "surface",
      "skipOverlayLike": true,
      "selectors": [".site-navbar", ".site-header"]
    }
  ]
}

アダプター formula forcedskin-adapter-formula/v1

提出する JSON はサーバー検証に合格する必要があります — ルートスキーマは次のとおりです:

フィールド必須説明
schemastring必須リテラルバージョン文字列 forcedskin-adapter-formula/v1
idstring必須論理アダプター id — 通常はサイトのコードネーム(例: bilibili)
prioritynumber任意実行順(昇順)。サイト固有アダプターは通常 100
match.hostnameRule[]必須hostname ルールオブジェクトの配列(下表参照)
layersLayer[]必須順序付きペイント layer — kind は layer 表に記載

match.hostname ルール

演算子意味
equalshostname が value と等しい(大文字小文字を区別しない)
suffixDomainhostname が value と等しい、または .value で終わる(example.com と *.example.com の両方をカバー)

パレットキー (richText cssVars / color)

フィールド説明
backgroundページ背景
foreground主要テキスト
surfaceカード / パネル塗り
surfaceMuted控えめなコンテナ / hover 塗り
borderボーダー色
muted二次テキスト
primary500主要強調(リンク)— テーマ primary.500 からマップ
primary700より深い primary 状態 — primary.700 または 800 からマップ

推奨 layer kind

要素名だけでなく意味的に考えてください。リポジトリ内のサンプル home/server/seeds/bilibili-adapter.formula.json で完全な解説を参照してください。

Layer kind用途典型的なマッピングmarkApplied
surfaceパネル、リスト行、ナビシェルbackground→surface, color→foreground, border→borderbg + text + border
accentアクティブタブ / 現在のリスト行background+border→primary700, color→background(コントラスト用)bg + text + border
canvasラスター背景を持つヒーロースラブbackground-image を除去、background→palette background通常は background のみ
richTextブランド web component を公開しているサイト必要な CSS 変数 + テキスト色マッピングを出力text(場合により + background)
svgRecolorインライン SVG グリフデフォルト fill/stroke→currentColor; 固定色用に fill/stroke パレットキーを任意指定クリーンアップ干渉を避けるための任意マーキング

リスクの高い領域をスキップ: エンジンは media、canvas、iframe、heavy blend/backdrop layer、マスク/オーバーレイを示す class を持つノードを既に無視します — 半透明プレイヤー UI 向けにこれらのガードレールを拡張してください。

ホスト側オプトアウト: サブツリー(例: プレビュー)に data-gts-ignore を付与すると、子孫はグローバル再配色をスキップします。

完全サンプル抜粋

{
  "schema": "forcedskin-adapter-formula/v1",
  "id": "bilibili",
  "priority": 100,
  "match": {
    "hostname": [
      { "op": "equals", "value": "bilibili.com" },
      { "op": "suffixDomain", "value": "bilibili.com" }
    ]
  },
  "layers": [
    {
      "kind": "surface",
      "skipOverlayLike": true,
      "selectors": ["[class*='bili-']", "[class*='bpx-']"]
    },
    {
      "kind": "accent",
      "selectors": ["[class*='active']", ".bili-dyn-list-tabs__item.active"]
    },
    {
      "kind": "canvas",
      "selectors": [".message-bg", ".message-bgc"]
    },
    {
      "kind": "richText",
      "selectors": ["bili-rich-text"],
      "cssVars": {
        "--bili-rich-text-color": "foreground",
        "--bili-rich-text-link-color": "primary500",
        "--bili-rich-text-link-color-hover": "primary700"
      },
      "color": "foreground"
    },
    {
      "kind": "svgRecolor",
      "selectors": ["svg path", "svg rect", "svg circle"]
    }
  ]
}

ベストプラクティス

JSON は厳密に検証される必要があります

無効な schema/layer/host ルールは審査で却下されます — 提出前に本ガイドの最小サンプルと seed を確認してください。

surface で skipOverlayLike を有効化

コアエンジンの安全策と一致し、半透明 HUD/動画スタックが単色塗りつぶしに潰されないようにします。

JavaScript を送らない

code フィールドは JSON のみです。拡張機能は new Function による文字列 eval は行いません。

提出チェックリスト

🔐 提出前にサインインしてください。

📋 表示名、カンマ区切りドメイン、JSON をアダプター code テキストエリアに貼り付けてください。

⏳ スタッフが安全性と selector スコープを検証するまで pending 状態になります。

✅ 承認されたアダプターは、アダプターを同期するすべての拡張機能ユーザーに配信されます。

❌ 過度に広い selector や無効な JSON はフィードバック付きで却下 — 修正して再提出してください。

対象ドメイン形式

siteDomain は人間向けのリストです。実際のマッチングは match.hostname で行われます:

[
  { "op": "equals", "value": "bilibili.com" },
  { "op": "suffixDomain", "value": "bilibili.com" }
]

準備はできましたか?アダプターギャラリーを開いて JSON を提出しましょう。

🔌 アダプターを投稿