🔌 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:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| schema | string | Obligatorio | Cadena literal de versión forcedskin-adapter-formula/v1 |
| id | string | Obligatorio | ID lógico del adaptador — normalmente el nombre en código del sitio, p. ej. bilibili |
| priority | number | Opcional | Orden de ejecución (ascendente). Los adaptadores específicos de sitio suelen usar 100 |
| match.hostname | Rule[] | Obligatorio | Array de objetos de reglas de hostname (ver tabla abajo) |
| layers | Layer[] | Obligatorio | Capas de pintado ordenadas — tipos documentados en la tabla de capas |
Reglas de match.hostname
| Operador | Significado |
|---|---|
| equals | El hostname es igual al valor (sin distinguir mayúsculas) |
| suffixDomain | El hostname es igual al valor o termina en .value (cubre example.com y *.example.com) |
Claves de paleta (richText cssVars / color)
| Campo | Descripción |
|---|---|
| background | Fondo de página |
| foreground | Texto principal |
| surface | Relleno de tarjeta / panel |
| surfaceMuted | Contenedores atenuados / rellenos hover |
| border | Color de borde |
| muted | Texto secundario |
| primary500 | Énfasis principal (enlaces) mapeado desde theme primary.500 |
| primary700 | Estado 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 capa | Propósito | Mapeo típico | markApplied |
|---|---|---|---|
| surface | Paneles, filas de lista, shells de navegación | background→surface, color→foreground, border→border | bg + text + border |
| accent | Pestañas activas / filas de lista actuales | background+border→primary700, color→background para contraste | bg + text + border |
| canvas | Bloques hero con fondos raster | Elimina background-image, background→palette background | Normalmente solo background |
| richText | Sitios que exponen web components de marca | Emite las CSS variables requeridas + mapeo de color textual | text (a veces + background) |
| svgRecolor | Glifos SVG inline | fill/stroke por defecto→currentColor; claves de paleta fill/stroke opcionales para color fijo | Marcado 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