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 @@ -234,6 +234,7 @@ Provider and query may be given as positionals after `s` / `-s`, or with `-P` /
| `publicdomainarchive` | [Public Domain Archive](https://publicdomainarchive.com) | 100% free public domain and vintage stock photos | Direct high-resolution photo downloads. |
| `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. |

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

Expand Down Expand Up @@ -296,6 +297,7 @@ src/
publicdomainarchive.zig
magdeleine.zig
splitshire.zig
deviantart.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 @@ -157,3 +157,16 @@ This document summarizes the network endpoints, transport models, and image down
* Author credited from user profile or Daniel Nanescu.
* **Download**: Direct GET on high-resolution CDN assets.
* **License**: Free for Personal and Commercial Use (SplitShire License / CC0).

---

## 14. DeviantArt (`deviantart.com`)

* **Search**: `GET https://www.deviantart.com/tag/{tag}`
* **Format**: SSR HTML with artwork cards, titles, author links, and direct Wixmp CDN URLs.
* **Extraction**:
* Title extracted and HTML unescaped from image `alt` attribute.
* Author parsed from artwork page link (`/{author}/art/...`).
* 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.
11 changes: 6 additions & 5 deletions docs/stock_photo_sites.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ This document tracks all 24 stock photo websites listed in the [GrayGrids 21+ Be
## Summary Status

- **Total Sites Listed**: 24
- **Implemented & Working**: 12 (`unsplash`, `isorepublic`, `picjumbo`, `foodiesfeed`, `picography`, `gratisography`, `startupstockphotos`, `burst`, `jaymantri`, `publicdomainarchive`, `magdeleine`, `splitshire`)
- **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))
- **Unimplemented / Blocked / Pending**: 12
- **Unimplemented / Blocked / Pending**: 11

---

Expand Down Expand Up @@ -115,9 +115,10 @@ This document tracks all 24 stock photo websites listed in the [GrayGrids 21+ Be
- **Status**: Unimplemented.
- **Notes**: Large repository of free stock photography.

- [ ] **DeviantArt** ([deviantart.com](http://www.deviantart.com/))
- **Status**: Unimplemented.
- **Notes**: Community art platform; requires auth/API for asset retrieval.
- [x] **DeviantArt** ([deviantart.com](http://www.deviantart.com/))
- **CLI Provider**: `deviantart`
- **Status**: Implemented & verified.
- **Notes**: Tag search and direct image downloading via Wixmp CDN supported.

- [ ] **Reshot** ([reshot.com](https://www.reshot.com/))
- **Status**: Unimplemented.
Expand Down
9 changes: 9 additions & 0 deletions src/commands/search.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const jaymantri = @import("../providers/jaymantri.zig");
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 stdio = @import("../stdio.zig");
const Allocator = std.mem.Allocator;
const Io = std.Io;
Expand Down Expand Up @@ -76,6 +77,9 @@ fn doSearch(
if (std.ascii.eqlIgnoreCase(provider_id, "splitshire")) {
return splitshire.search(client, allocator, query, limit);
}
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) {
return deviantart.search(client, allocator, query, limit);
}
return error.UnknownProvider;
}

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

Expand All @@ -143,6 +150,7 @@ fn doPrompt(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "publicdomainarchive")) return publicdomainarchive.getPrompt(a);
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);
return a.prompt orelse a.description;
}

Expand All @@ -160,6 +168,7 @@ fn doUrl(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "publicdomainarchive")) return publicdomainarchive.getUrl(a);
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);
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 @@ -16,6 +16,7 @@ const jaymantri = @import("providers/jaymantri.zig");
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 registry = @import("providers/registry.zig");
const stdio = @import("stdio.zig");
const Allocator = std.mem.Allocator;
Expand Down Expand Up @@ -73,6 +74,9 @@ fn searchProvider(
if (std.ascii.eqlIgnoreCase(provider_id, "splitshire")) {
return splitshire.search(client, allocator, query, config.default_limit);
}
if (std.ascii.eqlIgnoreCase(provider_id, "deviantart")) {
return deviantart.search(client, allocator, query, config.default_limit);
}
return error.UnknownProvider;
}

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

Expand All @@ -139,6 +146,7 @@ fn promptOf(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "publicdomainarchive")) return publicdomainarchive.getPrompt(a);
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);
return a.prompt;
}

Expand All @@ -156,6 +164,7 @@ fn urlOf(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "publicdomainarchive")) return publicdomainarchive.getUrl(a);
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);
return a.image_url;
}

Expand Down
Loading
Loading