Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ Provider and query may be given as positionals after `s` / `-s`, or with `-P` /
| `magdeleine` | [Magdeleine](https://magdeleine.co) | Hand-picked free high-resolution stock photography | Direct master resolution photo downloads. |
| `splitshire` | [SplitShire](https://splitshire.com) | Free stock photos and artwork by Daniel Nanescu & community | Direct high-resolution photo downloads. |
| `deviantart` | [DeviantArt](https://deviantart.com) | Creative reference photos, stock imagery, and digital art | Direct image downloads via Wixmp CDN. |
| `negativespace` | [NegativeSpace](https://negativespace.co) | Free high-resolution CC0 stock photography | Direct master full-resolution photo downloads. |

List providers at runtime via `ast help` (printed under **PROVIDERS**).

Expand Down Expand Up @@ -298,6 +299,7 @@ src/
magdeleine.zig
splitshire.zig
deviantart.zig
negativespace.zig
assets/ README banner and static assets
docs/ Provider implementation notes
install.sh curl | bash installer (Linux release binaries)
Expand Down
13 changes: 13 additions & 0 deletions docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,16 @@ This document summarizes the network endpoints, transport models, and image down
* Image path extracted directly from signed Wixmp CDN URLs.
* **Download**: Direct GET on Wixmp CDN URL with browser headers.
* **License**: Free Creative Community Stock / Artwork Reference.

---

## 15. NegativeSpace (`negativespace.co`)

* **Search**: `GET https://negativespace.co/?s={query}`
* **Format**: WordPress photography feed with card images and post links.
* **Extraction**:
* Title extracted and unescaped from image `alt` attribute.
* Slug ID parsed from post permalink.
* Master full-resolution URL extracted by stripping geometry suffixes (`-1062x708`) from `wp-content/uploads/` image URLs.
* **Download**: Direct GET on original full-resolution master photo JPEG/PNG (up to 20+ MB).
* **License**: CC0 / 100% Free High-Resolution Stock Photos.
2 changes: 1 addition & 1 deletion docs/stock_photo_sites.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This document tracks all 24 stock photo websites listed in the [GrayGrids 21+ Be

- **Total Sites Listed**: 24
- **Implemented & Working**: 13 (`unsplash`, `isorepublic`, `picjumbo`, `foodiesfeed`, `picography`, `gratisography`, `startupstockphotos`, `burst`, `jaymantri`, `publicdomainarchive`, `magdeleine`, `splitshire`, `deviantart`)
- *(Additional Provider Implemented in `ast`)*: `aura` ([Aura.build](https://www.aura.build))
- *(Additional Providers Implemented in `ast`)*: `aura` ([Aura.build](https://www.aura.build)), `negativespace` ([NegativeSpace.co](https://negativespace.co))
- **Unimplemented / Blocked / Pending**: 11

---
Expand Down
9 changes: 9 additions & 0 deletions src/commands/search.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const publicdomainarchive = @import("../providers/publicdomainarchive.zig");
const magdeleine = @import("../providers/magdeleine.zig");
const splitshire = @import("../providers/splitshire.zig");
const deviantart = @import("../providers/deviantart.zig");
const negativespace = @import("../providers/negativespace.zig");
const stdio = @import("../stdio.zig");
const Allocator = std.mem.Allocator;
const Io = std.Io;
Expand Down Expand Up @@ -80,6 +81,9 @@ fn doSearch(
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) {
return deviantart.search(client, allocator, query, limit);
}
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) {
return negativespace.search(client, allocator, query, limit);
}
return error.UnknownProvider;
}

Expand Down Expand Up @@ -133,6 +137,9 @@ fn doDownload(
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) {
return deviantart.download(client, allocator, io, a, output_dir);
}
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) {
return negativespace.download(client, allocator, io, a, output_dir);
}
return error.UnknownProvider;
}

Expand All @@ -151,6 +158,7 @@ fn doPrompt(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "magdeleine")) return magdeleine.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "splitshire")) return splitshire.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) return deviantart.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) return negativespace.getPrompt(a);
return a.prompt orelse a.description;
}

Expand All @@ -169,6 +177,7 @@ fn doUrl(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "magdeleine")) return magdeleine.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "splitshire")) return splitshire.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) return deviantart.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) return negativespace.getUrl(a);
return a.image_url;
}

Expand Down
9 changes: 9 additions & 0 deletions src/interactive.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const publicdomainarchive = @import("providers/publicdomainarchive.zig");
const magdeleine = @import("providers/magdeleine.zig");
const splitshire = @import("providers/splitshire.zig");
const deviantart = @import("providers/deviantart.zig");
const negativespace = @import("providers/negativespace.zig");
const registry = @import("providers/registry.zig");
const stdio = @import("stdio.zig");
const Allocator = std.mem.Allocator;
Expand Down Expand Up @@ -77,6 +78,9 @@ fn searchProvider(
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) {
return deviantart.search(client, allocator, query, config.default_limit);
}
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) {
return negativespace.search(client, allocator, query, config.default_limit);
}
return error.UnknownProvider;
}

Expand Down Expand Up @@ -129,6 +133,9 @@ fn downloadAsset(
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) {
return deviantart.download(client, allocator, io, a, config.default_output_dir);
}
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) {
return negativespace.download(client, allocator, io, a, config.default_output_dir);
}
return error.UnknownProvider;
}

Expand All @@ -147,6 +154,7 @@ fn promptOf(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "magdeleine")) return magdeleine.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "splitshire")) return splitshire.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) return deviantart.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) return negativespace.getPrompt(a);
return a.prompt;
}

Expand All @@ -165,6 +173,7 @@ fn urlOf(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "magdeleine")) return magdeleine.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "splitshire")) return splitshire.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) return deviantart.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "negativespace")) return negativespace.getUrl(a);
return a.image_url;
}

Expand Down
Loading
Loading