Skip to content

ajiiisai/booru-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

88 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ci-badge crates.io version docs.rs

booru-rs

An async Rust client for various booru image board APIs.

Features

  • Type-safe API — Compile-time checks ensure you use the correct rating types for each booru
  • Async/await — Built on tokio and reqwest for efficient async I/O
  • Connection pooling — Shared HTTP client with automatic connection reuse
  • Proper error handling — No panics, all errors are returned as Result types
  • Common Post trait — Write generic code that works with any booru site
  • Async streams — Paginate through results with async iterators
  • Image downloads — Download images with progress tracking and concurrent downloads
  • Automatic retries — Transient failures are retried with exponential backoff
  • Rate limiting — Protect against API throttling
  • Response caching — Reduce redundant API calls
  • Tag validation — Catch common mistakes before making requests
  • Tag autocomplete — Get tag suggestions as users type

Supported Sites

Site Client Tag Limit Auth Required
Danbooru DanbooruClient 2 No
Gelbooru GelbooruClient Unlimited Yes
Safebooru SafebooruClient Unlimited No
Rule34 Rule34Client Unlimited Yes

Note: Gelbooru and Rule34 require API credentials. See Authentication below.

Planned:

  • Konachan
  • 3DBooru

Installation

Add to your Cargo.toml:

[dependencies]
booru-rs = "0.3"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Selective Features

By default, all booru clients are included. You can enable only the ones you need:

[dependencies]
# Only Danbooru support
booru-rs = { version = "0.3", default-features = false, features = ["danbooru"] }

# Danbooru + Safebooru
booru-rs = { version = "0.3", default-features = false, features = ["danbooru", "safebooru"] }

Available features: danbooru, gelbooru, safebooru, rule34

Quick Start

Use the prelude for convenient imports:

use booru_rs::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let posts = GelbooruClient::builder()
        .tag("kafuu_chino")?
        .tag("2girls")?
        .rating(GelbooruRating::General)
        .sort(Sort::Random)  // Or use .random() shorthand
        .limit(5)
        .blacklist_tag(GelbooruRating::Explicit)
        .build()
        .get()
        .await?;

    for post in &posts {
        println!("Post #{}: {}", post.id, post.file_url);
    }

    Ok(())
}

API Examples

Basic Usage

use booru_rs::prelude::*;

// Danbooru (limited to 2 tags)
let posts = DanbooruClient::builder()
    .tag("cat_ears")?
    .rating(DanbooruRating::General)
    .limit(10)
    .build()
    .get()
    .await?;

// Get a specific post by ID
let post = DanbooruClient::builder()
    .build()
    .get_by_id(12345)
    .await?;

Multiple Tags at Once

use booru_rs::prelude::*;

// Gelbooru has no tag limit
let posts = GelbooruClient::builder()
    .tags(["cat_ears", "blue_eyes", "1girl"])?
    .blacklist_tags(["ugly", "low_quality"])
    .sort(Sort::Score)
    .build()
    .get()
    .await?;

Generic Code with the Post Trait

use booru_rs::prelude::*;
use booru_rs::Post;

fn print_post_info(post: &impl Post) {
    println!("#{}: {}x{}", post.id(), post.width(), post.height());
    if let Some(url) = post.file_url() {
        println!("  URL: {}", url);
    }
}

Pagination

use booru_rs::prelude::*;

// Get page 5 of results
let posts = SafebooruClient::builder()
    .tag("landscape")?
    .page(5)
    .limit(100)
    .build()
    .get()
    .await?;

Async Pagination Stream

use booru_rs::prelude::*;

// Stream through all results automatically
let mut stream = SafebooruClient::builder()
    .tag("landscape")?
    .limit(100)
    .into_post_stream()
    .max_posts(500);  // Stop after 500 posts

while let Some(post) = stream.next().await {
    println!("Post #{}", post?.id);
}

Rate Limiting

use booru_rs::prelude::*;
use std::time::Duration;

// 2 requests per second
let limiter = RateLimiter::new(2, Duration::from_secs(1));

for tag in ["cat", "dog", "bird"] {
    limiter.acquire().await;  // Waits if needed
    let posts = SafebooruClient::builder()
        .tag(tag)?
        .build()
        .get()
        .await?;
}

Response Caching

use booru_rs::prelude::*;

let cache = Cache::new();

// First call hits the API
let posts = SafebooruClient::builder()
    .tag("nature")?
    .build()
    .get()
    .await?;
cache.insert("nature_search".to_string(), &posts).await;

// Later, check cache first
if let Some(cached) = cache.get::<Vec<SafebooruPost>>(&"nature_search".to_string()).await {
    println!("Cache hit! {} posts", cached.len());
}

Tag Validation

use booru_rs::validation::{validate_tag, validate_tag_strict};

// Get warnings about potential issues
let result = validate_tag("cat ears");  // Space instead of underscore
if result.has_warnings() {
    println!("Suggested: {}", result.tag());  // "cat_ears"
}

// Or get normalized tag directly
let tag = validate_tag_strict("  cat ears  ")?;  // Returns "cat_ears"

Tag Autocomplete

Get tag suggestions as the user types:

use booru_rs::prelude::*;

// Get tag suggestions for "cat_"
let suggestions = DanbooruClient::autocomplete("cat_", 10).await?;

for tag in suggestions {
    println!("{}: {} posts", tag.name, tag.post_count.unwrap_or(0));
    if let Some(category) = tag.category_name() {
        println!("  Category: {}", category);
    }
}

// Works with all booru clients
let safebooru_tags = SafebooruClient::autocomplete("land", 5).await?;
let gelbooru_tags = GelbooruClient::autocomplete("blue", 5).await?;

Custom HTTP Client

use booru_rs::prelude::*;
use booru_rs::client::ClientBuilder;

let custom_client = reqwest::Client::builder()
    .timeout(std::time::Duration::from_secs(60))
    .build()?;

// Use with_client to create a builder with custom HTTP client
let posts = ClientBuilder::<SafebooruClient>::with_client(custom_client)
    .tag("nature")?
    .build()
    .get()
    .await?;

Note: Most users won't need a custom HTTP client. The default shared client provides connection pooling and sensible timeouts.

Downloading Images

use booru_rs::prelude::*;
use std::path::Path;

let posts = SafebooruClient::builder()
    .tag("landscape")?
    .limit(10)
    .build()
    .get()
    .await?;

// Download posts directly
let downloader = Downloader::new();
for post in &posts {
    let result = downloader.download_post(post, Path::new("./downloads")).await?;
    println!("Downloaded: {}", result.path.display());
}

// Download with progress tracking
for post in &posts {
    let result = downloader
        .download_post_with_progress(post, Path::new("./downloads"), |progress| {
            println!("{}/{} bytes (post #{})", 
                progress.downloaded, 
                progress.total.unwrap_or(0),
                progress.post_id);
        })
        .await?;
}

// Concurrent downloads (4 at a time)
let results = downloader.download_posts(&posts, Path::new("./downloads"), 4).await;

// Custom options
let downloader = Downloader::new()
    .options(DownloadOptions::default().overwrite().filename("{id}_{md5}.{ext}"));

Gelbooru Authentication

Gelbooru requires API credentials for all API requests. To get your credentials:

  1. Create an account at gelbooru.com
  2. Go to My AccountOptionsAPI Access Credentials
  3. Copy your API Key and User ID
use booru_rs::prelude::*;

let posts = GelbooruClient::builder()
    .set_credentials("your_api_key", "your_user_id")
    .tag("landscape")?
    .build()
    .get()
    .await?;

You can also load credentials from environment variables:

use booru_rs::prelude::*;

let api_key = std::env::var("GELBOORU_API_KEY").expect("GELBOORU_API_KEY not set");
let user_id = std::env::var("GELBOORU_USER_ID").expect("GELBOORU_USER_ID not set");

let posts = GelbooruClient::builder()
    .set_credentials(api_key, user_id)
    .tag("cat_ears")?
    .build()
    .get()
    .await?;

Rule34 Authentication

Rule34 also requires API credentials:

  1. Create an account at rule34.xxx
  2. Go to My AccountOptionsAPI Access Credentials
  3. Copy your API Key and User ID
use booru_rs::prelude::*;

let posts = Rule34Client::builder()
    .set_credentials("your_api_key", "your_user_id")
    .tag("landscape")?
    .build()
    .get()
    .await?;

Error Handling

All fallible operations return Result<T, BooruError>:

use booru_rs::prelude::*;

match DanbooruClient::builder().tag("a")?.tag("b")?.tag("c") {
    Ok(_) => unreachable!(),
    Err(BooruError::TagLimitExceeded { client, max, actual }) => {
        println!("{} only allows {} tags, tried to add {}", client, max, actual);
    }
    Err(e) => println!("Other error: {}", e),
}

Minimum Supported Rust Version

This crate requires Rust 1.92 or later (2024 edition).

License

Licensed under the MIT License.

About

An async Booru client for Rust

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages