A Tampermonkey (or scriptcat) userscript that adds keyboard controls and an original-image download button to the lightbox (presentation mode) on assesphoto.com.
<gallery title> - <filename>.jpg, so images from different galleries never collideKeys are only intercepted while the lightbox is open, and never while focus is in an input field (e.g. the site's search box) — normal page browsing is unaffected.
+ icon).assesphoto-keyboard-nav.user.js and save (Ctrl+S).https://www.assesphoto.com/granny-exposing-her-asshole.shtml, click a photo to enter presentation mode, and use the keyboard.| Key | Action |
|---|---|
← |
Previous image |
→ |
Next image |
Esc |
Close lightbox |
D |
Download current image (plain D only — not Ctrl+D/Cmd+D/Alt+D) |
The site's lightbox is driven by plain global functions (prevImage(), nextImage(), closeModal()) attached to onclick handlers. The script:
keydown on document in the capture phase, so it runs before any page-level handlers.#myModal) is open before acting.GM.xmlHttpRequest + content validation?The images are served from a separate host (img.assesphoto.com) behind Cloudflare that sends no CORS headers. Both fetch() and the <a download> attribute are therefore useless from the page context — the browser either blocks the request or ignores the download attribute for cross-origin URLs.
The script fetches the image as a Blob via Tampermonkey's GM.xmlHttpRequest (with the corresponding @connect img.assesphoto.com declaration), which bypasses page CORS. Before saving, it validates the blob's magic bytes (JPEG/PNG/GIF/WebP) — if Cloudflare occasionally serves an HTML challenge page instead of the image, the download is aborted and the image opens in a new tab as a fallback, so you never end up with a corrupt .jpg file on disk.
Two further guards against corrupt downloads:
Ctrl+D / Cmd+D / Alt+D belong to the browser (bookmark dialog) and no longer trigger a download at the same time.D or pressing it twice quickly can no longer start two concurrent downloads of the same file, which would race Chrome's .crdownload handling and leave a broken file. A downloading flag gates concurrent requests, and the button shows ⏳ / ✓ / ⚠ states.Two server-side behaviors shape the download pipeline:
404 Not Found (13 bytes of plain text) to any request without a Referer: https://www.assesphoto.com/ header. GM.xmlHttpRequest calls fired from the extension background don't carry the page's Referer automatically, so the script sets it explicitly. This matters because that tiny 404 text body was the source of the "downloaded file is invalid" bug in earlier versions (see below).Sec-Fetch-Dest: image) always pass, while XHR-fingerprinted requests can be challenged. Before fetching, the script pre-loads the URL through a plain <img> element in the page, which both verifies the URL decodes as a real image and writes the response into the browser's HTTP cache and Cloudflare's edge cache. The subsequent GM.xmlHttpRequest for the exact same URL hits the warm cache and receives the just-validated bytes. If the image is already displayed and fully loaded in the lightbox (the common case), even the pre-warm step is skipped. Measured on the live site: first load ≈ 1.2 s, immediately-repeated load of the same URL = 0 ms (pure cache hit).The full download pipeline, in order:
<img> pre-warm (skipped if the displayed image is already complete) — fails only if the URL itself is deadGM.xmlHttpRequest blob fetch (with Referer header) + magic-byte validation, up to 3 attempts (re-warming between attempts, escalating backoff)GM_download is deliberately not used anywhere: it goes through chrome.downloads, which sends no Referer — on this host that guarantees the 404 text body gets saved as a .jpg. Every byte written to disk passes magic-byte validation first.
The gallery's images array lists every image twice (and the thumbnail strip renders matching duplicate pairs), so the array has 20 entries for 10 actual photos. This means a single keypress sometimes lands on the "same" photo twice before advancing to a new one — this mirrors the on-screen arrows exactly, since the script reuses the site's own navigation logic.
"The download fires, but the saved file is invalid." (Fixed in v1.5.0)
Two stacked causes: the image host enforces hotlink protection and returns a 13-byte 404 Not Found text body to Referer-less requests (which GM.xmlHttpRequest was sending after the header was removed in v1.2.0), and the v1.4.0 GM_download fallback saved whatever arrived without validation — including that 404 text — as a .jpg. The Referer header is now sent explicitly, and the unvalidatable GM_download fallback was removed entirely; nothing reaches disk without passing the magic-byte check.
"Sometimes clicking download just opens a new tab instead of downloading." (Fixed in v1.4.0)
The new tab was the fallback path: the XHR-fingerprinted fetch was being consistently challenged by Cloudflare for certain requests, so even retries failed. The script now pre-warms the URL through a plain <img> load (which Cloudflare never challenges) before fetching, so the download request hits a validated cache entry instead of the challengeable cold path. See Referer requirement & cache pre-warming.
"Sometimes the downloaded file is not a valid image." (Fixed in v1.2.0)
This was traced to three separate causes, all now handled:
Ctrl+D used to trigger a download and open the browser's bookmark dialog at the same time; the focus steal could interrupt the download mid-write. Downloads now only fire on a plain D press.D) or double-pressing started parallel downloads of the same file, racing Chrome's .crdownload handling and leaving a broken file. A downloading flag now serializes downloads..jpg.If a download still fails, check the browser console for [kb-nav] warnings, which log each failed attempt with the response status and content type.
Tested with Tampermonkey on Chrome. The script uses GM.xmlHttpRequest, which is supported by Violentmonkey and Greasemonkey 4 (GM.xmlHttpRequest) as well.
Referer header on the blob fetch (the host's hotlink protection 404s Referer-less requests with a text body), and removal of the GM_download fallback, which saved that unvalidated 404 text as a .jpg. Every byte written to disk now passes magic-byte validation.<img> load before the blob fetch, so Cloudflare's challengeable cold path is bypassed entirely; GM_download added as an intermediate fallback before the new-tab escape hatch.Accept header, 15 s request timeout. Fixes intermittent "download degrades to new-tab fallback".GM.xmlHttpRequest blob fetch replaces GM_download, modifier-key (Ctrl/Cmd/Alt+D) and key-repeat guards, concurrent-download lock, button progress states, filename trailing-dot/space sanitization. Fixes intermittent corrupt downloads.D key and floating ⬇ button, GM_download-based, <gallery title> - <filename> naming.← / → lightbox navigation, Esc to close.MIT