"""Platform information API endpoints. Provides information about supported platforms and their configuration options. """ from quart import Blueprint, jsonify from app.services.gallery_dl import GalleryDLService bp = Blueprint("platforms", __name__) @bp.route("", methods=["GET"]) async def list_platforms(): """List all supported platforms with their configuration options.""" gdl_service = GalleryDLService() platforms = { "patreon": { "name": "Patreon", "description": "Download posts from Patreon creators", "requires_auth": True, "auth_type": "cookies", "url_pattern": "https://www.patreon.com/{creator}", "url_examples": [ "https://www.patreon.com/example_artist", "https://www.patreon.com/user?u=12345678", ], "content_types": gdl_service.get_platform_content_types("patreon"), "content_type_descriptions": { "images": "Standard post images", "image_large": "High-resolution image versions", "attachments": "File attachments (ZIP, PSD, etc.)", "postfile": "Post-specific files", "content": "Embedded content in post body", }, "default_config": gdl_service.get_default_config_for_platform("patreon"), }, "subscribestar": { "name": "SubscribeStar", "description": "Download posts from SubscribeStar creators", "requires_auth": True, "auth_type": "cookies", "url_pattern": "https://subscribestar.{domain}/{creator}", "url_examples": [ "https://subscribestar.adult/example_artist", "https://www.subscribestar.com/example_artist", ], "content_types": gdl_service.get_platform_content_types("subscribestar"), "content_type_descriptions": { "all": "All available content", }, "default_config": gdl_service.get_default_config_for_platform("subscribestar"), }, "hentaifoundry": { "name": "Hentai Foundry", "description": "Download artwork from Hentai Foundry artists", "requires_auth": False, "auth_type": "cookies", # Optional but improves access "url_pattern": "https://www.hentai-foundry.com/user/{artist}", "url_examples": [ "https://www.hentai-foundry.com/user/example_artist", "https://www.hentai-foundry.com/pictures/user/example_artist", ], "content_types": gdl_service.get_platform_content_types("hentaifoundry"), "content_type_descriptions": { "pictures": "Artwork/pictures", "stories": "Written stories", }, "default_config": gdl_service.get_default_config_for_platform("hentaifoundry"), }, "discord": { "name": "Discord", "description": "Download attachments from Discord channels", "requires_auth": True, "auth_type": "token", "url_pattern": "https://discord.com/channels/{server}/{channel}", "url_examples": [ "https://discord.com/channels/123456789/987654321", ], "content_types": gdl_service.get_platform_content_types("discord"), "content_type_descriptions": { "all": "All attachments and embeds", }, "default_config": gdl_service.get_default_config_for_platform("discord"), "notes": "Requires Discord user token (not bot token)", }, "pixiv": { "name": "Pixiv", "description": "Download artwork from Pixiv artists", "requires_auth": True, "auth_type": "cookies", "url_pattern": "https://www.pixiv.net/users/{user_id}", "url_examples": [ "https://www.pixiv.net/users/12345678", "https://www.pixiv.net/en/users/12345678", ], "content_types": gdl_service.get_platform_content_types("pixiv"), "content_type_descriptions": { "all": "All artwork (illustrations and manga)", "illusts": "Illustrations only", "manga": "Manga only", "bookmarks": "Bookmarked works", }, "default_config": gdl_service.get_default_config_for_platform("pixiv"), }, "deviantart": { "name": "DeviantArt", "description": "Download artwork from DeviantArt artists", "requires_auth": False, "auth_type": "cookies", "url_pattern": "https://www.deviantart.com/{username}", "url_examples": [ "https://www.deviantart.com/example-artist", "https://www.deviantart.com/example-artist/gallery", ], "content_types": gdl_service.get_platform_content_types("deviantart"), "content_type_descriptions": { "all": "All deviations", "gallery": "Gallery artwork", "scraps": "Scraps folder", "favorites": "Favorited works", }, "default_config": gdl_service.get_default_config_for_platform("deviantart"), }, } return jsonify({"platforms": platforms}) @bp.route("/", methods=["GET"]) async def get_platform(platform: str): """Get detailed information about a specific platform.""" gdl_service = GalleryDLService() if platform not in ["patreon", "subscribestar", "hentaifoundry", "discord", "pixiv", "deviantart"]: return jsonify({"error": f"Unknown platform: {platform}"}), 404 # Get the full platform info from list_platforms all_platforms = (await list_platforms()).get_json() platform_info = all_platforms["platforms"].get(platform) if not platform_info: return jsonify({"error": f"Platform not found: {platform}"}), 404 return jsonify(platform_info) @bp.route("//config-schema", methods=["GET"]) async def get_config_schema(platform: str): """Get the configuration schema for a platform. This defines what options can be set per-source for this platform. Useful for building dynamic forms in the frontend. """ if platform not in ["patreon", "subscribestar", "hentaifoundry", "discord", "pixiv", "deviantart"]: return jsonify({"error": f"Unknown platform: {platform}"}), 404 gdl_service = GalleryDLService() content_types = gdl_service.get_platform_content_types(platform) defaults = gdl_service.get_default_config_for_platform(platform) schema = { "platform": platform, "fields": [ { "name": "content_types", "type": "multiselect", "label": "Content Types to Download", "description": "Select which types of content to download from this source", "options": content_types, "default": defaults.get("content_types", ["all"]), }, { "name": "sleep", "type": "number", "label": "Delay Between Downloads (seconds)", "description": "Time to wait between downloading files. Higher values are safer but slower.", "min": 0.5, "max": 30.0, "step": 0.5, "default": defaults.get("sleep", 3.0), }, { "name": "sleep_request", "type": "number", "label": "Delay Between Requests (seconds)", "description": "Time to wait between API requests. Helps avoid rate limiting.", "min": 0.5, "max": 15.0, "step": 0.5, "default": defaults.get("sleep_request", 1.5), }, { "name": "skip_existing", "type": "boolean", "label": "Skip Already Downloaded", "description": "Skip files that have already been downloaded (uses archive database)", "default": True, }, { "name": "save_metadata", "type": "boolean", "label": "Save Metadata", "description": "Save JSON metadata file alongside each downloaded file", "default": True, }, { "name": "timeout", "type": "number", "label": "Download Timeout (seconds)", "description": "Maximum time to wait for a single download before giving up", "min": 60, "max": 7200, "step": 60, "default": 3600, }, { "name": "directory_pattern", "type": "text", "label": "Directory Pattern (Advanced)", "description": "Custom directory structure pattern. Leave empty for platform default.", "placeholder": defaults.get("directory_pattern", ""), "default": None, }, { "name": "filename_pattern", "type": "text", "label": "Filename Pattern (Advanced)", "description": "Custom filename pattern. Leave empty for platform default.", "placeholder": defaults.get("filename_pattern", ""), "default": None, }, ], } return jsonify(schema)