/** * Platform definitions — mirrored from backend/app/services/platforms.py. * Two copies of these regexes (here and in backend extension_service.py) * is cheaper than building a shared definition file for two short * tables in different runtimes. Keep in sync by hand; reviewers catch * drift. */ const PLATFORMS = { patreon: { name: 'Patreon', domains: ['.patreon.com', 'www.patreon.com', 'patreon.com'], authType: 'cookies', color: '#FF424D', urlPattern: /^https?:\/\/(www\.)?patreon\.com/, // Patreon's `/api/current_user` returns 200 + the logged-in user // when authenticated, 401 otherwise. Cheapest definitive check. verify: { url: 'https://www.patreon.com/api/current_user', method: 'GET', okStatuses: [200], }, }, subscribestar: { name: 'SubscribeStar', domains: [ '.subscribestar.com', '.subscribestar.adult', 'www.subscribestar.com', 'subscribestar.com', 'subscribestar.adult', ], authType: 'cookies', color: '#FFD700', urlPattern: /^https?:\/\/(www\.)?subscribestar\.(com|adult)/, // No known stable auth-required endpoint that returns a definitive // status code; skipping verify so we don't false-positive-fail // good cookies. Operator can add later if a clean endpoint surfaces. }, hentaifoundry: { name: 'Hentai Foundry', domains: ['.hentai-foundry.com', 'www.hentai-foundry.com', 'hentai-foundry.com'], authType: 'cookies', color: '#9C27B0', urlPattern: /^https?:\/\/(www\.)?hentai-foundry\.com/, // Mirror gallery-dl's _init_site_filters: HEAD on `?enterAgree=1`. // Logged in → 200, logged out → 401. Catches the exact failure mode // the backend extractor would hit later. verify: { url: 'https://www.hentai-foundry.com/?enterAgree=1', method: 'HEAD', okStatuses: [200], }, }, discord: { name: 'Discord', domains: ['.discord.com', 'discord.com'], authType: 'token', color: '#5865F2', urlPattern: /^https?:\/\/(www\.)?discord\.com/, note: 'Open Discord in browser to capture token', }, pixiv: { name: 'Pixiv', domains: ['.pixiv.net', 'www.pixiv.net', 'pixiv.net'], authType: 'token', color: '#0096FA', urlPattern: /^https?:\/\/(www\.)?pixiv\.net/, note: 'Click to authenticate via OAuth', }, deviantart: { name: 'DeviantArt', domains: ['.deviantart.com', 'www.deviantart.com', 'deviantart.com'], authType: 'cookies', color: '#05CC47', urlPattern: /^https?:\/\/(www\.)?deviantart\.com/, // DA's logged-in-only endpoints sit behind their internal _napi // namespace which shifts; skipping verify until a stable check // surfaces. Same posture as SubscribeStar. }, }; /** * Per-platform artist-page URL patterns — these are STRICTER than * urlPattern (which matches any URL on the domain). Used by the content * script to decide whether to show the floating "Add as source" button. */ const PLATFORM_ARTIST_PATTERNS = { patreon: /^https?:\/\/(www\.)?patreon\.com\/(?!home$|search\b|messages\b|notifications\b|library\b|settings\b|posts\b|c\/)[^/?#]+\/?$/i, subscribestar: /^https?:\/\/(www\.)?subscribestar\.(com|adult)\/(?!feed$|messages$|library$)[^/?#]+\/?$/i, hentaifoundry: /^https?:\/\/(www\.)?hentai-foundry\.com\/user\/[^/?#]+/i, deviantart: /^https?:\/\/(www\.)?deviantart\.com\/(?!home$|watch\b|tag\b|browse\b)[^/?#]+\/?$/i, pixiv: /^https?:\/\/(www\.)?pixiv\.net\/(en\/)?users\/\d+/i, }; function getPlatformFromUrl(url) { for (const [key, platform] of Object.entries(PLATFORMS)) { if (platform.urlPattern.test(url)) return key; } return null; } function isArtistPage(url, platformKey) { const pattern = PLATFORM_ARTIST_PATTERNS[platformKey]; return pattern ? pattern.test(url) : false; }