549 lines
18 KiB
Markdown
549 lines
18 KiB
Markdown
# 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*
|