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.
- 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).
- Explicit
refresh_tokens()method β no automatic refresh during requests. - Returns
TokenPairafter refresh so the caller can persist new credentials. - Clean separation of
AuthProviderlogic.
- 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). - Create comment:
create_comment(blog_name, post_id, blocks, reply_id). - 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_blog_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).
- Get bundles via
get_bundles(blog_name). - Get bundle via
get_bundle(blog_name, bundle_id, query).
- 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.30.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, then explicitly refresh:
// api_client.set_refresh_token_and_device_id("your-refresh-token", "your-device-id").await?;
// let tokens = api_client.refresh_tokens().await?;
let post = api_client.get_post("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, then explicitly refresh:
// api_client.set_refresh_token_and_device_id("your-refresh-token", "your-device-id").await?;
// let tokens = api_client.refresh_tokens().await?;
let limit = 50;
let page_size = 10;
let posts = api_client
.get_posts("blog_name", limit, Some(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 _clientId 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?;
let tokens = api_client.refresh_tokens().await?;
// tokens.access_token, tokens.refresh_token, tokens.expires_in
// Persist tokens.refresh_token for next session.Important: set_refresh_token_and_device_id(...) only stores refresh credentials.
The client adds Authorization header from refresh flow only after an explicit
refresh_tokens() call. No automatic token refresh is performed during requests.
From this version, token refresh is explicit. If you previously relied on implicit
refresh behavior, call refresh_tokens() before API requests (and when you decide
the token should be renewed).
api_clientβ Main entry point. Handles API requests (e.g. fetching posts), manages HTTP headers, and authentication flow.auth_providerβ Internal module responsible for token storage and explicit refresh.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.