CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 4s
CI / frontend-build (push) Successful in 23s
extension / lint (push) Successful in 26s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m43s
Executes the 2026-07-05 product decision (FC downloaders = art-dedicated
services only), which removed Twitter/X and Bluesky but left deviantart
fully wired for seven weeks — the half-retired state rule 22 exists to
prevent.
Removed: the PlatformInfo module and its registry entry, the gallery-dl
extractor block, extension_service's artist-page pattern, the extension's
PLATFORMS + PLATFORM_ARTIST_PATTERNS entries, its manifest host permission
and content-script match, the frontend icon/colour/label, and the operator-
facing "supported platforms" list that still advertised it.
Two judgment calls, both recorded in migration 0088:
* existing `source` rows are DISABLED, not deleted. The row is the only
record of the artist's DeviantArt URL. Disabling is also required for
correctness rather than tidiness: with the platform unregistered the
download path falls through to gallery-dl, which carries its OWN
deviantart extractor, so an enabled row would have kept downloading
from a dropped platform.
* the `credential` row IS deleted — a live session cookie for a site FC
will never call again.
Adds the invariant whose absence is why manifest.json drifted in the first
place: nothing tied its domain lists back to the platform table. The
extension suite now asserts both directions, plus that no host permission
belongs to an unclaimed domain (`*://*/*` exempted — FC is self-hosted at
an operator-chosen URL the extension cannot enumerate).
Extension version 1.0.10 -> 1.0.11: ci.yml's guard hard-fails a packaged
extension change without a bump. No release is cut — build.yml's
sign-extension job only runs on main.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
"""FC-3b platforms registry — single source of truth for what
|
|
FabledCurator supports + where each platform's quirks live.
|
|
|
|
Adding a new platform: drop a new module `<platform>.py` next to this
|
|
one, declare an `INFO = PlatformInfo(...)`, add the import + entry in
|
|
PLATFORMS below. Sidecar parsing, cookie materialization, and
|
|
`/api/platforms` pick it up automatically.
|
|
|
|
Lifted from GallerySubscriber's
|
|
~/Nextcloud/Projects/GallerySubscriber/backend/app/api/platforms.py
|
|
and ~/.../extension/lib/platforms.js. Five platforms; auth_type and
|
|
URL patterns match GS exactly so the existing browser extension
|
|
hits FC unmodified. deviantart was dropped at #3069 (2026-08-27) —
|
|
FC downloaders are art-dedicated services only.
|
|
"""
|
|
|
|
from .base import (
|
|
DEFAULT_DESCRIPTION_KEYS,
|
|
DEFAULT_EXTERNAL_POST_ID_KEYS,
|
|
PlatformInfo,
|
|
)
|
|
from .discord import INFO as _DISCORD
|
|
from .hentaifoundry import INFO as _HENTAIFOUNDRY
|
|
from .patreon import INFO as _PATREON
|
|
from .pixiv import INFO as _PIXIV
|
|
from .subscribestar import INFO as _SUBSCRIBESTAR
|
|
|
|
PLATFORMS: dict[str, PlatformInfo] = {
|
|
info.key: info
|
|
for info in (
|
|
_PATREON,
|
|
_SUBSCRIBESTAR,
|
|
_HENTAIFOUNDRY,
|
|
_DISCORD,
|
|
_PIXIV,
|
|
)
|
|
}
|
|
|
|
|
|
def known_platform_keys() -> frozenset[str]:
|
|
return frozenset(PLATFORMS.keys())
|
|
|
|
|
|
def auth_type_for(platform: str) -> str | None:
|
|
info = PLATFORMS.get(platform)
|
|
return info.auth_type if info else None
|
|
|
|
|
|
def to_dict(info: PlatformInfo) -> dict:
|
|
"""Serialize a PlatformInfo to a JSON-safe dict for /api/platforms.
|
|
|
|
Behavioral fields (callables, sidecar-chain overrides) are
|
|
intentionally omitted — they aren't useful to API consumers.
|
|
"""
|
|
return {
|
|
"key": info.key,
|
|
"name": info.name,
|
|
"description": info.description,
|
|
"auth_type": info.auth_type,
|
|
"requires_auth": info.requires_auth,
|
|
"url_pattern": info.url_pattern,
|
|
"url_examples": info.url_examples,
|
|
"default_config": info.default_config,
|
|
"notes": info.notes,
|
|
}
|
|
|
|
|
|
def external_post_id_keys_for(platform: str | None) -> tuple[str, ...]:
|
|
"""Resolve the external_post_id lookup chain for a given platform,
|
|
falling back to the module default when the platform isn't
|
|
registered or hasn't overridden the chain."""
|
|
info = PLATFORMS.get(platform) if platform else None
|
|
if info is not None and info.external_post_id_keys is not None:
|
|
return info.external_post_id_keys
|
|
return DEFAULT_EXTERNAL_POST_ID_KEYS
|
|
|
|
|
|
def description_keys_for(platform: str | None) -> tuple[str, ...]:
|
|
"""Resolve the description body lookup chain for a given platform."""
|
|
info = PLATFORMS.get(platform) if platform else None
|
|
if info is not None and info.description_keys is not None:
|
|
return info.description_keys
|
|
return DEFAULT_DESCRIPTION_KEYS
|
|
|
|
|
|
__all__ = [
|
|
"PLATFORMS",
|
|
"PlatformInfo",
|
|
"auth_type_for",
|
|
"description_keys_for",
|
|
"external_post_id_keys_for",
|
|
"known_platform_keys",
|
|
"to_dict",
|
|
]
|