A minimal, async-ready client for getting post data from a remote blogging API that requires either a bearer token or a refresh token + device ID combo for authentication. This crate is designed with resiliency in mind: it transparently handles token expiration and retries requests when needed.
- Disclaimer
- Project Status
- Features
- Installation
- Example: Fetching a Single Post
- Example: Fetching Multiple Posts
- Extracting Content from a Post
- Authentication
- Crate Structure
- Error Handling
- API Documentation
- Contributing
- License
This crate is intended for research and personal use only. By using it, you agree to:
- Access only your own content from the Boosty platform.
- Refrain from scraping, redistributing, or otherwise misusing content that you do not own.
- Comply with Boosty's Terms of Service and any applicable copyright laws.
The author is not responsible for any misuse of this software.
π§ This library is under active development.
Breaking changes, refactoring, and architectural updates may occur frequently.
Use with caution in production environments and pin specific versions if needed.
- Static bearer token or refresh-token + device ID (OAuth2-like).
- Automatic token refresh and retry on expiration.
- Clean separation of
AuthProviderlogic.
The client automatically retries HTTP requests that fail due to transient network errors or expired access tokens.
- Retry logic is centralized in the
get_request()method. - On token expiration, the client performs a refresh (if refresh-token and device ID are set) and retries the request.
- Other error types (like 4xx or business-logic errors) are not retried.
- Get single post:
get_post(blog, id). - Get multiple posts:
get_posts(blog, limit, page_size, start_offset). - Strongly typed
Poststruct withserdesupport. - Handles
"not available"status gracefully.
- Get single comments response:
get_comments_response(blog_name, post_id, limit, reply_limit, order, offset). - Get multiple comments:
get_all_comments(blog_name, post_id, limit, reply_limit, order). - Strongly typed
CommentandCommentResponsestructs withserdesupport. - Handles
"not available"status gracefully.
- Get targets via
get_blog_targets(blog_name). - Create target via
create_blog_target(blog_name, description, target_sum, target_type). - Update target via
update_blog_target(target_id, description, target_sum). - Delete target via
delete_blog_target(target_id).
- Get subscription levels via
get_subscription_levels(blog_name, show_free_level). - Get current user subscriptions via
get_user_subscriptions(limit, with_follow), returning a paginatedSubscriptionsResponse.
- Get showcase data via
get_showcase(blog_name, limit, only_visible, offset). - Change showcase status via
change_showcase_status(blog_name, status).
- Async-ready
ApiClientusingreqwest. - Custom headers with real-world
User-Agent,DNT,Cache-Control, etc. - Unified error types:
ApiError,AuthErrorwith detailed variants.
Add this to your Cargo.toml:
[dependencies]
boosty_api = "0.24.0"or
cargo add boosty_apiuse boosty_api::api_client::ApiClient;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let base_url = "https://api.example.com";
let api_client = ApiClient::new(client, base_url);
// Use static bearer token (optional)
api_client.set_bearer_token("your-access-token").await?;
// Or use refresh token + device ID
// api.set_refresh_token_and_device_id("your-refresh-token", "your-device-id").await?;
let post = api_client.get_post("some-blog-name", "post-id").await?;
println!("{:#?}", post);
Ok(())
}use boosty_api::api_client::ApiClient;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let base_url = "https://api.example.com";
let api_client = ApiClient::new(client, base_url);
// Use static bearer token (optional)
api_client.set_bearer_token("your-access-token").await?;
// Or use refresh token + device ID
// api.set_refresh_token_and_device_id("your-refresh-token", "your-device-id").await?;
let limit = 50;
let page_size = 10;
let posts = api_client.get_posts("blog_name", limit, page_size, None).await?;
println!("{:#?}", posts);
Ok(())
}Offset can be used to skip already downloaded posts or to start from a specific post. It consists of fields Post: "sortOrder": 1762949608 + "int_id": 9555337 or PostsResponse: extra: {"offset": "1762949608:9555337"}.
use boosty_api::{model::Post, media_content::ContentItem, traits::HasContent};
fn print_content(post: &Post) {
let content_items = post.extract_content();
for item in content_items {
match item {
ContentItem::Image { url, id } => {
println!("Image URL: {url}, ID: {id}");
}
ContentItem::Video { url } => {
println!("Video URL: {url}");
}
ContentItem::OkVideo { url, title, vid } => {
println!("OK Video URL: {url}, Title: {title}, ID: {vid}");
}
ContentItem::Audio {
url,
title,
file_type,
size,
} => {
println!(
"Audio URL: {url}, Title: {title}, Type: {}, Size: {size}",
file_type.as_deref().unwrap_or("unknown")
);
}
ContentItem::Text {
modificator,
content,
} => {
println!("Text: {content}, Modificator: {modificator}");
}
ContentItem::Smile {
small_url,
medium_url,
large_url,
name,
is_animated,
} => {
println!(
"Smile: {name}, Small URL: {small_url}, Medium URL: {medium_url}, Large URL: {large_url}, Animated: {is_animated}"
);
}
ContentItem::Link {
explicit,
content,
url,
} => {
println!("Link: {url}, Content: {content}, Explicit: {explicit}");
}
ContentItem::File { url, title, size } => {
println!("File: {title}, URL: {url}, Size: {size}");
}
ContentItem::List { style, items } => {
println!("List style: {style}");
for (i, group) in items.iter().enumerate() {
println!(" Group {i}:");
for (j, item) in group.iter().enumerate() {
println!(" Item {j}: {item:?}");
}
}
}
ContentItem::Unknown => {
println!("Unknown content type");
}
}
}
}To get access token or refresh token and device_id, you need to log in to the service, then press F12 in the browser and go to the application tab, where you can select local storage. The required keys are _clentId and auth.
There are two options:
api_client.set_bearer_token("access-token").await?;api_client.set_refresh_token_and_device_id("refresh-token", "device-id").await?;If a post is unavailable and refresh credentials are present, the client will automatically attempt a refresh.
api_clientβ Main entry point. Handles API requests (e.g. fetching posts), manages HTTP headers, and authentication flow.auth_providerβ Internal module responsible for refresh-token and access-token lifecycle management.modelβ Typed deserialization models for all Boosty API entities (e.g. posts, comments, users, media).errorβ Unified error types covering API, network, and authorization layers.media_contentβ DefinesContentItemand provides utilities for extracting structured media content from API responses.traitsβ Common traits (HasContent,HasTitle,IsAvailable) shared across multiple Boosty entities.
All API and auth operations return Result<T, ApiError> or Result<T, AuthError>, depending on context. Errors are
strongly typed and cover:
- HTTP request failures
- JSON (de)serialization issues
- Invalid or expired credentials
- Unsuccessful API status codes
For detailed documentation, please refer to docs.rs.
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
This project is licensed under the MIT License.