diff --git a/queries/get_user.graphql b/queries/get_user.graphql new file mode 100644 index 0000000..46f009b --- /dev/null +++ b/queries/get_user.graphql @@ -0,0 +1,188 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2022-2025 Andriel Ferreira + +query ($id: Int, $name: String) { + User(id: $id, name: $name) { + id + name + about + avatar { + large + medium + } + bannerImage + favourites { + anime { + nodes { + id + title { + romaji + english + native + } + siteUrl + genres + isAdult + coverImage { + medium + large + extraLarge + } + bannerImage + source + hashtag + synonyms + averageScore + meanScore + } + } + manga { + nodes { + id + title { + romaji + english + native + userPreferred + } + siteUrl + genres + isAdult + coverImage { + medium + large + extraLarge + } + bannerImage + source + hashtag + synonyms + averageScore + meanScore + } + } + characters { + nodes { + id + name { + first + middle + last + full + native + userPreferred + } + image { + large + medium + } + description + gender + dateOfBirth { + year + month + day + } + age + siteUrl + favourites + } + } + staff { + nodes { + id + name { + first + middle + last + full + native + userPreferred + } + languageV2 + image { + large + medium + } + description + primaryOccupations + gender + dateOfBirth { + year + month + day + } + dateOfDeath { + year + month + day + } + age + yearsActive + homeTown + siteUrl + favourites + } + } + studios { + nodes { + id + name + isAnimationStudio + siteUrl + favourites + } + } + } + statistics { + anime { + count + meanScore + minutesWatched + episodesWatched + statuses { + status + count + } + genres(sort: COUNT_DESC) { + genre + count + } + tags(sort: COUNT_DESC) { + tag { + name + } + count + } + } + manga { + count + meanScore + chaptersRead + volumesRead + statuses { + status + count + } + genres(sort: COUNT_DESC) { + genre + count + } + tags(sort: COUNT_DESC) { + tag { + name + } + count + } + } + } + siteUrl + donatorTier + donatorBadge + createdAt + updatedAt + options { + profileColor + } + } +} diff --git a/src/client.rs b/src/client.rs index 9864f13..8257036 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022-2025 Andriel Ferreira +use std::io::Write; use std::time::Duration; -use crate::models::{Anime, Character, Manga, Person}; +use crate::models::{Anime, Character, Manga, Person, User}; use crate::Result; #[derive(Clone)] @@ -177,6 +178,82 @@ impl Client { self.get_character(id).await } + /// Get a user by its ID. + /// + /// # Arguments + /// + /// * `id` - The ID of the user. + /// + /// # Errors + /// + /// Returns an error if the request fails. + /// + /// # Example + /// + /// ``` + /// # async fn f(client: rust_anilist::Client) -> rust_anilist::Result<()> { + /// let user = client.get_user(1).await?; + /// + /// # Ok(()) + /// # } + /// ``` + pub async fn get_user(&self, id: i64) -> Result { + let data = self + .request( + MediaType::User, + Action::Get, + serde_json::json!({ "id": id }), + ) + .await + .unwrap(); + + std::fs::File::create("user.json") + .unwrap() + .write_all(data["data"]["User"].to_string().as_bytes()) + .unwrap(); + match serde_json::from_str::(&data["data"]["User"].to_string()) { + Ok(user) => Ok(user), + Err(e) => Err(crate::Error::ApiError(e.to_string())), + } + } + + /// Get a user by its name. + /// + /// # Arguments + /// + /// * `name` - The name of the user. + /// + /// # Errors + /// + /// Returns an error if the request fails. + /// + /// # Example + /// + /// ``` + /// # async fn f(client: rust_anilist::Client) -> rust_anilist::Result<()> { + /// let user = client.get_user_by_name("andrielfr").await?; + /// + /// # Ok(()) + /// # } + /// ``` + pub async fn get_user_by_name(&self, name: N) -> Result { + let name = name.to_string(); + + let data = self + .request( + MediaType::User, + Action::Get, + serde_json::json!({ "name": name }), + ) + .await + .unwrap(); + + match serde_json::from_str::(&data["data"]["User"].to_string()) { + Ok(user) => Ok(user), + Err(e) => Err(crate::Error::ApiError(e.to_string())), + } + } + /// Get a person by its ID. /// /// # Arguments @@ -286,7 +363,7 @@ impl Client { MediaType::Character => { include_str!("../queries/get_character.graphql").to_string() } - // MediaType::User => include_str!("../queries/get_user.graphql").to_string(), + MediaType::User => include_str!("../queries/get_user.graphql").to_string(), MediaType::Person => include_str!("../queries/get_person.graphql").to_string(), // MediaType::Studio => include_str!("../queries/get_studio.graphql").to_string(), _ => unimplemented!(), diff --git a/src/models/user.rs b/src/models/user.rs index 15b37d5..f6221fd 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -22,12 +22,14 @@ pub struct User { name: String, about: Option, avatar: Option, + #[serde(rename = "bannerImage")] banner: Option, is_following: Option, is_follower: Option, is_blocked: Option, options: Option, - media_list_options: MediaListOptions, + media_list_options: Option, + #[serde(skip)] favourites: Favourites, statistics: UserStatisticTypes, } @@ -35,16 +37,21 @@ pub struct User { #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] struct Options { - title_language: UserTitleLanguage, + title_language: Option, + #[serde(default)] display_adult_content: bool, + #[serde(default)] airing_notifications: bool, profile_color: Color, - notifications_options: Vec, - timezone: String, + notifications_options: Option>, + timezone: Option, + #[serde(default)] activity_merge_time: i32, + #[serde(default)] staff_name_language: UserStaffNameLanguage, + #[serde(default)] restrict_messages_to_following: bool, - disabled_list_activity: Vec, + disabled_list_activity: Option>, } #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] @@ -94,7 +101,6 @@ pub struct MediaListTypeOptions { } #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] -#[serde(rename_all(deserialize = "camelCase"))] pub struct Favourites { anime: Vec, manga: Vec, @@ -114,12 +120,12 @@ pub struct UserStatisticTypes { #[serde(rename_all(deserialize = "camelCase"))] pub struct UserStatistics { count: i32, - standard_deviation: f32, + standard_deviation: Option, minutes_watched: Option, episodes_watched: Option, chapters_read: Option, volumes_read: Option, - formats: Vec, + formats: Option>, statuses: Vec, } @@ -129,6 +135,7 @@ pub struct UserFormatStatistic { count: i32, minutes_watched: Option, chapters_read: Option, + #[serde(default)] media_ids: Vec, format: Format, } @@ -139,6 +146,7 @@ pub struct UserStatusStatistic { count: i32, minutes_watched: Option, chapters_read: Option, + #[serde(default)] media_ids: Vec, status: Status, } diff --git a/tests/user.rs b/tests/user.rs new file mode 100644 index 0000000..44f2500 --- /dev/null +++ b/tests/user.rs @@ -0,0 +1,14 @@ +use rust_anilist::Client; + +#[tokio::test] +async fn get_user() { + let user = Client::default().get_user(5375822).await; + user.as_ref().unwrap(); + assert!(user.is_ok()) +} + +#[tokio::test] +async fn get_user_by_name() { + let user = Client::default().get_user_by_name("andrielfr").await; + assert!(user.is_ok()) +}