aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
blob: 41727e00a382dc3bbcf2a5476743f08dcaa097b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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:<id>` 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:<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-<id>_<WxH>.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 <script>

loading the app with `importlib.machinery.SourceFileLoader` (the file has no
`.py` extension), and `w.grab().save(...)` for screenshots.

## Repo facts

- License: GPLv2 only (`LICENSE`, header in each source file).
- `origin` is the personal git server (cgit section "Generic Projects").