signing for extension deployment, tuning for download crawl depth, and dependency version visibility in settings view

This commit is contained in:
Bryan Van Deusen
2026-01-26 22:37:15 -05:00
parent 2a0e37a73d
commit 204e8efcdc
10 changed files with 426 additions and 553 deletions
+7 -1
View File
@@ -7,7 +7,13 @@
"Bash(docker compose logs:*)",
"Bash(docker compose exec celery sh -c \"grep -i session /data/cookies/patreon_cookies.txt || echo ''No session cookie found''\")",
"Bash(docker compose exec celery sh -c \"gallery-dl --cookies /data/cookies/patreon_cookies.txt -v ''https://www.patreon.com/knuxy'' 2>&1 | head -20\")",
"Bash(docker compose exec:*)"
"Bash(docker compose exec:*)",
"Bash(powershell -File - <<'EOF'\nAdd-Type -AssemblyName System.IO.Compression.FileSystem\n$zip = [System.IO.Compression.ZipFile]::OpenRead\\('c:\\\\Users\\\\bvandeusen\\\\Nextcloud\\\\Projects\\\\GallerySubscriber\\\\gallerysubscriber.xpi'\\)\n$zip.Entries | Select-Object FullName\n$zip.Dispose\\(\\)\nEOF)",
"Bash(npx web-ext sign:*)",
"Bash(where:*)",
"Bash(where npm:*)",
"Bash(powershell -Command \"Copy-Item ''c:\\\\Users\\\\bvandeusen\\\\Nextcloud\\\\Projects\\\\GallerySubscriber\\\\extension\\\\web-ext-artifacts\\\\be3706e9e89640a5b70b-1.0.0.xpi'' ''c:\\\\Users\\\\bvandeusen\\\\Nextcloud\\\\Projects\\\\GallerySubscriber\\\\gallerysubscriber-signed.xpi'' -Force; Get-Item ''c:\\\\Users\\\\bvandeusen\\\\Nextcloud\\\\Projects\\\\GallerySubscriber\\\\gallerysubscriber-signed.xpi'' | Select-Object Name, Length\")",
"WebFetch(domain:github.com)"
]
}
}
+4
View File
@@ -83,3 +83,7 @@ config/
# Build artifacts
backend/static/
# Extension packages
*.xpi
extension-dist/
-548
View File
@@ -1,548 +0,0 @@
# Gallery Subscriber
A Docker-based web application for managing gallery-dl downloads with a modern web interface.
---
## Table of Contents
1. [Project Overview](#project-overview)
2. [Architecture](#architecture)
3. [Technology Stack](#technology-stack)
4. [Project Structure](#project-structure)
5. [Database Schema](#database-schema)
6. [API Specification](#api-specification)
7. [Firefox Extension](#firefox-extension)
8. [Configuration](#configuration)
9. [Development Phases](#development-phases)
10. [Migration from Legacy Project](#migration-from-legacy-project)
---
## Project Overview
### Goals
- **Containerized Deployment**: Run the entire download manager in Docker for portability
- **Web Interface**: Modern UI for managing subscriptions, viewing downloads, and monitoring new content
- **Flexible Authentication**: Firefox extension for cookie export OR manual cookie upload via web UI
- **Notifications**: Real-time updates when new content is downloaded
- **Reliability**: Maintain the robust scheduling and error handling from the original project
### Non-Goals (Initial Release)
- Multi-user support (single-user initially)
- Mobile app
- SSL termination (handled by external reverse proxy)
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ External Reverse Proxy │
│ (Traefik/Caddy/nginx - handles SSL) │
└──────────────────────────┬──────────────────────────────────────┘
│ HTTP (port 8080)
┌─────────────────────────────────────────────────────────────────┐
│ Docker Compose Stack │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Quart Backend │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ REST API │ │WebSocket │ │ Static │ │ gallery-dl│ │ │
│ │ │ /api/* │ │ /ws/* │ │ Files │ │ Wrapper │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────┴───────────────────────────────┐ │
│ │ Celery Worker │ │
│ │ • Download tasks • Scheduled checks │ │
│ │ • Manifest generation • Error retry logic │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────┐ ┌─────────┴───────┐ ┌──────────────────┐ │
│ │ PostgreSQL │ │ Redis │ │ Vue Frontend │ │
│ │ (data) │ │ (task queue) │ │ (static build) │ │
│ └──────────────┘ └─────────────────┘ └──────────────────┘ │
│ │
│ Volumes: │
│ ├── /data/downloads (downloaded content) │
│ ├── /data/config (gallery-dl.conf) │
│ ├── /data/cookies (cookie files) │
│ └── /data/db (PostgreSQL data) │
└─────────────────────────────────────────────────────────────────┘
```
---
## Technology Stack
### Backend
| Component | Technology | Rationale |
|-----------|------------|-----------|
| Web Framework | **Quart** | Flask-like API with native async, WebSocket support |
| Task Queue | **Celery + Redis** | Reliable background tasks, scheduling with Celery Beat |
| Database | **PostgreSQL** | JSON fields for metadata, robust concurrency |
| ORM | **SQLAlchemy 2.0** | Async support, mature ecosystem |
| Migrations | **Alembic** | Standard for SQLAlchemy |
### Frontend
| Component | Technology | Rationale |
|-----------|------------|-----------|
| Framework | **Vue 3** | Reactive, component-based |
| UI Library | **Vuetify 3** | Material Design, comprehensive components |
| State | **Pinia** | Official Vue state management |
| HTTP Client | **Axios** | Request interceptors for auth |
| WebSocket | **Native** | Real-time updates |
### Infrastructure
| Component | Technology | Rationale |
|-----------|------------|-----------|
| Container | **Docker Compose** | Easy deployment |
| Process Manager | **Supervisor** | Manage Quart + Celery in container |
---
## Project Structure
```
gallery-subscriber/
├── docker-compose.yml
├── docker-compose.dev.yml
├── .env.example
├── README.md
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── supervisord.conf
│ │
│ ├── app/
│ │ ├── __init__.py
│ │ ├── main.py # Quart app factory
│ │ ├── config.py # Settings (from env)
│ │ │
│ │ ├── api/
│ │ │ ├── __init__.py
│ │ │ ├── sources.py # Source CRUD
│ │ │ ├── downloads.py # Download history
│ │ │ ├── credentials.py # Cookie management
│ │ │ ├── settings.py # App settings
│ │ │ └── websocket.py # Real-time events
│ │ │
│ │ ├── models/
│ │ │ ├── __init__.py
│ │ │ ├── source.py
│ │ │ ├── download.py
│ │ │ ├── credential.py
│ │ │ └── content.py
│ │ │
│ │ ├── services/
│ │ │ ├── __init__.py
│ │ │ ├── gallery_dl.py # gallery-dl subprocess wrapper
│ │ │ ├── download_manager.py
│ │ │ ├── credential_manager.py
│ │ │ └── manifest.py # Manifest generation
│ │ │
│ │ ├── tasks/
│ │ │ ├── __init__.py
│ │ │ ├── celery_app.py # Celery configuration
│ │ │ ├── downloads.py # Download tasks
│ │ │ └── scheduler.py # Scheduled jobs
│ │ │
│ │ └── utils/
│ │ ├── __init__.py
│ │ ├── cookies.py # Cookie parsing
│ │ └── encryption.py # Credential encryption
│ │
│ ├── alembic/
│ │ ├── env.py
│ │ └── versions/
│ │
│ └── tests/
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ ├── vite.config.js
│ │
│ └── src/
│ ├── main.js
│ ├── App.vue
│ ├── router/
│ ├── stores/
│ ├── views/
│ │ ├── Dashboard.vue
│ │ ├── Sources.vue
│ │ ├── Downloads.vue
│ │ ├── NewContent.vue
│ │ └── Settings.vue
│ ├── components/
│ └── services/
│ └── api.js
├── extension/
│ ├── manifest.json
│ ├── popup/
│ │ ├── popup.html
│ │ ├── popup.css
│ │ └── popup.js
│ ├── background/
│ │ └── background.js
│ └── icons/
└── scripts/
├── migrate_legacy.py # Import from old project
└── init_db.py
```
---
## Database Schema
```sql
-- Sources (artists/creators to download from)
CREATE TABLE sources (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
platform VARCHAR(50) NOT NULL, -- patreon, subscribestar, hentaifoundry, discord
url TEXT NOT NULL,
enabled BOOLEAN DEFAULT true,
priority INTEGER DEFAULT 0,
check_interval INTEGER DEFAULT 3600, -- seconds between checks
last_check TIMESTAMP,
last_success TIMESTAMP,
error_count INTEGER DEFAULT 0,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(platform, url)
);
-- Downloads (individual download jobs)
CREATE TABLE downloads (
id SERIAL PRIMARY KEY,
source_id INTEGER REFERENCES sources(id) ON DELETE SET NULL,
url TEXT NOT NULL,
status VARCHAR(20) DEFAULT 'pending', -- pending, running, completed, failed, skipped
error_type VARCHAR(50), -- auth_error, rate_limited, not_found, etc.
error_message TEXT,
file_count INTEGER DEFAULT 0,
started_at TIMESTAMP,
completed_at TIMESTAMP,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_downloads_status ON downloads(status);
CREATE INDEX idx_downloads_source ON downloads(source_id);
-- Content Items (discovered files)
CREATE TABLE content_items (
id SERIAL PRIMARY KEY,
source_id INTEGER REFERENCES sources(id) ON DELETE CASCADE,
download_id INTEGER REFERENCES downloads(id) ON DELETE SET NULL,
external_id VARCHAR(255), -- Platform-specific ID
title VARCHAR(500),
content_type VARCHAR(50), -- image, video, attachment
file_path VARCHAR(500),
file_size BIGINT,
thumbnail_path VARCHAR(500),
published_at TIMESTAMP,
discovered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
metadata JSONB DEFAULT '{}',
UNIQUE(source_id, external_id)
);
CREATE INDEX idx_content_discovered ON content_items(discovered_at DESC);
-- Credentials (encrypted cookie/token storage)
CREATE TABLE credentials (
id SERIAL PRIMARY KEY,
platform VARCHAR(50) NOT NULL UNIQUE,
credential_type VARCHAR(20) NOT NULL, -- cookies, token
data BYTEA NOT NULL, -- Encrypted
expires_at TIMESTAMP,
last_verified TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Settings (key-value store)
CREATE TABLE settings (
key VARCHAR(100) PRIMARY KEY,
value JSONB NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
---
## API Specification
### Sources
```
GET /api/sources List all sources (with pagination)
POST /api/sources Add new source
GET /api/sources/:id Get source details
PATCH /api/sources/:id Update source
DELETE /api/sources/:id Delete source
POST /api/sources/:id/check Trigger immediate download check
```
### Downloads
```
GET /api/downloads List download history
GET /api/downloads/:id Get download details with logs
POST /api/downloads/:id/retry Retry failed download
GET /api/downloads/stats Get statistics (by platform, by day)
```
### Content
```
GET /api/content List content items (filterable)
GET /api/content/new Get new content since timestamp
```
### Credentials
```
GET /api/credentials List credential status (no secrets)
POST /api/credentials Upload cookies (manual or from extension)
DELETE /api/credentials/:platform Remove credentials for platform
POST /api/credentials/verify Verify credentials are working
```
### Settings
```
GET /api/settings Get all settings
PATCH /api/settings Update settings
GET /api/settings/gallery-dl Get gallery-dl config
PUT /api/settings/gallery-dl Update gallery-dl config
```
### WebSocket
```
WS /ws/events
Events (server -> client):
download.started { download_id, source_id, url }
download.progress { download_id, file_count }
download.completed { download_id, file_count }
download.failed { download_id, error_type, error_message }
content.new { content_id, source_name, title, thumbnail }
credential.expiring { platform, expires_in_hours }
```
---
## Firefox Extension
### Features
- Export cookies for supported platforms (Patreon, SubscribeStar, HentaiFoundry)
- Export Discord token
- Auto-refresh option (re-export when cookies change)
- Connection status indicator
- Manual export button per platform
### Supported Platforms
| Platform | Auth Type | Domains |
|----------|-----------|---------|
| Patreon | Cookies | `.patreon.com` |
| SubscribeStar | Cookies | `.subscribestar.com`, `.subscribestar.adult` |
| HentaiFoundry | Cookies | `.hentai-foundry.com` |
| Discord | Token | `.discord.com` |
### Manual Alternative
For users who don't want the extension:
1. Export cookies using browser dev tools or "cookies.txt" extension
2. Upload via Settings page in web UI
3. Paste cookie content or upload file
---
## Configuration
### Environment Variables (.env)
```bash
# Database
DATABASE_URL=postgresql://gdl:password@db:5432/gallery_subscriber
# Redis
REDIS_URL=redis://redis:6379/0
# Security
SECRET_KEY=your-secret-key-at-least-32-characters
EXTENSION_API_KEY=key-for-firefox-extension
# Paths (inside container)
DOWNLOAD_PATH=/data/downloads
CONFIG_PATH=/data/config
COOKIES_PATH=/data/cookies
# Download settings
DOWNLOAD_PARALLEL_LIMIT=3
DOWNLOAD_RATE_LIMIT=3.0
DEFAULT_CHECK_INTERVAL=3600
# Server
HOST=0.0.0.0
PORT=8080
```
### docker-compose.yml
```yaml
services:
app:
build: ./backend
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgresql://gdl:${DB_PASSWORD}@db:5432/gallery_subscriber
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY=${SECRET_KEY}
- EXTENSION_API_KEY=${EXTENSION_API_KEY}
volumes:
- downloads:/data/downloads
- config:/data/config
- cookies:/data/cookies
depends_on:
- db
- redis
restart: unless-stopped
celery:
build: ./backend
command: celery -A app.tasks.celery_app worker -B --loglevel=info
environment:
- DATABASE_URL=postgresql://gdl:${DB_PASSWORD}@db:5432/gallery_subscriber
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY=${SECRET_KEY}
volumes:
- downloads:/data/downloads
- config:/data/config
- cookies:/data/cookies
depends_on:
- db
- redis
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=gdl
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=gallery_subscriber
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
postgres_data:
downloads:
config:
cookies:
```
---
## Development Phases
### Phase 1: Foundation
- [ ] Set up project structure
- [ ] Create Docker Compose development environment
- [ ] Initialize Quart app with basic routing
- [ ] Set up PostgreSQL + Alembic migrations
- [ ] Create database models
- [ ] Basic API endpoints (sources CRUD)
### Phase 2: Download Engine
- [ ] Port gallery-dl wrapper from legacy project
- [ ] Implement Celery tasks for downloads
- [ ] Add platform-specific locking (from legacy)
- [ ] Error categorization and handling
- [ ] Download history tracking
### Phase 3: Scheduling & State
- [ ] Celery Beat for scheduled downloads
- [ ] Round-robin scheduling logic (from legacy)
- [ ] Priority queue for failed tasks
- [ ] State persistence in PostgreSQL
### Phase 4: Frontend
- [ ] Vue 3 + Vuetify scaffold
- [ ] Dashboard view
- [ ] Sources management UI
- [ ] Download history view
- [ ] Settings page
### Phase 5: Authentication
- [ ] Credential storage (encrypted)
- [ ] Manual cookie upload via web UI
- [ ] Firefox extension
- [ ] Credential verification
### Phase 6: Real-time & Content
- [ ] WebSocket event streaming
- [ ] New content feed
- [ ] Content browsing with thumbnails
- [ ] Notifications
### Phase 7: Polish & Migration
- [ ] Migration script for legacy sources.yaml
- [ ] Import legacy gallery-dl.conf
- [ ] Testing
- [ ] Documentation
---
## Migration from Legacy Project
### What Gets Migrated
| Legacy File | Destination |
|-------------|-------------|
| `sources.yaml` | `sources` table |
| `gallery-dl.conf` | `/data/config/gallery-dl.conf` (paths updated) |
| `state.json` | `sources` table (last_check, error_count) |
| Download history | Optional - start fresh or import logs |
### Migration Script Usage
```bash
python scripts/migrate_legacy.py \
--sources /path/to/sources.yaml \
--config /path/to/gallery-dl.conf \
--state /path/to/state.json
```
### Key Patterns Preserved from Legacy
1. **Platform locking** - One download per platform at a time
2. **Round-robin scheduling** - Fair distribution across sources
3. **Priority queue** - Failed tasks get priority on next run
4. **Error categorization** - AUTH_ERROR, RATE_LIMITED, NOT_FOUND, etc.
5. **Rate limiting** - Configurable delay between requests
---
*Document Version: 2.0*
*Created: 2025-01-24*
*Project: Gallery Subscriber*
+55
View File
@@ -266,3 +266,58 @@ async def get_logs():
})
return jsonify({"logs": logs, "count": len(logs)})
@bp.route("/system-info", methods=["GET"])
async def get_system_info():
"""Get system information including dependency versions."""
import sys
import importlib.metadata
config = get_settings()
# Get all installed packages (preserve original names for display)
all_packages = {}
# Also build a normalized lookup (lowercase, underscores normalized to hyphens)
normalized_lookup = {}
for dist in importlib.metadata.distributions():
name = dist.metadata["Name"]
if name:
all_packages[name] = dist.version
# Normalize: lowercase and replace underscores with hyphens
normalized = name.lower().replace("_", "-")
normalized_lookup[normalized] = dist.version
def get_version(name):
"""Get version with case-insensitive and underscore/hyphen-insensitive lookup."""
normalized = name.lower().replace("_", "-")
return normalized_lookup.get(normalized, "not installed")
# Key packages to highlight (sorted alphabetically)
key_package_names = [
"alembic",
"asyncpg",
"celery",
"gallery-dl",
"hypercorn",
"pydantic",
"quart",
"redis",
"sqlalchemy",
]
key_packages = {
name: get_version(name)
for name in key_package_names
}
return jsonify({
"version": "1.0.0",
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"python_full": sys.version,
"gallery_dl_version": get_version("gallery-dl"),
"key_packages": key_packages,
"all_packages": all_packages,
"download_path": config.download_path,
"config_path": config.config_path,
})
+7 -1
View File
@@ -144,17 +144,21 @@ class GalleryDLService:
# Additional options
"videos": True, # Download video content
"embeds": True, # Download embedded URLs (YouTube, etc.)
# Deep crawling options
"cursor": True, # Enable cursor-based pagination for complete history
},
"subscribestar": {
"content_types": ["all"],
"directory": ["{date:%Y-%m-%d}_{id}_{title[:40]}"],
"filename": "{num:>02}_{filename}.{extension}",
# Automatic infinite scroll pagination is built-in
},
"hentaifoundry": {
"content_types": ["pictures"],
"directory": [], # Flat structure within platform folder
"filename": "{category}_{index:>03}_{title[:50]}.{extension}",
"include": "all", # Include all content types
"include": "all", # Include all content types (pictures, scraps, stories)
# Automatic pagination through all pages (25 items/page)
},
"discord": {
"content_types": ["all"],
@@ -164,6 +168,8 @@ class GalleryDLService:
"embeds": True, # Download embedded URLs
"stickers": True, # Download stickers
"reactions": False, # Skip reaction images
# Deep crawling options
"threads": True, # Include thread content for complete history
},
}
+1
View File
@@ -0,0 +1 @@
{"uploadUuid":"fe2853742907461a9436c0b20d45bcd9","channel":"unlisted","xpiCrcHash":"59496211d207900b7b0f4ee7cbece6aba1be1d2f6523229fee17f6652dc38d95"}
+170
View File
@@ -0,0 +1,170 @@
# GallerySubscriber Browser Extension
A Firefox extension that exports authentication cookies and tokens from supported platforms to the GallerySubscriber backend for automated content downloading.
## Features
### Cookie Export
Automatically extracts authentication cookies from supported platforms and sends them to your GallerySubscriber backend. This enables gallery-dl to download content that requires login authentication.
### Supported Platforms
| Platform | Auth Type | How It Works |
|----------|-----------|--------------|
| **Patreon** | Cookies | Exports session cookies after you log in |
| **SubscribeStar** | Cookies | Exports session cookies (supports both .com and .adult domains) |
| **Hentai-Foundry** | Cookies | Exports session cookies after you log in |
| **Discord** | Token | Captures authorization token from API requests |
### Discord Token Capture
Unlike other platforms that use cookies, Discord requires an authorization token. The extension automatically captures this token by monitoring Discord API requests when you use Discord in your browser. Simply:
1. Open Discord in Firefox
2. Interact with it (switch channels, send a message, etc.)
3. The extension captures and stores your token automatically
### Dark/Light Theme Support
The extension automatically matches your system's color scheme preference:
- Detects `prefers-color-scheme` media query
- Supports manual theme override via `data-theme` attribute
- Consistent theming across popup and options pages
## Installation
### Temporary Installation (Development)
1. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`
2. Click "Load Temporary Add-on"
3. Select any file in the `extension` folder (e.g., `manifest.json`)
### Permanent Installation (Unsigned)
Requires Firefox Developer Edition or Nightly:
1. Navigate to `about:config`
2. Set `xpinstall.signatures.required` to `false`
3. Go to `about:addons` → gear icon → "Install Add-on From File"
4. Select `gallerysubscriber.xpi`
### Permanent Installation (Signed)
1. Create an account at [addons.mozilla.org](https://addons.mozilla.org)
2. Use `web-ext sign` to sign the extension
3. Install the signed `.xpi` file
## Configuration
### Initial Setup
1. Click the extension icon in the toolbar
2. Click "Open Settings" or the gear icon
3. Enter your GallerySubscriber backend URL (e.g., `http://localhost:8000`)
4. Enter your API key (found in GallerySubscriber web UI → Settings)
5. Click "Test Connection" to verify
6. Click "Save"
### Options Page Settings
- **API URL**: The base URL of your GallerySubscriber backend
- **API Key**: Authentication key for the extension API
## Usage
### Exporting Cookies for a Single Platform
1. Log into the platform in Firefox (e.g., patreon.com)
2. Click the GallerySubscriber extension icon
3. Click on the platform card
4. Cookies are exported to the backend
### Exporting All Platforms
1. Ensure you're logged into all desired platforms
2. Click the extension icon
3. Click "Export All Platforms"
4. All available cookies/tokens are exported
### Checking Platform Status
The popup shows the status of each platform:
- **"X cookies ready"** - Cookies found, ready to export
- **"No cookies found"** - Not logged in or cookies expired
- **"Token captured"** - Discord token available
- **"Open Discord in browser"** - Need to use Discord to capture token
## Architecture
```
extension/
├── manifest.json # Extension manifest (Manifest V2)
├── background/
│ └── background.js # Background script - handles API calls & cookie extraction
├── popup/
│ ├── popup.html # Popup UI
│ ├── popup.js # Popup logic
│ └── popup.css # Popup styles with theme support
├── options/
│ └── options.html # Settings page (inline CSS/JS)
├── lib/
│ ├── platforms.js # Platform definitions and configuration
│ ├── cookies.js # Cookie extraction utilities
│ └── api.js # Backend API client
└── icons/
└── icon.svg # Extension icon
```
## Permissions
The extension requires the following permissions:
| Permission | Purpose |
|------------|---------|
| `cookies` | Read cookies from supported platform domains |
| `storage` | Store API configuration and Discord token |
| `tabs` | Access active tab information |
| `activeTab` | Interact with the current tab |
| `webRequest` | Monitor Discord API requests to capture token |
| `*://*.patreon.com/*` | Access Patreon cookies |
| `*://*.subscribestar.com/*` | Access SubscribeStar cookies |
| `*://*.subscribestar.adult/*` | Access SubscribeStar Adult cookies |
| `*://*.hentai-foundry.com/*` | Access Hentai-Foundry cookies |
| `*://*.discord.com/*` | Monitor Discord API for token capture |
## API Endpoints Used
The extension communicates with these GallerySubscriber backend endpoints:
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/health` | GET | Connection test |
| `/api/credentials` | POST | Upload cookies/tokens |
## Troubleshooting
### Extension shows "Setup Required"
- Open settings and configure the API URL and key
- Ensure the backend is running and accessible
### Connection test fails
- Verify the API URL is correct (include protocol, e.g., `http://`)
- Check that the backend is running
- Verify the API key matches the one in GallerySubscriber settings
### "No cookies found" for a platform
- Ensure you're logged into the platform in Firefox
- Try logging out and back in
- Clear cookies and re-authenticate
### Discord token not captured
- Open discord.com in Firefox (not the desktop app)
- Interact with Discord (switch channels, scroll, etc.)
- The token is captured from API requests automatically
### Popup is blank or slow
- Check the browser console for errors (`about:debugging` → Inspect)
- The extension caches connection status for 2 minutes to improve performance
## Privacy & Security
- Cookies and tokens are sent only to your configured backend URL
- No data is sent to third parties
- API key is stored locally in browser storage
- Discord tokens are stored locally and only sent to your backend when you export
## Version History
### 1.0.0
- Initial release
- Support for Patreon, SubscribeStar, Hentai-Foundry, Discord
- Dark/light theme support
- Connection caching for improved performance
+74
View File
@@ -0,0 +1,74 @@
# GallerySubscriber Extension - Release Notes
## Version 1.0.0
**Release Date:** January 2026
### Overview
Initial release of the GallerySubscriber browser extension for Firefox. This extension streamlines the process of exporting authentication credentials from supported platforms to your self-hosted GallerySubscriber backend, enabling automated content downloading via gallery-dl.
### Features
#### Multi-Platform Cookie Export
- **Patreon** - Export session cookies for accessing patron-only content
- **SubscribeStar** - Support for both .com and .adult domains
- **Hentai-Foundry** - Export authentication cookies for mature content access
#### Discord Token Capture
- Automatic authorization token capture from Discord API requests
- No manual token entry required - just use Discord in your browser
- Secure local storage with timestamp tracking
#### User Interface
- Clean, modern popup interface for quick access
- Real-time platform status indicators showing cookie/token availability
- One-click export for individual platforms or bulk export all at once
- Connection status indicator showing backend connectivity
#### Theme Support
- Automatic light/dark theme matching system preferences
- Uses CSS custom properties for consistent theming
- Seamless appearance across popup and options pages
#### Performance Optimizations
- Connection test caching (2-minute interval) to reduce API calls
- Non-blocking UI - interface loads immediately while data fetches in background
- Efficient background script initialization
#### Configuration
- Simple setup via options page
- Secure API key authentication with your GallerySubscriber backend
- Connection test to verify setup before use
### Technical Details
- **Manifest Version:** 2
- **Minimum Firefox Version:** 57+
- **Permissions Required:**
- `cookies` - Read authentication cookies from supported sites
- `storage` - Store configuration and captured tokens
- `webRequest` - Monitor Discord API for token capture
- Host permissions for supported platforms
### Installation
1. Download `gallerysubscriber-signed.xpi`
2. Open Firefox and navigate to `about:addons`
3. Click the gear icon and select "Install Add-on From File"
4. Select the downloaded .xpi file
5. Click the extension icon and configure your backend URL and API key
### Known Limitations
- Discord token capture requires using Discord in the browser (not the desktop app)
- Cookies must be refreshed if you log out and back into platforms
- Token-based platforms other than Discord require manual configuration in the web UI
### Support
For issues, feature requests, or contributions, please visit the project repository.
---
*GallerySubscriber Extension is designed for personal use with self-hosted GallerySubscriber instances.*
+6
View File
@@ -4,6 +4,12 @@
"version": "1.0.0",
"description": "Export cookies from supported platforms to GallerySubscriber for automated downloads",
"browser_specific_settings": {
"gecko": {
"id": "gallerysubscriber@fabledsword.com"
}
},
"permissions": [
"cookies",
"storage",
+102 -3
View File
@@ -288,7 +288,7 @@
<tbody>
<tr>
<td width="200"><strong>Version</strong></td>
<td>1.0.0</td>
<td>{{ systemInfo.version }}</td>
</tr>
<tr>
<td><strong>API Status</strong></td>
@@ -298,19 +298,90 @@
</v-chip>
</td>
</tr>
<tr>
<td><strong>Python Version</strong></td>
<td>{{ systemInfo.python_version }}</td>
</tr>
<tr>
<td><strong>Gallery-dl Version</strong></td>
<td>
<v-chip color="primary" size="small" variant="tonal">
{{ systemInfo.gallery_dl_version }}
</v-chip>
</td>
</tr>
<tr>
<td><strong>Download Path</strong></td>
<td>/data/downloads</td>
<td>{{ systemInfo.download_path }}</td>
</tr>
<tr>
<td><strong>Config Path</strong></td>
<td>{{ systemInfo.config_path }}</td>
</tr>
</tbody>
</v-table>
<v-divider class="my-4" />
<!-- Dependencies Section -->
<h4 class="text-subtitle-1 mb-3">
<v-icon class="mr-1" size="small">mdi-package-variant</v-icon>
Dependencies
</h4>
<v-table density="compact" class="mb-4">
<thead>
<tr>
<th width="200">Package</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr v-for="(version, name) in systemInfo.key_packages" :key="name">
<td><code>{{ name }}</code></td>
<td>
<v-chip size="x-small" variant="outlined">
{{ version }}
</v-chip>
</td>
</tr>
</tbody>
</v-table>
<v-btn
variant="text"
size="small"
@click="showAllPackages = !showAllPackages"
>
<v-icon start>{{ showAllPackages ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
{{ showAllPackages ? 'Hide' : 'Show' }} all packages ({{ Object.keys(systemInfo.all_packages || {}).length }})
</v-btn>
<v-expand-transition>
<div v-if="showAllPackages">
<v-table density="compact" class="mt-3">
<thead>
<tr>
<th width="250">Package</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr v-for="(version, name) in sortedAllPackages" :key="name">
<td><code class="text-caption">{{ name }}</code></td>
<td class="text-caption">{{ version }}</td>
</tr>
</tbody>
</v-table>
</div>
</v-expand-transition>
</v-card-text>
</v-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useSettingsStore } from '../stores/settings'
import { useNotificationStore } from '../stores/notifications'
import { platformsApi } from '../services/api'
@@ -329,6 +400,24 @@ const platforms = ref({})
const apiHealthy = ref(true)
const showApiKey = ref(false)
const regenerateDialog = ref(false)
const systemInfo = ref({
version: '...',
python_version: '...',
gallery_dl_version: '...',
key_packages: {},
download_path: '...',
config_path: '...',
})
const showAllPackages = ref(false)
const sortedAllPackages = computed(() => {
const packages = systemInfo.value.all_packages || {}
const sorted = {}
Object.keys(packages).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).forEach(key => {
sorted[key] = packages[key]
})
return sorted
})
onMounted(() => {
// Don't block UI - load data in background
@@ -343,6 +432,7 @@ async function loadAll() {
loadGalleryDLConfig(),
loadPlatforms(),
checkApiHealth(),
loadSystemInfo(),
])
} finally {
loading.value = false
@@ -422,6 +512,15 @@ async function checkApiHealth() {
}
}
async function loadSystemInfo() {
try {
const response = await api.get('/settings/system-info')
systemInfo.value = response.data
} catch (error) {
console.error('Failed to load system info:', error)
}
}
function getPlatformIcon(platform) {
const icons = {
patreon: 'mdi-patreon',