Adaptadores de sitiosGuía de desarrollo

🔌 Guía de desarrollo de adaptadores

Los adaptadores de ForcedSkin se crean como fórmulas JSON declarativas ( forcedskin-adapter-formula/v1 ) que describen qué hostnames apuntar y qué capas de selectores teñir. El servidor solo almacena JSON; la extensión incluye un intérprete fijo que aplica reglas de pintado — nunca JavaScript arbitrario.

Cómo funciona

El motor principal parte de overlays con CSS variables + inline para el recolorizado base.

Cuando coincide un hostname, los adaptadores se ejecutan en orden ascendente por prioridad — cuanto menor el número, antes se ejecuta el intérprete (los adaptadores de sitio recomendados usan 100).

Cada adaptador ajusta nodos objetivo mediante los helpers expuestos de engineApi; omite overlays transparentes y pilas de reproductor según las notas de Buenas prácticas.

El runtime ya observa cambios en el DOM y reaplica adaptadores con limitación — no necesitas boilerplate de MutationObserver.

Las fórmulas llegan desde GET /api/pub/extension-adapters ; el blob JSON de cada registro se cachea localmente. Actualizar la copia del portal surte efecto cuando los usuarios refrescan adaptadores o reabren el navegador — sin reinstalar.

Ejemplo 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

El JSON enviado debe pasar la validación del servidor — el esquema raíz tiene esta forma:

CampoTipoObligatorioDescripción
schemastringObligatorioCadena literal de versión forcedskin-adapter-formula/v1
idstringObligatorioID lógico del adaptador — normalmente el nombre en código del sitio, p. ej. bilibili
prioritynumberOpcionalOrden de ejecución (ascendente). Los adaptadores específicos de sitio suelen usar 100
match.hostnameRule[]ObligatorioArray de objetos de reglas de hostname (ver tabla abajo)
layersLayer[]ObligatorioCapas de pintado ordenadas — tipos documentados en la tabla de capas

Reglas de match.hostname

OperadorSignificado
equalsEl hostname es igual al valor (sin distinguir mayúsculas)
suffixDomainEl hostname es igual al valor o termina en .value (cubre example.com y *.example.com)

Claves de paleta (richText cssVars / color)

CampoDescripción
backgroundFondo de página
foregroundTexto principal
surfaceRelleno de tarjeta / panel
surfaceMutedContenedores atenuados / rellenos hover
borderColor de borde
mutedTexto secundario
primary500Énfasis principal (enlaces) mapeado desde theme primary.500
primary700Estado primary más profundo mapeado desde primary.700 u 800

Tipos de capa recomendados

Piensa en semántica — no solo en nombres de elementos. Consulta el ejemplo incluido en el repositorio home/server/seeds/bilibili-adapter.formula.json para un recorrido completo.

Tipo de capaPropósitoMapeo típicomarkApplied
surfacePaneles, filas de lista, shells de navegaciónbackground→surface, color→foreground, border→borderbg + text + border
accentPestañas activas / filas de lista actualesbackground+border→primary700, color→background para contrastebg + text + border
canvasBloques hero con fondos rasterElimina background-image, background→palette backgroundNormalmente solo background
richTextSitios que exponen web components de marcaEmite las CSS variables requeridas + mapeo de color textualtext (a veces + background)
svgRecolorGlifos SVG inlinefill/stroke por defecto→currentColor; claves de paleta fill/stroke opcionales para color fijoMarcado opcional para no interferir con la limpieza

Omitir regiones de riesgo: El motor ya ignora media, canvas, iframe, capas blend/backdrop pesadas y nodos cuyas clases sugieren máscaras u overlays — extiende esas salvaguardas para el chrome translúcido del reproductor.

Exclusión por host: Marca subárboles (p. ej. vistas previas) con data-gts-ignore para que sus descendientes omitan el repintado global.

Extracto de ejemplo 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"]
    }
  ]
}

Buenas prácticas

El JSON debe validar estrictamente

Esquema/capas/reglas de host inválidos se rechazan en revisión — integra el ejemplo mínimo y los seeds de esta guía antes de enviar.

Activa skipOverlayLike en surface

Coincide con las salvaguardas del motor principal para que HUDs translúcidos y pilas de vídeo no se aplanen a rellenos sólidos.

Nunca envíes JavaScript

El campo code es solo JSON; la extensión ya no evalúa cadenas mediante new Function.

Lista de comprobación del envío

🔐 Inicia sesión antes de presentar un envío.

📋 Indica nombre visible, dominios separados por comas y pega el JSON en el área de texto del código del adaptador.

⏳ Las entradas quedan en pendiente hasta que el equipo valide seguridad y alcance de selectores.

✅ Los adaptadores aprobados se despliegan a todos los usuarios de la extensión que sincronicen adaptadores.

❌ Selectores demasiado amplios o JSON inválido devuelven comentarios — corrige y vuelve a enviar.

Formato del dominio objetivo

siteDomain es la lista orientada al usuario; el emparejamiento real usa match.hostname así:

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

¿Listo? Abre la galería de adaptadores y envía tu JSON.

🔌 Enviar un adaptador