🔌 Adapter-Entwicklungsleitfaden
ForcedSkin-Adapter werden als deklarative JSON-Formeln ( forcedskin-adapter-formula/v1 ) erstellt, die festlegen, welche Hostnames angesprochen und welche Selector-Layer eingefärbt werden. Der Server speichert nur JSON; die Erweiterung enthält einen festen Interpreter , der Paint-Regeln anwendet — niemals beliebiges JavaScript.
So funktioniert es
Die Core-Engine startet mit CSS-Variable- und Inline-Overlays für die Basiseinfärbung.
Bei Treffer eines Hostnames laufen Adapter aufsteigend nach priority — je kleiner die Zahl, desto früher führt der Interpreter aus (empfohlen für Website-Adapter: 100).
Jeder Adapter passt gezielte Nodes über die exponierten engineApi-Helper an; überspringe transparente Overlays/Player-Stacks gemäß den Best-Practices-Hinweisen.
Die Runtime beobachtet DOM-Änderungen bereits und wendet Adapter gedrosselt erneut an — kein MutationObserver-Boilerplate nötig.
Formeln kommen von GET /api/pub/extension-adapters ; der JSON-Blob jedes Datensatzes wird lokal gecacht. Aktualisierungen im Portal wirken, sobald Nutzer Adapter aktualisieren oder den Browser neu öffnen — keine Neuinstallation nötig.
Minimales Beispiel
{
"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"]
}
]
}Adapter-Formel forcedskin-adapter-formula/v1
Eingereichtes JSON muss die Server-Validierung bestehen — das Root-Schema sieht so aus:
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
| schema | string | Erforderlich | Literaler Versionsstring forcedskin-adapter-formula/v1 |
| id | string | Erforderlich | Logische Adapter-ID — meist der Website-Codename, z. B. bilibili |
| priority | number | Optional | Ausführungsreihenfolge (aufsteigend). Website-spezifische Adapter verwenden typischerweise 100 |
| match.hostname | Rule[] | Erforderlich | Array von Hostname-Regelobjekten (siehe Tabelle unten) |
| layers | Layer[] | Erforderlich | Geordnete Paint-Layer — Typen in der Layer-Tabelle dokumentiert |
match.hostname-Regeln
| Operator | Bedeutung |
|---|---|
| equals | Hostname entspricht dem Wert (Groß-/Kleinschreibung ignoriert) |
| suffixDomain | Hostname entspricht dem Wert oder endet mit .value (deckt example.com und *.example.com ab) |
Paletten-Keys (richText cssVars / color)
| Feld | Beschreibung |
|---|---|
| background | Seitenhintergrund |
| foreground | Primärer Text |
| surface | Karten-/Panel-Füllung |
| surfaceMuted | Gedämpfte Container / Hover-Füllungen |
| border | Rahmenfarbe |
| muted | Sekundärer Text |
| primary500 | Primäre Betonung (Links), gemappt von Theme primary.500 |
| primary700 | Tieferer Primary-Zustand, gemappt von primary.700 oder 800 |
Empfohlene Layer-Typen
Denke semantisch — nicht nur nach Elementnamen. Siehe das eingecheckte Beispiel home/server/seeds/bilibili-adapter.formula.json für eine vollständige Anleitung.
| Layer-Typ | Zweck | Typisches Mapping | markApplied |
|---|---|---|---|
| surface | Panels, Listenzeilen, Nav-Shells | background→surface, color→foreground, border→border | bg + text + border |
| accent | Aktive Tabs / aktuelle Listenzeilen | background+border→primary700, color→background für Kontrast | bg + text + border |
| canvas | Hero-Flächen mit Raster-Hintergründen | background-image entfernen, background→Palette background | Meist nur background |
| richText | Websites mit gebrandeten Web Components | Erforderliche CSS-Variablen + textuelles Color-Mapping ausgeben | text (manchmal + background) |
| svgRecolor | Inline-SVG-Glyphen | Standard fill/stroke→currentColor; optionale fill/stroke-Paletten-Keys für feste Farbe | Optionales Marking, um Cleanup nicht zu stören |
Riskante Bereiche überspringen: Die Engine ignoriert bereits media, canvas, iframe, schwere blend/backdrop-Layer und Nodes, deren Klassen auf Masken oder Overlays hindeuten — erweitere diese Guardrails für transluzentes Player-Chrome.
Host-Opt-out: Markiere Teilbäume (z. B. Vorschauen) mit data-gts-ignore , damit deren Nachfahren die globale Neufärbung überspringen.
Vollständiger Beispielauszug
{
"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"]
}
]
}Best Practices
JSON muss strikt validieren
Ungültiges Schema/Layer/Host-Regeln werden in der Prüfung abgelehnt — nutze das minimale Beispiel und Seeds aus diesem Leitfaden vor dem Einreichen.
skipOverlayLike auf surface aktivieren
Entspricht den Core-Engine-Sicherungen, damit transluzente HUDs/Video-Stacks nicht zu soliden Flächen werden.
Niemals JavaScript ausliefern
Das code-Feld ist nur JSON; die Erweiterung evaluiert Strings nicht mehr über new Function.
Checkliste für die Einreichung
🔐 Vor dem Einreichen anmelden.
📋 Anzeigename, kommagetrennte Domains angeben und das JSON in das Adapter-Code-Textfeld einfügen.
⏳ Einträge landen im Status pending, bis das Team Sicherheit und Selector-Umfang prüft.
✅ Freigegebene Adapter werden an alle Erweiterungsnutzer ausgerollt, die Adapter synchronisieren.
❌ Zu breite Selectors oder ungültiges JSON führen zu Feedback — korrigieren und erneut einreichen.
Format der Ziel-Domain
siteDomain ist die menschenlesbare Liste; das Matching erfolgt tatsächlich über match.hostname wie:
[
{ "op": "equals", "value": "bilibili.com" },
{ "op": "suffixDomain", "value": "bilibili.com" }
]Bereit? Öffne die Adapter-Galerie und reiche dein JSON ein.
🔌 Adapter einreichen