| Crates.io | anilist_moe |
| lib.rs | anilist_moe |
| version | 0.3.2 |
| created_at | 2025-08-26 07:03:29.255243+00 |
| updated_at | 2026-01-18 11:13:59.034564+00 |
| description | Anilist_Moe is a Rust Wrapper for the Anilist API. This library allows you to seamlessly interact with Anilist's Public API with and without authentication. This currently supports Anime, Manga, Users, Staff, Forum, Threads, Recommendations, Reviews and User Activities |
| homepage | |
| repository | https://github.com/Thunder-Blaze/anilist_moe/ |
| max_upload_size | |
| id | 1810626 |
| size | 560,581 |
A comprehensive, type-safe Rust wrapper for the AniList GraphQL API.
AniListClient::fetch helperinclude_* flags let you opt into heavy nested data only when you need itAdd this to your Cargo.toml:
[dependencies]
anilist_moe = "0.3.1"
tokio = { version = "1.0", features = ["full"] }
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
// Create a client (no authentication needed for public data)
let client = AniListClient::new();
// Get trending anime with proper type safety
let response = client.anime().get_trending(Some(1), Some(10)).await?;
// Access the data with clean, simple structure (v0.2.2+)
for anime in &response.data {
println!(
"Title: {} - Score: {}/100",
anime.title.as_ref().and_then(|t| t.romaji.as_ref()).unwrap_or(&"Unknown".to_string()),
anime.average_score.unwrap_or(0)
);
}
Ok(())
}
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
// Search for an anime
let search_results = client.anime().search_anime("Attack on Titan", Some(1), Some(5)).await?;
// Access the data - search_results is Page<Vec<Media>>
if let Some(first_result) = search_results.data.first() {
let anime_id = first_result.id.unwrap_or(0);
// Get detailed information by ID - returns just Media
let anime = client.anime().get_by_id(anime_id).await?;
println!("Title: {}", anime.title.as_ref()
.and_then(|t| t.romaji.as_ref())
.unwrap_or(&"Unknown".to_string()));
println!("Synopsis: {}", anime.description.as_ref()
.unwrap_or(&"No description".to_string()));
println!("Score: {}/100", anime.average_score.unwrap_or(0));
println!("Episodes: {}", anime.episodes.unwrap_or(0));
if let Some(genres) = &anime.genres {
println!("Genres: {}", genres.join(", "));
}
}
Ok(())
}
use anilist_moe::{
AniListClient,
AniListError,
objects::{media::Media, responses::Page},
};
use serde_json::json;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
let mut variables = HashMap::new();
variables.insert("page".to_string(), json!(1));
variables.insert("perPage".to_string(), json!(5));
let query = r#"
query ($page: Int, $perPage: Int) {
Page(page: $page, perPage: $perPage) {
media {
id
title {
romaji
english
native
}
}
}
}
"#;
let page: Page<Vec<Media>> = client.fetch(query, Some(&variables)).await?;
for media in page.data {
if let (Some(id), Some(title)) = (media.id, media.title.as_ref()) {
println!("{} - {:?}", id, title.romaji);
}
}
Ok(())
}
use anilist_moe::{AniListClient, AniListError};
use anilist_moe::endpoints::media::FetchMediaOptions;
use anilist_moe::enums::media::{MediaSort, MediaType};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
let options = FetchMediaOptions {
media_type: Some(MediaType::Anime),
sort: Some(vec![MediaSort::PopularityDesc]),
page: Some(1),
per_page: Some(3),
include_characters: Some(true),
include_staff: Some(true),
include_reviews: Some(true),
include_recommendations: Some(true),
..Default::default()
};
let response = client.anime().fetch(&options).await?;
for media in response.data {
println!(
"{} has {} character edges",
media.title
.as_ref()
.and_then(|t| t.romaji.as_ref())
.unwrap_or(&"Unknown".to_string()),
media
.characters
.as_ref()
.and_then(|c| c.edges.as_ref())
.map(|edges| edges.len())
.unwrap_or(0)
);
}
Ok(())
}
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
// Get popular anime - returns Page<Vec<Media>>
let popular = client.anime().get_popular_anime(Some(1), Some(5)).await?;
for anime in &popular.data {
println!("{}", anime.title.as_ref().unwrap().romaji.as_ref().unwrap());
}
// Get trending anime
let trending = client.anime().get_trending_anime(Some(1), Some(5)).await?;
// Get anime by ID - returns Media directly
let anime = client.anime().get_by_id(16498).await?; // Attack on Titan
// Search anime
let search_results = client.anime().search_anime("Naruto", Some(1), Some(10)).await?;
// Get anime by season
use anilist_moe::enums::media::MediaSeason;
let fall_2023 = client.anime().get_by_season(MediaSeason::Fall, 2023, Some(1), Some(10)).await?;
// Get top rated anime
let top_rated = client.anime().get_top_rated_anime(Some(1), Some(10)).await?;
// Get currently airing anime
let airing = client.anime().get_airing_anime(Some(1), Some(10)).await?;
Ok(())
}
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
// Get popular manga
let popular = client.manga().get_popular_manga(Some(1), Some(5)).await?;
// Search manga
let search_results = client.manga().search_manga("One Piece", Some(1), Some(10)).await?;
// Get top rated manga
let top_rated = client.manga().get_top_rated_manga(Some(1), Some(10)).await?;
// Get currently releasing manga
let releasing = client.manga().get_releasing_manga(Some(1), Some(10)).await?;
Ok(())
}
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
// Get popular characters - returns Page<Vec<Character>>
let popular = client.character().get_popular_characters(Some(1), Some(10)).await?;
// Get character by ID - returns Character directly
let character = client.character().get_by_id(40).await?; // Luffy
// Search characters - returns Page<Vec<Character>>
let search_results = client.character().search_character("Luffy", Some(1), Some(10)).await?;
// Get characters with birthday today - returns Page<Vec<Character>>
let birthday_chars = client.character().get_by_birthday(5, 5, Some(1), Some(10)).await?;
// Get most favorited characters - returns Page<Vec<Character>>
let most_favorited = client.character().get_most_favorited_characters(Some(1), Some(10)).await?;
Ok(())
}
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
// Get recent threads - returns Page<Vec<Thread>>
let recent = client.forum().get_recent_threads(Some(1), Some(10)).await?;
for thread in &recent.data {
println!("Thread: {}", thread.title.as_ref().unwrap_or(&"Untitled".to_string()));
}
// Search threads - returns Page<Vec<Thread>>
let search = client.forum().search_thread("recommendation", Some(1), Some(10)).await?;
// Get thread by ID - returns Thread directly
let thread = client.forum().get_thread_by_id(12345).await?;
// Get thread comments - returns Page<Vec<ThreadComment>>
let comments = client.forum().get_thread_comments(12345, Some(1), Some(20)).await?;
Ok(())
}
For endpoints requiring authentication:
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
// Create an authenticated client
let token = std::env::var("ANILIST_TOKEN")?;
let client = AniListClient::with_token(&token);
// Get current user information - returns User directly
let current_user = client.user().get_current_user().await?;
println!("Logged in as: {}", current_user.name);
// Get current user's anime list - returns Page<Vec<MediaList>>
let anime_list = client.medialist()
.get_user_anime_list(¤t_user.name, None, Some(1), Some(50))
.await?;
Ok(())
}
To get an access token for authentication:
AniListClient::with_token()The library provides comprehensive error handling:
use anilist_moe::{AniListClient, errors::AniListError};
#[tokio::main]
async fn main() {
let client = AniListClient::new();
match client.anime().get_by_id(999999).await {
Ok(anime) => println!("Found anime: {:?}", anime),
Err(AniListError::Network(e)) => eprintln!("Network error: {}", e),
Err(AniListError::GraphQL { message, .. }) => eprintln!("GraphQL error: {}", message),
Err(AniListError::Json(e)) => eprintln!("JSON parsing error: {}", e),
Err(AniListError::RateLimit) => eprintln!("Rate limited"),
Err(AniListError::NotFound) => eprintln!("Not found"),
}
}
All responses are fully typed. No more dealing with serde_json::Value:
// Fully typed response - Page<Vec<Media>>
let response = client.anime().get_trending_anime(Some(1), Some(10)).await?;
// Access the Vec<Media> directly through response.data
let anime = &response.data[0];
let title = anime.title.as_ref().unwrap();
let romaji_title = &title.romaji;
let score = anime.average_score.unwrap_or(0);
let genres = anime.genres.as_ref().unwrap();
// Access pagination info
if let Some(page_info) = &response.page_info {
println!("Current page: {:?}", page_info.current_page);
println!("Has next page: {:?}", page_info.has_next_page);
}
The library includes comprehensive data models for all AniList entities:
Run the test suite:
cargo test
Run specific endpoint tests:
cargo test --test endpoint_tests
The library includes comprehensive tests for:
The AniList API has rate limiting (90 requests per minute). The client handles basic error responses, but you should implement your own rate limiting logic for production applications:
use anilist_moe::{AniListClient, errors::AniListError};
use std::time::Duration;
use tokio::time::sleep;
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
for page in 1..=10 {
match client.anime().get_popular_anime(Some(page), Some(50)).await {
Ok(response) => {
// Process response
}
Err(AniListError::RateLimit) => {
// Wait and retry
sleep(Duration::from_secs(60)).await;
continue;
}
Err(e) => return Err(e.into()),
}
// Be nice to the API
sleep(Duration::from_millis(700)).await;
}
Ok(())
}
All list endpoints support pagination:
use anilist_moe::{AniListClient, AniListError};
#[tokio::main]
async fn main() -> Result<(), AniListError> {
let client = AniListClient::new();
let mut page = 1;
let per_page = 50;
loop {
let response = client.anime().get_popular_anime(Some(page), Some(per_page)).await?;
// Process current page - response.data is Vec<Media>
for anime in &response.data {
println!("{:?}", anime.title);
}
// Check pagination info
if let Some(page_info) = &response.page_info {
if !page_info.has_next_page.unwrap_or(false) {
break;
}
} else {
break;
}
page += 1;
}
Ok(())
}
Contributions are welcome! Please feel free to submit a Pull Request. See CONTRIBUTING.md for detailed guidelines.
This project is licensed under the MIT License. See the LICENSE file for details.
See CHANGELOG.md for detailed version history.