"""FC-3b platforms registry — the single source of truth for what FabledCurator supports. Lifted from GallerySubscriber's ~/Nextcloud/Projects/GallerySubscriber/backend/app/api/platforms.py and ~/.../extension/lib/platforms.js. Six platforms; auth_type and URL patterns match GS exactly so the existing browser extension hits FC unmodified. """ from dataclasses import dataclass from typing import Literal @dataclass(frozen=True) class PlatformInfo: key: str name: str description: str auth_type: Literal["cookies", "token"] requires_auth: bool url_pattern: str url_examples: list[str] default_config: dict notes: str | None = None # Common defaults used across most platforms; embedded per-platform # below so per-platform overrides remain explicit. _DEFAULTS = { "sleep": 3.0, "sleep_request": 1.5, "skip_existing": True, "save_metadata": True, "timeout": 3600, } PLATFORMS: dict[str, PlatformInfo] = { "patreon": PlatformInfo( key="patreon", name="Patreon", description="Download posts from Patreon creators", auth_type="cookies", requires_auth=True, url_pattern=r"^https?://(www\.)?patreon\.com/", url_examples=[ "https://www.patreon.com/example_artist", "https://www.patreon.com/user?u=12345678", ], default_config={**_DEFAULTS, "content_types": ["images", "attachments"]}, ), "subscribestar": PlatformInfo( key="subscribestar", name="SubscribeStar", description="Download posts from SubscribeStar creators", auth_type="cookies", requires_auth=True, url_pattern=r"^https?://(www\.)?subscribestar\.(com|adult)/", url_examples=[ "https://subscribestar.adult/example_artist", "https://www.subscribestar.com/example_artist", ], default_config={**_DEFAULTS, "content_types": ["all"]}, ), "hentaifoundry": PlatformInfo( key="hentaifoundry", name="Hentai Foundry", description="Download artwork from Hentai Foundry artists", auth_type="cookies", requires_auth=False, url_pattern=r"^https?://(www\.)?hentai-foundry\.com/", url_examples=[ "https://www.hentai-foundry.com/user/example_artist", "https://www.hentai-foundry.com/pictures/user/example_artist", ], default_config={**_DEFAULTS, "content_types": ["pictures"]}, ), "discord": PlatformInfo( key="discord", name="Discord", description="Download attachments from Discord channels", auth_type="token", requires_auth=True, url_pattern=r"^https?://(www\.)?discord\.com/channels/", url_examples=["https://discord.com/channels/123456789/987654321"], default_config={**_DEFAULTS, "content_types": ["all"]}, notes="Requires Discord user token (not bot token).", ), "pixiv": PlatformInfo( key="pixiv", name="Pixiv", description="Download artwork from Pixiv artists", auth_type="token", requires_auth=True, url_pattern=r"^https?://(www\.)?pixiv\.net/", url_examples=[ "https://www.pixiv.net/users/12345678", "https://www.pixiv.net/en/users/12345678", ], default_config={**_DEFAULTS, "content_types": ["all"]}, notes="Requires OAuth refresh token. Run `gallery-dl oauth:pixiv` to obtain one.", ), "deviantart": PlatformInfo( key="deviantart", name="DeviantArt", description="Download artwork from DeviantArt artists", auth_type="cookies", requires_auth=False, url_pattern=r"^https?://(www\.)?deviantart\.com/", url_examples=[ "https://www.deviantart.com/example-artist", "https://www.deviantart.com/example-artist/gallery", ], default_config={**_DEFAULTS, "content_types": ["gallery"]}, ), } 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: 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, }