Adaptadores de siteGuia de desenvolvimento

🔌 Guia de desenvolvimento de adaptadores

Os adaptadores ForcedSkin são escritos como fórmulas JSON declarativas ( forcedskin-adapter-formula/v1 ) descrevendo quais hostnames atingir e quais camadas de seletor colorir. O servidor armazena apenas JSON; a extensão traz um interpretador fixo que aplica regras de pintura — nunca JavaScript arbitrário.

Como funciona

O motor principal começa com sobreposições de variáveis CSS + estilos inline para a recoloração básica.

Quando um hostname corresponde, os adaptadores rodam em ordem crescente de prioridade — quanto menor o número, mais cedo o interpretador executa (adaptadores de site recomendados usam 100).

Cada adaptador ajusta nós alvo via os helpers engineApi expostos; pule sobreposições transparentes/pilhas de player conforme as boas práticas.

O runtime já observa deltas do DOM e reaplica adaptadores com throttle — você não precisa de MutationObserver.

As fórmulas chegam de GET /api/pub/extension-adapters ; o blob JSON de cada registro é armazenado em cache local. Atualizar a cópia no portal passa a valer quando os usuários atualizam os adaptadores ou reabrem o navegador — sem reinstalar.

Exemplo mínimo

{
  "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"]
    }
  ]
}

Fórmula de adaptador forcedskin-adapter-formula/v1

O JSON enviado deve passar na validação do servidor — o esquema raiz é assim:

CampoTipoObrigatórioDescrição
schemastringObrigatórioString de versão literal forcedskin-adapter-formula/v1
idstringObrigatórioId lógico do adaptador — geralmente o codinome do site, ex. bilibili
prioritynumberOpcionalOrdem de execução (crescente). Adaptadores específicos de site costumam usar 100
match.hostnameRule[]ObrigatórioArray de objetos de regra de hostname (veja a tabela abaixo)
layersLayer[]ObrigatórioCamadas de pintura ordenadas — tipos documentados na tabela de camadas

Regras de match.hostname

OperadorSignificado
equalsHostname igual a value (sem distinguir maiúsculas)
suffixDomainHostname igual a value ou termina com .value (cobre example.com e *.example.com)

Chaves de paleta (richText cssVars / color)

CampoDescrição
backgroundFundo da página
foregroundTexto principal
surfacePreenchimento de cartão / painel
surfaceMutedContêineres atenuados / fundos de hover
borderCor da borda
mutedTexto secundário
primary500Ênfase primária (links) mapeada de theme primary.500
primary700Estado primary mais profundo mapeado de primary.700 ou 800

Tipos de camada recomendados

Pense semanticamente — não só pelos nomes dos elementos. Veja o exemplo versionado home/server/seeds/bilibili-adapter.formula.json para um passo a passo completo.

Tipo de camadaUsoMapeamento típicomarkApplied
surfacePainéis, linhas de lista, cascas de navegaçãobackground→surface, color→foreground, border→borderbg + texto + borda
accentAbas ativas / linhas de lista atuaisbackground+border→primary700, color→background para contrastebg + texto + borda
canvasBlocos hero com fundos rasterRemover background-image, background→palette backgroundGeralmente só o fundo
richTextSites que expõem web components da marcaEmitir as variáveis CSS necessárias + mapeamento de cor textualtexto (às vezes + fundo)
svgRecolorGlifos SVG inlinefill/stroke padrão→currentColor; chaves de paleta fill/stroke opcionais para cor fixaMarcação opcional para não interferir na limpeza

Evite regiões arriscadas: O motor já ignora media, canvas, iframe, camadas pesadas de blend/backdrop e nós cujas classes sugerem máscaras ou overlays — estenda essas proteções para o chrome translúcido de players.

Opt-out do host: Marque subárvores (ex. prévias) com data-gts-ignore para que os descendentes pulem a repintura global.

Trecho de exemplo completo

{
  "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"]
    }
  ]
}

Boas práticas

O JSON deve validar de forma estrita

Esquema/camadas/regras de host inválidos são recusados na revisão — use o exemplo mínimo e as seeds deste guia antes de enviar.

Ative skipOverlayLike em surface

Alinha-se às proteções do motor para que HUDs translúcidos/pilhas de vídeo não sejam achatados em preenchimentos sólidos.

Nunca envie JavaScript

O campo code é apenas JSON; a extensão não avalia mais strings com new Function.

Checklist de envio

🔐 Entre antes de enviar.

📋 Informe o nome de exibição, os domínios separados por vírgula e cole o JSON na área de código do adaptador.

⏳ O servidor valida o esquema JSON e as regras de segurança; JavaScript ou outro código de risco é rejeitado.

✅ Fórmulas válidas são publicadas automaticamente e chegam aos usuários da extensão que sincronizam adaptadores.

❌ Seletores amplos demais ou JSON inválido geram retorno — corrija e reenvie.

Formato do domínio de destino

siteDomain é a lista visível; a correspondência real usa match.hostname, por exemplo:

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

Pronto? Abra a galeria de adaptadores e envie seu JSON.

🔌 Enviar um adaptador