An app to help with Publishing to DA
  • TypeScript 42.9%
  • JavaScript 29.2%
  • CSS 16%
  • HTML 11.5%
  • Dockerfile 0.4%
Find a file
2026-06-29 12:14:17 +02:00
public Add token keep-alive + better error message in UI 2026-06-29 12:14:17 +02:00
src Add token keep-alive + better error message in UI 2026-06-29 12:14:17 +02:00
.dockerignore Initial commit 2026-06-15 15:12:45 +02:00
.env.example Fix missing date/time settings 2026-06-23 13:46:02 +02:00
.gitignore Add gitignore 2026-06-15 15:36:48 +02:00
CLAUDE.md Initial commit 2026-06-15 15:12:45 +02:00
docker-compose.yml allow schedule.yaml to be written by the app 2026-06-23 14:09:28 +02:00
Dockerfile Install tzdata so timezone adjust can be done 2026-06-23 13:56:34 +02:00
package-lock.json Add bulk upload, AI/watermark/license controls, queue management improvements 2026-06-15 20:47:34 +02:00
package.json Add bulk upload, AI/watermark/license controls, queue management improvements 2026-06-15 20:47:34 +02:00
README.md Update Readme 2026-06-15 20:51:40 +02:00
schedule.example.yaml Fix missing date/time settings 2026-06-23 13:46:02 +02:00
tsconfig.json Initial commit 2026-06-15 15:12:45 +02:00

DA Scheduler

A self-hosted Node.js/TypeScript scheduler for posting images to DeviantArt on a fixed weekly schedule, with a web UI, auto-tagging via ImageDNA, and bulk upload support.

Features

  • Posts queued images to DeviantArt at fixed times per weekday (configurable; days can be skipped entirely)
  • Auto-suggests tags via a local ImageDNA instance, formatted to DA's rules (lowercase, no spaces, underscores, capped at 30)
  • Web UI for managing the queue, schedule, auth, and settings — no CLI required for day-to-day use
  • Bulk upload mode — drop multiple images at once, review/edit each card, queue or send to Sta.sh in one click
  • Per-post flags: mature content, AI generated, add watermark
  • Global publish settings via .env: Creative Commons license, commercial use, modification rights, display resolution
  • Queue thumbnail previews with configurable size (64512 px) and aspect ratio (1:1, 16:9, 9:16)
  • Force-post next item immediately (bypasses schedule, useful for testing)
  • Retry failed posts; clear published history
  • SQLite queue persists across restarts
  • OAuth2 PKCE flow with automatic token refresh

What it does NOT do (by design)

  • Subscriber-exclusive posts — the DA API has no working parameter for this. Post exclusives manually via DA's Studio scheduler and leave those slots out of schedule.yaml.
  • Gallery/folder targeting — the galleryids parameter on the publish endpoint is unreliable and falls back to the Featured gallery. Sort posts into folders manually after publishing.

Setup

1. Register a DeviantArt application

Go to https://www.deviantart.com/developers/apps and create an app.

  • Set the redirect URI to http://localhost:4000/callback (must match DA_REDIRECT_URI in .env exactly)
  • Note the Client ID and Client Secret

2. Install dependencies

npm install

3. Configure

cp .env.example .env
cp schedule.example.yaml schedule.yaml

Edit .env — at minimum set DA_CLIENT_ID, DA_CLIENT_SECRET, and DA_REDIRECT_URI. See .env.example for all options including license settings and thumbnail configuration.

Edit schedule.yaml to set posting times per weekday. Empty list = no posts that day.

timezone: "Europe/Amsterdam"
slots:
  monday:    ["09:00", "18:00"]
  tuesday:   ["09:00", "18:00"]
  wednesday: ["09:00", "18:00"]
  thursday:  ["09:00", "18:00"]
  friday:    ["09:00", "18:00"]
  saturday:  []
  sunday:    []

4. Start ImageDNA (optional, for auto-tagging)

docker run -d -p 5000:5000 nuclear314/image-dna:latest

Set IMAGEDNA_URL=http://localhost:5000 in .env. If ImageDNA is unavailable, tagging is skipped and you can enter tags manually.

5. Run the scheduler

# Development (TypeScript directly)
npm run dev

# Production (compile first)
npm run build
npm start

Or with Docker Compose:

docker compose up -d

Leave the process running (systemd, pm2, Docker, or screen/tmux). Every minute it checks whether the current time matches a configured slot; if so it publishes the next pending item.

A missed slot (queue was empty at the time) is skipped — it does not carry over.

6. Connect your DeviantArt account

Open the web UI at http://localhost:4000 and go to the Auth tab, or run:

npm run authorize

Authorize the app in the browser. Tokens are saved to data/tokens.json and refreshed automatically — you only need to do this once unless you revoke access.


Web UI

Open http://localhost:4000 (or your configured WEB_PORT).

Queue tab

Lists all posts (pending, posted, failed) with thumbnail previews. From here you can:

  • Reorder pending posts (↑ / ↓)
  • Delete pending posts
  • Retry failed posts (resets them to pending)
  • Force Post Next — publishes the next pending item immediately, bypassing the schedule
  • Clear Published — removes all posted entries from the history

Add Post tab

Two modes:

Single — drop one image, get auto-tag suggestions, edit title/description/tags, then either add to queue or upload directly to Sta.sh.

Bulk — drop multiple images at once. Each image gets a card with:

  • Auto-populated title (derived from filename)
  • Description field
  • Editable tags (auto-suggested)
  • Mature content, AI generated, and watermark checkboxes

Use the action bar to mark all images as mature, AI generated, or watermarked in one click. Then Queue All or Upload All to Sta.sh.

Schedule tab

Displays your current schedule.yaml posting slots per weekday.

Settings tab

Configure all .env values through the UI without editing files directly. Changes take effect after restarting the server.

Group Settings
DeviantArt Client ID, Client Secret, Redirect URI
ImageDNA Instance URL, tag confidence threshold, max tags
Server Web UI port
Thumbnails Size (64/128/256/512 px), aspect ratio (1:1 / 16:9 / 9:16)
License Creative Commons, commercial use, modification rights
Display Display resolution (08), DA display resolution

CLI (alternative to web UI)

# Add a single image interactively
npm run add -- path/to/image.png

# List queue with statuses
npm run queue

# OAuth2 authorization (if not using the web UI)
npm run authorize

Configuration reference (.env)

# Required
DA_CLIENT_ID=
DA_CLIENT_SECRET=
DA_REDIRECT_URI=http://localhost:4000/callback

# ImageDNA
IMAGEDNA_URL=http://localhost:5000
DA_TAG_LIMIT=30        # max tags (DA hard limit is 30)
DA_THRESHOLD=0.35      # confidence threshold 0.01.0

# Server
WEB_PORT=4000

# License (applied to all published deviations)
DA_LICENSE_CC=true           # Creative Commons
DA_LICENSE_COMMERCIAL=true   # allow commercial use
DA_LICENSE_MODIFY=no         # no | yes | share

# Display
DA_DISPLAY_RESOLUTION=1      # 0=original (no watermark) 1=400px … 8=1920px

# Queue thumbnails
THUMB_SIZE=128      # 64 | 128 | 256 | 512
THUMB_ASPECT=1:1    # 1:1 | 16:9 | 9:16

Known rough edges

The DA Sta.sh API is sparsely documented. Two details in src/deviantart/client.ts are best-effort:

  • File field name on /stash/submit is currently test (matching DA's own API console example). If uploads are rejected, try renaming it to file or image.
  • Tag handling — tags are sent as tags[] on both submit and publish. If tags don't appear on published deviations, check the logged API response; it's usually a one-line fix.

Debug logging for both stash/submit and stash/publish is active and visible in the server console.