signing for extension deployment, tuning for download crawl depth, and dependency version visibility in settings view
This commit is contained in:
@@ -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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,3 +83,7 @@ config/
|
||||
|
||||
# Build artifacts
|
||||
backend/static/
|
||||
|
||||
# Extension packages
|
||||
*.xpi
|
||||
extension-dist/
|
||||
|
||||
-548
@@ -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*
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"uploadUuid":"fe2853742907461a9436c0b20d45bcd9","channel":"unlisted","xpiCrcHash":"59496211d207900b7b0f4ee7cbece6aba1be1d2f6523229fee17f6652dc38d95"}
|
||||
@@ -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
|
||||
@@ -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.*
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user