Skip to main content

da-cli-deviantart-sync

Sync DeviantArt galleries to local folders with OAuth 2.1 PKCE, SQLite indexing, and scheduled syncs using da-cli

Ir a la instalación

Datos de origen

Repositorio
reason-machines/devtools-skills
Última actividad en el origen
3 de agosto de 2026 a las 23:53
Idioma detectado de SKILL.md
inglés
Estrellas
4
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
da-cli-deviantart-sync
description
Sync DeviantArt galleries to local folders with OAuth 2.1 PKCE, SQLite indexing, and scheduled syncs using da-cli
triggers
["sync my DeviantArt gallery","download art from DeviantArt","set up DeviantArt backup","authenticate with DeviantArt API","schedule automatic DeviantArt sync","search DeviantArt from command line","backup watched artists on DeviantArt","configure da-cli for DeviantArt"]
# da-cli DeviantArt Sync Skill > Skill by [ara.so](https://ara.so) — Devtools Skills collection. ## Overview da-cli is a zero-dependency Python CLI tool that syncs DeviantArt galleries to local folders. It uses OAuth 2.1 with PKCE for authentication, maintains a SQLite index for incremental syncs, and supports scheduled backups via launchd (macOS) or systemd (Linux). Key features: - **Zero runtime dependencies** — pure Python 3.10+ stdlib - **Incremental sync** — SQLite index tracks what's downloaded; re-runs cost one API call when nothing's new - **OAuth 2.1 PKCE** — secure authentication with automatic token refresh - **Multiple sync modes** — watch feed, specific artists, all watched users - **Scheduled automation** — launchd/systemd integration for unattended syncs - **Search & browse** — tags, topics, daily deviations, user profiles - **Secrets in Keychain** — macOS Keychain integration for client secrets ## Installation ### Option A: pipx (recommended) ```bash # Install with isolated environment pipx install da-sync # Verify installation da --version ``` ### Option B: git clone (for development) ```bash # Clone repository git clone https://github.com/FZ2000/da-cli.git ~/Documents/da-cli cd ~/Documents/da-cli # Install shim (creates ~/.local/bin/da) ./install.sh # Verify da --version ``` **Requirements:** Python 3.10+ (uses `argparse.BooleanOptionalAction` and `X | None` syntax) ## Initial Setup ### 1. Create DeviantArt OAuth App ```bash # Navigate to DeviantArt developers portal # https://www.deviantart.com/developers/ # Click "Register Your Application" # Set these values: # - Application Type: Confidential # - Redirect URI Whitelist: https://localhost:8765/ (exact, with trailing slash) # Save your client_id and client_secret ``` ### 2. Configure da-cli ```bash # Set client credentials (secret goes to Keychain on macOS) da config set client_id YOUR_CLIENT_ID da config set client_secret YOUR_CLIENT_SECRET # Set download destination da config set destination ~/Pictures/DA # Optional: customize scope (defaults to browse+collection+user) da config set scope "browse collection user" # View current configuration da config show ``` ### 3. Authenticate ```bash # Start OAuth flow (opens browser) da auth # Verify authentication da whoami # Check token status da refresh ``` ## Core Commands ### Authentication ```bash # Initial login (browser-based PKCE flow) da auth # Check current user da whoami # Force token refresh da refresh # Logout (clears local tokens) da auth logout # Check auth status da auth status ``` ### Syncing Art ```bash # Sync your watch feed (incremental) da sync feed # Sync without mature content da sync feed --no-mature # Sync specific artist's full gallery da sync artist username # Sync all watched artists da sync watched # Discover watched artists via feed (works with browse scope only) da sync watched --via-feed # Add random jitter to avoid burst patterns da sync feed --jitter 0.4 # Limit sync time da sync feed --max-minutes 30 # Resume interrupted sync da sync feed # automatically resumes from checkpoint ``` ### Search & Browse ```bash # Search by tag da search tag nature da search tag "digital art" --limit 50 # Browse curated topics da search topic digitalart da search topics # list all valid topics # Daily deviations da daily 2026-01-15 da daily today # User lookup da search user deviantart # Get user profile da user profile username # Deviation details da deviation show 123456789 da deviation morelikethis 123456789 # List watched users (requires user scope) da watch list ``` ### Configuration Management ```bash # Show all configuration da config show # Show config file locations da config path # Set value (secrets go to Keychain on macOS) da config set key value da config set max_retries 5 da config set sleep_between_requests 1.5 # Get value (secrets masked by default) da config get client_secret da config get client_secret --unmask # Remove setting da config unset key ``` ### Index & Maintenance ```bash # Show sync index stats da index show # Rebuild index from destination folder da index rebuild # Run health check da diagnose # Benchmark sync performance da bench ``` ## Scheduling Automated Syncs ### macOS (launchd) ```bash # Install daily sync at 03:00 ./install_schedule.sh # Manual launchd setup cat > ~/Library/LaunchAgents/com.user.da-sync.plist <<'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.user.da-sync</string> <key>ProgramArguments</key> <array> <string>/Users/USERNAME/.local/bin/da</string> <string>sync</string> <string>feed</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>3</integer> <key>Minute</key> <integer>0</integer> </dict> <key>StandardOutPath</key> <string>/Users/USERNAME/.local/state/da-cli/sync.log</string> <key>StandardErrorPath</key> <string>/Users/USERNAME/.local/state/da-cli/sync.log</string> </dict> </plist> EOF # Load the job launchctl load ~/Library/LaunchAgents/com.user.da-sync.plist # Verify launchctl list | grep da-sync ``` **Important:** Grant Full Disk Access to Terminal/iTerm in System Preferences → Privacy & Security → Full Disk Access ### Linux (systemd) ```bash # Create service unit mkdir -p ~/.config/systemd/user cat > ~/.config/systemd/user/da-sync.service <<'EOF' [Unit] Description=DeviantArt sync [Service] Type=oneshot ExecStart=/home/USERNAME/.local/bin/da sync feed StandardOutput=append:/home/USERNAME/.local/state/da-cli/sync.log StandardError=append:/home/USERNAME/.local/state/da-cli/sync.log EOF # Create timer unit cat > ~/.config/systemd/user/da-sync.timer <<'EOF' [Unit] Description=Daily DeviantArt sync [Timer] OnCalendar=daily OnCalendar=03:00 Persistent=true [Install] WantedBy=timers.target EOF # Enable and start systemctl --user daemon-reload systemctl --user enable da-sync.timer systemctl --user start da-sync.timer # Enable lingering (allows timer to run when not logged in) loginctl enable-linger $USER # Check status systemctl --user list-timers systemctl --user status da-sync.timer ``` ## Python Scripting ### Export Sync Index to CSV ```python #!/usr/bin/env python3 import sqlite3 import csv from pathlib import Path def export_index_to_csv(output_path: str = "sync_index.csv"): """Export da-cli sync index to CSV.""" # Default index location db_path = Path.home() / ".local/state/da-cli/sync.db" conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute(""" SELECT deviation_id, title, author, published_time, content_url, is_mature, local_path FROM deviations ORDER BY published_time DESC """) with open(output_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(['deviation_id', 'title', 'author', 'published_time', 'content_url', 'is_mature', 'local_path']) writer.writerows(cursor.fetchall()) conn.close() print(f"Exported to {output_path}") if __name__ == "__main__": export_index_to_csv() ``` ### Post-Sync Webhook ```python #!/usr/bin/env python3 import json import sqlite3 import subprocess from pathlib import Path from datetime import datetime, timedelta def check_new_deviations(since_hours: int = 24): """Check for deviations synced in the last N hours.""" db_path = Path.home() / ".local/state/da-cli/sync.db" conn = sqlite3.connect(db_path) cursor = conn.cursor() cutoff = datetime.now() - timedelta(hours=since_hours) cutoff_str = cutoff.isoformat() cursor.execute(""" SELECT COUNT(*), author FROM deviations WHERE synced_at > ? GROUP BY author ORDER BY COUNT(*) DESC """, (cutoff_str,)) results = cursor.fetchall() conn.close() if results: print(f"New deviations in last {since_hours}h:") for count, author in results: print(f" {author}: {count} new") return True return False def send_notification(message: str): """Send macOS notification.""" subprocess.run([ 'osascript', '-e', f'display notification "{message}" with title "da-cli sync"' ]) if __name__ == "__main__": if check_new_deviations(24): send_notification("New DeviantArt content synced!") ``` ### Query Specific Artist ```python #!/usr/bin/env python3 import sqlite3 from pathlib import Path def get_artist_stats(username: str): """Get stats for a specific artist in sync index.""" db_path = Path.home() / ".local/state/da-cli/sync.db" conn = sqlite3.connect(db_path) cursor = conn.cursor() # Count deviations cursor.execute(""" SELECT COUNT(*), MIN(published_time), MAX(published_time) FROM deviations WHERE author = ? """, (username,)) count, first_date, last_date = cursor.fetchone() # Get recent titles cursor.execute(""" SELECT title, published_time, local_path FROM deviations WHERE author = ? ORDER BY published_time DESC LIMIT 5 """, (username,)) recent = cursor.fetchall() conn.close() print(f"\n{username}:") print(f" Total deviations: {count}") print(f" Date range: {first_date} to {last_date}") print(f"\n Recent 5:") for title, pub_time, path in recent: print(f" - {title} ({pub_time})") print(f" {path}") if __name__ == "__main__": import sys if len(sys.argv) < 2: print("Usage: artist_stats.py USERNAME") sys.exit(1) get_artist_stats(sys.argv[1]) ``` ## Configuration Reference ### Config File Locations ```bash # Config file (settings) ~/.config/da-cli/config.json # State file (tokens) ~/.local/state/da-cli/state.json # Sync index (SQLite) ~/.local/state/da-cli/sync.db # Logs ~/.local/state/da-cli/sync.log ``` ### Key Settings ```json { "client_id": "12345", "destination": "/Users/username/Pictures/DA", "scope": "browse collection user", "mature_content": true, "sleep_between_requests": 1.0, "max_retries": 3, "checkpoint_interval": 50, "default_limit": 24 } ``` ### Environment Variables ```bash # Override config location export DA_CLI_CONFIG_DIR=~/custom/config/path # Override state location export DA_CLI_STATE_DIR=~/custom/state/path # Set client credentials (alternative to config file) export DA_CLIENT_ID=your_client_id export DA_CLIENT_SECRET=your_client_secret # Set destination export DA_DESTINATION=~/Pictures/DA ``` ## Common Patterns ### Initial Full Sync of Watched Artists ```bash # First, sync watch feed to discover artists da sync feed --limit 500 # Then backfill each artist's full gallery da sync watched # Or do it individually da sync artist artist1 da sync artist artist2 ``` ### Sync with Rate Limiting ```bash # Add random jitter to avoid detection da sync feed --jitter 0.5 # Increase sleep between requests da config set sleep_between_requests 2.0 da sync feed # Limit total sync time da sync feed --max-minutes 15 ``` ### Filter Content ```bash # Exclude mature content da sync feed --no-mature # Sync only mature content (edit directly) # In sync flow, filter is applied to API params ``` ### Resume Interrupted Sync ```bash # Sync automatically resumes from last checkpoint # Checkpoint is saved every 50 deviations (configurable) da sync feed # View checkpoint da index show | grep checkpoint
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub