From 87ff20c96d9a8faa71b03eb0ca912d0ebf8aca7a Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 6 Oct 2026 12:11:58 +0200 Subject: Initial commit: wallhaven-dl wallpaper browser and downloader Single-file PyQt6 app for wallhaven.cc: - search with purity tabs, category checkboxes, per-monitor size filter and a color swatch row; all filters sent explicitly so account preferences never leak into keyed searches - thumbnail grid with a downloaded overlay, preview panel with full image, details and download - API key from pass, sent only to the API in the X-API-Key header - client-side rate limiter (40/min) with a status bar meter, and a debounce on filter changes - library-wide duplicate detection by wallhaven id under ~/Pictures/wallpapers - low-res finder: flags images smaller than their monitor and searches replacements by first tag or by locally extracted dominant color, with a floating reference window and replace-to-trash - desktop entry and Candy-style icon Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 82 +++++++ CLAUDE.md | 6 + LICENSE | 339 ++++++++++++++++++++++++++ README.md | 59 +++++ test_wallhaven_dl.py | 83 +++++++ wallhaven-dl | 668 +++++++++++++++++++++++++++++++++++++++++++++++++++ wallhaven-dl.desktop | 11 + wallhaven-dl.svg | 21 ++ 8 files changed, 1269 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 test_wallhaven_dl.py create mode 100755 wallhaven-dl create mode 100644 wallhaven-dl.desktop create mode 100644 wallhaven-dl.svg diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..41727e0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,82 @@ +# AGENTS.md + +Guidance for AI coding agents working in this repository. + +## What this is + +`wallhaven-dl`: single-file PyQt6 desktop app to search wallhaven.cc, preview +and download wallpapers, and find better-resolution replacements for low-res +images already on disk. The only dependency is PyQt6 (QtWidgets, QtNetwork); +everything else is stdlib. Keep it a single file and do not add dependencies. + +On the maintainer's machine the script, menu entry and icon are symlinks into +the repo, so edits are live on the next launch: + + ~/bin/wallhaven-dl -> wallhaven-dl + ~/.local/share/applications/wallhaven-dl.desktop -> wallhaven-dl.desktop + ~/.local/share/icons/hicolor/scalable/apps/wallhaven-dl.svg -> wallhaven-dl.svg + +## wallhaven API facts (verified, not in the docs) + +- Rate limit is 45 API calls per minute. The app caps itself at 40 + (`API_LIMIT`) with a sliding-window limiter in `get()`, and debounces filter + changes by 400 ms. Thumbnails (`th.wallhaven.cc`) and full images + (`w.wallhaven.cc`) do not count. Keep test runs cheap: stub + `Main.search` when no network is needed, and never loop over the API. +- With an API key, every parameter the request omits falls back to the + account's saved search settings (categories, resolutions, ...). That is why + `categories`, `purity` and `atleast` are always sent explicitly + (`atleast=1x1` for "any size"). +- The key goes in the `X-API-Key` header, only for URLs under `API`, never in + the query string. It is read from `pass` (`WALLHAVEN_PASS_ENTRY`, default + `wallhaven.cc/api-key`) at startup. +- `like:` similarity search is behind a Cloudflare bot challenge (403, for + the API and the site alike). Do not try to get around it. The low-res + feature uses the wallpaper's first tag (`id:`) instead, under the + wallpaper's own purity. +- `colors` accepts only the 29 palette values in `COLORS`. Each wallpaper + stores five colors and a color search matches any of the five. The local + extraction (`palette_histogram`, redmean distance) agrees with the site's + list well: measured on 72 wallpapers, the picked color was in the site's + list 72/72 times. +- There is no reverse image search on wallhaven. Sending local images to + third-party services was rejected for privacy. + +## Library and files + +- `LIBRARY` (`~/Pictures/wallpapers`, recursive, hidden dirs skipped) is what + "downloaded" means. Files are matched by the wallhaven id in their name + (`WALL_ID`, also `wallhaven-_.png` copies), never by path. +- Writes go to a `.part` file renamed into place. Replacing an original saves + the new file first and only then moves the old one to the desktop trash + (`QFile.moveToTrash`). Never delete user images outright. + +## Conventions + +- Non-GUI logic lives in module-level functions (`read_apikey`, `api_delay`, + `target`, `scan_library`, `nearest_color`, `search_pick`, ...) so it can be + tested without a display. +- Async replies check `gen` before touching grid items: `grid.clear()` deletes + them, and a stale callback would hit a dead C++ object. +- Do not name methods after `QWidget` ones (`show` once shadowed + `QWidget.show()` and broke startup). +- Placeholders only in code, docs, tests and commits: no real account names, + file names from the maintainer's library, or `pass` entry names beyond the + generic default. + +## Testing + + python3 test_wallhaven_dl.py + +Covers the non-GUI functions with plain asserts and a fake `pass` on `PATH`. +For GUI checks, run headless and keep settings away from the real config: + + XDG_CONFIG_HOME=/tmp/whdl-cfg QT_QPA_PLATFORM=offscreen python3 -I