Spaces:
Sleeping
Goal
For every image included in the downloaded ZIP, also add:
<same-basename>.txtβ UTF-8 tags file (space-separated, one line, matches the image filename exactly except for extension).<post-id>.mdβ human-readable markdown containing all extracted post metadata (all tags, source link β blank if missing, file size, sample link, original file link, rating, score, dimensions, uploader, created date, md5, etc.).
Approach
The server currently only receives {id, fileUrl, ext} per post. To write rich metadata into the ZIP without a second network round-trip per post, the frontend will forward the full normalized post (plus a raw metadata bag) to /api/download-zip, and the server will emit the extra files alongside each image.
To capture "any data related to that post", searchPosts will additionally return the raw JSON entry for each post (as raw: unknown). The Markdown generator will pretty-print known fields first, then append the full raw JSON as a fenced code block so nothing is lost.
New optional fields (sampleUrl, fileSize, width, height, md5, uploader, createdAt) get added to SiteConfig.fields as optional dotted paths, defaulted for the built-in e621 site. Existing custom sites keep working because every new field is optional.
Files to change
src/lib/grabber/sites.ts
- Extend
FieldMapwith optional:sampleUrl?,fileSize?,width?,height?,md5?,uploader?,createdAt?. - Extend
NormalizedPostwith the same optional fields plusraw: unknown. - Update
normalizePost()to resolve each new path when present and attachraw(the original entry). - Update
BUILTIN_SITESe621 entryfieldswith:sampleUrl: "sample.url",fileSize: "file.size",width: "file.width",height: "file.height",md5: "file.md5",uploader: "uploader_id",createdAt: "created_at".
src/lib/grabber/search.functions.ts
- Extend the
SiteSchema.fieldszod object with the new optional string paths. - No handler logic changes needed β normalization already carries
rawand new fields through.
src/components/grabber/AddSiteDialog.tsx
- Add the new optional field keys to the
(["id",...] as const)list on line 73 so users can map them for custom sites (sampleUrl,fileSize,width,height,md5,uploader,createdAt). - Extend
DEFAULT(lines 22β31) with sensible Danbooru mappings (sampleUrl: "large_file_url",fileSize: "file_size",width: "image_width",height: "image_height",md5: "md5",uploader: "uploader_name",createdAt: "created_at").
src/routes/api/download-zip.ts (major rewrite)
- Expand
BodySchema.postsitem to accept the full normalized post shape (all new optional fields +tags: string[]+raw: unknown). - For each post:
- Fetch the image bytes as today. Build image filename
base = "<siteName>_<id>"and<base>.<ext>. - Write
<base>.txtβstrToU8(tags.join(" "))(UTF-8). Uses the exact same basename as the image so tag files pair 1:1. - Write
<id>.mdβ pretty markdown with:# Post <id>heading- Table/list of: Source (blank line if missing), Rating, Score, Dimensions (
width x height), File size (human-readable KB/MB), MD5, Uploader, Created at, Original file URL, Sample URL, Preview URL, Ext. ## Tagssection with the tags space-separated.## Raw metadatafenced ```json block containingJSON.stringify(raw, null, 2).
- Fetch the image bytes as today. Build image filename
- Keep the existing
_errors.txtbehavior and 200-post cap. - Sanitizer stays the same; md/txt names use
sanitize(id)so filenames match the image exactly.
src/routes/index.tsx
- In
downloadZip()(thefetch("/api/download-zip", β¦)call), send the full post objects instead of a stripped{id,fileUrl,ext}triple. Change:
to:posts: chosen.map((p) => ({ id: p.id, fileUrl: p.fileUrl, ext: p.ext })),posts: chosen, // full NormalizedPost incl. tags + raw + optional metadata - No other UI changes.
ZIP layout example
grabber-2026-07-17T...zip
βββ e621_1234567.jpg
βββ e621_1234567.txt β "tag1 tag2 tag3 ..."
βββ 1234567.md β human-readable metadata + raw JSON
βββ e621_1234568.png
βββ e621_1234568.txt
βββ 1234568.md
βββ _errors.txt (only if any fetch failed)
Notes / non-goals
- No new dependencies (still
fflate+zod). - No DB, no auth, no schema changes β this is presentation + payload shape only.
- Custom sites without the new mappings still work; missing fields render as blank lines in the
.mdand the raw JSON section still contains everything from the API.