From 49ab60e690528f0afbf5367f012ffda3ef042885 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 28 Jul 2024 00:36:33 +0900 Subject: [PATCH 001/255] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20API=20URLs=20in?= =?UTF-8?q?=20fetch=20request=20were=20incorrect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why had this library working correctly so far??? --- src/client.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/client.rs b/src/client.rs index 185ea36..c756882 100644 --- a/src/client.rs +++ b/src/client.rs @@ -91,7 +91,7 @@ impl Client { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user(self, user: &str) -> RspErr { - let url = format!("{}/users/{}", API_URL, user.to_lowercase()); + let url = format!("{}users/{}", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } @@ -187,7 +187,7 @@ impl Client { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_records(self, user: &str) -> RspErr { - let url = format!("{}/users/{}/records", API_URL, user.to_lowercase()); + let url = format!("{}users/{}/records", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } @@ -403,7 +403,7 @@ impl Client { } // Cloned the `query` here because the query parameters will be referenced later. let q = query.clone().build(); - let url = format!("{}/users/lists/xp", API_URL); + let url = format!("{}users/lists/xp", API_URL); let r = self.client.get(url); let res = match q.len() { 1 => r.query(&[&q[0]]), @@ -516,7 +516,7 @@ impl Client { String::new() } ); - let url = format!("{}/streams/{}", API_URL, stream_id.to_lowercase()); + let url = format!("{}streams/{}", API_URL, stream_id.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } @@ -653,7 +653,7 @@ impl Client { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn search_user(self, discord_user: &str) -> RspErr { - let url = format!("{}/users/search/{}", API_URL, discord_user); + let url = format!("{}users/search/{}", API_URL, discord_user); let res = self.client.get(url).send().await; response(res).await } From cc7a4004ed10b6e24d98899bfe090813df4c2246 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 15:12:27 +0900 Subject: [PATCH 002/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20READM?= =?UTF-8?q?E=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c80b33c..00d08be 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ You can get the following from the TETRA CHANNEL API with this library: Also you can search for [TETR.IO](https://tetr.io) accounts by Discord account. -But TETRA CHANNEL API is in alpha. -So this library may not work properly in the future:( +But the TETRA CHANNEL API is in beta +so this library may not work properly in the future :( **\* This library is NOT official.** diff --git a/src/lib.rs b/src/lib.rs index 92f0485..e930515 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,8 @@ //! //! Also you can search for [TETR.IO](https://tetr.io) accounts by Discord account. //! -//! But TETRA CHANNEL API is in alpha. -//! So this library may not work properly in the future:( +//! But the TETRA CHANNEL API is in beta +//! so this library may not work properly in the future :( //! //! **\* This library is NOT official.** //! From 9b5ffc14440a32d4d3700f65c767fb4af65b0b01 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 17:00:40 +0900 Subject: [PATCH 003/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20Serve?= =?UTF-8?q?r=20Statistics=20models=20[#16]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✨ Add: `total_accounts` field to `ServerStats` struct - ✨ Add: `avg_pieces_per_second` methods and `avg_keys_per_second` methods - ♻️ Improve: refactor `ServerStatsResponse::registered_players` method - πŸ“š Docs: update and improve docs --- src/model/server_stats.rs | 74 +++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/src/model/server_stats.rs b/src/model/server_stats.rs index 7b5bce4..dd70f3a 100644 --- a/src/model/server_stats.rs +++ b/src/model/server_stats.rs @@ -1,10 +1,10 @@ -//! Server stats model. +//! The Server Statistics model. use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the server stats information. -/// Describes the some statistics about the TETR.IO in detail. +/// The response for the Server Statistics data. +/// Some statistics about the TETR.IO. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ServerStatsResponse { @@ -15,7 +15,7 @@ pub struct ServerStatsResponse { pub error: Option, /// Data about how this request was cached. pub cache: Option, - /// The requested user data. + /// The requested data. pub data: Option, } @@ -26,8 +26,25 @@ impl ServerStatsResponse { /// /// Panics if the request was not successful. pub fn registered_players(&self) -> u64 { - let ss = self.get_server_stats(); - ss.user_count - ss.anon_count + self.get_server_stats().registered_players() + } + + /// Returns the average amount of pieces placed per second. + /// + /// # Panics + /// + /// Panics if the request was not successful. + pub fn avg_pieces_per_second(&self) -> f64 { + self.get_server_stats().avg_pieces_per_second() + } + + /// Returns the average amount of keys pressed per second. + /// + /// # Panics + /// + /// Panics if the request was not successful. + pub fn avg_keys_per_second(&self) -> f64 { + self.get_server_stats().avg_keys_per_second() } /// Returns a UNIX timestamp when this resource was cached. @@ -74,7 +91,7 @@ impl AsRef for ServerStatsResponse { } } -/// The requested server stats data: +/// The Server Statistics data. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ServerStats { @@ -82,37 +99,48 @@ pub struct ServerStats { /// including anonymous accounts. #[serde(rename = "usercount")] pub user_count: u64, - /// The amount of users created a second (through the last minute). + /// The amount of users created a second + /// (through the last minute). #[serde(rename = "usercount_delta")] pub user_count_delta: f64, /// The amount of anonymous accounts on the server. #[serde(rename = "anoncount")] pub anon_count: u64, - /// The amount of ranked (visible in TETRA LEAGUE leaderboard) accounts on the server. + /// The total amount of accounts ever created + /// (including pruned anons etc.). + #[serde(rename = "totalaccounts")] + pub total_accounts: u64, + /// The amount of ranked + /// (visible in TETRA LEAGUE leaderboard) accounts on the server. #[serde(rename = "rankedcount")] pub ranked_count: u64, - /// The amount of replays stored on the server. - #[serde(rename = "replaycount")] - pub replay_count: u64, + /// The amount of game records stored on the server. + #[serde(rename = "recordcount")] + pub record_count: u64, /// The amount of games played across all users, /// including both off- and online modes. #[serde(rename = "gamesplayed")] pub games_play_count: u64, - /// The amount of games played a second (through the last minute). + /// The amount of games played a second + /// (through the last minute). #[serde(rename = "gamesplayed_delta")] pub games_play_count_delta: f64, /// The amount of games played across all users, - /// including both off- and online modes, excluding games that were not completed (e.g. retries) + /// including both off- and online modes, excluding games that were not completed + /// (e.g. retries) #[serde(rename = "gamesfinished")] pub games_finish_count: u64, - /// The amount of seconds spent playing across all users, including both off- and online modes. + /// The amount of seconds spent playing across all users, + /// including both off- and online modes. #[serde(rename = "gametime")] pub play_time: f64, - /// The amount of keys pressed across all users, including both off- and online modes. + /// The amount of keys pressed across all users, + /// including both off- and online modes. pub inputs: u64, - /// The amount of pieces placed across all users, including both off- and online modes. + /// The amount of pieces placed across all users, + /// including both off- and online modes. #[serde(rename = "piecesplaced")] - pub pieces_placed: u64, + pub pieces_place_count: u64, } impl ServerStats { @@ -120,6 +148,16 @@ impl ServerStats { pub fn registered_players(&self) -> u64 { self.user_count - self.anon_count } + + /// Returns the average amount of pieces placed per second. + pub fn avg_pieces_per_second(&self) -> f64 { + self.pieces_place_count as f64 / self.play_time + } + + /// Returns the average amount of keys pressed per second. + pub fn avg_keys_per_second(&self) -> f64 { + self.inputs as f64 / self.play_time + } } impl AsRef for ServerStats { From 923cab3f6fc2314cee8046f815dbd34f8ccee09c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 17:06:25 +0900 Subject: [PATCH 004/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20update=20docs=20?= =?UTF-8?q?for=20`get=5Fserver=5Fstats`=20method=20[#16]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/client.rs b/src/client.rs index c756882..cf84015 100644 --- a/src/client.rs +++ b/src/client.rs @@ -96,19 +96,17 @@ impl Client { response(res).await } - /// Returns the server stats model. + /// Returns some statistics about the TETR.IO. /// /// # Examples /// - /// Getting the server stats object: - /// /// ```no_run /// use tetr_ch::client::Client; /// # use std::io; /// /// # async fn run() -> io::Result<()> { /// let client = Client::new(); - /// // Get the server stats. + /// // Get the Server Statistics. /// let user = client.get_server_stats().await?; /// # Ok(()) /// # } From 23a3d17d578a5c139caaf1b0b8395e15117ddd6f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 17:43:26 +0900 Subject: [PATCH 005/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20update=20and=20i?= =?UTF-8?q?mprove=20docs=20for=20methods=20and=20models=20to=20conform=20t?= =?UTF-8?q?o=20new=20Server=20Activity=20endpoint=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 ++---- src/model/server_activity.rs | 10 +++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/client.rs b/src/client.rs index cf84015..903b18b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -126,19 +126,17 @@ impl Client { response(res).await } - /// Returns the server activity model. + /// Returns an array of user activity over the last 2 days. /// /// # Examples /// - /// Getting the server activity object: - /// /// ```no_run /// use tetr_ch::client::Client; /// # use std::io; /// /// # async fn run() -> io::Result<()> { /// let client = Client::new(); - /// // Get the server activity. + /// // Get the Server Activity. /// let user = client.get_server_activity().await?; /// # Ok(()) /// # } diff --git a/src/model/server_activity.rs b/src/model/server_activity.rs index c70545e..f658e3a 100644 --- a/src/model/server_activity.rs +++ b/src/model/server_activity.rs @@ -1,11 +1,11 @@ -//! Server Activity model. +//! The Server Activity model. use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the server activity information. +/// The response for the Server Activity data. /// -/// A graph of count of active players over the last 2 days. +/// An array of user activity over the last 2 days. /// A user is seen as active if they logged in or received XP within the last 30 minutes. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] @@ -17,7 +17,7 @@ pub struct ServerActivityResponse { pub error: Option, /// Data about how this request was cached. pub cache: Option, - /// The requested user data. + /// The requested data. pub data: Option, } @@ -66,7 +66,7 @@ impl AsRef for ServerActivityResponse { } } -/// The requested user data. +/// The Server Activity data. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ServerActivity { From 13cbf47ad5816562d9b61df6d10434c0d84909f6 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 20:28:36 +0900 Subject: [PATCH 006/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20model?= =?UTF-8?q?s=20to=20conform=20to=20new=20User=20Info=20endpoint=20[#19]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - πŸ› οΈ Update: `UserResponse` struct now directly has `User` struct instead of wrapping in `UserData` struct - ⚰️ Remove: `UserData` struct - ✨ Add: three fields to `User` struct - `achievements` - `achievement_rating` - `achievement_rating_counts` - ✨ Add: `Hidden` enumerator to `Role` enum - ✨ Add: methods to get boolean value whether user is hidden - ✨ Add: `desc` filed to `Badge` struct - πŸ› οΈ Update: `Connections` struct recursively - πŸ”₯ Remove: `league` field from `User` struct - πŸ”₯ Remove: TETRA LEAGUE related methods - πŸ”₯ Remove: `verified` field from `User` struct - πŸ”₯ Remove: methods to get boolean value whether user is verified - ✏️ Renamed: `user` filed in `User` struct to `username` - πŸ“š Docs: update and improve docs --- src/model/user.rs | 359 +++++++++++++++++++++------------------------- 1 file changed, 167 insertions(+), 192 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index 02d0929..1f33d66 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,11 +1,10 @@ -//! User-related models. +//! The User Info models. use crate::{ client::Client, error::ResponseError, model::{ cache::CacheData, - league::LeagueData, record::{single_play_end_ctx::SinglePlayEndCtx, EndContext, Record}, }, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, @@ -13,8 +12,8 @@ use crate::{ use serde::Deserialize; use std::fmt::{self, Display, Formatter}; -/// The response for the User information. -/// Describes the user in detail. +/// The response for User Info data. +/// An object describing the user in detail. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct UserResponse { @@ -25,12 +24,12 @@ pub struct UserResponse { pub error: Option, /// Data about how this request was cached. pub cache: Option, - /// The requested user data. - pub data: Option, + /// The requested data. + pub data: Option, } impl UserResponse { - /// Returns UNIX timestamp when the user's account created, if one exists. + /// Returns the UNIX timestamp when the user's account created, if one exists. /// /// # Panics /// @@ -169,31 +168,31 @@ impl UserResponse { self.get_user().role.is_banned() } - /// Whether the user is bad standing. + /// Whether the user is hidden. /// /// # Panics /// /// Panics if the request was not successful. - pub fn is_badstanding(&self) -> bool { - self.get_user().is_badstanding.unwrap_or(false) + pub fn is_hidden(&self) -> bool { + self.get_user().role.is_hidden() } - /// Whether the user is a supporter. + /// Whether the user is bad standing. /// /// # Panics /// /// Panics if the request was not successful. - pub fn is_supporter(&self) -> bool { - self.get_user().is_supporter.unwrap_or(false) + pub fn is_badstanding(&self) -> bool { + self.get_user().is_badstanding.unwrap_or(false) } - /// Whether the user is verified. + /// Whether the user is a supporter. /// /// # Panics /// /// Panics if the request was not successful. - pub fn is_verified(&self) -> bool { - self.get_user().is_verified + pub fn is_supporter(&self) -> bool { + self.get_user().is_supporter.unwrap_or(false) } /// Returns the user's profile URL. @@ -202,60 +201,7 @@ impl UserResponse { /// /// Panics if the request was not successful. pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.get_user().name) - } - - /// Returns an icon URL of the user's rank. - /// If the user is unranked, returns ?-rank(z) icon URL. - /// If the user has no rank, returns `None`. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn rank_icon_url(&self) -> Option { - self.get_user().rank_icon_url() - } - - /// Returns a rank color. (Hex color codes) - /// If the user has no rank, returns `None`. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn rank_color(&self) -> Option { - self.get_user().rank_color() - } - - /// Returns an icon URL of the user's percentile rank. - /// If not applicable, returns `None`. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn percentile_rank_icon_url(&self) -> Option { - self.get_user().percentile_rank_icon_url() - } - - /// Returns a percentile rank color. (Hex color codes) - /// If not applicable, returns `None`. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn percentile_rank_color(&self) -> Option { - self.get_user().percentile_rank_color() - } - - /// Returns an icon URL of the user's highest achieved rank. - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_icon_url(&self) -> Option { - self.get_user().best_rank_icon_url() - } - - /// Returns a color of the user's highest achieved rank. - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_color(&self) -> Option { - self.get_user().best_rank_color() + format!("https://ch.tetr.io/u/{}", self.get_user().username) } /// Returns an `Option`. @@ -274,25 +220,6 @@ impl UserResponse { .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) } - /// Returns the user's progress percentage in the rank. - /// Returns `None` if there is no user's position in global leaderboards. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn rank_progress(&self) -> Option { - let usr = self.get_user(); - let current_standing = usr.league.standing as f64; - let prev_at = usr.league.prev_at as f64; - let next_at = usr.league.next_at as f64; - - if prev_at < 0. || next_at < 0. { - return None; - } - - Some((current_standing - prev_at) / (next_at - prev_at) * 100.) - } - /// Returns the badges count. /// /// # Panics @@ -332,9 +259,10 @@ impl UserResponse { /// /// Panics if the request was not successful. fn get_user(&self) -> &User { - match self.data.as_ref() { - Some(d) => d.user.as_ref(), - None => panic!("There is no user object because the request was not successful."), + if let Some(d) = self.data.as_ref() { + d + } else { + panic!("There is no user object because the request was not successful.") } } } @@ -345,20 +273,7 @@ impl AsRef for UserResponse { } } -/// The requested user data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct UserData { - pub user: User, -} - -impl AsRef for UserData { - fn as_ref(&self) -> &Self { - self - } -} - -/// The requested user. +/// The User Info data. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct User { @@ -366,8 +281,7 @@ pub struct User { #[serde(rename = "_id")] pub id: UserId, /// The user's username. - #[serde(rename = "username")] - pub name: String, + pub username: String, /// The user's role. pub role: Role, /// When the user account was created. @@ -389,11 +303,12 @@ pub struct User { /// If the user has chosen to hide this statistic, it will be -1. #[serde(rename = "gameswon")] pub won_count: i32, - /// The amount of seconds this user spent playing, both on- and offline. + /// The amount of seconds this user spent playing,both on- and offline. /// If the user has chosen to hide this statistic, it will be -1. #[serde(rename = "gametime")] pub play_time: f64, - /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. Some vanity flags exist. + /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. + /// Some vanity flags exist. pub country: Option, /// Whether this user currently has a bad standing (recently banned). #[serde(rename = "badstanding")] @@ -401,13 +316,9 @@ pub struct User { /// Whether this user is currently supporting TETR.IO <3 #[serde(rename = "supporter")] pub is_supporter: Option, // EXCEPTION - /// An indicator of their total amount supported, between 0 and 4 inclusive. + /// An indicator of their total amount supported, + /// between 0 and 4 inclusive. pub supporter_tier: u8, - /// Whether this user is a verified account. - #[serde(rename = "verified")] - pub is_verified: bool, - /// This user's current TETRA LEAGUE standing. - pub league: LeagueData, /// This user's avatar ID. /// We can get their avatar at /// `https://tetr.io/user-content/avatars/{ USERID }.jpg?rv={ AVATAR_REVISION }`. @@ -419,16 +330,24 @@ pub struct User { pub banner_revision: Option, /// This user's "About Me" section. /// Ignore this field if the user is not a supporter. - /// - /// ***Even if the user is not currently a supporter, - /// the bio may be exist if the bio was once set.** pub bio: Option, - /// The amount of players who have added this user to their friends list. - pub friend_count: Option, // EXCEPTION /// This user's third party connections. pub connections: Connections, + /// The amount of players who have added this user to their friends list. + /// + /// ***This field is optional but the API documentation does not mention it.** + pub friend_count: Option, // This user's distinguishment banner. pub distinguishment: Option, + /// This user's featured achievements. + /// Up to three integers which correspond to Achievement IDs. + pub achievements: Vec, + /// This user's Achievement Rating. + #[serde(rename = "ar")] + pub achievement_rating: i32, + /// The breakdown of the source of this user's Achievement Rating. + #[serde(rename = "ar_counts")] + pub achievement_rating_counts: AchievementRatingCounts, } impl User { @@ -521,6 +440,11 @@ impl User { self.role.is_banned() } + /// Whether this user is hidden. + pub fn is_hidden(&self) -> bool { + self.role.is_hidden() + } + /// Whether this user is bad standing. pub fn is_badstanding(&self) -> bool { self.is_badstanding.unwrap_or(false) @@ -531,51 +455,9 @@ impl User { self.is_supporter.unwrap_or(false) } - /// Whether this user is verified. - pub fn is_verified(&self) -> bool { - self.is_verified - } - /// Returns the user's profile URL. pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.name) - } - - /// Returns an icon URL of the user's rank. - /// If the user is unranked, returns ?-rank(z) icon URL. - /// If the user has no rank, returns `None`. - pub fn rank_icon_url(&self) -> Option { - self.league.rank_icon_url() - } - - /// Returns a rank color. (Hex color codes) - /// If the user has no rank, returns `None`. - pub fn rank_color(&self) -> Option { - self.league.rank_color() - } - - /// Returns an icon URL of the user's percentile rank. - /// f not applicable, returns `None`. - pub fn percentile_rank_icon_url(&self) -> Option { - self.league.percentile_rank_icon_url() - } - - /// Returns a percentile rank color. (Hex color codes) - /// If not applicable, returns `None`. - pub fn percentile_rank_color(&self) -> Option { - self.league.percentile_rank_color() - } - - /// Returns a URL of the user's highest achieved rank. - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_icon_url(&self) -> Option { - self.league.best_rank_icon_url() - } - - /// Returns a color of the user's highest achieved rank. - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_color(&self) -> Option { - self.league.best_rank_color() + format!("https://ch.tetr.io/u/{}", self.username) } /// Returns an i @@ -591,20 +473,6 @@ impl User { .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) } - /// Returns the user's progress percentage in the rank. - /// Returns `None` if there is no user's position in global leaderboards. - pub fn rank_progress(&self) -> Option { - let current_standing = self.league.standing as f64; - let prev_at = self.league.prev_at as f64; - let next_at = self.league.next_at as f64; - - if prev_at < 0. || next_at < 0. { - return None; - } - - Some((current_standing - prev_at) / (next_at - prev_at) * 100.) - } - /// Returns the badges count. pub fn badges_count(&self) -> usize { self.badges.len() @@ -644,6 +512,9 @@ pub enum Role { /// The banned user. #[serde(rename = "banned")] Banned, + /// The hidden user. + #[serde(rename = "hidden")] + Hidden, } impl Role { @@ -681,6 +552,11 @@ impl Role { pub fn is_banned(&self) -> bool { matches!(self, Role::Banned) } + + /// Whether the user is hidden. + pub fn is_hidden(&self) -> bool { + matches!(self, Role::Hidden) + } } impl AsRef for Role { @@ -704,6 +580,7 @@ impl ToString for Role { /// assert_eq!(Role::Mod.to_string(), "Moderator"); /// assert_eq!(Role::Halfmod.to_string(), "Community moderator"); /// assert_eq!(Role::Banned.to_string(), "Banned user"); + /// assert_eq!(Role::Hidden.to_string(), "Hidden user"); /// ``` fn to_string(&self) -> String { match self { @@ -715,6 +592,7 @@ impl ToString for Role { Role::Mod => "Moderator", Role::Halfmod => "Community moderator", Role::Banned => "Banned user", + Role::Hidden => "Hidden user", } .to_string() } @@ -725,10 +603,21 @@ impl ToString for Role { #[non_exhaustive] pub struct Badge { /// The badge's internal ID, - /// and the filename of the badge icon (all PNGs within `/res/badges/`) + /// and the filename of the badge icon + /// (all PNGs within `/res/badges/`). + /// Note that badge IDs may include forward slashes. + /// Please do not encode them! + /// Follow the folder structure. pub id: String, + /// The badge's group ID. + /// If multiple badges have the same group ID, they are rendered together. + pub group: Option, /// The badge's label, shown when hovered. pub label: String, + /// Extra flavor text for the badge, shown when hovered. + /// + /// ***This field is optional but the API documentation does not mention it.** + pub desc: Option, /// The badge's timestamp, if shown. /// /// Why it uses `deserialize_with` attribute? @@ -739,10 +628,6 @@ pub struct Badge { default )] pub received_at: Option, - /// The badge's group, if specified. - /// - /// ***This property is not said in the [API document](https://tetr.io/about/api).** - pub group: Option, } impl Badge { @@ -768,7 +653,42 @@ impl AsRef for Badge { #[non_exhaustive] pub struct Connections { /// This user's connection to Discord. - pub discord: Option, + /// + /// - `id`: Discord ID. + /// - `username`: Discord username. + /// - `display_username`: Same as `username`. + pub discord: Option, + /// This user's connection to Twitch. + /// + /// - `id`: Twitch user ID. + /// - `username`: Twitch username (as used in the URL). + /// - `display_username`: Twitch display name (may include Unicode). + pub twitch: Option, + /// This user's connection to X + /// (kept in the API as twitter for readability). + /// + /// - `id`: X user ID. + /// - `username`: X handle (as used in the URL). + /// - `display_username`: X display name (may include Unicode). + pub twitter: Option, + /// This user's connection to Reddit. + /// + /// - `id`: Reddit user ID. + /// - `username`: Reddit username. + /// - `display_username`: Same as `username`. + pub reddit: Option, + /// This user's connection to YouTube. + /// + /// - `id`: YouTube user ID (as used in the URL). + /// - `username`: YouTube display name. + /// - `display_username`: Same as `username`. + pub youtube: Option, + /// This user's connection to Steam. + /// + /// - `id`: SteamID. + /// - `username`: Steam display name. + /// - `display_username`: Same as `username`. + pub steam: Option, } impl AsRef for Connections { @@ -777,18 +697,19 @@ impl AsRef for Connections { } } -/// This user's connection to Discord. +/// This user's connection. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct DiscordUser { - /// This user's Discord ID. +pub struct Connection { + /// This user's user ID on the service. pub id: String, - /// This user's Discord Tag. - #[serde(rename = "username")] - pub name: String, + /// This user's username on the service. + pub username: String, + /// This user's display username on the service. + pub display_username: String, } -impl AsRef for DiscordUser { +impl AsRef for Connection { fn as_ref(&self) -> &Self { self } @@ -802,10 +723,16 @@ pub struct Distinguishment { #[serde(rename = "type")] pub _type: String, /// The detail of distinguishment banner. + /// + /// ***This field is not documented in the API documentation.** pub detail: Option, /// The header of distinguishment banner. + /// + /// ***This field is not documented in the API documentation.** pub header: Option, /// the footer of distinguishment banner. + /// + /// ***This field is not documented in the API documentation.** pub footer: Option, } @@ -815,6 +742,54 @@ impl AsRef for Distinguishment { } } +/// The breakdown of the source of this user's Achievement Rating. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct AchievementRatingCounts { + /// The amount of ranked Bronze achievements this user has. + #[serde(rename = "1")] + pub bronze: Option, + /// The amount of ranked Silver achievements this user has. + #[serde(rename = "2")] + pub silver: Option, + /// The amount of ranked Gold achievements this user has. + #[serde(rename = "3")] + pub gold: Option, + /// The amount of ranked Platinum achievements this user has. + #[serde(rename = "4")] + pub platinum: Option, + /// The amount of ranked Diamond achievements this user has. + #[serde(rename = "5")] + pub diamond: Option, + /// The amount of ranked Issued achievements this user has. + #[serde(rename = "100")] + pub issued: Option, + /// The amount of competitive achievements this user has ranked into the top 100 with. + #[serde(rename = "t100")] + pub top100: Option, + /// The amount of competitive achievements this user has ranked into the top 50 with. + #[serde(rename = "t50")] + pub top50: Option, + /// The amount of competitive achievements this user has ranked into the top 25 with. + #[serde(rename = "t25")] + pub top25: Option, + /// The amount of competitive achievements this user has ranked into the top 10 with. + #[serde(rename = "t10")] + pub top10: Option, + /// The amount of competitive achievements this user has ranked into the top 5 with. + #[serde(rename = "t5")] + pub top5: Option, + /// The amount of competitive achievements this user has ranked into the top 3 with. + #[serde(rename = "t3")] + pub top3: Option, +} + +impl AsRef for AchievementRatingCounts { + fn as_ref(&self) -> &Self { + self + } +} + /// The response for the user records. /// Describes the user records. #[derive(Clone, Debug, Deserialize)] @@ -1655,7 +1630,7 @@ impl UserId { &self.0 } - /// Gets the user's data. + /// Gets the User Info data. /// /// # Errors /// From f6c297ded1a10bb66413105c19c5b51d3b13fc8a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 20:49:22 +0900 Subject: [PATCH 007/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20docs?= =?UTF-8?q?=20for=20`get=5Fuser`=20method=20[#19]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/client.rs b/src/client.rs index 903b18b..dfdfa45 100644 --- a/src/client.rs +++ b/src/client.rs @@ -64,19 +64,17 @@ impl Client { } } - /// Returns the user model. + /// Returns the object describing the user in detail. /// /// # Examples /// - /// Getting a user object: - /// /// ```no_run /// use tetr_ch::client::Client; /// # use std::io; /// /// # async fn run() -> io::Result<()> { /// let client = Client::new(); - /// // Get information for user `RINRIN-RS`. + /// // Get the User Info. /// let user = client.get_user("rinrin-rs").await?; /// # Ok(()) /// # } From b24b431ecb814292d2db2144c7a2d1e44a3c92e7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 23:30:03 +0900 Subject: [PATCH 008/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20update=20and=20i?= =?UTF-8?q?mprove=20docs=20for=20models=20to=20conform=20to=20new=20Search?= =?UTF-8?q?=20User=20endpoint=20[#21]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/searched_user.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index ecb0169..a2c78b0 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -1,4 +1,4 @@ -//! Searched user model. +//! The Searched User model. use crate::{ client::Client, @@ -10,8 +10,8 @@ use crate::{ }; use serde::Deserialize; -/// The response for the searched user. -/// Describes the found userm or `None` if the user was not found. +/// The response for the Searched User data. +/// An object describing the user found. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct SearchedUserResponse { @@ -22,8 +22,7 @@ pub struct SearchedUserResponse { pub error: Option, /// Data about how this request was cached. pub cache: Option, - /// The found user. - /// If the user was not found, this is `None`. + /// The requested data. pub data: Option, } @@ -104,11 +103,15 @@ impl AsRef for SearchedUserResponse { } } -/// The found user. +/// The Searched User data. +/// +/// Only one user is contained. +/// Generally, you won't see two users with the same social linked, though, +/// as it would be against TETR.IO multiaccounting policies. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct UserData { - /// The user info. (TETRA.IO account) + /// The user information (TETRA.IO user account). pub user: UserInfo, } @@ -153,7 +156,7 @@ impl AsRef for UserData { } } -/// The user info. (TETRA.IO account) +/// The user information (TETRA.IO user account). #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct UserInfo { From 3b6ee1654b9aa93384379760eb5065caaa648f70 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 23:30:52 +0900 Subject: [PATCH 009/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20metho?= =?UTF-8?q?d=20to=20conform=20to=20new=20Search=20User=20endpoint=20[#21]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - πŸ› οΈ Update: argument of `search_user` method - πŸ“š Docs: update and improve docs for method --- src/client.rs | 50 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/client.rs b/src/client.rs index dfdfa45..488a1ef 100644 --- a/src/client.rs +++ b/src/client.rs @@ -611,27 +611,28 @@ impl Client { response(res).await } - /// Search a TETR.IO user account by Discord account. + /// Searches for a TETR.IO user account by the social account. /// /// # Arguments /// - /// - `discord_user`: + /// - `social_connection`: /// - /// The Discord username or Discord ID to look up. + /// The social connection to look up. + /// This argument requires a [`search_user::SocialConnection`]. /// /// # Examples /// - /// Search a user by Discord account: - /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::client::{Client, search_user::SocialConnection}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { /// let client = Client::new(); /// - /// // Search a user by Discord ID. - /// let user = client.search_user("724976600873041940").await?; + /// // Search for a TETR.IO user account. + /// let user = client.search_user( + /// SocialConnection::Discord("724976600873041940".to_string()) + /// ).await?; /// # Ok(()) /// # } /// @@ -646,8 +647,8 @@ impl Client { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn search_user(self, discord_user: &str) -> RspErr { - let url = format!("{}users/search/{}", API_URL, discord_user); + pub async fn search_user(self, social_connection: search_user::SocialConnection) -> RspErr { + let url = format!("{}users/search/{}", API_URL, social_connection.to_param()); let res = self.client.get(url).send().await; response(res).await } @@ -1407,6 +1408,35 @@ pub mod stream { } } +pub mod search_user { + //! Features for searching users. + + /// The social connection. + /// + /// The API documentation says searching for the other social links will be added in the near future. + pub enum SocialConnection { + /// A Discord ID. + Discord(String), + } + + impl SocialConnection { + /// Converts into a parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::search_user::SocialConnection; + /// let discord_id = "724976600873041940".to_string(); + /// assert_eq!(SocialConnection::Discord(discord_id).to_param(), "discord:724976600873041940"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + SocialConnection::Discord(id) => format!("discord:{}", id), + } + } + } +} + #[cfg(test)] mod tests { use super::*; From e2ef6d201d7cc5ad04109f99bf1c1ee52cd79257 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 1 Nov 2024 23:36:32 +0900 Subject: [PATCH 010/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index 488a1ef..4cfcde7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -647,7 +647,10 @@ impl Client { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn search_user(self, social_connection: search_user::SocialConnection) -> RspErr { + pub async fn search_user( + self, + social_connection: search_user::SocialConnection, + ) -> RspErr { let url = format!("{}users/search/{}", API_URL, social_connection.to_param()); let res = self.client.get(url).send().await; response(res).await From b900846043c2070513bc45d6258ffa3fd1bbccfa Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 03:59:02 +0900 Subject: [PATCH 011/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20deriv?= =?UTF-8?q?e=20traits=20`Eq`,=20`PartialEq`,=20`Hash`=20tp=20`UserId`=20st?= =?UTF-8?q?ruct=20[#23]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/user.rs b/src/model/user.rs index 1f33d66..1c8da32 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1619,7 +1619,7 @@ pub struct Zen { } /// The user's internal ID. -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] pub struct UserId(pub String); type RspErr = Result; From 8abaf01e4c3de2bf900b1d0ecf5f1dda438e7ca0 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 04:03:51 +0900 Subject: [PATCH 012/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`Record`=20struct=20?= =?UTF-8?q?[#23]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/mod.rs | 1 + src/model/summary/mod.rs | 3 + src/model/summary/record.rs | 284 ++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 src/model/summary/mod.rs create mode 100644 src/model/summary/record.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index 1ccd772..2dbf4b3 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -9,5 +9,6 @@ pub mod searched_user; pub mod server_activity; pub mod server_stats; pub mod stream; +pub mod summary; pub mod user; pub mod xp_leaderboard; diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs new file mode 100644 index 0000000..b7749ed --- /dev/null +++ b/src/model/summary/mod.rs @@ -0,0 +1,3 @@ +//! Easy-to-use models of the various objects returned by the User Summaries API endpoint. + +pub mod record; diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs new file mode 100644 index 0000000..9f37953 --- /dev/null +++ b/src/model/summary/record.rs @@ -0,0 +1,284 @@ +//! The Record Data models. + +use crate::{model::{league::Rank, user::UserId}, util::to_unix_ts}; +use serde::Deserialize; +use std::collections::HashMap; + +/// The record data. +/// Achieved scores and matches. +/// +/// ***This structure may be changed drastically at any time. +/// See the [official API documentation](https://tetr.io/about/api/#recorddata) for more information.** +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Record { + /// The Record's ID. + #[serde(rename = "_id")] + pub id: String, + /// The Record's ReplayID. + #[serde(rename = "replayid")] + pub replay_id: String, + /// Whether the Replay has been pruned. + #[serde(rename = "stub")] + pub is_stub: bool, + /// The played game mode. + #[serde(rename = "gamemode")] + pub game_mode: String, + /// Whether this is the user's current personal best in the game mode. + #[serde(rename = "pb")] + pub is_personal_best: bool, + /// Whether this was once the user's personal best in the game mode. + #[serde(rename = "oncepb")] + pub has_been_personal_best: bool, + /// The time the Record was submitted. + #[serde(rename = "ts")] + pub submitted_at: String, + /// If revolved away, the revolution it belongs to. + pub revolution: Option, + /// The user owning the Record. + pub user: User, + /// Other users mentioned in the Record. + /// + /// If not empty, this is a multiplayer game + /// (this changes the enumerator of the [`Record::results`] field). + #[serde(rename = "otherusers")] + pub other_users: Vec, + /// The leaderboards this Record is mentioned in. + /// + /// e.g. `["40l_global", "40l_country_JP"]` + pub leaderboards: Vec, + /// Whether this Record is disputed. + #[serde(rename = "disputed")] + pub is_disputed: bool, + /// The results of this Record. + pub results: Results, + /// Extra metadata for this Record: + pub extras: Extras, +} + +impl Record { + /// Returns the URL to the replay. + pub fn replay_url(&self) -> String { + format!("https://tetr.io/#R:{}", self.replay_id) + } + + /// Returns a UNIX timestamp when this record was submitted. + pub fn submitted_at(&self) -> i64 { + to_unix_ts(&self.submitted_at) + } +} + +impl AsRef for Record { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User owning the Record. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct User { + /// The user's user ID. + pub id: UserId, + /// The user's username. + pub username: String, + /// The user's avatar revision (for obtaining avatar URLs). + pub avatar_revision: Option, + /// The user's banner revision (for obtaining banner URLs). + pub banner_revision: Option, + /// The user's country, if public. + pub country: Option, + /// Whether the user is supporting TETR.IO. + #[serde(rename = "supporter")] + pub is_supporter: bool, +} + +impl AsRef for User { + fn as_ref(&self) -> &Self { + self + } +} + +/// The results of a Record. +/// +/// If [`Record::other_users`] is empty, this is [`SinglePlayer`](`Results::SinglePlayer`). +/// Otherwise, this is [`MultiPlayer`](`Results::MultiPlayer`). +/// +/// ***This structure may be changed drastically at any time. +/// See the [official API documentation](https://tetr.io/about/api/#recorddata) for more information.** +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +#[non_exhaustive] +pub enum Results { + /// The results for a single-player games. + SinglePlayer(SinglePlayerResults), + /// The results for a multi-player games. + MultiPlayer(MultiPlayerResults), + /// An unknown result type. + Unknown(serde_json::Value), +} + +impl Results { + /// Whether the results are for a single-player game. + pub fn is_single_play(&self) -> bool { + matches!(self, Results::SinglePlayer(_)) + } + + /// Whether the results are for a multi-player game. + pub fn is_multi_play(&self) -> bool { + matches!(self, Results::MultiPlayer(_)) + } + + /// Whether the structure of the results is unknown. + pub fn is_unknown_structure(&self) -> bool { + matches!(self, Results::Unknown(_)) + } +} + +impl AsRef for Results { + fn as_ref(&self) -> &Self { + self + } +} + +/// The results for a single-player games. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct SinglePlayerResults { + /// The final stats of the game played. + #[serde(rename = "stats")] + pub final_stats: serde_json::Value, + /// Aggregate stats of the game played. + #[serde(rename = "aggregatestats")] + pub aggregate_stats: serde_json::Value, + /// The reason the game has ended. + #[serde(rename = "gameoverreason")] + pub game_over_reason: String, +} + +impl AsRef for SinglePlayerResults { + fn as_ref(&self) -> &Self { + self + } +} + +/// The results of a multi-player games. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct MultiPlayerResults { + /// The final leaderboard at the end of the match. + pub leaderboard: Vec, + /// The scoreboards for every round. + pub rounds: Vec>, +} + +impl AsRef for MultiPlayerResults { + fn as_ref(&self) -> &Self { + self + } +} + +/// The stats of a player in a multi-player game. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct PlayerStats { + /// The player's User ID. + pub id: UserId, + /// The player's username. + pub username: String, + /// Whether the player is still in the game. + /// If false, the user has likely been disqualified. + #[serde(rename = "active")] + pub is_active: bool, + /// The amount of rounds won by the player. + pub wins: u32, + /// The aggregate stats across all rounds. + pub stats: serde_json::Value, +} + +impl AsRef for PlayerStats { + fn as_ref(&self) -> &Self { + self + } +} + +/// The stats of a round in a multi-player game. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct PlayerStatsRound { + /// The player's User ID. + pub id: UserId, + /// The player's username. + pub username: String, + /// Whether the player is still in the game. + /// If false, the user has likely been disqualified for the round. + #[serde(rename = "active")] + pub is_active: bool, + /// Whether the player made it through the round alive. + #[serde(rename = "alive")] + pub is_alive: bool, + /// The time alive in this match. + pub lifetime: u32, + /// The aggregate stats for the player for this round. + pub stats: serde_json::Value, +} + +impl AsRef for PlayerStatsRound { + fn as_ref(&self) -> &Self { + self + } +} + +/// Extra metadata for a Record. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Extras { + /// A mapping of user IDs to before-and-afters, if user is being ranked. + pub league: Option>, + /// The result of the game, from the owner's point of view. + pub result: Option, + /// Extra data for QUICK PLAY, + pub zenith: Option, +} + +impl AsRef for Extras { + fn as_ref(&self) -> &Self { + self + } +} + +/// Extra stats for a player. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct PlayerExtraStats { + /// The Glicko-2 rating of the user. + pub glicko: f64, + /// The RD of the user. + pub rd: f64, + /// The TR of the user. + pub tr: f64, + /// The rank of the user. + pub rank: Rank, + /// The user's position in the global leaderboards. + pub placement: Option, +} + +impl AsRef for PlayerExtraStats { + fn as_ref(&self) -> &Self { + self + } +} + +/// Extra data for QUICK PLAY. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Zenith { + /// The mods used in the run. + pub mods: Vec, +} + +impl AsRef for Zenith { + fn as_ref(&self) -> &Self { + self + } +} From c86d7dd469b0419ea5aed25d4ba173a7b840cf28 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 04:04:56 +0900 Subject: [PATCH 013/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`FortyLines`=20struc?= =?UTF-8?q?t=20[#23]`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/forty_lines.rs | 47 ++++++++++++++++++++++++++++++++ src/model/summary/mod.rs | 1 + 2 files changed, 48 insertions(+) create mode 100644 src/model/summary/forty_lines.rs diff --git a/src/model/summary/forty_lines.rs b/src/model/summary/forty_lines.rs new file mode 100644 index 0000000..ab97674 --- /dev/null +++ b/src/model/summary/forty_lines.rs @@ -0,0 +1,47 @@ +//! The User Summary 40 LINES models. + +use crate::model::{ + cache::CacheData, + summary::record::Record, +}; +use serde::Deserialize; + +/// The response for the User Summary 40 LINES data. +/// An object describing a summary of the user's 40 LINES games. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct FortyLinesResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for FortyLinesResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User Summary 40 LINES data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct FortyLines { + /// The user's 40 LINES record, or `None` if never played. + pub record: Option, + /// The user's rank in global leaderboards, or -1 if not in global leaderboards. + pub rank: i32, + /// The user's rank in their country's leaderboards, or -1 if not in any. + pub rank_local: i32, +} + +impl AsRef for FortyLines { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index b7749ed..3742740 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,3 +1,4 @@ //! Easy-to-use models of the various objects returned by the User Summaries API endpoint. +pub mod forty_lines; pub mod record; From 1eb5d42dd400a2d7b1e09093f54272f0b154f947 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 04:05:58 +0900 Subject: [PATCH 014/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`Client::get=5Fuser?= =?UTF-8?q?=5F40l`=20method=20[#23]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/client.rs b/src/client.rs index 4cfcde7..b19360a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,6 +9,7 @@ use crate::{ server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, stream::StreamResponse, + summary::forty_lines::FortyLinesResponse, user::{UserRecordsResponse, UserResponse}, xp_leaderboard::{self, XPLeaderboardResponse}, }, @@ -186,6 +187,40 @@ impl Client { response(res).await } + /// Returns the object describing a summary of the user's 40 LINES games. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get the User Summary 40 LINES. + /// let user = client.get_user_40l("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_40l(self, user: &str) -> RspErr { + let url = format!("{}users/{}/summaries/40l", API_URL, user.to_lowercase()); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments From 85a5c257bfba52ea73b31da21db38a8e73aa3423 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 04:07:01 +0900 Subject: [PATCH 015/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/forty_lines.rs | 5 +- src/model/summary/record.rs | 317 ++++++++++++++++--------------- 2 files changed, 161 insertions(+), 161 deletions(-) diff --git a/src/model/summary/forty_lines.rs b/src/model/summary/forty_lines.rs index ab97674..bd2cb44 100644 --- a/src/model/summary/forty_lines.rs +++ b/src/model/summary/forty_lines.rs @@ -1,9 +1,6 @@ //! The User Summary 40 LINES models. -use crate::model::{ - cache::CacheData, - summary::record::Record, -}; +use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; /// The response for the User Summary 40 LINES data. diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 9f37953..bf13cae 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -1,6 +1,9 @@ //! The Record Data models. -use crate::{model::{league::Rank, user::UserId}, util::to_unix_ts}; +use crate::{ + model::{league::Rank, user::UserId}, + util::to_unix_ts, +}; use serde::Deserialize; use std::collections::HashMap; @@ -12,55 +15,55 @@ use std::collections::HashMap; #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Record { - /// The Record's ID. - #[serde(rename = "_id")] - pub id: String, - /// The Record's ReplayID. - #[serde(rename = "replayid")] - pub replay_id: String, - /// Whether the Replay has been pruned. - #[serde(rename = "stub")] - pub is_stub: bool, - /// The played game mode. - #[serde(rename = "gamemode")] - pub game_mode: String, - /// Whether this is the user's current personal best in the game mode. - #[serde(rename = "pb")] - pub is_personal_best: bool, - /// Whether this was once the user's personal best in the game mode. - #[serde(rename = "oncepb")] - pub has_been_personal_best: bool, - /// The time the Record was submitted. - #[serde(rename = "ts")] - pub submitted_at: String, - /// If revolved away, the revolution it belongs to. - pub revolution: Option, - /// The user owning the Record. - pub user: User, - /// Other users mentioned in the Record. - /// - /// If not empty, this is a multiplayer game - /// (this changes the enumerator of the [`Record::results`] field). - #[serde(rename = "otherusers")] - pub other_users: Vec, - /// The leaderboards this Record is mentioned in. - /// - /// e.g. `["40l_global", "40l_country_JP"]` - pub leaderboards: Vec, - /// Whether this Record is disputed. - #[serde(rename = "disputed")] - pub is_disputed: bool, - /// The results of this Record. - pub results: Results, - /// Extra metadata for this Record: - pub extras: Extras, + /// The Record's ID. + #[serde(rename = "_id")] + pub id: String, + /// The Record's ReplayID. + #[serde(rename = "replayid")] + pub replay_id: String, + /// Whether the Replay has been pruned. + #[serde(rename = "stub")] + pub is_stub: bool, + /// The played game mode. + #[serde(rename = "gamemode")] + pub game_mode: String, + /// Whether this is the user's current personal best in the game mode. + #[serde(rename = "pb")] + pub is_personal_best: bool, + /// Whether this was once the user's personal best in the game mode. + #[serde(rename = "oncepb")] + pub has_been_personal_best: bool, + /// The time the Record was submitted. + #[serde(rename = "ts")] + pub submitted_at: String, + /// If revolved away, the revolution it belongs to. + pub revolution: Option, + /// The user owning the Record. + pub user: User, + /// Other users mentioned in the Record. + /// + /// If not empty, this is a multiplayer game + /// (this changes the enumerator of the [`Record::results`] field). + #[serde(rename = "otherusers")] + pub other_users: Vec, + /// The leaderboards this Record is mentioned in. + /// + /// e.g. `["40l_global", "40l_country_JP"]` + pub leaderboards: Vec, + /// Whether this Record is disputed. + #[serde(rename = "disputed")] + pub is_disputed: bool, + /// The results of this Record. + pub results: Results, + /// Extra metadata for this Record: + pub extras: Extras, } impl Record { /// Returns the URL to the replay. pub fn replay_url(&self) -> String { - format!("https://tetr.io/#R:{}", self.replay_id) - } + format!("https://tetr.io/#R:{}", self.replay_id) + } /// Returns a UNIX timestamp when this record was submitted. pub fn submitted_at(&self) -> i64 { @@ -78,25 +81,25 @@ impl AsRef for Record { #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct User { - /// The user's user ID. - pub id: UserId, - /// The user's username. - pub username: String, - /// The user's avatar revision (for obtaining avatar URLs). - pub avatar_revision: Option, - /// The user's banner revision (for obtaining banner URLs). - pub banner_revision: Option, - /// The user's country, if public. - pub country: Option, - /// Whether the user is supporting TETR.IO. - #[serde(rename = "supporter")] - pub is_supporter: bool, + /// The user's user ID. + pub id: UserId, + /// The user's username. + pub username: String, + /// The user's avatar revision (for obtaining avatar URLs). + pub avatar_revision: Option, + /// The user's banner revision (for obtaining banner URLs). + pub banner_revision: Option, + /// The user's country, if public. + pub country: Option, + /// Whether the user is supporting TETR.IO. + #[serde(rename = "supporter")] + pub is_supporter: bool, } impl AsRef for User { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// The results of a Record. @@ -110,12 +113,12 @@ impl AsRef for User { #[serde(untagged)] #[non_exhaustive] pub enum Results { - /// The results for a single-player games. - SinglePlayer(SinglePlayerResults), - /// The results for a multi-player games. - MultiPlayer(MultiPlayerResults), - /// An unknown result type. - Unknown(serde_json::Value), + /// The results for a single-player games. + SinglePlayer(SinglePlayerResults), + /// The results for a multi-player games. + MultiPlayer(MultiPlayerResults), + /// An unknown result type. + Unknown(serde_json::Value), } impl Results { @@ -124,161 +127,161 @@ impl Results { matches!(self, Results::SinglePlayer(_)) } - /// Whether the results are for a multi-player game. - pub fn is_multi_play(&self) -> bool { - matches!(self, Results::MultiPlayer(_)) - } + /// Whether the results are for a multi-player game. + pub fn is_multi_play(&self) -> bool { + matches!(self, Results::MultiPlayer(_)) + } - /// Whether the structure of the results is unknown. - pub fn is_unknown_structure(&self) -> bool { - matches!(self, Results::Unknown(_)) - } + /// Whether the structure of the results is unknown. + pub fn is_unknown_structure(&self) -> bool { + matches!(self, Results::Unknown(_)) + } } impl AsRef for Results { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// The results for a single-player games. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct SinglePlayerResults { - /// The final stats of the game played. - #[serde(rename = "stats")] - pub final_stats: serde_json::Value, - /// Aggregate stats of the game played. - #[serde(rename = "aggregatestats")] - pub aggregate_stats: serde_json::Value, - /// The reason the game has ended. - #[serde(rename = "gameoverreason")] - pub game_over_reason: String, + /// The final stats of the game played. + #[serde(rename = "stats")] + pub final_stats: serde_json::Value, + /// Aggregate stats of the game played. + #[serde(rename = "aggregatestats")] + pub aggregate_stats: serde_json::Value, + /// The reason the game has ended. + #[serde(rename = "gameoverreason")] + pub game_over_reason: String, } impl AsRef for SinglePlayerResults { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// The results of a multi-player games. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct MultiPlayerResults { - /// The final leaderboard at the end of the match. - pub leaderboard: Vec, - /// The scoreboards for every round. - pub rounds: Vec>, + /// The final leaderboard at the end of the match. + pub leaderboard: Vec, + /// The scoreboards for every round. + pub rounds: Vec>, } impl AsRef for MultiPlayerResults { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// The stats of a player in a multi-player game. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct PlayerStats { - /// The player's User ID. - pub id: UserId, - /// The player's username. - pub username: String, - /// Whether the player is still in the game. - /// If false, the user has likely been disqualified. - #[serde(rename = "active")] - pub is_active: bool, - /// The amount of rounds won by the player. - pub wins: u32, - /// The aggregate stats across all rounds. - pub stats: serde_json::Value, + /// The player's User ID. + pub id: UserId, + /// The player's username. + pub username: String, + /// Whether the player is still in the game. + /// If false, the user has likely been disqualified. + #[serde(rename = "active")] + pub is_active: bool, + /// The amount of rounds won by the player. + pub wins: u32, + /// The aggregate stats across all rounds. + pub stats: serde_json::Value, } impl AsRef for PlayerStats { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// The stats of a round in a multi-player game. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct PlayerStatsRound { - /// The player's User ID. - pub id: UserId, - /// The player's username. - pub username: String, - /// Whether the player is still in the game. - /// If false, the user has likely been disqualified for the round. - #[serde(rename = "active")] - pub is_active: bool, - /// Whether the player made it through the round alive. - #[serde(rename = "alive")] - pub is_alive: bool, - /// The time alive in this match. - pub lifetime: u32, - /// The aggregate stats for the player for this round. - pub stats: serde_json::Value, + /// The player's User ID. + pub id: UserId, + /// The player's username. + pub username: String, + /// Whether the player is still in the game. + /// If false, the user has likely been disqualified for the round. + #[serde(rename = "active")] + pub is_active: bool, + /// Whether the player made it through the round alive. + #[serde(rename = "alive")] + pub is_alive: bool, + /// The time alive in this match. + pub lifetime: u32, + /// The aggregate stats for the player for this round. + pub stats: serde_json::Value, } impl AsRef for PlayerStatsRound { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// Extra metadata for a Record. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Extras { - /// A mapping of user IDs to before-and-afters, if user is being ranked. - pub league: Option>, - /// The result of the game, from the owner's point of view. - pub result: Option, - /// Extra data for QUICK PLAY, - pub zenith: Option, + /// A mapping of user IDs to before-and-afters, if user is being ranked. + pub league: Option>, + /// The result of the game, from the owner's point of view. + pub result: Option, + /// Extra data for QUICK PLAY, + pub zenith: Option, } impl AsRef for Extras { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// Extra stats for a player. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct PlayerExtraStats { - /// The Glicko-2 rating of the user. - pub glicko: f64, - /// The RD of the user. - pub rd: f64, - /// The TR of the user. - pub tr: f64, - /// The rank of the user. - pub rank: Rank, - /// The user's position in the global leaderboards. - pub placement: Option, + /// The Glicko-2 rating of the user. + pub glicko: f64, + /// The RD of the user. + pub rd: f64, + /// The TR of the user. + pub tr: f64, + /// The rank of the user. + pub rank: Rank, + /// The user's position in the global leaderboards. + pub placement: Option, } impl AsRef for PlayerExtraStats { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } /// Extra data for QUICK PLAY. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Zenith { - /// The mods used in the run. - pub mods: Vec, + /// The mods used in the run. + pub mods: Vec, } impl AsRef for Zenith { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } From c5218922c9505e28affdc8f7d5519d307209908d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 04:34:08 +0900 Subject: [PATCH 016/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fuser=5Fblitz`?= =?UTF-8?q?=20method=20[#25]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 36 ++++++++++++++++++++++++++++++- src/model/summary/blitz.rs | 44 ++++++++++++++++++++++++++++++++++++++ src/model/summary/mod.rs | 1 + 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/model/summary/blitz.rs diff --git a/src/client.rs b/src/client.rs index b19360a..af94b8e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,7 @@ use crate::{ server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, stream::StreamResponse, - summary::forty_lines::FortyLinesResponse, + summary::{blitz::BlitzResponse, forty_lines::FortyLinesResponse}, user::{UserRecordsResponse, UserResponse}, xp_leaderboard::{self, XPLeaderboardResponse}, }, @@ -221,6 +221,40 @@ impl Client { response(res).await } + /// Returns the object describing a summary of the user's BLITZ games. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get the User Summary BLITZ. + /// let user = client.get_user_blitz("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_blitz(self, user: &str) -> RspErr { + let url = format!("{}users/{}/summaries/blitz", API_URL, user.to_lowercase()); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments diff --git a/src/model/summary/blitz.rs b/src/model/summary/blitz.rs new file mode 100644 index 0000000..6da5d85 --- /dev/null +++ b/src/model/summary/blitz.rs @@ -0,0 +1,44 @@ +//! The User Summary BLITZ models. + +use crate::model::{cache::CacheData, summary::record::Record}; +use serde::Deserialize; + +/// The response for the User Summary BLITZ data. +/// An object describing a summary of the user's BLITZ games. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct BlitzResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for BlitzResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User Summary BLITZ data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Blitz { + /// The user's BLITZ record, or `None` if never played. + pub record: Option, + /// The user's rank in global leaderboards, or -1 if not in global leaderboards. + pub rank: i32, + /// The user's rank in their country's leaderboards, or -1 if not in any. + pub rank_local: i32, +} + +impl AsRef for Blitz { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index 3742740..b4fad0d 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,4 +1,5 @@ //! Easy-to-use models of the various objects returned by the User Summaries API endpoint. +pub mod blitz; pub mod forty_lines; pub mod record; From f27120bc61db02c596ab88916e9b6eea723fdc6c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 19:06:00 +0900 Subject: [PATCH 017/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fuser=5Fzenith?= =?UTF-8?q?`=20method=20[#27]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 36 +++++++++++++++++- src/model/summary/mod.rs | 1 + src/model/summary/zenith.rs | 75 +++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/model/summary/zenith.rs diff --git a/src/client.rs b/src/client.rs index af94b8e..7169eea 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,7 @@ use crate::{ server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, stream::StreamResponse, - summary::{blitz::BlitzResponse, forty_lines::FortyLinesResponse}, + summary::{blitz::BlitzResponse, forty_lines::FortyLinesResponse, zenith::ZenithResponse}, user::{UserRecordsResponse, UserResponse}, xp_leaderboard::{self, XPLeaderboardResponse}, }, @@ -255,6 +255,40 @@ impl Client { response(res).await } + /// Returns the object describing a summary of the user's QUICK PLAY games. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get the User Summary QUICK PLAY. + /// let user = client.get_user_zenith("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_zenith(self, user: &str) -> RspErr { + let url = format!("{}users/{}/summaries/zenith", API_URL, user.to_lowercase()); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index b4fad0d..733b8d9 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -3,3 +3,4 @@ pub mod blitz; pub mod forty_lines; pub mod record; +pub mod zenith; diff --git a/src/model/summary/zenith.rs b/src/model/summary/zenith.rs new file mode 100644 index 0000000..2d83c82 --- /dev/null +++ b/src/model/summary/zenith.rs @@ -0,0 +1,75 @@ +//! The User Summary QUICK PLAY models. + +use crate::model::{cache::CacheData, summary::record::Record}; +use serde::Deserialize; + +/// The response for the User Summary QUICK PLAY data. +/// An object describing a summary of the user's QUICK PLAY games. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct ZenithResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for ZenithResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User Summary QUICK PLAY data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Zenith { + /// The user's QUICK PLAY record, or `None` if the user hasn't played this week. + pub record: Option, + /// The user's rank in global leaderboards, or -1 if not in global leaderboards. + pub rank: i32, + /// The user's rank in their country's leaderboards, or -1 if not in any. + pub rank_local: i32, + /// The user's career best. + /// + /// Career bests are only updated on revolve time + /// (when the week changes, which is 12AM on Monday, UTC). + /// This is because if the record is at Floor 10, + /// the final leaderboard position is considered first + /// (the mode is multiplayer, after all). + pub best: ZenithBest, +} + +impl AsRef for Zenith { + fn as_ref(&self) -> &Self { + self + } +} + +/// The user's career best QUICK PLAY data. +/// +/// Career bests are only updated on revolve time +/// (when the week changes, which is 12AM on Monday, UTC). +/// This is because if the record is at Floor 10, +/// the final leaderboard position is considered first +/// (the mode is multiplayer, after all). +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct ZenithBest { + /// The user's best record, or `None` if the user hasn't placed one yet. + pub record: Option, + /// The rank said record had in global leaderboards at the end of the week, + /// or -1 if it was not ranked. + pub rank: i32, +} + +impl AsRef for ZenithBest { + fn as_ref(&self) -> &Self { + self + } +} From 88e5aacd26028857886cd26f90ab3250b124e4ef Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 5 Nov 2024 23:01:53 +0900 Subject: [PATCH 018/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fuser=5Fzenith?= =?UTF-8?q?=5Fex`=20method=20[#29]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 44 ++++++++++++++++++++++++++++++++++++- src/model/summary/zenith.rs | 24 +++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 7169eea..051ea4d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,11 @@ use crate::{ server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, stream::StreamResponse, - summary::{blitz::BlitzResponse, forty_lines::FortyLinesResponse, zenith::ZenithResponse}, + summary::{ + blitz::BlitzResponse, + forty_lines::FortyLinesResponse, + zenith::{ZenithExResponse, ZenithResponse}, + }, user::{UserRecordsResponse, UserResponse}, xp_leaderboard::{self, XPLeaderboardResponse}, }, @@ -289,6 +293,44 @@ impl Client { response(res).await } + /// Returns the object describing a summary of the user's EXPERT QUICK PLAY games. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get the User Summary EXPERT QUICK PLAY. + /// let user = client.get_user_zenith_ex("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_zenith_ex(self, user: &str) -> RspErr { + let url = format!( + "{}users/{}/summaries/zenithex", + API_URL, + user.to_lowercase() + ); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments diff --git a/src/model/summary/zenith.rs b/src/model/summary/zenith.rs index 2d83c82..1ae5ffe 100644 --- a/src/model/summary/zenith.rs +++ b/src/model/summary/zenith.rs @@ -1,4 +1,4 @@ -//! The User Summary QUICK PLAY models. +//! The User Summaries QUICK PLAY, EXPERT QUICK PLAY models. use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; @@ -73,3 +73,25 @@ impl AsRef for ZenithBest { self } } + +/// The response for the User Summary EXPERT QUICK PLAY data. +/// An object describing a summary of the user's EXPERT QUICK PLAY games. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct ZenithExResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for ZenithExResponse { + fn as_ref(&self) -> &Self { + self + } +} From f3ceb743b6fce59a5d67c17a72c0570ae448ce9f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 6 Nov 2024 00:21:55 +0900 Subject: [PATCH 019/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fuser=5Fleague?= =?UTF-8?q?`=20method=20[#31]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 35 +++++++++ src/model/summary/league.rs | 141 ++++++++++++++++++++++++++++++++++++ src/model/summary/mod.rs | 1 + 3 files changed, 177 insertions(+) create mode 100644 src/model/summary/league.rs diff --git a/src/client.rs b/src/client.rs index 051ea4d..740cf9c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,6 +12,7 @@ use crate::{ summary::{ blitz::BlitzResponse, forty_lines::FortyLinesResponse, + league::LeagueResponse, zenith::{ZenithExResponse, ZenithResponse}, }, user::{UserRecordsResponse, UserResponse}, @@ -331,6 +332,40 @@ impl Client { response(res).await } + /// Returns the object describing a summary of the user's TETRA LEAGUE standing. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get the User Summary TETRA LEAGUE. + /// let user = client.get_user_league("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_league(self, user: &str) -> RspErr { + let url = format!("{}users/{}/summaries/league", API_URL, user.to_lowercase()); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs new file mode 100644 index 0000000..26dcc3c --- /dev/null +++ b/src/model/summary/league.rs @@ -0,0 +1,141 @@ +//! The User Summary TETRA LEAGUE models. + +use crate::model::{cache::CacheData, league::Rank}; +use serde::Deserialize; +use std::collections::HashMap; + +/// The response for the User Summary TETRA LEAGUE data. +/// An object describing a summary of the user's TETRA LEAGUE standing. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LeagueResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for LeagueResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User Summary TETRA LEAGUE data. +/// +/// Season information is only saved if the user had finished placements in the season, +/// and was not banned or hidden. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct League { + /// The amount of TETRA LEAGUE games played by this user. + #[serde(rename = "gamesplayed")] + pub games_played: u32, + /// The amount of TETRA LEAGUE games won by this user. + #[serde(rename = "gameswon")] + pub games_won: u32, + /// This user's Glicko-2 rating, or -1 if less than 10 games were played. + pub glicko: f64, + /// This user's Glicko-2 Rating Deviation,or -1 if less than 10 games were played. + /// If over 100, this user is unranked. + pub rd: Option, + /// Whether this user's RD is rising (has not played in the last week). + pub decaying: bool, + /// This user's TR (Tetra Rating), or -1 if less than 10 games were played. + pub tr: f64, + /// This user's GLIXARE score (a % chance of beating an average player), + /// or -1 if less than 10 games were played. + pub gxe: f64, + /// This user's letter rank. z is unranked. + pub rank: Rank, + /// This user's highest achieved rank this season. + #[serde(rename = "bestrank")] + pub best_rank: Option, + /// This user's average APM (attack per minute) over the last 10 games. + pub apm: Option, + /// This user's average PPS (pieces per second) over the last 10 games. + pub pps: Option, + /// This user's average VS (versus score) over the last 10 games. + pub vs: Option, + /// This user's position in global leaderboards, or -1 if not applicable. + pub standing: Option, + /// This user's position in local leaderboards, or -1 if not applicable. + pub standing_local: Option, + /// This user's percentile position (0 is best, 1 is worst). + pub percentile: Option, + /// This user's percentile rank, or z if not applicable. + pub percentile_rank: Option, + /// The next rank this user can achieve, if they win more games, + /// or `None` if unranked (or the best rank). + pub next_rank: Option, + /// The previous rank this user can achieve, if they lose more games, + /// or null if unranked (or the worst rank). + pub prev_rank: Option, + /// The position of the best player in the user's current rank, + /// surpass them to go up a rank. -1 if unranked (or the best rank). + pub next_at: Option, + /// The position of the worst player in the user's current rank, + /// dip below them to go down a rank. -1 if unranked (or the worst rank). + pub prev_at: Option, + /// An object mapping past season IDs to past season final placement information. + pub past: HashMap, +} + +impl AsRef for League { + fn as_ref(&self) -> &Self { + self + } +} + +/// Past season final placement information. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct PastSeason { + /// The season ID. + pub season: String, + /// The username the user had at the time. + pub username: String, + /// The country the user represented at the time. + pub country: Option, + /// This user's final position in the season's global leaderboards. + pub placement: Option, + /// Whether the user was ranked at the time of the season's end. + #[serde(rename = "ranked")] + pub is_ranked: bool, + /// The amount of TETRA LEAGUE games played by this user. + #[serde(rename = "gamesplayed")] + pub games_played: u32, + /// The amount of TETRA LEAGUE games won by this user. + #[serde(rename = "gameswon")] + pub games_won: u32, + /// This user's final Glicko-2 rating. + pub glicko: f64, + /// This user's final Glicko-2 Rating Deviation. + pub rd: f64, + /// This user's final TR (Tetra Rating). + pub tr: f64, + /// This user's final GLIXARE score (a % chance of beating an average player). + pub gxe: f64, + /// This user's final letter rank. z is unranked. + pub rank: Rank, + /// This user's highest achieved rank in the season. + #[serde(rename = "bestrank")] + pub best_rank: Option, + /// This user's average APM (attack per minute) over the last 10 games in the season. + pub apm: f64, + /// This user's average PPS (pieces per second) over the last 10 games in the season. + pub pps: f64, + /// This user's average VS (versus score) over the last 10 games in the season. + pub vs: f64, +} + +impl AsRef for PastSeason { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index 733b8d9..eba54d7 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -2,5 +2,6 @@ pub mod blitz; pub mod forty_lines; +pub mod league; pub mod record; pub mod zenith; From f348f999d937b92ced33386337b81d165a4f5442 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 6 Nov 2024 00:36:04 +0900 Subject: [PATCH 020/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fuser=5Fzen`?= =?UTF-8?q?=20method=20[#33]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 35 +++++++++++++++++++++++++++++++++ src/model/summary/mod.rs | 1 + src/model/summary/zen.rs | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/model/summary/zen.rs diff --git a/src/client.rs b/src/client.rs index 740cf9c..8614a74 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,6 +13,7 @@ use crate::{ blitz::BlitzResponse, forty_lines::FortyLinesResponse, league::LeagueResponse, + zen::ZenResponse, zenith::{ZenithExResponse, ZenithResponse}, }, user::{UserRecordsResponse, UserResponse}, @@ -366,6 +367,40 @@ impl Client { response(res).await } + /// Returns the object describing a summary of the user's ZEN progress. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get the User Summary ZEN. + /// let user = client.get_user_zen("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_zen(self, user: &str) -> RspErr { + let url = format!("{}users/{}/summaries/zen", API_URL, user.to_lowercase()); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index eba54d7..745ee10 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -4,4 +4,5 @@ pub mod blitz; pub mod forty_lines; pub mod league; pub mod record; +pub mod zen; pub mod zenith; diff --git a/src/model/summary/zen.rs b/src/model/summary/zen.rs new file mode 100644 index 0000000..5dda43c --- /dev/null +++ b/src/model/summary/zen.rs @@ -0,0 +1,42 @@ +//! The User Summary ZEN models. + +use crate::model::cache::CacheData; +use serde::Deserialize; + +/// The response for the User Summary ZEN data. +/// An object describing a summary of the user's ZEN progress. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct ZenResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for ZenResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User Summary ZEN data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Zen { + /// The user's level. + pub level: u32, + /// The user's score. + pub score: f64, +} + +impl AsRef for Zen { + fn as_ref(&self) -> &Self { + self + } +} From 0857149d5aed656e76b751acdc95a6fd0a0f7ae1 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 6 Nov 2024 01:19:42 +0900 Subject: [PATCH 021/255] =?UTF-8?q?=E2=9C=A8=20Add:=20get=5Fuser=5Fachieve?= =?UTF-8?q?ments=20[#35]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 39 +++++++++++ src/model/summary/achievements.rs | 112 ++++++++++++++++++++++++++++++ src/model/summary/mod.rs | 1 + 3 files changed, 152 insertions(+) create mode 100644 src/model/summary/achievements.rs diff --git a/src/client.rs b/src/client.rs index 8614a74..ddb45f7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -10,6 +10,7 @@ use crate::{ server_stats::ServerStatsResponse, stream::StreamResponse, summary::{ + achievements::AchievementsResponse, blitz::BlitzResponse, forty_lines::FortyLinesResponse, league::LeagueResponse, @@ -401,6 +402,44 @@ impl Client { response(res).await } + /// Returns the object containing all the user's achievements. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get the User Summary Achievements. + /// let user = client.get_user_achievements("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_achievements(self, user: &str) -> RspErr { + let url = format!( + "{}users/{}/summaries/achievements", + API_URL, + user.to_lowercase() + ); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs new file mode 100644 index 0000000..9e49aef --- /dev/null +++ b/src/model/summary/achievements.rs @@ -0,0 +1,112 @@ +//! The User Summary Achievements models. + +use crate::model::cache::CacheData; +use serde::Deserialize; + +/// The response for the User Summary Achievements data. +/// An object containing all the user's achievements. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct AchievementsResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option>, +} + +impl AsRef for AchievementsResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// An object containing information about a user's achievement. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Achievement { + /// The Achievement ID, for every type of achievement. + #[serde(rename = "k")] + pub id: u32, + /// The category of the achievement. + pub category: String, + /// The primary name of the achievement. + pub name: String, + /// The objective of the achievement. + pub object: String, + /// The flavor text of the achievement. + pub desc: String, + /// The order of this achievement in its category. + #[serde(rename = "o")] + pub order: u32, + /// The rank type of this achievement. + /// + /// - 1 = PERCENTILE β€” ranked by percentile cutoffs (5% Diamond, 10% Platinum, 30% Gold, 50% Silver, 70% Bronze) + /// - 2 = ISSUE β€” always has the ISSUED rank + /// - 3 = ZENITH β€” ranked by QUICK PLAY floors + /// - 4 = PERCENTILELAX β€” ranked by percentile cutoffs (5% Diamond, 20% Platinum, 60% Gold, 100% Silver) + /// - 5 = PERCENTILEVLAX β€” ranked by percentile cutoffs (20% Diamond, 50% Platinum, 100% Gold) + /// - 6 = PERCENTILEMLAX β€” ranked by percentile cutoffs (10% Diamond, 20% Platinum, 50% Gold, 100% Silver) + #[serde(rename = "rt")] + pub rank_type: u32, + /// The value type of this achievement: + /// + /// - 0 = NONE β€” [`Achievement::value`] is `None` + /// - 1 = NUMBER β€” [`Achievement::value`] is a positive number + /// - 2 = TIME β€” [`Achievement::value`] is a positive amount of milliseconds + /// - 3 = TIME_INV β€” [`Achievement::value`] is a negative amount of milliseconds; negate it before displaying + /// - 4 = FLOOR β€” [`Achievement::value`] is an altitude, A is a floor number + /// - 5 = ISSUE β€” [`Achievement::value`] is the negative time of issue + /// - 6 = NUMBER_INV β€” [`Achievement::value`] is a negative number; negate it before displaying + #[serde(rename = "vt")] + pub value_type: u32, + /// The AR type of this achievement: + /// + /// - 0 = UNRANKED β€” no AR is given + /// - 1 = RANKED β€” AR is given for medal ranks + /// - 2 = COMPETITIVE β€” AR is given for medal ranks and leaderboard positions + #[serde(rename = "art")] + pub ar_type: u32, + /// The minimum score required to obtain the achievement. + pub min: i64, + /// The amount of decimal placed to show. + pub deci: u32, + /// Whether this achievement is usually not shown. + #[serde(rename = "hidden")] + pub is_hidden: bool, + /// The achieved score. + #[serde(rename = "v")] + pub value: Option, + /// Additional data (see [`Achievement::value_type`]). + #[serde(rename = "a")] + pub additional: Option, + /// The time the achievement was updated. + #[serde(rename = "t")] + pub time: Option, + /// The zero-indexed position in the achievement's leaderboards. + #[serde(rename = "pos")] + pub position: Option, + /// The total amount of players who have this achievement + /// (with a value of min or higher). + pub total: Option, + /// The rank of the achievement. + /// + /// - 0 = NONE, + /// - 1 = BRONZE, + /// - 2 = SILVER, + /// - 3 = GOLD, + /// - 4 = PLATINUM, + /// - 5 = DIAMOND, + /// - 100 = ISSUED + pub rank: Option, +} + +impl AsRef for Achievement { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index 745ee10..9aa0025 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,5 +1,6 @@ //! Easy-to-use models of the various objects returned by the User Summaries API endpoint. +pub mod achievements; pub mod blitz; pub mod forty_lines; pub mod league; From 047f5d5a41927ca0c8b6bb81b4d36927d3410d0e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 6 Nov 2024 01:46:39 +0900 Subject: [PATCH 022/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fuser=5Fall=5F?= =?UTF-8?q?summaries`=20method=20[#37]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 39 +++++++++++++++++++++++++++++++++ src/model/summary/mod.rs | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/src/client.rs b/src/client.rs index ddb45f7..6e60ac4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -16,6 +16,7 @@ use crate::{ league::LeagueResponse, zen::ZenResponse, zenith::{ZenithExResponse, ZenithResponse}, + AllSummariesResponse, }, user::{UserRecordsResponse, UserResponse}, xp_leaderboard::{self, XPLeaderboardResponse}, @@ -194,6 +195,44 @@ impl Client { response(res).await } + /// Returns the object containing all the user's summaries in one. + /// + /// ***consider whether you really need this. + /// If you only collect data for one or two game modes, + /// use the individual summaries' methods instead.** + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// // Get All the User Summaries. + /// let user = client.get_user_all_summaries("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_all_summaries(self, user: &str) -> RspErr { + let url = format!("{}users/{}/summaries", API_URL, user.to_lowercase()); + let res = self.client.get(url).send().await; + response(res).await + } + /// Returns the object describing a summary of the user's 40 LINES games. /// /// # Arguments diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index 9aa0025..e75fd6c 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,5 +1,8 @@ //! Easy-to-use models of the various objects returned by the User Summaries API endpoint. +use crate::model::cache::CacheData; +use serde::Deserialize; + pub mod achievements; pub mod blitz; pub mod forty_lines; @@ -7,3 +10,47 @@ pub mod league; pub mod record; pub mod zen; pub mod zenith; + +/// The response for the User Summary All data. +/// An object containing all the user's summaries in one. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct AllSummariesResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for AllSummariesResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// All the User Summary data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct AllSummaries { + /// The user's 40 LINES summary data. + #[serde(rename = "40l")] + pub forty_lines: forty_lines::FortyLines, + /// The user's BLITZ summary data. + pub blitz: blitz::Blitz, + /// The user's QUICK PLAY summary data. + pub zenith: zenith::Zenith, + /// The user's EXPERT QUICK PLAY summary data. + #[serde(rename = "zenithex")] + pub zenith_ex: zenith::Zenith, + /// The user's TETRA LEAGUE summary data. + pub league: league::League, + /// The user's ZEN summary data. + pub zen: zen::Zen, + /// The user's achievements. + pub achievements: Vec, +} From 90fa3caab1aa488262657a91a8436ec7cc01a012 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 6 Nov 2024 22:39:45 +0900 Subject: [PATCH 023/255] =?UTF-8?q?=E2=9C=A8=20Add:=20models=20for=20User?= =?UTF-8?q?=20Leaderboard=20endpoint=20[#39]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 234 +++++++++++++++++++++++++++++++++++++++ src/model/mod.rs | 1 + 2 files changed, 235 insertions(+) create mode 100644 src/model/leaderboard.rs diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs new file mode 100644 index 0000000..542ec48 --- /dev/null +++ b/src/model/leaderboard.rs @@ -0,0 +1,234 @@ +//! The User Leaderboard models. + +use crate::{model::{ + cache::CacheData, + league::Rank, + user::{AchievementRatingCounts, Role, UserId} +}, util::{max_f64, to_unix_ts}}; +use serde::Deserialize; + +/// The response for the User Leaderboard data. +/// +/// An array of users fulfilling the search criteria. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LeaderboardResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for LeaderboardResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User Leaderboard data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Leaderboard { + /// The matched users. + pub entries: Vec, +} + +impl AsRef for Leaderboard { + fn as_ref(&self) -> &Self { + self + } +} + +/// An entry in the User Leaderboard. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Entry { + /// The user's internal ID. + #[serde(rename = "_id")] + pub id: UserId, + /// The user's username. + pub username: String, + /// The user's role. + pub role: Role, + /// When the user account was created. + /// If not set, this account was created before join dates were recorded. + #[serde(rename = "ts")] + pub account_created_at: Option, + /// The user's XP in points. + pub xp: f64, + /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. + /// Some vanity flags exist. + pub country: Option, + /// Whether this user is currently supporting TETR.IO <3 + #[serde(rename = "supporter")] + pub is_supporter: Option, // EXCEPTION + /// This user's current TETRA LEAGUE standing. + pub league: League, + /// The amount of online games played by this user. + /// If the user has chosen to hide this statistic, it will be -1. + #[serde(rename = "gamesplayed")] + pub online_games_played: i32, + /// The amount of online games won by this user. + /// If the user has chosen to hide this statistic, it will be -1. + #[serde(rename = "gameswon")] + pub online_games_won: i32, + /// The amount of seconds this user spent playing, both on- and offline. + /// If the user has chosen to hide this statistic, it will be -1. + #[serde(rename = "gametime")] + pub game_time: f64, + /// This user's Achievement Rating. + #[serde(rename = "ar")] + pub achievement_rating: i32, + /// The breakdown of the source of this user's Achievement Rating. + #[serde(rename = "ar_counts")] + pub achievement_rating_counts: AchievementRatingCounts, + /// The prisecter of this entry. + /// + /// A **prisecter** is consisting of three floats. + /// It allows you to continue paginating. + #[serde(rename = "p")] + pub prisecter: Prisecter, +} + +impl Entry { + /// Whether this user is an anonymous. + pub fn is_anon(&self) -> bool { + self.role.is_anon() + } + + /// Whether this user is a bot. + pub fn is_bot(&self) -> bool { + self.role.is_bot() + } + + /// Whether this user is a SYSOP. + pub fn is_sysop(&self) -> bool { + self.role.is_sysop() + } + + /// Whether this user is an administrator. + pub fn is_admin(&self) -> bool { + self.role.is_admin() + } + + /// Whether this user is a moderator. + pub fn is_mod(&self) -> bool { + self.role.is_mod() + } + + /// Whether this user is a community moderator. + pub fn is_halfmod(&self) -> bool { + self.role.is_halfmod() + } + + /// Whether this user is banned. + pub fn is_banned(&self) -> bool { + self.role.is_banned() + } + + /// Whether this user is hidden. + pub fn is_hidden(&self) -> bool { + self.role.is_hidden() + } + + /// Returns an UNIX timestamp of when the account was created. + /// If this account was created before join dates were recorded, + /// returns `None`. + pub fn account_created_at(&self) -> Option { + self.account_created_at.as_ref().map(|ts| to_unix_ts(ts)) + } + + /// Returns the level based on the user's xp. + pub fn level(&self) -> u32 { + let xp = self.xp; + // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 + ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() + as u32 + } + + /// Returns the national flag URL of the user's country. + pub fn national_flag_url(&self) -> Option { + self.country + .as_ref() + .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) + } + + /// Whether this user is a supporter. + pub fn is_supporter(&self) -> bool { + self.is_supporter.unwrap_or(false) + } +} + +impl AsRef for Entry { + fn as_ref(&self) -> &Self { + self + } +} + +/// The user's current TETRA LEAGUE standing. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct League { + /// The amount of TETRA LEAGUE games played by this user. + #[serde(rename = "gamesplayed")] + pub games_played: u32, + /// The amount of TETRA LEAGUE games won by this user. + #[serde(rename = "gameswon")] + pub games_won: u32, + /// This user's TR (Tetra Rating). + pub tr: f64, + /// This user's GLIXARE. + pub gxe: f64, + /// This user's rank. + pub rank: Rank, + /// This user's highest achieved rank this season. + #[serde(rename = "bestrank")] + pub best_rank: Rank, + /// This user's Glicko-2 rating. + pub glicko: f64, + /// This user's Glicko-2 Rating Deviation. + pub rd: f64, + /// This user's average APM (attack per minute) over the last 10 games. + pub apm: f64, + /// This user's average PPS (pieces per second) over the last 10 games. + pub pps: f64, + /// This user's average VS (versus score) over the last 10 games. + pub vs: f64, + /// Whether this user's RD is rising (has not played in the last week). + #[serde(rename = "decaying")] + pub is_decaying: bool, +} + +impl AsRef for League { + fn as_ref(&self) -> &Self { + self + } +} + +/// A prisecter. +/// +/// A **prisecter** is consisting of three floats. +/// It allows you to continue paginating. +#[derive(Clone, Debug, Deserialize)] +pub struct Prisecter { + /// The primary sort key. + pub pri: f64, + /// The secondary sort key. + pub sec: f64, + /// The tertiary sort key. + pub ter: f64, +} + +impl Prisecter { + /// Converts this prisecter to an array. + /// + /// This array can be used as a bound for the next search. + pub fn to_array(&self) -> [f64; 3] { + [self.pri, self.sec, self.ter] + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 2dbf4b3..a918ea0 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -4,6 +4,7 @@ pub mod cache; pub mod latest_news; pub mod league; pub mod league_leaderboard; +pub mod leaderboard; pub mod record; pub mod searched_user; pub mod server_activity; From bb56179f88f2cb0b2c3ddd443ac874e736ec8151 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 6 Nov 2024 22:41:21 +0900 Subject: [PATCH 024/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fleaderboard`?= =?UTF-8?q?=20method=20[#39]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 388 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 388 insertions(+) diff --git a/src/client.rs b/src/client.rs index 6e60ac4..331a9a9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,6 +5,7 @@ use crate::{ model::{ latest_news::LatestNewsResponse, league_leaderboard::{self, LeagueLeaderboardResponse}, + leaderboard::LeaderboardResponse, searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, @@ -479,6 +480,80 @@ impl Client { response(res).await } + /// Returns the array of users fulfilling the search criteria. + /// + /// # Arguments + /// + /// - `leaderboard`: The leaderboard to sort users by. + /// - `search_criteria`: The search criteria to filter users by. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::{ + /// Client, + /// leaderboard::{LeaderboardType, LeaderboardSearchCriteria} + /// }; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// let criteria = LeaderboardSearchCriteria::new() + /// // Upper bound is `[15200, 0, 0]` + /// .after([15200.,0.,0.]) + /// // Three entries + /// .limit(3) + /// // Filter by Japan + /// .country("jp"); + /// + /// // Get the User Leaderboard. + /// let user = client.get_leaderboard( + /// LeaderboardType::League, + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_leaderboard( + self, + leaderboard: leaderboard::LeaderboardType, + search_criteria: Option, + ) -> RspErr { + let mut query_params = Vec::new(); + if let Some(criteria) = search_criteria { + if criteria.is_invalid_limit_range() { + panic!( + "The query parameter`limit` must be between 0 and 100.\n\ + Received: {}", + criteria.limit.unwrap() + ); + } + query_params = criteria.build(); + } + let url = format!( + "{}users/by/{}", + API_URL, + leaderboard.to_param() + ); + let res = self + .client + .get(url) + .query(&query_params) + .send() + .await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments @@ -1733,6 +1808,319 @@ pub mod search_user { } } +pub mod leaderboard { + //! Features for leaderboards. + + /// The leaderboard type. + pub enum LeaderboardType { + /// The TETRA LEAGUE leaderboard. + League, + /// The XP leaderboard. + Xp, + /// The Achievement Rating leaderboard. + Ar + } + + impl LeaderboardType { + /// Converts into a parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::leaderboard::LeaderboardType; + /// assert_eq!(LeaderboardType::League.to_param(), "league"); + /// assert_eq!(LeaderboardType::Xp.to_param(), "xp"); + /// assert_eq!(LeaderboardType::Ar.to_param(), "ar"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + LeaderboardType::League => "league".to_string(), + LeaderboardType::Xp => "xp".to_string(), + LeaderboardType::Ar => "ar".to_string(), + } + } + } + + /// The search criteria for the leaderboard. + /// + /// # Examples + /// + /// ``` + /// use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// + /// // Default search criteria. + /// let c1 = LeaderboardSearchCriteria::new(); + /// + /// // Upper bound is `[15200, 0, 0]`, three entries, filter by Japan. + /// let c2 = LeaderboardSearchCriteria::new() + /// .after([15200., 0., 0.]) + /// .limit(3) + /// .country("jp"); + /// + /// // Lower bound is `[15200, 0, 0]`. + /// // Also the search order is reversed. + /// let c3 = LeaderboardSearchCriteria::new() + /// .before([15200., 0., 0.]); + /// + /// // You can initialize the search criteria to default as follows: + /// let mut c4 = LeaderboardSearchCriteria::new().country("us"); + /// c4.init(); + /// ``` + #[derive(Clone, Debug, Default)] + pub struct LeaderboardSearchCriteria { + /// The bound to paginate. + pub bound: Option, + /// The amount of entries to return, + /// between 1 and 100. 25 by default. + pub limit: Option, + /// The ISO 3166-1 country code to filter to. + /// Leave unset to not filter by country. + pub country: Option, + } + + impl LeaderboardSearchCriteria { + /// Creates a new [`LeaderboardSearchCriteria`]. + /// The values are set to default. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let criteria = LeaderboardSearchCriteria::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Initializes the search criteria. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let mut criteria = LeaderboardSearchCriteria::new().country("us"); + /// criteria.init(); + /// ``` + pub fn init(self) -> Self { + Self::default() + } + + /// Sets the upper bound. + /// + /// # Arguments + /// + /// - `bound`: The upper bound to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the upper bound to `[10000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let mut criteria = LeaderboardSearchCriteria::new(); + /// criteria.after([10000.0, 0.0, 0.0]); + /// ``` + pub fn after(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::After(bound)), + ..self + } + } + + /// Sets the lower bound. + /// + /// # Arguments + /// + /// - `bound`: The lower bound to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the lower bound to `[10000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let mut criteria = LeaderboardSearchCriteria::new(); + /// criteria.before([10000.0, 0.0, 0.0]); + /// ``` + pub fn before(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::Before(bound)), + ..self + } + } + + /// Limits the amount of entries to return. + /// + /// # Arguments + /// + /// - `limit`: The amount of entries to return. + /// Between 1 and 100. 25 by default. + /// + /// # Examples + /// + /// Limits the amount of entries to return to `10`. + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let mut criteria = LeaderboardSearchCriteria::new(); + /// criteria.limit(10); + /// ``` + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between `1` and `100`. + /// + /// ```should_panic + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let mut criteria = LeaderboardSearchCriteria::new(); + /// criteria.limit(0); + /// ``` + /// + /// ```should_panic + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let mut criteria = LeaderboardSearchCriteria::new(); + /// criteria.limit(101); + /// ``` + pub fn limit(self, limit: u8) -> Self { + if (1..=100).contains(&limit) { + Self { + limit: Some(limit), + ..self + } + } else { + panic!( + "The argument `limit` must be between 1 and 100.\n\ + Received: {}", + limit + ); + } + } + + /// Sets the ISO 3166-1 country code to filter to. + /// + /// # Arguments + /// + /// - `country`: The ISO 3166-1 country code to filter to. + /// + /// # Examples + /// + /// Sets the country code to `jp`. + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let mut criteria = LeaderboardSearchCriteria::new(); + /// criteria.country("jp"); + /// ``` + pub fn country(self, country: &str) -> Self { + Self { + country: Some(country.to_owned().to_uppercase()), + ..self + } + } + + /// Whether the search criteria `limit` is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let invalid_criteria = LeaderboardSearchCriteria { + /// limit: Some(0), + /// ..LeaderboardSearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + /// + /// ``` + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let invalid_criteria = LeaderboardSearchCriteria { + /// limit: Some(101), + /// ..LeaderboardSearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + pub fn is_invalid_limit_range(&self) -> bool { + if let Some(l) = self.limit { + !(1..=100).contains(&l) + } else { + false + } + } + + /// Builds the search criteria to `Vec<(String, String)>`. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; + /// let criteria = LeaderboardSearchCriteria::new(); + /// let query_params = criteria.build(); + /// ``` + pub(crate) fn build(self) -> Vec<(String, String)> { + let mut result = Vec::new(); + if let Some(b) = self.bound { + result.push(b.to_query_param()); + } + if let Some(l) = self.limit { + result.push(("limit".to_string(), l.to_string())); + } + if let Some(c) = self.country { + result.push(("country".to_string(), c)); + } + result + } + } + + /// The bound to paginate. + #[derive(Clone, Debug)] + pub enum Bound { + /// The upper bound. + /// Use this to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + After([f64; 3]), + /// The lower bound. + /// Use this to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If set, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + Before([f64; 3]), + } + + impl Bound { + /// Converts into a query parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::leaderboard::Bound; + /// let bound = Bound::After([12345.678, 0.0, 0.0]); + /// assert_eq!(bound.to_query_param(), ("after".to_string(), "12345.678:0:0".to_string())); + /// ``` + pub(crate) fn to_query_param(&self) -> (String, String) { + match self { + Bound::After(b) => ("after".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), + Bound::Before(b) => ("before".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), + } + } + } +} + #[cfg(test)] mod tests { use super::*; From a3c9be2983d1d95fb5ba22a5f45949a7815f1396 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 6 Nov 2024 22:42:17 +0900 Subject: [PATCH 025/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 17 ++++------------- src/model/leaderboard.rs | 13 ++++++++----- src/model/mod.rs | 2 +- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/client.rs b/src/client.rs index 331a9a9..7929e36 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,8 +4,8 @@ use crate::{ error::{ResponseError, Status}, model::{ latest_news::LatestNewsResponse, - league_leaderboard::{self, LeagueLeaderboardResponse}, leaderboard::LeaderboardResponse, + league_leaderboard::{self, LeagueLeaderboardResponse}, searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, @@ -540,17 +540,8 @@ impl Client { } query_params = criteria.build(); } - let url = format!( - "{}users/by/{}", - API_URL, - leaderboard.to_param() - ); - let res = self - .client - .get(url) - .query(&query_params) - .send() - .await; + let url = format!("{}users/by/{}", API_URL, leaderboard.to_param()); + let res = self.client.get(url).query(&query_params).send().await; response(res).await } @@ -1818,7 +1809,7 @@ pub mod leaderboard { /// The XP leaderboard. Xp, /// The Achievement Rating leaderboard. - Ar + Ar, } impl LeaderboardType { diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 542ec48..1cf4b89 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -1,10 +1,13 @@ //! The User Leaderboard models. -use crate::{model::{ - cache::CacheData, - league::Rank, - user::{AchievementRatingCounts, Role, UserId} -}, util::{max_f64, to_unix_ts}}; +use crate::{ + model::{ + cache::CacheData, + league::Rank, + user::{AchievementRatingCounts, Role, UserId}, + }, + util::{max_f64, to_unix_ts}, +}; use serde::Deserialize; /// The response for the User Leaderboard data. diff --git a/src/model/mod.rs b/src/model/mod.rs index a918ea0..871a195 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -2,9 +2,9 @@ pub mod cache; pub mod latest_news; +pub mod leaderboard; pub mod league; pub mod league_leaderboard; -pub mod leaderboard; pub mod record; pub mod searched_user; pub mod server_activity; From b6b16b8a43d5f31167f2cddc3d9e541a029c0b75 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 7 Nov 2024 00:22:10 +0900 Subject: [PATCH 026/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`Entry`?= =?UTF-8?q?=20struct=20to=20`LeaderboardEntry`=20to=20ensure=20name=20cons?= =?UTF-8?q?istency=20[#41]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 1cf4b89..842626c 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -38,7 +38,7 @@ impl AsRef for LeaderboardResponse { #[non_exhaustive] pub struct Leaderboard { /// The matched users. - pub entries: Vec, + pub entries: Vec, } impl AsRef for Leaderboard { @@ -50,7 +50,7 @@ impl AsRef for Leaderboard { /// An entry in the User Leaderboard. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct Entry { +pub struct LeaderboardEntry { /// The user's internal ID. #[serde(rename = "_id")] pub id: UserId, @@ -98,7 +98,7 @@ pub struct Entry { pub prisecter: Prisecter, } -impl Entry { +impl LeaderboardEntry { /// Whether this user is an anonymous. pub fn is_anon(&self) -> bool { self.role.is_anon() @@ -167,7 +167,7 @@ impl Entry { } } -impl AsRef for Entry { +impl AsRef for LeaderboardEntry { fn as_ref(&self) -> &Self { self } From b693801ff6bc4cc95e2be2b10db3dd19ed59a9fa Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 7 Nov 2024 00:23:35 +0900 Subject: [PATCH 027/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fhistorical=5F?= =?UTF-8?q?league=5Fleaderboard`=20method=20[#41]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 72 +++++++++++++++++++++++++++- src/model/leaderboard.rs | 100 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index 7929e36..aeee07d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,7 +4,7 @@ use crate::{ error::{ResponseError, Status}, model::{ latest_news::LatestNewsResponse, - leaderboard::LeaderboardResponse, + leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, @@ -545,6 +545,76 @@ impl Client { response(res).await } + /// Returns the array of historical user blobs fulfilling the search criteria. + /// + /// # Arguments + /// + /// - `season`: The season to look up. (e.g. `"1"`) + /// - `search_criteria`: The search criteria to filter users by. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::{ + /// Client, + /// leaderboard::LeaderboardSearchCriteria + /// }; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// let criteria = LeaderboardSearchCriteria::new() + /// // Upper bound is `[15200, 0, 0]` + /// .after([15200.,0.,0.]) + /// // Three entries + /// .limit(3) + /// // Filter by Japan + /// .country("jp"); + /// + /// // Get the User Leaderboard. + /// let user = client.get_historical_league_leaderboard( + /// "1", + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_historical_league_leaderboard( + self, + season: &str, + search_criteria: Option, + ) -> RspErr { + let mut query_params = Vec::new(); + if let Some(criteria) = search_criteria { + if criteria.is_invalid_limit_range() { + panic!( + "The query parameter`limit` must be between 0 and 100.\n\ + Received: {}", + criteria.limit.unwrap() + ); + } + query_params = criteria.build(); + } + let url = format!( + "{}users/history/{}/{}", + API_URL, + leaderboard::LeaderboardType::League.to_param(), + season + ); + let res = self.client.get(url).query(&query_params).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 842626c..94b9e7b 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -235,3 +235,103 @@ impl Prisecter { [self.pri, self.sec, self.ter] } } + +impl AsRef for Prisecter { + fn as_ref(&self) -> &Self { + self + } +} + +/// The response for the Historical User Leaderboard data. +/// +/// An array of historical user blobs fulfilling the search criteria. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct HistoricalLeaderboardResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for HistoricalLeaderboardResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The Historical User Leaderboard data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct HistoricalLeaderboard { + /// The matched historical user blobs. + pub entries: Vec, +} + +impl AsRef for HistoricalLeaderboard { + fn as_ref(&self) -> &Self { + self + } +} + +/// An entry in the Historical User Leaderboard. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct HistoricalEntry { + /// The user's internal ID. + #[serde(rename = "_id")] + pub id: UserId, + /// The season ID. + pub season: String, + /// The username the user had at the time. + pub username: String, + /// The country the user represented at the time. + pub country: Option, + /// This user's final position in the season's global leaderboards. + pub placement: i32, + /// Whether the user was ranked at the time of the season's end. + #[serde(rename = "ranked")] + pub is_ranked: bool, + /// The amount of TETRA LEAGUE games played by this user. + #[serde(rename = "gamesplayed")] + pub games_played: u32, + /// The amount of TETRA LEAGUE games won by this user. + #[serde(rename = "gameswon")] + pub games_won: u32, + /// This user's final Glicko-2 rating. + pub glicko: f64, + /// This user's final Glicko-2 Rating Deviation. + pub rd: f64, + /// This user's final TR (Tetra Rating). + pub tr: f64, + /// This user's final GLIXARE score (a % chance of beating an average player). + pub gxe: f64, + /// This user's final letter rank. z is unranked. + pub rank: Rank, + /// This user's highest achieved rank in the season. + #[serde(rename = "bestrank")] + pub best_rank: Option, + /// This user's average APM (attack per minute) over the last 10 games in the season. + pub apm: f64, + /// This user's average PPS (pieces per second) over the last 10 games in the season. + pub pps: f64, + /// This user's average VS (versus score) over the last 10 games in the season. + pub vs: f64, + /// The prisecter of this entry. + /// + /// A **prisecter** is consisting of three floats. + /// It allows you to continue paginating. + #[serde(rename = "p")] + pub prisecter: Prisecter, +} + +impl AsRef for HistoricalEntry { + fn as_ref(&self) -> &Self { + self + } +} From 391357d903f35bac506d11cf7685f6850c6aae8a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 8 Nov 2024 23:23:59 +0900 Subject: [PATCH 028/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`get=5Fu?= =?UTF-8?q?ser=5Frecords`=20method=20to=20`get=5Fuser=5Frecords=5Fold`=20[?= =?UTF-8?q?#43]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 2 +- src/model/searched_user.rs | 6 +++--- src/model/user.rs | 2 +- tests/client.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client.rs b/src/client.rs index aeee07d..975366d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -190,7 +190,7 @@ impl Client { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_user_records(self, user: &str) -> RspErr { + pub async fn get_user_records_old(self, user: &str) -> RspErr { let url = format!("{}users/{}/records", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index a2c78b0..b4ba1f0 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -61,7 +61,7 @@ impl SearchedUserResponse { /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_records(&self) -> Option> { if let Some(u) = &self.data { - Some(Client::new().get_user_records(u.user.id.id()).await) + Some(Client::new().get_user_records_old(u.user.id.id()).await) } else { None } @@ -141,7 +141,7 @@ impl UserData { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_records(&self) -> RspErr { - Client::new().get_user_records(self.user.id.id()).await + Client::new().get_user_records_old(self.user.id.id()).await } /// Returns the user's profile URL. @@ -194,7 +194,7 @@ impl UserInfo { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_records(&self) -> RspErr { - Client::new().get_user_records(self.id.id()).await + Client::new().get_user_records_old(self.id.id()).await } /// Returns the user's profile URL. diff --git a/src/model/user.rs b/src/model/user.rs index 1c8da32..0f99eea 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1655,7 +1655,7 @@ impl UserId { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_records(&self) -> RspErr { - Client::new().get_user_records(self.id()).await + Client::new().get_user_records_old(self.id()).await } } diff --git a/tests/client.rs b/tests/client.rs index eccbaa0..8e152c9 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -19,7 +19,7 @@ fn get_server_activity_data() { #[test] fn get_usr_records_data() { let usr = "rinrin-rs"; - let _ = tokio_test::block_on(Client::new().get_user_records(usr)); + let _ = tokio_test::block_on(Client::new().get_user_records_old(usr)); } #[test] From cabc4402b72b45adab48025ae22d844820dd85df Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 00:52:19 +0900 Subject: [PATCH 029/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`UserRec?= =?UTF-8?q?ordsResponse`=20struct=20to=20`UserRecordsOldResponse`=20struct?= =?UTF-8?q?=20[#43]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 4 ++-- src/model/searched_user.rs | 8 ++++---- src/model/user.rs | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/client.rs b/src/client.rs index 975366d..eebe5be 100644 --- a/src/client.rs +++ b/src/client.rs @@ -19,7 +19,7 @@ use crate::{ zenith::{ZenithExResponse, ZenithResponse}, AllSummariesResponse, }, - user::{UserRecordsResponse, UserResponse}, + user::{UserRecordsOldResponse, UserResponse}, xp_leaderboard::{self, XPLeaderboardResponse}, }, }; @@ -190,7 +190,7 @@ impl Client { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_user_records_old(self, user: &str) -> RspErr { + pub async fn get_user_records_old(self, user: &str) -> RspErr { let url = format!("{}users/{}/records", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index b4ba1f0..b9dde25 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -5,7 +5,7 @@ use crate::{ error::ResponseError, model::{ cache::CacheData, - user::{UserId, UserRecordsResponse, UserResponse}, + user::{UserId, UserRecordsOldResponse, UserResponse}, }, }; use serde::Deserialize; @@ -59,7 +59,7 @@ impl SearchedUserResponse { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> Option> { + pub async fn get_records(&self) -> Option> { if let Some(u) = &self.data { Some(Client::new().get_user_records_old(u.user.id.id()).await) } else { @@ -140,7 +140,7 @@ impl UserData { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> RspErr { + pub async fn get_records(&self) -> RspErr { Client::new().get_user_records_old(self.user.id.id()).await } @@ -193,7 +193,7 @@ impl UserInfo { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> RspErr { + pub async fn get_records(&self) -> RspErr { Client::new().get_user_records_old(self.id.id()).await } diff --git a/src/model/user.rs b/src/model/user.rs index 0f99eea..af64dc3 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -794,7 +794,7 @@ impl AsRef for AchievementRatingCounts { /// Describes the user records. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct UserRecordsResponse { +pub struct UserRecordsOldResponse { /// Whether the request was successful. #[serde(rename = "success")] pub is_success: bool, @@ -806,7 +806,7 @@ pub struct UserRecordsResponse { pub data: Option, } -impl UserRecordsResponse { +impl UserRecordsOldResponse { /// Whether the user has a 40 LINES record. /// /// # Panics @@ -1020,7 +1020,7 @@ impl UserRecordsResponse { } } -impl AsRef for UserRecordsResponse { +impl AsRef for UserRecordsOldResponse { fn as_ref(&self) -> &Self { self } @@ -1654,7 +1654,7 @@ impl UserId { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> RspErr { + pub async fn get_records(&self) -> RspErr { Client::new().get_user_records_old(self.id()).await } } From 5fedb706f0d9b4f5283c13fccee70b74535285ee Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 01:29:59 +0900 Subject: [PATCH 030/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20Recor?= =?UTF-8?q?d=20Data=20models=20[#43]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - πŸ› οΈ Update: `Record::user` field is now optional - ✨ Add: `Record::prisecter` field - πŸ› οΈ Update: type of `Extras::league` field is now `Option>>` --- src/model/summary/record.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index bf13cae..afeb757 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -1,7 +1,7 @@ //! The Record Data models. use crate::{ - model::{league::Rank, user::UserId}, + model::{leaderboard::Prisecter, league::Rank, user::UserId}, util::to_unix_ts, }; use serde::Deserialize; @@ -39,7 +39,7 @@ pub struct Record { /// If revolved away, the revolution it belongs to. pub revolution: Option, /// The user owning the Record. - pub user: User, + pub user: Option, // EXCEPTION /// Other users mentioned in the Record. /// /// If not empty, this is a multiplayer game @@ -57,6 +57,13 @@ pub struct Record { pub results: Results, /// Extra metadata for this Record: pub extras: Extras, + /// The prisecter of this entry + /// if this record is part of a paginated response. + /// + /// A **prisecter** is consisting of three floats. + /// It allows you to continue paginating. + #[serde(rename = "p")] + pub prisecter: Option, } impl Record { @@ -237,7 +244,7 @@ impl AsRef for PlayerStatsRound { #[non_exhaustive] pub struct Extras { /// A mapping of user IDs to before-and-afters, if user is being ranked. - pub league: Option>, + pub league: Option>>, /// The result of the game, from the owner's point of view. pub result: Option, /// Extra data for QUICK PLAY, From 4ad009e9728f394c99ba275d6b9d16eb7c36094f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 01:33:30 +0900 Subject: [PATCH 031/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fuser=5Frecord?= =?UTF-8?q?s`=20method=20[#43]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 370 ++++++++++++++++++++++++++++++++++++++ src/model/mod.rs | 1 + src/model/user_records.rs | 35 ++++ 3 files changed, 406 insertions(+) create mode 100644 src/model/user_records.rs diff --git a/src/client.rs b/src/client.rs index eebe5be..c2060a4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -20,6 +20,7 @@ use crate::{ AllSummariesResponse, }, user::{UserRecordsOldResponse, UserResponse}, + user_records::UserRecordsResponse, xp_leaderboard::{self, XPLeaderboardResponse}, }, }; @@ -615,6 +616,82 @@ impl Client { response(res).await } + /// Returns the list of Records fulfilling the search criteria. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// - `gamemode`: The game mode to look up. + /// - `leaderboard`: The personal leaderboard to look up. + /// - `search_criteria`: The search criteria to filter records by. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::{ + /// Client, + /// user_record::{RecordGamemode, LeaderboardType, RecordSearchCriteria} + /// }; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Set the search criteria. + /// let criteria = RecordSearchCriteria::new() + /// // Upper bound is `[500000, 0, 0]` + /// .after([500000.,0.,0.]) + /// // Three entries + /// .limit(3); + /// + /// // Get the User Records. + /// let user = client.get_user_records( + /// "rinrin-rs", + /// RecordGamemode::FortyLines, + /// LeaderboardType::Top, + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user_records( + self, + user: &str, + gamemode: user_record::RecordGamemode, + leaderboard: user_record::LeaderboardType, + search_criteria: Option, + ) -> RspErr { + let mut query_params = Vec::new(); + if let Some(criteria) = search_criteria { + if criteria.is_invalid_limit_range() { + panic!( + "The query parameter`limit` must be between 0 and 100.\n\ + Received: {}", + criteria.limit.unwrap() + ); + } + query_params = criteria.build(); + } + let url = format!( + "{}users/{}/records/{}/{}", + API_URL, + user.to_lowercase(), + gamemode.to_param(), + leaderboard.to_param() + ); + let res = self.client.get(url).query(&query_params).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments @@ -2182,6 +2259,299 @@ pub mod leaderboard { } } +pub mod user_record { + //! Features for user records. + + /// The game mode of records. + pub enum RecordGamemode { + /// 40 LINES records. + FortyLines, + /// BLITZ records. + Blitz, + /// QUICK PLAY records. + Zenith, + /// EXPERT QUICK PLAY records. + ZenithEx, + /// TETRA LEAGUE history. + League, + } + + impl RecordGamemode { + /// Converts into a parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::user_record::RecordGamemode; + /// let forty_lines = RecordGamemode::FortyLines; + /// let blitz = RecordGamemode::Blitz; + /// let zenith = RecordGamemode::Zenith; + /// let zenith_ex = RecordGamemode::ZenithEx; + /// let league = RecordGamemode::League; + /// assert_eq!(forty_lines.to_param(), "40l"); + /// assert_eq!(blitz.to_param(), "blitz"); + /// assert_eq!(zenith.to_param(), "zenith"); + /// assert_eq!(zenith_ex.to_param(), "zenithex"); + /// assert_eq!(league.to_param(), "league"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + RecordGamemode::FortyLines => "40l", + RecordGamemode::Blitz => "blitz", + RecordGamemode::Zenith => "zenith", + RecordGamemode::ZenithEx => "zenithex", + RecordGamemode::League => "league", + } + .to_string() + } + } + + /// The leaderboard type. + pub enum LeaderboardType { + /// The top scores. + Top, + /// The most recently placed records. + Recent, + /// The top scores (Personal Bests only). + Progression, + } + + impl LeaderboardType { + /// Converts into a parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::user_record::LeaderboardType; + /// let top = LeaderboardType::Top; + /// let recent = LeaderboardType::Recent; + /// let progression = LeaderboardType::Progression; + /// assert_eq!(top.to_param(), "top"); + /// assert_eq!(recent.to_param(), "recent"); + /// assert_eq!(progression.to_param(), "progression"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + LeaderboardType::Top => "top", + LeaderboardType::Recent => "recent", + LeaderboardType::Progression => "progression", + } + .to_string() + } + } + + /// The search criteria for the user records. + /// + /// # Examples + /// + /// ``` + /// use tetr_ch::client::user_record::RecordSearchCriteria; + /// + /// // Default search criteria. + /// let c1 = RecordSearchCriteria::new(); + /// + /// // Upper bound is `[500000, 0, 0]`, three entries. + /// let c2 = RecordSearchCriteria::new() + /// .after([500000., 0., 0.]) + /// .limit(3); + /// + /// // Lower bound is `[500000, 0, 0]`. + /// // Also the search order is reversed. + /// let c3 = RecordSearchCriteria::new() + /// .before([500000., 0., 0.]); + /// + /// // You can initialize the search criteria to default as follows: + /// let mut c4 = RecordSearchCriteria::new().limit(10); + /// c4.init(); + /// ``` + #[derive(Clone, Debug, Default)] + pub struct RecordSearchCriteria { + /// The bound to paginate. + pub bound: Option, + /// The amount of entries to return, + /// between 1 and 100. 25 by default. + pub limit: Option, + } + + impl RecordSearchCriteria { + /// Creates a new [`RecordSearchCriteria`]. + /// The values are set to default. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let criteria = RecordSearchCriteria::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Initializes the search criteria. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let mut criteria = RecordSearchCriteria::new(); + /// criteria.init(); + /// ``` + pub fn init(self) -> Self { + Self::default() + } + + /// Sets the upper bound. + /// + /// # Arguments + /// + /// - `bound`: The upper bound to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the upper bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let mut criteria = RecordSearchCriteria::new(); + /// criteria.after([500000.0, 0.0, 0.0]); + /// ``` + pub fn after(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(super::leaderboard::Bound::After(bound)), + ..self + } + } + + /// Sets the lower bound. + /// + /// # Arguments + /// + /// - `bound`: The lower bound to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the lower bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let mut criteria = RecordSearchCriteria::new(); + /// criteria.before([500000.0, 0.0, 0.0]); + /// ``` + pub fn before(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(super::leaderboard::Bound::Before(bound)), + ..self + } + } + + /// Limits the amount of entries to return. + /// + /// # Arguments + /// + /// - `limit`: The amount of entries to return. + /// Between 1 and 100. 25 by default. + /// + /// # Examples + /// + /// Limits the amount of entries to return to `10`. + /// + /// ``` + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let mut criteria = RecordSearchCriteria::new(); + /// criteria.limit(10); + /// ``` + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between `1` and `100`. + /// + /// ```should_panic + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let mut criteria = RecordSearchCriteria::new(); + /// criteria.limit(0); + /// ``` + /// + /// ```should_panic + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let mut criteria = RecordSearchCriteria::new(); + /// criteria.limit(101); + /// ``` + pub fn limit(self, limit: u8) -> Self { + if (1..=100).contains(&limit) { + Self { + limit: Some(limit), + ..self + } + } else { + panic!( + "The argument `limit` must be between 1 and 100.\n\ + Received: {}", + limit + ); + } + } + + /// Whether the search criteria `limit` is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let invalid_criteria = RecordSearchCriteria { + /// limit: Some(0), + /// ..RecordSearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + /// + /// ``` + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let invalid_criteria = RecordSearchCriteria { + /// limit: Some(101), + /// ..RecordSearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + pub fn is_invalid_limit_range(&self) -> bool { + if let Some(l) = self.limit { + !(1..=100).contains(&l) + } else { + false + } + } + + /// Builds the search criteria to `Vec<(String, String)>`. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::user_record::RecordSearchCriteria; + /// let criteria = RecordSearchCriteria::new(); + /// let query_params = criteria.build(); + /// ``` + pub(crate) fn build(self) -> Vec<(String, String)> { + let mut result = Vec::new(); + if let Some(b) = self.bound { + result.push(b.to_query_param()); + } + if let Some(l) = self.limit { + result.push(("limit".to_string(), l.to_string())); + } + result + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/model/mod.rs b/src/model/mod.rs index 871a195..cb236ce 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -12,4 +12,5 @@ pub mod server_stats; pub mod stream; pub mod summary; pub mod user; +pub mod user_records; pub mod xp_leaderboard; diff --git a/src/model/user_records.rs b/src/model/user_records.rs new file mode 100644 index 0000000..85a7639 --- /dev/null +++ b/src/model/user_records.rs @@ -0,0 +1,35 @@ +//! The User Personal Records models. + +use crate::model::{cache::CacheData, summary::record::Record}; +use serde::Deserialize; + +/// The response for the User Personal Records data. +/// +/// A list of Records fulfilling the search criteria. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct UserRecordsResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for UserRecordsResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The User Personal Records data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct UserRecords { + /// The matched records. + pub entries: Vec, +} From 26fd4dc17b2ec199adcdb9407c6fab01b4d7d2a4 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 02:50:37 +0900 Subject: [PATCH 032/255] =?UTF-8?q?=E2=9C=A8=20Add:=20models=20for=20Recor?= =?UTF-8?q?ds=20Leaderboard=20endpoint=20[#45]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/mod.rs | 1 + src/model/records_leaderboard.rs | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/model/records_leaderboard.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index cb236ce..db9e306 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -6,6 +6,7 @@ pub mod leaderboard; pub mod league; pub mod league_leaderboard; pub mod record; +pub mod records_leaderboard; pub mod searched_user; pub mod server_activity; pub mod server_stats; diff --git a/src/model/records_leaderboard.rs b/src/model/records_leaderboard.rs new file mode 100644 index 0000000..5c0c887 --- /dev/null +++ b/src/model/records_leaderboard.rs @@ -0,0 +1,35 @@ +//! The Records Leaderboard models. + +use crate::model::{cache::CacheData, summary::record::Record}; +use serde::Deserialize; + +/// The response for the Records Leaderboard data. +/// +/// A list of Records fulfilling the search criteria. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct RecordsLeaderboardResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for RecordsLeaderboardResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The Records Leaderboard data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct RecordsLeaderboard { + /// The matched records. + pub entries: Vec, +} From aad082b3296639d3a3507f127e78f0f9eaf7ed39 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 02:51:06 +0900 Subject: [PATCH 033/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Frecords=5Flea?= =?UTF-8?q?derboard`=20method=20[#45]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 351 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) diff --git a/src/client.rs b/src/client.rs index c2060a4..7a7820e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,6 +6,7 @@ use crate::{ latest_news::LatestNewsResponse, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, + records_leaderboard::RecordsLeaderboardResponse, searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, @@ -692,6 +693,78 @@ impl Client { response(res).await } + /// Returns the list of Records fulfilling the search criteria. + /// + /// # Arguments + /// + /// - `leaderboard`: The leaderboard to look up. + /// - `search_criteria`: The search criteria to filter records by. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::{ + /// Client, + /// records_leaderboard::{ + /// RecordsLeaderboardId, + /// RecordsLeaderboardSearchCriteria, + /// Scope + /// } + /// }; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Set the search criteria. + /// let criteria = RecordsLeaderboardSearchCriteria::new() + /// // Upper bound is `[500000, 0, 0]` + /// .after([500000.,0.,0.]) + /// // Three entries + /// .limit(3); + /// + /// // Get the Records Leaderboard. + /// let user = client.get_records_leaderboard( + /// RecordsLeaderboardId::new( + /// "blitz", + /// Scope::Country("JP".to_string()), + /// Some("@2024w31") + /// ), + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_records_leaderboard( + self, + leaderboard: records_leaderboard::RecordsLeaderboardId, + search_criteria: Option, + ) -> RspErr { + let mut query_params = Vec::new(); + if let Some(criteria) = search_criteria { + if criteria.is_invalid_limit_range() { + panic!( + "The query parameter`limit` must be between 0 and 100.\n\ + Received: {}", + criteria.limit.unwrap() + ); + } + query_params = criteria.build(); + } + let url = format!("{}records/{}", API_URL, leaderboard.to_param()); + let res = self.client.get(url).query(&query_params).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments @@ -2552,6 +2625,284 @@ pub mod user_record { } } +pub mod records_leaderboard { + //! Features for records leaderboards. + + /// The records leaderboard ID. + pub struct RecordsLeaderboardId { + /// The game mode. e.g. `40l`. + pub gamemode: String, + /// The scope. + pub scope: Scope, + /// An optional Revolution ID. e.g. `@2024w31`. + pub revolution_id: Option, + } + + impl RecordsLeaderboardId { + /// Creates a new [`RecordsLeaderboardId`]. + /// + /// # Arguments + /// + /// - `gamemode`: The game mode. e.g. `40l`. + /// - `scope`: The scope. ether [`Scope::Global`] or [`Scope::Country`]. + /// - `revolution_id`: An optional Revolution ID. e.g. `@2024w31`. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::{RecordsLeaderboardId, Scope}; + /// let id = RecordsLeaderboardId::new("40l", Scope::Global, None); + /// ``` + pub fn new(gamemode: &str, scope: Scope, revolution_id: Option<&str>) -> Self { + Self { + gamemode: gamemode.to_owned(), + scope, + revolution_id: revolution_id.map(|s| s.to_owned()), + } + } + + /// Converts into a parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::records_leaderboard::{RecordsLeaderboardId, Scope}; + /// let id1 = RecordsLeaderboardId::new("40l", Scope::Global, None); + /// let id2 = RecordsLeaderboardId::new("blitz", Scope::Country("JP".to_string()), None); + /// let id3 = RecordsLeaderboardId::new("zenith", Scope::Global, Some("@2024w31")); + /// assert_eq!(id1.to_param(), "40l_global"); + /// assert_eq!(id2.to_param(), "blitz_country_JP"); + /// assert_eq!(id3.to_param(), "zenith_global@2024w31"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match &self.scope { + Scope::Global => format!("{}_global", self.gamemode), + Scope::Country(c) => format!("{}_country_{}", self.gamemode, c.to_uppercase()), + } + } + } + + /// The scope of the records leaderboard. + pub enum Scope { + /// The global scope. + Global, + /// The country scope. + /// e.g. `JP`. + Country(String), + } + + /// The search criteria for the records leaderboard. + /// + /// # Examples + /// + /// ``` + /// use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// + /// // Default search criteria. + /// let c1 = RecordsLeaderboardSearchCriteria::new(); + /// + /// // Upper bound is `[500000, 0, 0]`, three entries. + /// let c2 = RecordsLeaderboardSearchCriteria::new() + /// .after([500000., 0., 0.]) + /// .limit(3); + /// + /// // Lower bound is `[500000, 0, 0]`. + /// // Also the search order is reversed. + /// let c3 = RecordsLeaderboardSearchCriteria::new() + /// .before([500000., 0., 0.]); + /// + /// // You can initialize the search criteria to default as follows: + /// let mut c4 = RecordsLeaderboardSearchCriteria::new().limit(10); + /// c4.init(); + /// ``` + #[derive(Clone, Debug, Default)] + pub struct RecordsLeaderboardSearchCriteria { + /// The bound to paginate. + pub bound: Option, + /// The amount of entries to return, + /// between 1 and 100. 25 by default. + pub limit: Option, + } + + impl RecordsLeaderboardSearchCriteria { + /// Creates a new [`RecordsLeaderboardSearchCriteria`]. + /// The values are set to default. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let criteria = RecordsLeaderboardSearchCriteria::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Initializes the search criteria. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); + /// criteria.init(); + /// ``` + pub fn init(self) -> Self { + Self::default() + } + + /// Sets the upper bound. + /// + /// # Arguments + /// + /// - `bound`: The upper bound to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the upper bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); + /// criteria.after([500000.0, 0.0, 0.0]); + /// ``` + pub fn after(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(super::leaderboard::Bound::After(bound)), + ..self + } + } + + /// Sets the lower bound. + /// + /// # Arguments + /// + /// - `bound`: The lower bound to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the lower bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); + /// criteria.before([500000.0, 0.0, 0.0]); + /// ``` + pub fn before(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(super::leaderboard::Bound::Before(bound)), + ..self + } + } + + /// Limits the amount of entries to return. + /// + /// # Arguments + /// + /// - `limit`: The amount of entries to return. + /// Between 1 and 100. 25 by default. + /// + /// # Examples + /// + /// Limits the amount of entries to return to `10`. + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); + /// criteria.limit(10); + /// ``` + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between `1` and `100`. + /// + /// ```should_panic + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); + /// criteria.limit(0); + /// ``` + /// + /// ```should_panic + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); + /// criteria.limit(101); + /// ``` + pub fn limit(self, limit: u8) -> Self { + if (1..=100).contains(&limit) { + Self { + limit: Some(limit), + ..self + } + } else { + panic!( + "The argument `limit` must be between 1 and 100.\n\ + Received: {}", + limit + ); + } + } + + /// Whether the search criteria `limit` is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let invalid_criteria = RecordsLeaderboardSearchCriteria { + /// limit: Some(0), + /// ..RecordsLeaderboardSearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + /// + /// ``` + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let invalid_criteria = RecordsLeaderboardSearchCriteria { + /// limit: Some(101), + /// ..RecordsLeaderboardSearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + pub fn is_invalid_limit_range(&self) -> bool { + if let Some(l) = self.limit { + !(1..=100).contains(&l) + } else { + false + } + } + + /// Builds the search criteria to `Vec<(String, String)>`. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; + /// let criteria = RecordsLeaderboardSearchCriteria::new(); + /// let query_params = criteria.build(); + /// ``` + pub(crate) fn build(self) -> Vec<(String, String)> { + let mut result = Vec::new(); + if let Some(b) = self.bound { + result.push(b.to_query_param()); + } + if let Some(l) = self.limit { + result.push(("limit".to_string(), l.to_string())); + } + result + } + } +} + #[cfg(test)] mod tests { use super::*; From 1d46fca9cab2c4d2663b9514aff8a6e47d1876c1 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 03:40:44 +0900 Subject: [PATCH 034/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`search=5Frecord`=20?= =?UTF-8?q?method=20[#47]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/client.rs b/src/client.rs index 7a7820e..657494d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -765,6 +765,60 @@ impl Client { response(res).await } + /// Searches for a record. + /// + /// Only one record is returned. + /// It is generally not possible for a player to play the same gamemode twice in a millisecond. + /// + /// # Arguments + /// + /// - `user_id`: The user ID to look up. + /// - `gamemode`: The game mode to look up. + /// - `timestamp`: The timestamp of the record to find. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::{Client, user_record::RecordGamemode}; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Get the User Record. + /// let user = client.search_record( + /// "621db46d1d638ea850be2aa0", + /// RecordGamemode::Blitz, + /// 1680053762145 + /// ).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn search_record( + self, + user_id: &str, + gamemode: user_record::RecordGamemode, + timestamp: i64, + ) -> RspErr { + let query_params = [ + ("user", user_id.to_string()), + ("gamemode", gamemode.to_param()), + ("ts", timestamp.to_string()), + ]; + let url = format!("{}records/reverse", API_URL); + let res = self.client.get(url).query(&query_params).send().await; + response(res).await + } + /// Returns the TETRA LEAGUE leaderboard model. /// /// # Arguments From cf32db0775bdf46c7d23322ec4d54bfc49b5914b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 04:35:14 +0900 Subject: [PATCH 035/255] =?UTF-8?q?=E2=9C=A8=20Add:=20models=20for=20"All?= =?UTF-8?q?=20Latest=20News"=20endpoint=20[#49]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/mod.rs | 1 + src/model/news.rs | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 src/model/news.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index db9e306..5590552 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,6 +5,7 @@ pub mod latest_news; pub mod leaderboard; pub mod league; pub mod league_leaderboard; +pub mod news; pub mod record; pub mod records_leaderboard; pub mod searched_user; diff --git a/src/model/news.rs b/src/model/news.rs new file mode 100644 index 0000000..670bb20 --- /dev/null +++ b/src/model/news.rs @@ -0,0 +1,200 @@ +//! The All Latest News models. + +use crate::model::{cache::CacheData, league::Rank}; +use serde::Deserialize; + +/// The response for the All Latest News data. +/// +/// The latest news items in any stream. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct NewsAllResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for NewsAllResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The All Latest News data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct NewsAll { + /// The latest news items. + pub news: Vec, +} + +impl AsRef for NewsAll { + fn as_ref(&self) -> &Self { + self + } +} + +/// A news. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct News { + /// The item's internal ID. + #[serde(rename = "_id")] + pub id: String, + /// The item's stream. + pub stream: String, + /// The item's type. + pub r#type: String, + /// The item's records. + pub data: NewsData, + /// The item's creation date. + #[serde(rename = "ts")] + pub created_at: String, +} + +impl AsRef for News { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data of a news item. +/// +/// News data may be stored in different enumerators depending on the type of news item. +/// +/// ***New news types may be added at any moment.** +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +#[non_exhaustive] +pub enum NewsData { + /// When a user's new personal best enters a global leaderboard. + /// Seen in the global stream only. + LeaderboardNews(LeaderboardNews), + /// When a user gets a personal best. Seen in user streams only. + PersonalBestNews(PersonalBestNews), + /// When a user gets a badge. + /// Seen in user streams only. + BadgeNews(BadgeNews), + /// When a user gets a new top rank in TETRA LEAGUE. + /// Seen in user streams only. + RankUpNews(RankUpNews), + /// When a user gets TETR.IO Supporter. Seen in user streams only. + SupporterNews(SupporterNews), + /// When a user is gifted TETR.IO Supporter. Seen in user streams only. + SupporterGiftNews(SupporterGiftNews), + /// An unknown news type. + Unknown(serde_json::Value), +} + +impl AsRef for NewsData { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data of a leaderboard news item. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LeaderboardNews { + /// The username of the person who got the leaderboard spot. + pub username: String, + /// The game mode played. + pub gametype: String, + /// The global rank achieved. + pub rank: u32, + /// The result (score or time) achieved. + pub result: f64, + /// The replay's shortID. + #[serde(rename = "replayid")] + pub replay_id: String, +} + +impl AsRef for LeaderboardNews { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data of a personal best news item. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct PersonalBestNews { + /// The username of the player. + pub username: String, + /// The game mode played. + pub gametype: String, + /// The result (score or time) achieved. + pub result: f64, + /// The replay's shortID. + #[serde(rename = "replayid")] + pub replay_id: String, +} + +impl AsRef for PersonalBestNews { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data of a badge news item. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct BadgeNews { + /// The username of the player. + pub username: String, + /// The badge's internal ID, and the filename of the badge icon + /// (all PNGs within `/res/badges/`) + pub r#type: String, + /// The badge's label. + pub label: String, +} + +impl AsRef for BadgeNews { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data of a rank up news item. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct RankUpNews { + /// The username of the player. + pub username: String, + /// The new rank. + pub rank: Rank, +} + +impl AsRef for RankUpNews { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data of a supporter news item. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct SupporterNews { + /// The username of the player. + pub username: String, +} + +impl AsRef for SupporterNews { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data of a supporter gift news item. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct SupporterGiftNews { + /// The username of the recipient. + pub username: String, +} From a5b4070431e5c2e045704ba6883b46ca6e2e4550 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 04:35:54 +0900 Subject: [PATCH 036/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fnews=5Fall`?= =?UTF-8?q?=20method=20[#49]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/client.rs b/src/client.rs index 657494d..adc8377 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,6 +6,7 @@ use crate::{ latest_news::LatestNewsResponse, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, + news::NewsAllResponse, records_leaderboard::RecordsLeaderboardResponse, searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, @@ -1148,6 +1149,54 @@ impl Client { response(res).await } + /// Returns the latest news items in any stream. + /// + /// # Arguments + /// + /// - `limit`:The amount of entries to return, + /// between 1 and 100. 25 by default. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Get the All Latest News. + /// let user = client.get_news_all(Some(3)).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Error + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_news_all(self, limit: Option) -> RspErr { + let mut query_param = Vec::new(); + if let Some(l) = limit { + if !(1..=100).contains(&l) { + // !(1 <= limit && limit <= 100) + panic!( + "The query parameter`limit` must be between 1 and 100.\n\ + Received: {}", + l + ); + } + query_param.push(("limit", l.to_string())); + } + let url = format!("{}news/", API_URL); + let res = self.client.get(url).query(&query_param).send().await; + response(res).await + } + /// Returns the latest news model. /// /// # Arguments From e79b35af8cc996e75a24649bc7162ec49c5a5657 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 04:48:00 +0900 Subject: [PATCH 037/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`get=5Fl?= =?UTF-8?q?atest=5Fnews`=20method=20to=20`get=5Fnews=5Flatest`=20[#51]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 +++--- tests/client.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client.rs b/src/client.rs index adc8377..f9c6d75 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1224,7 +1224,7 @@ impl Client { /// let client = Client::new(); /// /// // Get the latest news. - /// let user = client.get_latest_news( + /// let user = client.get_news_latest( /// // News of the user `621db46d1d638ea850be2aa0`. /// NewsSubject::User("621db46d1d638ea850be2aa0".to_string()), /// // three news. @@ -1256,7 +1256,7 @@ impl Client { /// # async fn run() -> io::Result<()> { /// let client = Client::new(); /// - /// let user = client.get_latest_news( + /// let user = client.get_news_latest( /// NewsSubject::User("621db46d1d638ea850be2aa0".to_string()), /// // 101 news. /// 101, @@ -1266,7 +1266,7 @@ impl Client { /// /// # tokio_test::block_on(run()); /// ``` - pub async fn get_latest_news( + pub async fn get_news_latest( self, subject: stream::NewsSubject, limit: u8, diff --git a/tests/client.rs b/tests/client.rs index 8e152c9..4fc5079 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -128,17 +128,17 @@ fn get_user_40l_best_stream_data() { #[test] fn get_latest_any_news_data() { - let _ = tokio_test::block_on(Client::new().get_latest_news(stream::NewsSubject::Any, 3)); + let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsSubject::Any, 3)); } #[test] fn get_latest_global_news_data() { - let _ = tokio_test::block_on(Client::new().get_latest_news(stream::NewsSubject::Global, 3)); + let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsSubject::Global, 3)); } #[test] fn get_latest_user_scale_news_data() { - let _ = tokio_test::block_on(Client::new().get_latest_news( + let _ = tokio_test::block_on(Client::new().get_news_latest( stream::NewsSubject::User("621db46d1d638ea850be2aa0".to_string()), 3, )); @@ -147,5 +147,5 @@ fn get_latest_user_scale_news_data() { #[test] #[should_panic] fn panic_if_invalid_limit_range_in_getting_latest_news() { - let _ = tokio_test::block_on(Client::new().get_latest_news(stream::NewsSubject::Any, 0)); + let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsSubject::Any, 0)); } From 23666946e8141b11e1c7136ab03d45a8d831d13a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 05:10:55 +0900 Subject: [PATCH 038/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`NewsAll?= =?UTF-8?q?`=20struct=20to=20`NewsItems`=20[#51]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index 670bb20..d4eaf18 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -17,7 +17,7 @@ pub struct NewsAllResponse { /// Data about how this request was cached. pub cache: Option, /// The requested data. - pub data: Option, + pub data: Option, } impl AsRef for NewsAllResponse { @@ -29,12 +29,12 @@ impl AsRef for NewsAllResponse { /// The All Latest News data. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct NewsAll { +pub struct NewsItems { /// The latest news items. pub news: Vec, } -impl AsRef for NewsAll { +impl AsRef for NewsItems { fn as_ref(&self) -> &Self { self } From 2211dcb842cbda93fd9326c6bfb406887b1ad30d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 05:17:05 +0900 Subject: [PATCH 039/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fnews=5Flatest?= =?UTF-8?q?`=20method=20[#51]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 73 ++++++++++++++++++++++++----------------------- src/model/news.rs | 23 +++++++++++++++ tests/client.rs | 8 +++--- 3 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/client.rs b/src/client.rs index f9c6d75..3139e3e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,10 +3,9 @@ use crate::{ error::{ResponseError, Status}, model::{ - latest_news::LatestNewsResponse, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, - news::NewsAllResponse, + news::{NewsAllResponse, NewsLatestResponse}, records_leaderboard::RecordsLeaderboardResponse, searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, @@ -1197,27 +1196,18 @@ impl Client { response(res).await } - /// Returns the latest news model. + /// Returns latest news items in the stream. /// /// # Arguments /// - /// - `subject`: + /// - `stream`: The news stream to look up. /// - /// The news subject. - /// This argument requires a [`stream::NewsSubject`]. - /// - /// - `limit`: - /// - /// The amount of entries to return. - /// Between 1 and 100. - /// 25 by default. + /// - `limit`: The amount of entries to return, between 1 and 100. /// /// # Examples /// - /// Getting the latest news object: - /// /// ```no_run - /// use tetr_ch::client::{Client, stream::NewsSubject}; + /// use tetr_ch::client::{Client, stream::NewsStream}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { @@ -1226,7 +1216,7 @@ impl Client { /// // Get the latest news. /// let user = client.get_news_latest( /// // News of the user `621db46d1d638ea850be2aa0`. - /// NewsSubject::User("621db46d1d638ea850be2aa0".to_string()), + /// NewsStream::User("621db46d1d638ea850be2aa0".to_string()), /// // three news. /// 3, /// ).await?; @@ -1234,8 +1224,6 @@ impl Client { /// # } /// ``` /// - /// Go to [`stream::StreamType`] | [`stream::StreamContext`]. - /// /// # Errors /// /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, @@ -1247,18 +1235,18 @@ impl Client { /// /// # Panics /// - /// Panics if the query parameter`limit` is not between 1 and 100. + /// Panics if the query parameter `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// use tetr_ch::client::{Client, stream::NewsSubject}; + /// use tetr_ch::client::{Client, stream::NewsStream}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { /// let client = Client::new(); /// /// let user = client.get_news_latest( - /// NewsSubject::User("621db46d1d638ea850be2aa0".to_string()), - /// // 101 news. + /// NewsStream::Global, + /// // 101 news. (not allowed) /// 101, /// ).await?; /// # Ok(()) @@ -1268,9 +1256,9 @@ impl Client { /// ``` pub async fn get_news_latest( self, - subject: stream::NewsSubject, + stream: stream::NewsStream, limit: u8, - ) -> RspErr { + ) -> RspErr { if !(1..=100).contains(&limit) { // !(1 <= limit && limit <= 100) panic!( @@ -1279,15 +1267,10 @@ impl Client { limit ); } - use stream::NewsSubject; let url = format!( - "{}/news/{}", + "{}news/{}", API_URL, - match subject { - NewsSubject::Any => String::new(), - NewsSubject::Global => "global".to_string(), - NewsSubject::User(id) => format!("user_{}", id), - } + stream.to_param() ); let res = self.client.get(url).query(&[("limit", limit)]).send().await; response(res).await @@ -2082,15 +2065,33 @@ pub mod stream { } /// The news subject. - pub enum NewsSubject { - /// News of all users - Any, + pub enum NewsStream { /// Global news. Global, - /// The news of the user. - /// Enter the user's **ID** to `String`. + /// News of the user. + /// The user ID is required. User(String), } + + impl NewsStream { + /// Converts into a parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::stream::NewsStream; + /// let global = NewsStream::Global; + /// let user = NewsStream::User("621db46d1d638ea850be2aa0".to_string()); + /// assert_eq!(global.to_param(), "global"); + /// assert_eq!(user.to_param(), "user_621db46d1d638ea850be2aa0"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + NewsStream::Global => "global".to_string(), + NewsStream::User(id) => format!("user_{}", id), + } + } + } } pub mod search_user { diff --git a/src/model/news.rs b/src/model/news.rs index d4eaf18..25dee7b 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -198,3 +198,26 @@ pub struct SupporterGiftNews { /// The username of the recipient. pub username: String, } + +/// The response for the Latest News data. +/// +/// The latest news items in the stream. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct NewsLatestResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for NewsLatestResponse { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/tests/client.rs b/tests/client.rs index 4fc5079..2b254ca 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -128,18 +128,18 @@ fn get_user_40l_best_stream_data() { #[test] fn get_latest_any_news_data() { - let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsSubject::Any, 3)); + let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Any, 3)); } #[test] fn get_latest_global_news_data() { - let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsSubject::Global, 3)); + let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Global, 3)); } #[test] fn get_latest_user_scale_news_data() { let _ = tokio_test::block_on(Client::new().get_news_latest( - stream::NewsSubject::User("621db46d1d638ea850be2aa0".to_string()), + stream::NewsStream::User("621db46d1d638ea850be2aa0".to_string()), 3, )); } @@ -147,5 +147,5 @@ fn get_latest_user_scale_news_data() { #[test] #[should_panic] fn panic_if_invalid_limit_range_in_getting_latest_news() { - let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsSubject::Any, 0)); + let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Any, 0)); } From c9c1cdbf861fbc8ab7e6f54e9a8df84f3564a9e1 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 05:18:13 +0900 Subject: [PATCH 040/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 +----- src/model/news.rs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/client.rs b/src/client.rs index 3139e3e..1f85f22 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1267,11 +1267,7 @@ impl Client { limit ); } - let url = format!( - "{}news/{}", - API_URL, - stream.to_param() - ); + let url = format!("{}news/{}", API_URL, stream.to_param()); let res = self.client.get(url).query(&[("limit", limit)]).send().await; response(res).await } diff --git a/src/model/news.rs b/src/model/news.rs index 25dee7b..8aeea11 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -205,19 +205,19 @@ pub struct SupporterGiftNews { #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct NewsLatestResponse { - /// Whether the request was successful. - #[serde(rename = "success")] - pub is_success: bool, - /// The reason the request failed. - pub error: Option, - /// Data about how this request was cached. - pub cache: Option, - /// The requested data. - pub data: Option, + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, } impl AsRef for NewsLatestResponse { - fn as_ref(&self) -> &Self { - self - } + fn as_ref(&self) -> &Self { + self + } } From ed2225ecba3b97071886f355ea72da0c7a9abf48 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 05:21:12 +0900 Subject: [PATCH 041/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20integ?= =?UTF-8?q?ration=20test=20for=20`get=5Fnews=5Flatest`=20method=20[#51]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/client.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/client.rs b/tests/client.rs index 2b254ca..b8843e4 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -126,11 +126,6 @@ fn get_user_40l_best_stream_data() { )); } -#[test] -fn get_latest_any_news_data() { - let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Any, 3)); -} - #[test] fn get_latest_global_news_data() { let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Global, 3)); @@ -147,5 +142,5 @@ fn get_latest_user_scale_news_data() { #[test] #[should_panic] fn panic_if_invalid_limit_range_in_getting_latest_news() { - let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Any, 0)); + let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Global, 0)); } From 5299ba2239a61704d415bbee1e402b01e5a7de41 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 06:01:47 +0900 Subject: [PATCH 042/255] =?UTF-8?q?=E2=9C=A8=20Add:=20models=20for=20Labs?= =?UTF-8?q?=20Scoreflow=20endpoint=20[#53]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/labs/mod.rs | 3 +++ src/model/labs/scoreflow.rs | 50 +++++++++++++++++++++++++++++++++++++ src/model/mod.rs | 1 + 3 files changed, 54 insertions(+) create mode 100644 src/model/labs/mod.rs create mode 100644 src/model/labs/scoreflow.rs diff --git a/src/model/labs/mod.rs b/src/model/labs/mod.rs new file mode 100644 index 0000000..ed7f837 --- /dev/null +++ b/src/model/labs/mod.rs @@ -0,0 +1,3 @@ +//! Easy-to-use models of the various objects returned by the Labs API endpoint. + +pub mod scoreflow; diff --git a/src/model/labs/scoreflow.rs b/src/model/labs/scoreflow.rs new file mode 100644 index 0000000..e4b5fef --- /dev/null +++ b/src/model/labs/scoreflow.rs @@ -0,0 +1,50 @@ +//! The Labs Scoreflow models. + +use crate::model::cache::CacheData; +use serde::Deserialize; + +/// The response for the Labs Scoreflow data. +/// +/// A condensed graph of all of the user's records in the gamemode. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LabsScoreflowResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for LabsScoreflowResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The Labs Scoreflow data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LabsScoreflow { + /// The timestamp of the oldest record found. + #[serde(rename = "startTime")] + pub oldest_record_ts: f64, + /// The points in the chart. + /// + /// - 0: The timestamp offset. + /// Add the [`LabsScoreflow::oldest_record_ts`] to get the true timestamp. + /// - 1: Whether the score set was a Personal Best. + /// 0 = not a Personal Best, 1 = Personal Best. + /// - 2: The score achieved. (For 40 LINES, this is negative.) + pub points: Vec<[i64; 3]>, +} + +impl AsRef for LabsScoreflow { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 5590552..e3ca3ae 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,6 +1,7 @@ //! Easy-to-use models of the various objects returned by the API. pub mod cache; +pub mod labs; pub mod latest_news; pub mod leaderboard; pub mod league; From d1f4da58e8af543457a218ffc5664374a7eea979 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 06:02:08 +0900 Subject: [PATCH 043/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Flabs=5Fscoref?= =?UTF-8?q?low`=20method=20[#53]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/client.rs b/src/client.rs index 1f85f22..a1e5d7c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,6 +3,7 @@ use crate::{ error::{ResponseError, Status}, model::{ + labs::scoreflow::LabsScoreflowResponse, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, news::{NewsAllResponse, NewsLatestResponse}, @@ -1316,6 +1317,55 @@ impl Client { let res = self.client.get(url).send().await; response(res).await } + + /// Returns the condensed graph of all of the user's records in the gamemode. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// - `gamemode`: The game mode to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::{Client, user_record::RecordGamemode}; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Get the Labs Scoreflow. + /// + /// let user = client.get_labs_scoreflow( + /// "rinrin-rs", + /// RecordGamemode::FortyLines + /// ).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_labs_scoreflow( + self, + user: &str, + gamemode: user_record::RecordGamemode, + ) -> RspErr { + let url = format!( + "{}labs/scoreflow/{}/{}", + API_URL, + user.to_lowercase(), + gamemode.to_param() + ); + let res = self.client.get(url).send().await; + response(res).await + } } /// Receives `Result` and returns `Result`. From 401cba62a9173633999be7d4abcd2125154c3617 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 9 Nov 2024 06:17:49 +0900 Subject: [PATCH 044/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Flabs=5Fleague?= =?UTF-8?q?flow`=20methods=20[#55]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 38 ++++++++++++++++++++++-- src/model/labs/leagueflow.rs | 56 ++++++++++++++++++++++++++++++++++++ src/model/labs/mod.rs | 1 + 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/model/labs/leagueflow.rs diff --git a/src/client.rs b/src/client.rs index a1e5d7c..fc1723c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,7 +3,7 @@ use crate::{ error::{ResponseError, Status}, model::{ - labs::scoreflow::LabsScoreflowResponse, + labs::{leagueflow::LabsLeagueflowResponse, scoreflow::LabsScoreflowResponse}, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, news::{NewsAllResponse, NewsLatestResponse}, @@ -1335,7 +1335,6 @@ impl Client { /// let client = Client::new(); /// /// // Get the Labs Scoreflow. - /// /// let user = client.get_labs_scoreflow( /// "rinrin-rs", /// RecordGamemode::FortyLines @@ -1366,6 +1365,41 @@ impl Client { let res = self.client.get(url).send().await; response(res).await } + + /// Returns the condensed graph of all of the user's matches in TETRA LEAGUE. + /// + /// # Arguments + /// + /// - `user`: The username or user ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Get the Labs Leagueflow. + /// let user = client.get_labs_leagueflow("rinrin-rs").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_labs_leagueflow(self, user: &str) -> RspErr { + let url = format!("{}labs/leagueflow/{}", API_URL, user.to_lowercase()); + let res = self.client.get(url).send().await; + response(res).await + } } /// Receives `Result` and returns `Result`. diff --git a/src/model/labs/leagueflow.rs b/src/model/labs/leagueflow.rs new file mode 100644 index 0000000..b84c1b7 --- /dev/null +++ b/src/model/labs/leagueflow.rs @@ -0,0 +1,56 @@ +//! The Labs Leagueflow models. + +use crate::model::cache::CacheData; +use serde::Deserialize; + +/// The response for the Labs Leagueflow data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LabsLeagueflowResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for LabsLeagueflowResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The Labs Leagueflow data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LabsLeagueflow { + /// The timestamp of the oldest record found. + #[serde(rename = "startTime")] + pub oldest_record_ts: f64, + /// The points in the chart. + /// + /// - 0: The timestamp offset. + /// Add the [`LabsLeagueflow::oldest_record_ts`] to get the true timestamp. + /// - 1: The result of the match, where: + /// - 1: victory + /// - 2: defeat + /// - 3: victory by disqualification + /// - 4: defeat by disqualification + /// - 5: tie + /// - 6: no contest + /// - 7: match nullified + /// - 2: The user's TR after the match. + /// - 3: The opponent's TR before the match. + /// (If the opponent was unranked, same as 2.) + pub points: Vec<[i64; 4]>, +} + +impl AsRef for LabsLeagueflow { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/labs/mod.rs b/src/model/labs/mod.rs index ed7f837..c958969 100644 --- a/src/model/labs/mod.rs +++ b/src/model/labs/mod.rs @@ -1,3 +1,4 @@ //! Easy-to-use models of the various objects returned by the Labs API endpoint. +pub mod leagueflow; pub mod scoreflow; From 9e27724dc92e27cb28a68f3cb4f7bcc25d253712 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 19:33:52 +0900 Subject: [PATCH 045/255] =?UTF-8?q?=E2=9C=A8=20Add:=20models=20for=20Labs?= =?UTF-8?q?=20League=20Ranks=20endpoint=20[#57]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/labs/league_ranks.rs | 149 +++++++++++++++++++++++++++++++++ src/model/labs/mod.rs | 1 + 2 files changed, 150 insertions(+) create mode 100644 src/model/labs/league_ranks.rs diff --git a/src/model/labs/league_ranks.rs b/src/model/labs/league_ranks.rs new file mode 100644 index 0000000..f51e48e --- /dev/null +++ b/src/model/labs/league_ranks.rs @@ -0,0 +1,149 @@ +//! The Labs League Ranks models. + +use crate::model::cache::CacheData; +use serde::Deserialize; + +/// The response for the Labs League Ranks data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LabsLeagueRanksResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for LabsLeagueRanksResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The Labs League Ranks data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LabsLeagueRanks { + /// The internal ID of the Labs data point. + #[serde(rename = "_id")] + pub id: String, + /// The stream ID the Labs data point belongs to. + #[serde(rename = "s")] + pub stream_id: String, + /// The time at which the data point was created. + #[serde(rename = "t")] + pub created_at: String, + /// The data point. + pub data: LeagueRanksData, +} + +impl AsRef for LabsLeagueRanks { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data point for the Labs League Ranks data. +/// +/// If there are any unwrapped ranks, please create an Issue on GitHub. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct LeagueRanksData { + /// The total amount of players. + pub total: u32, + /// The data of the X+ rank. + #[serde(rename = "x+")] + pub rank_x_plus: RankData, + /// The data of the X rank. + #[serde(rename = "x")] + pub rank_x: RankData, + /// The data of the U rank. + #[serde(rename = "u")] + pub rank_u: RankData, + /// The data of the SS rank. + #[serde(rename = "ss")] + pub rank_ss: RankData, + /// The data of the S+ rank. + #[serde(rename = "s+")] + pub rank_s_plus: RankData, + /// The data of the S rank. + #[serde(rename = "s")] + pub rank_s: RankData, + /// The data of the S- rank. + #[serde(rename = "s-")] + pub rank_s_minus: RankData, + /// The data of the A+ rank. + #[serde(rename = "a+")] + pub rank_a_plus: RankData, + /// The data of the A rank. + #[serde(rename = "a")] + pub rank_a: RankData, + /// The data of the A- rank. + #[serde(rename = "a-")] + pub rank_a_minus: RankData, + /// The data of the B+ rank. + #[serde(rename = "b+")] + pub rank_b_plus: RankData, + /// The data of the B rank. + #[serde(rename = "b")] + pub rank_b: RankData, + /// The data of the B- rank. + #[serde(rename = "b-")] + pub rank_b_minus: RankData, + /// The data of the C+ rank. + #[serde(rename = "c+")] + pub rank_c_plus: RankData, + /// The data of the C rank. + #[serde(rename = "c")] + pub rank_c: RankData, + /// The data of the C- rank. + #[serde(rename = "c-")] + pub rank_c_minus: RankData, + /// The data of the D+ rank. + #[serde(rename = "d+")] + pub rank_d_plus: RankData, + /// The data of the D rank. + #[serde(rename = "d")] + pub rank_d: RankData, +} + +impl AsRef for LeagueRanksData { + fn as_ref(&self) -> &Self { + self + } +} + +/// The data for a rank. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct RankData { + /// The leaderboard position required to attain this rank. + #[serde(rename = "pos")] + pub position: u32, + /// The percentile (0~1) this rank is for. + pub percentile: f64, + /// The TR required to obtain a leaderboard position that will award this rank. + #[serde(rename = "tr")] + pub tr: f64, + /// The TR this rank will gravitate toward (using de- and inflation zones). + #[serde(rename = "targettr")] + pub target_tr: f64, + /// The average APM across all players in this rank. + pub apm: Option, + /// The average PPS across all players in this rank. + pub pps: Option, + /// The average Versus Score across all players in this rank. + pub vs: Option, + /// The amount of players with this rank. + pub count: u32, +} + +impl AsRef for RankData { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/labs/mod.rs b/src/model/labs/mod.rs index c958969..4f60f42 100644 --- a/src/model/labs/mod.rs +++ b/src/model/labs/mod.rs @@ -1,4 +1,5 @@ //! Easy-to-use models of the various objects returned by the Labs API endpoint. pub mod leagueflow; +pub mod league_ranks; pub mod scoreflow; From 8a94348b9c4781eb0f58af44bb55af38d367df6d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 19:34:22 +0900 Subject: [PATCH 046/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Flabs=5Fleague?= =?UTF-8?q?=5Franks`=20method=20[#57]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index fc1723c..85e99d8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,7 +3,7 @@ use crate::{ error::{ResponseError, Status}, model::{ - labs::{leagueflow::LabsLeagueflowResponse, scoreflow::LabsScoreflowResponse}, + labs::{league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, scoreflow::LabsScoreflowResponse}, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, news::{NewsAllResponse, NewsLatestResponse}, @@ -1400,6 +1400,37 @@ impl Client { let res = self.client.get(url).send().await; response(res).await } + + /// Returns the view over all TETRA LEAGUE ranks and their metadata. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Get the Labs League Ranks. + /// let user = client.get_labs_league_ranks().await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_labs_league_ranks(self) -> RspErr { + let url = format!("{}labs/league_ranks", API_URL); + let res = self.client.get(url).send().await; + response(res).await + } } /// Receives `Result` and returns `Result`. From d0bac6e7d5750fdfd15532e545cf975715b2114e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 19:35:18 +0900 Subject: [PATCH 047/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 5 ++++- src/model/labs/mod.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 85e99d8..5e9fe1e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,7 +3,10 @@ use crate::{ error::{ResponseError, Status}, model::{ - labs::{league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, scoreflow::LabsScoreflowResponse}, + labs::{ + league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, + scoreflow::LabsScoreflowResponse, + }, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, league_leaderboard::{self, LeagueLeaderboardResponse}, news::{NewsAllResponse, NewsLatestResponse}, diff --git a/src/model/labs/mod.rs b/src/model/labs/mod.rs index 4f60f42..851833d 100644 --- a/src/model/labs/mod.rs +++ b/src/model/labs/mod.rs @@ -1,5 +1,5 @@ //! Easy-to-use models of the various objects returned by the Labs API endpoint. -pub mod leagueflow; pub mod league_ranks; +pub mod leagueflow; pub mod scoreflow; From 9c76219b8020b4c278cde102a79ec21083479944 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:09:30 +0900 Subject: [PATCH 048/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20`Achi?= =?UTF-8?q?evement::order`=20field=20is=20now=20optional=20[#59]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/achievements.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs index 9e49aef..1f41cba 100644 --- a/src/model/summary/achievements.rs +++ b/src/model/summary/achievements.rs @@ -42,7 +42,7 @@ pub struct Achievement { pub desc: String, /// The order of this achievement in its category. #[serde(rename = "o")] - pub order: u32, + pub order: Option, // EXCEPTION /// The rank type of this achievement. /// /// - 1 = PERCENTILE β€” ranked by percentile cutoffs (5% Diamond, 10% Platinum, 30% Gold, 50% Silver, 70% Bronze) From 79bfda2ba5ffb0284f055437322d37448935d85b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:09:53 +0900 Subject: [PATCH 049/255] =?UTF-8?q?=E2=9C=A8=20Add:=20models=20for=20Achie?= =?UTF-8?q?vement=20Info=20endpoint=20[#59]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 123 ++++++++++++++++++++++++++++++++++ src/model/mod.rs | 1 + 2 files changed, 124 insertions(+) create mode 100644 src/model/achievement_info.rs diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs new file mode 100644 index 0000000..971ca1b --- /dev/null +++ b/src/model/achievement_info.rs @@ -0,0 +1,123 @@ +//! The Achievement Info models. + +use crate::model::{ + cache::CacheData, + summary::achievements::Achievement, + user::UserId, +}; +use serde::Deserialize; + +use super::user::Role; + +/// The response for the Achievement Info data. +/// +/// Data about the achievement itself, its cutoffs, and its leaderboard. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct AchievementInfoResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} + +impl AsRef for AchievementInfoResponse { + fn as_ref(&self) -> &Self { + self + } +} + +/// The Achievement Info data. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct AchievementInfo { + /// The achievement info. + pub achievement: Achievement, + /// The entries in the achievement's leaderboard. + pub leaderboard: Vec, + /// Scores required to obtain the achievement: + pub cutoffs: Cutoffs, +} + +impl AsRef for AchievementInfo { + fn as_ref(&self) -> &Self { + self + } +} + +/// An entry in an achievement's leaderboard. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct AchievementLeaderboardEntry { + /// The user owning the achievement. + #[serde(rename = "u")] + pub user: User, + /// The achieved score in the achievement. + #[serde(rename = "v")] + pub value: f64, + /// Additional score for the achievement. + #[serde(rename = "a")] + pub additional_value: Option, + /// The time the achievement was last updated. + #[serde(rename = "t")] + pub last_updated_at: String, +} + +impl AsRef for AchievementLeaderboardEntry { + fn as_ref(&self) -> &Self { + self + } +} + +/// A user owning an achievement. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct User { + /// The user's internal ID. + #[serde(rename = "_id")] + pub id : UserId, + /// The user's username. + pub username: String, + /// The user's role. + pub role: Role, + /// Whether the user is supporting TETR.IO. + #[serde(rename = "supporter")] + pub is_supporter: bool, + /// The user's country, if public. + pub country: Option, +} + +impl AsRef for User { + fn as_ref(&self) -> &Self { + self + } +} + +/// Scores required to obtain the achievement. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Cutoffs { + /// The total amount of users with this achievement. + pub total: u32, + /// If applicable, the score required to obtain a Diamond rank. + pub diamond: Option, + /// If applicable, the score required to obtain a Platinum rank. + pub platinum: Option, + /// If applicable, the score required to obtain a Gold rank. + pub gold: Option, + /// If applicable, the score required to obtain a Silver rank. + pub silver: Option, + /// If applicable, the score required to obtain a Bronze rank. + pub bronze: Option, +} + +impl AsRef for Cutoffs { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index e3ca3ae..d725bf7 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,5 +1,6 @@ //! Easy-to-use models of the various objects returned by the API. +pub mod achievement_info; pub mod cache; pub mod labs; pub mod latest_news; From 066b16b72b669eae4ce154f1ff44dea17e054551 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:10:14 +0900 Subject: [PATCH 050/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`get=5Fachievement?= =?UTF-8?q?=5Finfo`=20method=20[#59]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/client.rs b/src/client.rs index 5e9fe1e..0cf2c39 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,6 +3,7 @@ use crate::{ error::{ResponseError, Status}, model::{ + achievement_info::AchievementInfoResponse, labs::{ league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, scoreflow::LabsScoreflowResponse, @@ -1434,6 +1435,42 @@ impl Client { let res = self.client.get(url).send().await; response(res).await } + + /// Returns the data about the achievement itself, its cutoffs, and its leaderboard. + /// + /// # Arguments + /// + /// - `achievement_id`: The achievement ID to look up. + /// + /// # Examples + /// + /// ```no_run + /// use tetr_ch::client::Client; + /// # use std::io; + /// + /// # async fn run() -> io::Result<()> { + /// let client = Client::new(); + /// + /// // Get the Achievement Info. + /// let user = client.get_achievement_info("15").await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_achievement_info(self, achievement_id: &str) -> RspErr { + let url = format!("{}achievements/{}", API_URL, achievement_id); + let res = self.client.get(url).send().await; + response(res).await + } } /// Receives `Result` and returns `Result`. From 05a1f1179ad22b5eea1b41a535ae396f9860d89c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:10:49 +0900 Subject: [PATCH 051/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 5 ++++- src/model/achievement_info.rs | 8 ++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/client.rs b/src/client.rs index 0cf2c39..3c94c72 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1466,7 +1466,10 @@ impl Client { /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_achievement_info(self, achievement_id: &str) -> RspErr { + pub async fn get_achievement_info( + self, + achievement_id: &str, + ) -> RspErr { let url = format!("{}achievements/{}", API_URL, achievement_id); let res = self.client.get(url).send().await; response(res).await diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 971ca1b..70304c9 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -1,10 +1,6 @@ //! The Achievement Info models. -use crate::model::{ - cache::CacheData, - summary::achievements::Achievement, - user::UserId, -}; +use crate::model::{cache::CacheData, summary::achievements::Achievement, user::UserId}; use serde::Deserialize; use super::user::Role; @@ -80,7 +76,7 @@ impl AsRef for AchievementLeaderboardEntry { pub struct User { /// The user's internal ID. #[serde(rename = "_id")] - pub id : UserId, + pub id: UserId, /// The user's username. pub username: String, /// The user's role. From 61370951ee26446f4ffe26c8e7cf819316ebe29a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:32:17 +0900 Subject: [PATCH 052/255] =?UTF-8?q?=E2=9C=A8=20Add:=20X+=20rank=20[#61]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.rs | 3 +++ src/model/league.rs | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/constants.rs b/src/constants.rs index a65d1f7..f257113 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -58,6 +58,9 @@ pub mod rank_col { /// The X rank color. /// #ff45ff pub const X: u32 = 0xff45ff; + /// The X+ rank color. + /// #a763ea + pub const X_PLUS: u32 = 0xa763ea; /// The XX rank color. /// #ff8fff pub const XX: u32 = 0xff8fff; diff --git a/src/model/league.rs b/src/model/league.rs index 357fd39..9e3bfb9 100644 --- a/src/model/league.rs +++ b/src/model/league.rs @@ -187,6 +187,9 @@ pub enum Rank { /// The X rank. #[serde(rename = "x")] X, + /// The X+ rank. + #[serde(rename = "x+")] + XPlus, /// Unranked. #[serde(rename = "z")] Z, @@ -216,6 +219,7 @@ impl Rank { /// assert_eq!(Rank::SS.as_str(), "SS"); /// assert_eq!(Rank::U.as_str(), "U"); /// assert_eq!(Rank::X.as_str(), "X"); + /// assert_eq!(Rank::XPlus.as_str(), "X+"); /// assert_eq!(Rank::Z.as_str(), "Unranked"); /// ``` pub fn as_str(&self) -> &str { @@ -237,6 +241,7 @@ impl Rank { Rank::SS => "SS", Rank::U => "U", Rank::X => "X", + Rank::XPlus => "X+", Rank::Z => "Unranked", } } @@ -279,6 +284,7 @@ impl Rank { /// assert_eq!(Rank::SS.icon_url(), "https://tetr.io/res/league-ranks/ss.png"); /// assert_eq!(Rank::U.icon_url(), "https://tetr.io/res/league-ranks/u.png"); /// assert_eq!(Rank::X.icon_url(), "https://tetr.io/res/league-ranks/x.png"); + /// assert_eq!(Rank::XPlus.icon_url(), "https://tetr.io/res/league-ranks/x+.png"); /// assert_eq!(Rank::Z.icon_url(), "https://tetr.io/res/league-ranks/z.png"); /// ``` pub fn icon_url(&self) -> String { @@ -308,6 +314,7 @@ impl Rank { /// assert_eq!(Rank::SS.color(), 0xDB8B1F); /// assert_eq!(Rank::U.color(), 0xFF3813); /// assert_eq!(Rank::X.color(), 0xff45ff); + /// assert_eq!(Rank::XPlus.color(), 0xa763ea); /// assert_eq!(Rank::Z.color(), 0x767671); /// ``` pub fn color(&self) -> u32 { @@ -329,6 +336,7 @@ impl Rank { Self::SS => Self::SS_COL, Self::U => Self::U_COL, Self::X => Self::X_COL, + Self::XPlus => Self::X_PLUS_COL, Self::Z => Self::Z_COL, } } @@ -401,6 +409,10 @@ impl Rank { /// #ff45ff pub const X_COL: u32 = 0xff45ff; + /// The X+ rank color. + /// #a763ea + pub const X_PLUS_COL: u32 = 0xa763ea; + /// The XX rank color. /// #ff8fff pub const XX_COL: u32 = 0xff8fff; @@ -436,6 +448,7 @@ impl Display for Rank { Rank::SS => write!(f, "ss"), Rank::U => write!(f, "u"), Rank::X => write!(f, "x"), + Rank::XPlus => write!(f, "x+"), Rank::Z => write!(f, "z"), } } @@ -542,6 +555,7 @@ mod tests { let rank_ss = Rank::SS; let rank_u = Rank::U; let rank_x = Rank::X; + let rank_x_plus = Rank::XPlus; let rank_z = Rank::Z; assert_eq!(rank_d.as_str(), "D"); assert_eq!(rank_d_plus.as_str(), "D+"); @@ -560,6 +574,7 @@ mod tests { assert_eq!(rank_ss.as_str(), "SS"); assert_eq!(rank_u.as_str(), "U"); assert_eq!(rank_x.as_str(), "X"); + assert_eq!(rank_x_plus.as_str(), "X+"); assert_eq!(rank_z.as_str(), "Unranked"); } @@ -599,6 +614,7 @@ mod tests { let rank_ss = Rank::SS; let rank_u = Rank::U; let rank_x = Rank::X; + let rank_x_plus = Rank::XPlus; let rank_z = Rank::Z; assert_eq!(rank_d.color(), 0x907591); assert_eq!(rank_d_plus.color(), 0x8e6091); @@ -617,6 +633,7 @@ mod tests { assert_eq!(rank_ss.color(), 0xdb8b1f); assert_eq!(rank_u.color(), 0xff3813); assert_eq!(rank_x.color(), 0xff45ff); + assert_eq!(rank_x_plus.color(), 0xa763ea); assert_eq!(rank_z.color(), 0x767671); } From 732221ec42c7474762ae82fa91df6e0d96ee8b6c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:54:18 +0900 Subject: [PATCH 053/255] =?UTF-8?q?=F0=9F=94=A5=20Remove:=20`get=5Fuser=5F?= =?UTF-8?q?records=5Fold`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/src/client.rs b/src/client.rs index 3c94c72..bcabf0a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -25,7 +25,7 @@ use crate::{ zenith::{ZenithExResponse, ZenithResponse}, AllSummariesResponse, }, - user::{UserRecordsOldResponse, UserResponse}, + user::UserResponse, user_records::UserRecordsResponse, xp_leaderboard::{self, XPLeaderboardResponse}, }, @@ -171,38 +171,6 @@ impl Client { response(res).await } - /// Returns the user records model. - /// - /// # Examples - /// - /// Getting the records object: - /// - /// ```no_run - /// use tetr_ch::client::Client; - /// # use std::io; - /// - /// # async fn run() -> io::Result<()> { - /// let client = Client::new(); - /// // Get the user records. - /// let user = client.get_user_records("621db46d1d638ea850be2aa0").await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_user_records_old(self, user: &str) -> RspErr { - let url = format!("{}users/{}/records", API_URL, user.to_lowercase()); - let res = self.client.get(url).send().await; - response(res).await - } - /// Returns the object containing all the user's summaries in one. /// /// ***consider whether you really need this. From 654750f7436f5da3de4ba2e614181c62315194f8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:57:03 +0900 Subject: [PATCH 054/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20`get=5Fr?= =?UTF-8?q?ecords`=20method=20in=20`searched=5Fuser.rs`=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_user_records_old` is removed. --- src/model/searched_user.rs | 49 +------------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index b9dde25..16c6819 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -5,7 +5,7 @@ use crate::{ error::ResponseError, model::{ cache::CacheData, - user::{UserId, UserRecordsOldResponse, UserResponse}, + user::{UserId, UserResponse}, }, }; use serde::Deserialize; @@ -48,25 +48,6 @@ impl SearchedUserResponse { } } - /// Gets the user's records data. - /// Returns `None` if the user was not found. - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> Option> { - if let Some(u) = &self.data { - Some(Client::new().get_user_records_old(u.user.id.id()).await) - } else { - None - } - } - /// Returns the user's profile URL or `None` if the user was not found. pub fn profile_url(&self) -> Option { self.data.as_ref().map(|u| u.profile_url()) @@ -130,20 +111,6 @@ impl UserData { Client::new().get_user(self.user.id.id()).await } - /// Gets the user's records data. - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> RspErr { - Client::new().get_user_records_old(self.user.id.id()).await - } - /// Returns the user's profile URL. pub fn profile_url(&self) -> String { self.user.profile_url() @@ -183,20 +150,6 @@ impl UserInfo { Client::new().get_user(self.id.id()).await } - /// Gets the user's records data. - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> RspErr { - Client::new().get_user_records_old(self.id.id()).await - } - /// Returns the user's profile URL. pub fn profile_url(&self) -> String { format!("https://ch.tetr.io/u/{}", self.name) From 424fd8f654b62b5d5e11423bdec7061dbb883d8b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 20:58:42 +0900 Subject: [PATCH 055/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20`UserId:?= =?UTF-8?q?:get=5Frecords`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_user_records_old` is removed. --- src/model/user.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index af64dc3..9a1b5d3 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1643,20 +1643,6 @@ impl UserId { pub async fn get_user(&self) -> RspErr { Client::new().get_user(self.id()).await } - - /// Gets the user's records data. - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_records(&self) -> RspErr { - Client::new().get_user_records_old(self.id()).await - } } impl AsRef for UserId { From 0a0c60dc8360242943f58c5aba66b7d5986ebd18 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 22:38:04 +0900 Subject: [PATCH 056/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20test=20f?= =?UTF-8?q?or=20`get=5Fuser=5Frecords=5Fold`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/client.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/client.rs b/tests/client.rs index b8843e4..6cc19d3 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -16,12 +16,6 @@ fn get_server_activity_data() { let _ = tokio_test::block_on(Client::new().get_server_activity()); } -#[test] -fn get_usr_records_data() { - let usr = "rinrin-rs"; - let _ = tokio_test::block_on(Client::new().get_user_records_old(usr)); -} - #[test] fn get_league_leaderboard_data() { let _ = tokio_test::block_on( From e609b3a5d73e90e29b59aea0d1aa04aad8543273 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 22:56:01 +0900 Subject: [PATCH 057/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20`UserRec?= =?UTF-8?q?ordsOldResponse`=20struct=20recursively=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_user_records_old` is removed. --- src/model/user.rs | 833 +--------------------------------------------- 1 file changed, 1 insertion(+), 832 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index 9a1b5d3..a252210 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -3,10 +3,7 @@ use crate::{ client::Client, error::ResponseError, - model::{ - cache::CacheData, - record::{single_play_end_ctx::SinglePlayEndCtx, EndContext, Record}, - }, + model::cache::CacheData, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; use serde::Deserialize; @@ -790,834 +787,6 @@ impl AsRef for AchievementRatingCounts { } } -/// The response for the user records. -/// Describes the user records. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct UserRecordsOldResponse { - /// Whether the request was successful. - #[serde(rename = "success")] - pub is_success: bool, - /// The reason the request failed. - pub error: Option, - /// Data about how this request was cached. - pub cache: Option, - /// The requested user records data. - pub data: Option, -} - -impl UserRecordsOldResponse { - /// Whether the user has a 40 LINES record. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn has_40l_record(&self) -> bool { - self.get_user_records().has_40l_record() - } - - /// Whether the user has a BLITZ record. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn has_blitz_record(&self) -> bool { - self.get_user_records().has_blitz_record() - } - - /// Returns the PPS(Pieces Per Second) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_pps(&self) -> f64 { - self.get_user_records().forty_lines_pps() - } - - /// Returns the PPS(Pieces Per Second) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - /// or the request was not successful. - pub fn blitz_pps(&self) -> f64 { - self.get_user_records().blitz_pps() - } - - /// Returns the KPP(Keys Per Piece) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_kpp(&self) -> f64 { - self.get_user_records().forty_lines_kpp() - } - - /// Returns the KPP(Keys Per Piece) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - /// or the request was not successful. - pub fn blitz_kpp(&self) -> f64 { - self.get_user_records().blitz_kpp() - } - - /// Returns the KPS(Keys Per Second) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_kps(&self) -> f64 { - self.get_user_records().forty_lines_kps() - } - - /// Returns the KPS(Keys Per Second) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - /// or the request was not successful. - pub fn blitz_kps(&self) -> f64 { - self.get_user_records().blitz_kps() - } - - /// Returns the LPM(Lines Per Minute) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_lpm(&self) -> f64 { - self.get_user_records().forty_lines_lpm() - } - - /// Returns the LPM(Lines Per Minute) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - /// or the request was not successful. - pub fn blitz_lpm(&self) -> f64 { - self.get_user_records().blitz_lpm() - } - - /// Returns the SPP(Score Per Piece) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_spp(&self) -> f64 { - self.get_user_records().forty_lines_spp() - } - - /// Returns the SPP(Score Per Piece) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - /// or the request was not successful. - pub fn blitz_spp(&self) -> f64 { - self.get_user_records().blitz_spp() - } - - /// Returns the finesse rate of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_finesse_rate(&self) -> f64 { - self.get_user_records().forty_lines_finesse_rate() - } - - /// Returns the finesse rate of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - /// or the request was not successful. - pub fn blitz_finesse_rate(&self) -> f64 { - self.get_user_records().blitz_finesse_rate() - } - - /// Returns the 40 LINES record URL. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_record_url(&self) -> String { - self.get_user_records().forty_lines_record_url() - } - /// Returns the BLITZ record URL. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - /// or the request was not successful. - pub fn blitz_record_url(&self) -> String { - self.get_user_records().blitz_record_url() - } - - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record, - /// or the request was not successful. - pub fn forty_lines_recorded_at(&self) -> i64 { - self.get_user_records().forty_lines_recorded_at() - } - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record, - pub fn blitz_recorded_at(&self) -> i64 { - self.get_user_records().blitz_recorded_at() - } - - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns the [`&RecordsData`](crate::model::user::RecordsData). - /// - /// # Panics - /// - /// Panics if the request was not successful. - fn get_user_records(&self) -> &RecordsData { - if let Some(d) = self.data.as_ref() { - d - } else { - panic!("There is no user records object because the request was not successful.") - } - } -} - -impl AsRef for UserRecordsOldResponse { - fn as_ref(&self) -> &Self { - self - } -} - -/// The requested user records data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct RecordsData { - /// The requested user's ranked records. - pub records: Records, - /// The user's ZEN record. - pub zen: Zen, -} - -impl RecordsData { - /// Whether the user has a 40 LINES record. - pub fn has_40l_record(&self) -> bool { - self.records.has_forty_lines() - } - - /// Whether the user has a BLITZ record. - pub fn has_blitz_record(&self) -> bool { - self.records.has_blitz() - } - - /// Returns the PPS(Pieces Per Second) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn forty_lines_pps(&self) -> f64 { - self.records.forty_lines_pps() - } - - /// Returns the PPS(Pieces Per Second) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_pps(&self) -> f64 { - self.records.blitz_pps() - } - - /// Returns the KPP(Keys Per Piece) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_kpp(&self) -> f64 { - self.records.forty_lines_kpp() - } - - /// Returns the KPP(Keys Per Piece) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_kpp(&self) -> f64 { - self.records.blitz_kpp() - } - - /// Returns the KPS(Keys Per Second) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_kps(&self) -> f64 { - self.records.forty_lines_kps() - } - - /// Returns the KPS(Keys Per Second) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_kps(&self) -> f64 { - self.records.blitz_kps() - } - - /// Returns the LPM(Lines Per Minute) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_lpm(&self) -> f64 { - self.records.forty_lines_lpm() - } - - /// Returns the LPM(Lines Per Minute) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_lpm(&self) -> f64 { - self.records.blitz_lpm() - } - - /// Returns the SPP(Score Per Piece) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_spp(&self) -> f64 { - self.records.forty_lines_spp() - } - - /// Returns the SPP(Score Per Piece) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_spp(&self) -> f64 { - self.records.blitz_spp() - } - - /// Returns the finesse rate of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_finesse_rate(&self) -> f64 { - self.records.forty_lines_finesse_rate() - } - - /// Returns the finesse rate of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_finesse_rate(&self) -> f64 { - self.records.blitz_finesse_rate() - } - - /// Returns the 40 LINES record URL. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn forty_lines_record_url(&self) -> String { - self.records.forty_lines_record_url() - } - - /// Returns the BLITZ record URL. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_record_url(&self) -> String { - self.records.blitz_record_url() - } - - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn forty_lines_recorded_at(&self) -> i64 { - self.records.forty_lines_recorded_at() - } - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_recorded_at(&self) -> i64 { - self.records.blitz_recorded_at() - } -} - -impl AsRef for RecordsData { - fn as_ref(&self) -> &Self { - self - } -} - -/// The requested user's ranked records. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct Records { - /// The user's 40 LINES record. - #[serde(rename = "40l")] - pub forty_lines: FortyLines, - /// The user's BLITZ record. - pub blitz: Blitz, -} - -impl Records { - /// Whether the user has a 40 LINES record. - pub fn has_forty_lines(&self) -> bool { - self.forty_lines.record.is_some() - } - - /// Whether the user has a BLITZ record. - pub fn has_blitz(&self) -> bool { - self.blitz.record.is_some() - } - - /// Returns the PPS(Pieces Per Second) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_pps(&self) -> f64 { - self.forty_lines.pps() - } - - /// Returns the PPS(Pieces Per Second) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_pps(&self) -> f64 { - self.blitz.pps() - } - - /// Returns the KPP(Keys Per Piece) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_kpp(&self) -> f64 { - self.forty_lines.kpp() - } - - /// Returns the KPP(Keys Per Piece) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_kpp(&self) -> f64 { - self.blitz.kpp() - } - - /// Returns the KPS(Keys Per Second) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_kps(&self) -> f64 { - self.forty_lines.kps() - } - - /// Returns the KPS(Keys Per Second) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_kps(&self) -> f64 { - self.blitz.kps() - } - - /// Returns the LPM(Lines Per Minute) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_lpm(&self) -> f64 { - self.forty_lines.lpm() - } - - /// Returns the LPM(Lines Per Minute) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_lpm(&self) -> f64 { - self.blitz.lpm() - } - - /// Returns the SPP(Score Per Piece) of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_spp(&self) -> f64 { - self.forty_lines.spp() - } - - /// Returns the SPP(Score Per Piece) of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_spp(&self) -> f64 { - self.blitz.spp() - } - - /// Returns the finesse rate of 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES. - pub fn forty_lines_finesse_rate(&self) -> f64 { - self.forty_lines.finesse_rate() - } - - /// Returns the finesse rate of BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_finesse_rate(&self) -> f64 { - self.blitz.finesse_rate() - } - - /// Returns the 40 LINES record URL. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn forty_lines_record_url(&self) -> String { - self.forty_lines.record_url() - } - - /// Returns the BLITZ record URL. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_record_url(&self) -> String { - self.blitz.record_url() - } - - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn forty_lines_recorded_at(&self) -> i64 { - self.forty_lines.recorded_at() - } - - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn blitz_recorded_at(&self) -> i64 { - self.blitz.recorded_at() - } -} - -impl AsRef for Records { - fn as_ref(&self) -> &Self { - self - } -} - -/// The user's 40 LINES record. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct FortyLines { - /// The user's 40 LINES record data, or `None` if never played. - pub record: Option, - /// The user's rank in global leaderboards, - /// or `None` if not in global leaderboards. - pub rank: Option, -} - -impl FortyLines { - /// Returns the PPS(Pieces Per Second) of this replay. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn pps(&self) -> f64 { - self.get_end_ctx().pps() - } - - /// Returns the KPP(Keys Per Piece) of this replay. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn kpp(&self) -> f64 { - self.get_end_ctx().kpp() - } - - /// Returns the KPS(Keys Per Second) of this replay. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn kps(&self) -> f64 { - self.get_end_ctx().kps() - } - - /// Returns the LPM(Lines Per Minute) of this replay. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn lpm(&self) -> f64 { - self.get_end_ctx().lpm() - } - - /// Returns the SPP(Score Per Piece) of this replay. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn spp(&self) -> f64 { - self.get_end_ctx().spp() - } - - /// Returns the finesse rate of this replay. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn finesse_rate(&self) -> f64 { - self.get_end_ctx().finesse_rate() - } - - /// Returns the 40 LINES record URL. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn record_url(&self) -> String { - self.get_record().record_url() - } - - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - pub fn recorded_at(&self) -> i64 { - self.get_record().recorded_at() - } - - /// Returns the [`&Record`](crate::model::record::Record) for 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - fn get_record(&self) -> &Record { - if let Some(d) = self.record.as_ref() { - d - } else { - panic!("There is no 40 LINES record.") - } - } - - /// Returns the - /// [`&SinglePlayEndCtx`](crate::model::record::single_play_end_ctx::SinglePlayEndCtx) - /// for 40 LINES. - /// - /// # Panics - /// - /// Panics if there is no 40 LINES record. - fn get_end_ctx(&self) -> &SinglePlayEndCtx { - if let EndContext::SinglePlay(ctx) = self.get_record().endcontext.as_ref() { - ctx - } else { - panic!("This 40 LINES record is multiplayer record.") - } - } -} - -impl AsRef for FortyLines { - fn as_ref(&self) -> &Self { - self - } -} - -/// The user's BLITZ record. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct Blitz { - /// The user's BLITZ record data, or `None` if never played. - pub record: Option, - /// The user's rank in global leaderboards, - /// or `None` if not in global leaderboards. - pub rank: Option, -} - -impl Blitz { - /// Returns the PPS(Pieces Per Second) of this replay. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn pps(&self) -> f64 { - self.get_end_ctx().pps() - } - - /// Returns the KPP(Keys Per Piece) of this replay. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn kpp(&self) -> f64 { - self.get_end_ctx().kpp() - } - - /// Returns the KPS(Keys Per Second) of this replay. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn kps(&self) -> f64 { - self.get_end_ctx().kps() - } - - /// Returns the LPM(Lines Per Minute) of this replay. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn lpm(&self) -> f64 { - self.get_end_ctx().lpm() - } - - /// Returns the SPP(Score Per Piece) of this replay. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn spp(&self) -> f64 { - self.get_end_ctx().spp() - } - - /// Returns the finesse rate of this replay. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn finesse_rate(&self) -> f64 { - self.get_end_ctx().finesse_rate() - } - - /// Returns the BLITZ record URL. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn record_url(&self) -> String { - self.get_record().record_url() - } - - /// Returns a UNIX timestamp when this record was recorded. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - pub fn recorded_at(&self) -> i64 { - self.get_record().recorded_at() - } - - /// Returns the [`&Record`](crate::model::record::Record) for BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - fn get_record(&self) -> &Record { - if let Some(d) = self.record.as_ref() { - d - } else { - panic!("There is no BLITZ record.") - } - } - - /// Returns the - /// [`&SinglePlayEndCtx`](crate::model::record::single_play_end_ctx::SinglePlayEndCtx) - /// for BLITZ. - /// - /// # Panics - /// - /// Panics if there is no BLITZ record. - fn get_end_ctx(&self) -> &SinglePlayEndCtx { - if let EndContext::SinglePlay(ctx) = self.get_record().endcontext.as_ref() { - ctx - } else { - panic!("This BLITZ record is multiplayer record.") - } - } -} - -impl AsRef for Blitz { - fn as_ref(&self) -> &Self { - self - } -} - -/// The user's ZEN record. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct Zen { - /// The user's level in ZEN mode. - pub level: u32, - /// The user's score in ZEN mode. - pub score: u64, -} - /// The user's internal ID. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] pub struct UserId(pub String); From d005c6b322846be8cfa8d6cad9b919e4dbff3d9f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:00:22 +0900 Subject: [PATCH 058/255] =?UTF-8?q?=F0=9F=94=A5=20Remove:=20`get=5Fleague?= =?UTF-8?q?=5Fleaderboard`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 129 -------------------------------------------------- 1 file changed, 129 deletions(-) diff --git a/src/client.rs b/src/client.rs index bcabf0a..096e014 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,6 @@ use crate::{ scoreflow::LabsScoreflowResponse, }, leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, - league_leaderboard::{self, LeagueLeaderboardResponse}, news::{NewsAllResponse, NewsLatestResponse}, records_leaderboard::RecordsLeaderboardResponse, searched_user::SearchedUserResponse, @@ -792,134 +791,6 @@ impl Client { response(res).await } - /// Returns the TETRA LEAGUE leaderboard model. - /// - /// # Arguments - /// - /// - `query`: - /// - /// The query parameters. - /// This argument requires a [`query::LeagueLeaderboardQuery`]. - /// - /// # Examples - /// - /// Getting the TETRA LEAGUE leaderboard object: - /// - /// ```no_run - /// use tetr_ch::client::{Client, query::LeagueLeaderboardQuery}; - /// # use std::io; - /// - /// # async fn run() -> io::Result<()> { - /// let client = Client::new(); - /// - /// // Set the query parameters. - /// let query = LeagueLeaderboardQuery::new() - /// // 15200TR or less. - /// .after(15200.) - /// // 3 users. - /// .limit(3) - /// // Japan. - /// .country("jp"); - /// - /// // Get the TETRA LEAGUE leaderboard. - /// let user = client.get_league_leaderboard(query).await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// See [here](query::LeagueLeaderboardQuery) for details on setting query parameters. - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - /// - /// # Panics - /// - /// Panics if the query parameter`limit` is not between 0 and 100. - /// - /// ```should_panic,no_run - /// use tetr_ch::client::{ - /// Client, - /// query::{LeagueLeaderboardQuery, Limit} - /// }; - /// # use std::io; - /// - /// # async fn run() -> io::Result<()> { - /// let client = Client::new(); - /// - /// let query = LeagueLeaderboardQuery { - /// // 101 users (not allowed). - /// limit: Some(Limit::Limit(101)), - /// ..LeagueLeaderboardQuery::new() - /// }; - /// - /// let user = client.get_league_leaderboard(query).await?; - /// # Ok(()) - /// # } - /// - /// # tokio_test::block_on(run()); - /// ``` - pub async fn get_league_leaderboard( - self, - query: query::LeagueLeaderboardQuery, - ) -> RspErr { - if query.is_invalid_limit_range() { - panic!( - "The query parameter`limit` must be between 0 and 100.\n\ - Received: {}", - query.limit.unwrap().to_string() - ); - } - // Cloned the `query` here because the query parameters will be referenced later. - let (q, url) = if query.will_full_export() { - ( - query.clone().build_as_full_export(), - format!("{}/users/lists/league/all", API_URL), - ) - } else { - ( - query.clone().build(), - format!("{}/users/lists/league", API_URL), - ) - }; - let r = self.client.get(url); - let res = match q.len() { - 1 => r.query(&[&q[0]]), - 2 => r.query(&[&q[0], &q[1]]), - 3 => r.query(&[&q[0], &q[1], &q[2]]), - _ => r, - } - .send() - .await; - match response::(res).await { - Ok(mut m) => { - let (before, after) = if let Some(b_a) = query.before_or_after { - match b_a { - query::BeforeAfter::Before(b) => (Some(b.to_string()), None), - query::BeforeAfter::After(b) => (None, Some(b.to_string())), - } - } else { - (None, None) - }; - let limit = query.limit.map(|l| l.to_string()); - let country = query.country; - m.query = Some(league_leaderboard::QueryCache { - before, - after, - limit, - country, - }); - Ok(m) - } - Err(e) => Err(e), - } - } - /// Returns the XP leaderboard model. /// /// # Arguments From 0afe893eb2bfc3b4885539bc54c9d1af75dc9ff8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:07:01 +0900 Subject: [PATCH 059/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20`LeagueL?= =?UTF-8?q?eaderboardQuery`=20struct=20recursively=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_league_leaderboard` is removed. --- src/client.rs | 348 -------------------------------------------------- 1 file changed, 348 deletions(-) diff --git a/src/client.rs b/src/client.rs index 096e014..c02408c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1349,354 +1349,6 @@ pub mod query { use std::num::NonZeroU8; - /// A struct for query parameters for the TETRA LEAGUE leaderboard. - /// - /// `None` means default value. - /// - /// This structure manages the following four query parameters: - /// - /// - `before`(f64): The lower bound in TR. - /// Use this to paginate upwards. - /// Take the highest seen TR and pass that back through this field to continue scrolling. - /// If set, the search order is reversed (returning the lowest items that match the query) - /// This parameter is ignored if specified to get the full leaderboard. - /// - /// - `after`(f64): The upper bound in TR. - /// Use this to paginate downwards. - /// Take the lowest seen TR and pass that back through this field to continue scrolling. - /// This parameter is ignored if specified to get the full leaderboard. - /// - /// - `limit`(u8): The amount of entries to return, Between `0` and `100`. - /// 50 by default. - /// You can specify to get the full leaderboard by passing `0`. - /// In this case the `before` and `after` parameters are ignored. - /// - /// - `country`(String): The ISO 3166-1 country code to filter to. - /// Leave unset to not filter by country. - /// - /// ***The `before` and `after` parameters may not be combined.** - /// - /// # Examples - /// - /// ``` - /// use tetr_ch::client::query::LeagueLeaderboardQuery; - /// - /// // Default(25000TR or less, 50 entries) query. - /// let q1 = LeagueLeaderboardQuery::new(); - /// - /// // 15200TR or less, three entries, filter by Japan. - /// let q2 = LeagueLeaderboardQuery::new() - /// .after(15200.) - /// .limit(3) - /// .country("jp"); - /// - /// // 15200TR or higher. - /// // Also sort by TR ascending. - /// let q3 = LeagueLeaderboardQuery::new() - /// .before(15200.); - /// - /// // Full leaderboard. - /// let q4 = LeagueLeaderboardQuery::new() - /// .limit(0); - /// - /// // You can restore the query parameters to default as follows: - /// let mut q5 = LeagueLeaderboardQuery::new().country("us"); - /// q5.init(); - /// ``` - #[derive(Clone, Debug, Default)] - pub struct LeagueLeaderboardQuery { - /// The bound in TR. - /// - /// The `before` and `after` parameters may not be combined, - /// so either set the parameter with an enum or set it to default(after) by passing `None`. - pub before_or_after: Option, - /// The amount of entries to return. - pub limit: Option, - /// The ISO 3166-1 country code to filter to. Leave unset to not filter by country. - /// But some vanity flags exist. - pub country: Option, - } - - impl LeagueLeaderboardQuery { - /// Creates a new[`LeagueLeaderboardQuery`]. - /// Values are set to default. - /// - /// # Examples - /// - /// Creates a new[`LeagueLeaderboardQuery`] with default parameters. - /// - /// ``` - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new(); - /// ``` - pub fn new() -> Self { - Self::default() - } - - /// Initializes the [`LeagueLeaderboardQuery`]. - /// - /// # Examples - /// - /// Initializes the [`LeagueLeaderboardQuery`] with default parameters. - /// - /// ``` - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let mut query = LeagueLeaderboardQuery::new().country("us"); - /// query.init(); - /// ``` - pub fn init(self) -> Self { - Self::default() - } - - /// Set the query parameter`before`. - /// - /// Disabled by default. - /// - /// The `before` and `after` parameters may not be combined, - /// so even if there is an `after` parameter, the `before` parameter takes precedence and overrides it. - /// Disabled by default. - /// - /// This parameter is ignored if specified to get the full leaderboard. - /// - /// # Examples - /// - /// Sets the query parameter`before` to `15200`. - /// - /// ``` - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new().before(15200.); - /// ``` - pub fn before(self, bound: f64) -> Self { - Self { - before_or_after: Some(BeforeAfter::Before(bound)), - ..self - } - } - - /// Set the query parameter`after`. - /// - /// 25000 by default. - /// - /// The `before` and `after` parameters may not be combined, - /// so even if there is a `before` parameter, the `after` parameter takes precedence and overrides it. - /// - /// This parameter is ignored if specified to get the full leaderboard. - /// - /// # Examples - /// - /// Sets the query parameter`after` to `15200`. - /// - /// ``` - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new().after(15200.); - /// ``` - pub fn after(self, bound: f64) -> Self { - Self { - before_or_after: Some(BeforeAfter::After(bound)), - ..self - } - } - - /// Set the query parameter`limit` - /// The amount of entries to return, Between `0` and `100`. - /// 50 by default. - /// - /// You can specify to get the full leaderboard by passing `0`. - /// In this case the `before` and `after` parameters are ignored. - /// - /// # Examples - /// - /// Sets the query parameter`limit` to `3`. - /// - /// ``` - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new().limit(3); - /// ``` - /// - /// # Panics - /// - /// Panics if argument`limit` is not between `0` and `100`. - /// - /// ```should_panic - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new().limit(101); - /// ``` - pub fn limit(self, limit: u8) -> Self { - if - /*0 <= limit && */ - limit <= 100 { - Self { - limit: Some(if limit == 0 { - Limit::Full - } else { - Limit::Limit(limit) - }), - ..self - } - } else { - panic!( - "The argument`limit` must be between and 100.\n\ - Received: {}", - limit - ); - } - } - - /// Set the query parameter`country`. - /// - /// # Examples - /// - /// Sets the query parameter`country` to `jp`. - /// - /// ``` - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new().country("jp"); - /// ``` - pub fn country(self, country: &str) -> Self { - Self { - country: Some(country.to_owned().to_uppercase()), - ..self - } - } - - /// Whether the query parameters`limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::query::{LeagueLeaderboardQuery, Limit}; - /// let invalid_query = LeagueLeaderboardQuery{ - /// limit: Some(Limit::Limit(101)), - /// ..LeagueLeaderboardQuery::new() - /// }; - /// assert!(invalid_query.is_invalid_limit_range()); - /// ``` - #[allow(clippy::nonminimal_bool)] - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit.clone() { - match l { - Limit::Limit(l) => !(l <= 100), - Limit::Full => false, - } - } else { - false - } - } - - /// Whether the query parameters`limit` specifies to get the full leaderboard. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new().limit(0); - /// assert!(query.will_full_export()); - /// ``` - pub fn will_full_export(&self) -> bool { - if let Some(l) = self.limit.clone() { - match l { - Limit::Limit(l) => l == 0, - Limit::Full => true, - } - } else { - false - } - } - - /// Builds the query parameters to `Vec<(String, String)>`. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new(); - /// let query_params = query.build(); - /// ``` - pub(crate) fn build(mut self) -> Vec<(String, String)> { - // For not pass "Full" to puery parameters. - if self.will_full_export() { - self.limit = Some(Limit::Full); - } - let mut result = Vec::new(); - if let Some(b_a) = self.before_or_after.clone() { - match b_a { - BeforeAfter::Before(b) => result.push(("before".to_string(), b.to_string())), - BeforeAfter::After(b) => result.push(("after".to_string(), b.to_string())), - } - } - if let Some(l) = self.limit.clone() { - if !self.will_full_export() { - result.push(("limit".to_string(), l.to_string())); - } - } - if let Some(c) = self.country { - result.push(("country".to_string(), c)); - } - result - } - - /// Builds the query parameters to `Vec<(String, String)>` as full export. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let query = LeagueLeaderboardQuery::new().limit(0); - /// let query_params = query.build_full_export(); - /// ``` - pub(crate) fn build_as_full_export(mut self) -> Vec<(String, String)> { - // For not pass "Full" to puery parameters. - if self.will_full_export() { - self.limit = Some(Limit::Full); - } - let mut result = Vec::new(); - result.push(("limit".to_string(), "0".to_string())); - if let Some(c) = self.country { - result.push(("country".to_string(), c)); - } - result - } - - /// Initializes the [`LeagueLeaderboardQuery`]. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::query::LeagueLeaderboardQuery; - /// let default_query = LeagueLeaderboardQuery::default(); - /// ``` - fn default() -> Self { - Self { - before_or_after: None, - limit: None, - country: None, - } - } - } - - /// Amount of entries to return. - #[derive(Clone, Debug)] - pub enum Limit { - /// Between 1 and 100. 50 by default. - Limit(u8), - Full, - } - - impl ToString for Limit { - fn to_string(&self) -> String { - match self { - Limit::Limit(l) => { - if l == &0 { - "Full".to_string() - } else { - l.to_string() - } - } - Limit::Full => "Full".to_string(), - } - } - } - /// A struct for query parameters for the XP leaderboard. /// /// `None` means default value. From 5e41bb220ce527bb516bdf1f5d54f32d4a3fad4e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:11:49 +0900 Subject: [PATCH 060/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20tests=20?= =?UTF-8?q?for=20`LeagueLeaderboardQuery`=20struct=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/client.rs b/src/client.rs index c02408c..f6dac73 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2661,19 +2661,6 @@ mod tests { let _ = Client::new(); } - #[test] - fn init_league_query() { - let mut _query = query::LeagueLeaderboardQuery::new(); - _query.init(); - } - - #[test] - #[should_panic] - fn panic_invalid_limit_range_in_league_query() { - let mut _query = query::LeagueLeaderboardQuery::new(); - _query.limit(101); - } - #[test] fn init_xp_query() { let mut _query = query::XPLeaderboardQuery::new(); From 4ac245fb494ace4b6ad3e5942fbe8129a5dfdff3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:31:41 +0900 Subject: [PATCH 061/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Delete:=20`league?= =?UTF-8?q?=5Fleaderboard.rs`=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_league_leaderboard` is removed. --- src/model/league_leaderboard.rs | 352 -------------------------------- src/model/mod.rs | 1 - 2 files changed, 353 deletions(-) delete mode 100644 src/model/league_leaderboard.rs diff --git a/src/model/league_leaderboard.rs b/src/model/league_leaderboard.rs deleted file mode 100644 index 922d00c..0000000 --- a/src/model/league_leaderboard.rs +++ /dev/null @@ -1,352 +0,0 @@ -//! The TETRA LEAGUE leaderboard data. - -use crate::{ - model::{ - cache::CacheData, - league::Rank, - user::{Role, UserId}, - }, - util::max_f64, -}; -use serde::Deserialize; - -/// The response for the TETRA LEAGUE leaderboard. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct LeagueLeaderboardResponse { - /// Whether the request was successful. - #[serde(rename = "success")] - pub is_success: bool, - /// The reason the request failed. - pub error: Option, - /// Data about how this request was cached. - pub cache: Option, - /// Query parameters used to request. - /// - /// # Notes - /// - /// This field will never be returned to you in the `None` state. - /// So you can call the `unwrap`method on this value, for example. - #[serde(default = "none")] - pub query: Option, - /// The requested TETRA LEAGUE leaderboard data. - pub data: Option, -} - -impl LeagueLeaderboardResponse { - /// Whether all query parameters are default. - pub fn is_default_query(&self) -> bool { - if let Some(qp) = self.query.as_ref() { - qp.before.is_none() && qp.after.is_none() && qp.limit.is_none() && qp.country.is_none() - } else { - true - } - } - - /// Whether the leaderboard is reversed. - pub fn is_reversed(&self) -> bool { - self.query.as_ref().unwrap().before.is_some() - } - - /// Whether the gotten leaderboard is a full export. - pub fn is_full(&self) -> bool { - if let Some(l) = self.query.as_ref().unwrap().limit.as_ref() { - l == "Full" - } else { - false - } - } - - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } -} - -impl AsRef for LeagueLeaderboardResponse { - fn as_ref(&self) -> &Self { - self - } -} - -/// Returns a `None`. -fn none() -> Option { - None -} - -/// A cache of query parameters used to the request. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct QueryCache { - /// The lower bound in TR. - /// Use this to paginate upwards. - /// Take the highest seen TR and pass that back through this field to continue scrolling. - /// If set, the search order is reversed (returning the lowest items that match the query) - pub before: Option, - /// The upper bound in TR. - /// Use this to paginate downwards. - /// Take the lowest seen TR and pass that back through this field to continue scrolling. - /// 25000 by default. - pub after: Option, - /// The amount of entries to return. - /// Between 1 and 100. - /// 50 by default. - pub limit: Option, - /// The ISO 3166-1 country code to filter to. - pub country: Option, -} - -impl QueryCache { - /// Whether all query parameters are default. - pub fn is_default_query(&self) -> bool { - self.before.is_none() - && self.after.is_none() - && self.limit.is_none() - && self.country.is_none() - } - - /// Whether the gotten leaderboard is a full export. - pub fn is_full(&self) -> bool { - if let Some(l) = self.limit.as_ref() { - l == "Full" - } else { - false - } - } -} - -impl AsRef for QueryCache { - fn as_ref(&self) -> &Self { - self - } -} - -/// The requested TETRA LEAGUE leaderboard data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct LeagueLeaderboardData { - /// An array of the matched users. - pub users: Vec, -} - -impl AsRef for LeagueLeaderboardData { - fn as_ref(&self) -> &Self { - self - } -} - -/// The matched user's data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct User { - /// The user's internal ID. - #[serde(rename = "_id")] - pub id: UserId, - /// The user's username. - #[serde(rename = "username")] - pub name: String, - /// The user's role. - pub role: Role, - /// The user's XP in points. - pub xp: f64, - /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. Some vanity flags exist. - pub country: Option, - /// Whether this user is currently supporting TETR.IO <3 - #[serde(rename = "supporter")] - pub is_supporter: Option, // EXCEPTION - /// Whether this user is a verified account. - #[serde(rename = "verified")] - pub is_verified: bool, - /// This user's current TETRA LEAGUE standing. - pub league: LeagueDataMini, -} - -impl User { - /// Returns the level based on the user's xp. - pub fn level(&self) -> u32 { - let xp = self.xp; - // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 - ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() - as u32 - } - - /// Returns an icon URL of the user's rank. - /// If the user is unranked, returns ?-rank(z) icon URL. - /// If the user has no rank, returns `None`. - pub fn rank_icon_url(&self) -> Option { - self.league.rank_icon_url() - } - - /// Returns a rank color. (Hex color codes) - /// If the user has no rank, returns `None`. - pub fn rank_color(&self) -> Option { - self.league.rank_color() - } - - /// Returns an icon URL of the user's highest achieved rank. - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_icon_url(&self) -> Option { - self.league.best_rank_icon_url() - } - - /// Returns a color of the user's highest achieved rank. (Hex color codes) - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_color(&self) -> Option { - self.league.best_rank_color() - } - - /// Returns an `Option`. - /// - /// If user is displaying the country, - /// returns `Some(String)` with an image URL of the national flag based on the user's ISO 3166-1 country code. - /// If the user is not displaying the country, returns `None`. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } - - /// Whether the user is an anonymous. - pub fn is_anon(&self) -> bool { - self.role.is_anon() - } - - /// Whether the user is a bot. - pub fn is_bot(&self) -> bool { - self.role.is_bot() - } - - /// Whether the user is a SYSOP. - pub fn is_sysop(&self) -> bool { - self.role.is_sysop() - } - - /// Whether the user is an administrator. - pub fn is_admin(&self) -> bool { - self.role.is_admin() - } - - /// Whether the user is a moderator. - pub fn is_mod(&self) -> bool { - self.role.is_mod() - } - - /// Whether the user is a community moderator. - pub fn is_halfmod(&self) -> bool { - self.role.is_halfmod() - } - - /// Whether the user is banned. - pub fn is_banned(&self) -> bool { - self.role.is_banned() - } - - /// Whether the user is a supporter. - pub fn is_supporter(&self) -> bool { - self.is_supporter.unwrap_or(false) - } - - /// Whether the user is verified. - pub fn is_verified(&self) -> bool { - self.is_verified - } -} - -impl AsRef for User { - fn as_ref(&self) -> &Self { - self - } -} - -/// The user's current TETRA LEAGUE standing. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct LeagueDataMini { - /// The amount of TETRA LEAGUE games played by this user. - #[serde(rename = "gamesplayed")] - pub play_count: u32, - /// The amount of TETRA LEAGUE games won by this user. - #[serde(rename = "gameswon")] - pub win_count: u32, - /// This user's TR (Tetra Rating), or -1 if less than 10 games were played. - pub rating: f64, - /// This user's letter rank. Z is unranked. - pub rank: Rank, - /// This user's highest achieved rank this season. - #[serde(rename = "bestrank")] - pub best_rank: Option, - /// This user's Glicko-2 rating. - pub glicko: Option, - /// This user's Glicko-2 Rating Deviation. - /// If over 100, this user is unranked. - pub rd: Option, - /// This user's average APM (attack per minute) over the last 10 games. - pub apm: Option, - /// This user's average PPS (pieces per second) over the last 10 games. - pub pps: Option, - /// This user's average VS (versus score) over the last 10 games. - pub vs: Option, - /// Whether this user's RD is rising (has not played in the last week). - #[serde(rename = "decaying")] - pub is_decaying: bool, -} - -impl LeagueDataMini { - /// Returns an icon URL of the user's rank. - /// If the user is unranked, returns ?-rank(z) icon URL. - /// If the user has no rank, returns `None`. - pub fn rank_icon_url(&self) -> Option { - if 10 <= self.play_count { - Some(self.rank.icon_url()) - } else { - None - } - } - - /// Returns a rank color. (Hex color codes) - /// If the user has no rank, returns `None`. - pub fn rank_color(&self) -> Option { - if 10 <= self.play_count { - Some(self.rank.color()) - } else { - None - } - } - - /// Returns an icon URL of the user's highest achieved rank. - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_icon_url(&self) -> Option { - self.best_rank.as_ref().map(|r| r.icon_url()) - } - - /// Returns a highest achieved rank color. (Hex color codes) - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_color(&self) -> Option { - self.best_rank.as_ref().map(|r| r.color()) - } -} - -impl AsRef for LeagueDataMini { - fn as_ref(&self) -> &Self { - self - } -} diff --git a/src/model/mod.rs b/src/model/mod.rs index d725bf7..0895434 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -6,7 +6,6 @@ pub mod labs; pub mod latest_news; pub mod leaderboard; pub mod league; -pub mod league_leaderboard; pub mod news; pub mod record; pub mod records_leaderboard; From cc6e24c457c25e4f159d0f889576f625809369e9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:33:59 +0900 Subject: [PATCH 062/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20tests=20?= =?UTF-8?q?for=20`get=5Fleague=5Fleaderboard`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/client.rs | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/tests/client.rs b/tests/client.rs index 6cc19d3..6effb2b 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -16,50 +16,6 @@ fn get_server_activity_data() { let _ = tokio_test::block_on(Client::new().get_server_activity()); } -#[test] -fn get_league_leaderboard_data() { - let _ = tokio_test::block_on( - Client::new().get_league_leaderboard(query::LeagueLeaderboardQuery::new()), - ); -} - -#[test] -fn get_league_leaderboard_data_with_two_queries() { - let _ = tokio_test::block_on( - Client::new() - .get_league_leaderboard(query::LeagueLeaderboardQuery::new().limit(2).before(23000.)), - ); -} - -#[test] -fn get_league_leaderboard_data_with_three_queries() { - let _ = tokio_test::block_on( - Client::new().get_league_leaderboard( - query::LeagueLeaderboardQuery::new() - .limit(2) - .country("us") - .after(13000.), - ), - ); -} - -#[test] -fn get_full_league_leaderboard_data() { - let _ = tokio_test::block_on( - Client::new().get_league_leaderboard(query::LeagueLeaderboardQuery::new().limit(0)), - ); -} - -#[test] -#[should_panic] -fn panic_if_invalid_limit_range_exhaustivegetting_league_leaderboard() { - let q = query::LeagueLeaderboardQuery { - limit: Some(query::Limit::Limit(101)), - ..query::LeagueLeaderboardQuery::new() - }; - let _ = tokio_test::block_on(Client::new().get_league_leaderboard(q)); -} - #[test] fn get_xp_leaderboard_data() { let _ = From 030d675ecfd9a56a21056029454af142ae2ee5e6 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:36:58 +0900 Subject: [PATCH 063/255] =?UTF-8?q?=F0=9F=94=A5=20Remove:=20`get=5Fxp=5Fle?= =?UTF-8?q?aderboard`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 118 -------------------------------------------------- 1 file changed, 118 deletions(-) diff --git a/src/client.rs b/src/client.rs index f6dac73..3d620e0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -26,7 +26,6 @@ use crate::{ }, user::UserResponse, user_records::UserRecordsResponse, - xp_leaderboard::{self, XPLeaderboardResponse}, }, }; use http::status::StatusCode; @@ -791,123 +790,6 @@ impl Client { response(res).await } - /// Returns the XP leaderboard model. - /// - /// # Arguments - /// - /// - `query`: - /// - /// The query parameters. - /// This argument requires a [`query::XPLeaderboardQuery`]. - /// - /// # Examples - /// - /// Getting the XP leaderboard object: - /// - /// ```no_run - /// use tetr_ch::client::{Client, query::XPLeaderboardQuery}; - /// # use std::io; - /// - /// # async fn run() -> io::Result<()> { - /// let client = Client::new(); - /// - /// // Set the query parameters. - /// let query = XPLeaderboardQuery::new() - /// // 50,000,000,000,000xp or less. - /// .after(50_000_000_000_000.) - /// // 10 users. - /// .limit(10) - /// // Serbia. - /// .country("rs"); - /// - /// // Get the XP leaderboard. - /// let user = client.get_xp_leaderboard(query).await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// See [here](query::XPLeaderboardQuery) for details on setting query parameters. - /// - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - /// - /// # Panics - /// - /// Panics if the query parameter`limit` is not between 1 and 100. - /// - /// ```should_panic,no_run - /// use tetr_ch::client::{Client, query::XPLeaderboardQuery}; - /// # use std::io; - /// - /// # async fn run() -> io::Result<()> { - /// let client = Client::new(); - /// - /// let query = XPLeaderboardQuery { - /// // 101 users(not allowed). - /// limit: Some(std::num::NonZeroU8::new(101).unwrap()), - /// ..XPLeaderboardQuery::new() - /// }; - /// - /// let user = client.get_xp_leaderboard(query).await?; - /// # Ok(()) - /// # } - /// - /// # tokio_test::block_on(run()); - /// ``` - pub async fn get_xp_leaderboard( - self, - query: query::XPLeaderboardQuery, - ) -> RspErr { - if query.is_invalid_limit_range() { - panic!( - "The query parameter`limit` must be between 1 and 100.\n\ - Received: {}", - query.limit.unwrap() - ); - } - // Cloned the `query` here because the query parameters will be referenced later. - let q = query.clone().build(); - let url = format!("{}users/lists/xp", API_URL); - let r = self.client.get(url); - let res = match q.len() { - 1 => r.query(&[&q[0]]), - 2 => r.query(&[&q[0], &q[1]]), - 3 => r.query(&[&q[0], &q[1], &q[2]]), - _ => r, - } - .send() - .await; - match response::(res).await { - Ok(mut m) => { - let (before, after) = if let Some(b_a) = query.before_or_after { - match b_a { - query::BeforeAfter::Before(b) => (Some(b.to_string()), None), - query::BeforeAfter::After(b) => (None, Some(b.to_string())), - } - } else { - (None, None) - }; - let limit = query.limit.map(|l| l.to_string()); - let country = query.country; - m.query = Some(xp_leaderboard::QueryCache { - before, - after, - limit, - country, - }); - Ok(m) - } - Err(e) => Err(e), - } - } - /// Returns the stream model. /// /// # Arguments From 9c75f289a52a78ed4b853b001c1aeed002729bac Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:39:17 +0900 Subject: [PATCH 064/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20`client:?= =?UTF-8?q?:query`=20module=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_xp_leaderboard` is removed. --- src/client.rs | 295 -------------------------------------------------- 1 file changed, 295 deletions(-) diff --git a/src/client.rs b/src/client.rs index 3d620e0..8fc3c45 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1226,301 +1226,6 @@ where } } -pub mod query { - //! Structs for query parameters. - - use std::num::NonZeroU8; - - /// A struct for query parameters for the XP leaderboard. - /// - /// `None` means default value. - /// - /// This structure manages the following four query parameters: - /// - /// - `before`(f64): The lower bound in XP. - /// Use this to paginate upwards. - /// Take the highest seen XP and pass that back through this field to continue scrolling. - /// If set, the search order is reversed (returning the lowest items that match the query) - /// - /// - `after`(f64): The upper bound in XP. - /// Use this to paginate downwards. - /// Take the lowest seen XP and pass that back through this field to continue scrolling. - /// Infinite([`f64::INFINITY`]) by default. - /// - /// - `limit`([NonZeroU8]): The amount of entries to return. - /// Between 1 and 100. - /// 50 by default. - /// - /// - `country`(String): The ISO 3166-1 country code to filter to. - /// Leave unset to not filter by country. - /// - /// ***The `before` and `after` parameters may not be combined.** - /// - /// # Examples - /// - /// ``` - /// use tetr_ch::client::query::XPLeaderboardQuery; - /// - /// // Default(descending, fifty entries) query. - /// let q1 = XPLeaderboardQuery::new(); - /// - /// // 50,000,000,000,000xp or less, thirty entries, filter by Japan. - /// let q2 = XPLeaderboardQuery::new() - /// .after(50_000_000_000_000.) - /// .limit(3) - /// .country("jp"); - /// - /// // 50,000,000,000,000xp or higher. - /// // Also sort by XP ascending. - /// let q3 = XPLeaderboardQuery::new() - /// .before(50_000_000_000_000.); - /// - /// // You can restore the query parameters to default as follows: - /// let mut q4 = XPLeaderboardQuery::new().country("us"); - /// q4.init(); - /// ``` - #[derive(Clone, Debug, Default)] - pub struct XPLeaderboardQuery { - /// The bound in XP. - /// - /// The `before` and `after` parameters may not be combined, - /// so either set the parameter with an enum or set it to default(after) by passing `None`. - pub before_or_after: Option, - /// The amount of entries to return. - /// Between 1 and 100. 50 by default. - pub limit: Option, - /// The ISO 3166-1 country code to filter to. Leave unset to not filter by country. - /// But some vanity flags exist. - pub country: Option, - } - - impl XPLeaderboardQuery { - /// Creates a new[`XPLeaderboardQuery`]. - /// Values are set to default. - /// - /// # Examples - /// - /// Creates a new[`XPLeaderboardQuery`] with default parameters. - /// - /// ``` - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let query = XPLeaderboardQuery::new(); - /// ``` - pub fn new() -> Self { - Self::default() - } - - /// Initializes the [`XPLeaderboardQuery`]. - /// - /// # Examples - /// - /// Initializes the [`XPLeaderboardQuery`] with default parameters. - /// - /// ``` - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let mut query = XPLeaderboardQuery::new(); - /// query.init(); - /// ``` - pub fn init(self) -> Self { - Self::default() - } - - /// Set the query parameter`before`. - /// - /// The `before` and `after` parameters may not be combined, - /// so even if there is an `after` parameter, the `before` parameter takes precedence and overrides it. - /// Disabled by default. - /// - /// # Examples - /// - /// Sets the query parameter`before` to `50,000,000,000,000`. - /// - /// ``` - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let mut query = XPLeaderboardQuery::new() - /// .before(50_000_000_000_000.); - /// ``` - pub fn before(self, bound: f64) -> Self { - Self { - before_or_after: if bound.is_infinite() { - Some(BeforeAfter::Before(bound)) - } else { - None - }, - ..self - } - } - - /// Set the query parameter`after`. - /// - /// The `before` and `after` parameters may not be combined, - /// so even if there is a `before` parameter, the `after` parameter takes precedence and overrides it. - /// Infinite([`f64::INFINITY`]) by default. - /// - /// # Examples - /// - /// Sets the query parameter`after` to `50,000,000,000,000`. - /// - /// ``` - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let mut query = XPLeaderboardQuery::new() - /// .after(50_000_000_000_000.); - /// ``` - pub fn after(self, bound: f64) -> Self { - Self { - before_or_after: Some(BeforeAfter::After(bound)), - ..self - } - } - - /// Set the query parameter`limit` - /// The amount of entries to return, Between `1` and `100`. - /// 50 by default. - /// - /// # Examples - /// - /// Sets the query parameter`limit` to `5`. - /// - /// ``` - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let mut query = XPLeaderboardQuery::new().limit(5); - /// ``` - /// - /// # Panics - /// - /// Panics if argument`limit` is not between `1` and `100`. - /// - /// ```should_panic - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let mut query = XPLeaderboardQuery::new().limit(0); - /// ``` - /// - /// ```should_panic - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let mut query = XPLeaderboardQuery::new().limit(101); - /// ``` - pub fn limit(self, limit: u8) -> Self { - if (1..=100).contains(&limit) { - // 1 <= limit && limit <= 100 - Self { - limit: Some(NonZeroU8::new(limit).unwrap()), - ..self - } - } else { - panic!( - "The argument`limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); - } - } - - /// Set the query parameter`country`. - /// - /// # Examples - /// - /// Sets the query parameter`country` to `ca`. - /// - /// ``` - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let mut query = XPLeaderboardQuery::new().country("ca"); - /// ``` - pub fn country(self, country: &str) -> Self { - Self { - country: Some(country.to_owned().to_uppercase()), - ..self - } - } - - /// Whether the query parameters`limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// use std::num::NonZeroU8; - /// - /// let invalid_query = XPLeaderboardQuery{ - /// limit: Some(NonZeroU8::new(101).unwrap()), - /// ..XPLeaderboardQuery::new() - /// }; - /// assert!(invalid_query.is_invalid_limit_range()); - /// ``` - #[allow(clippy::nonminimal_bool)] - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit { - !(l <= NonZeroU8::new(100).unwrap()) - } else { - false - } - } - - /// Builds the query parameters to `Vec<(String, String)>`. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let query = XPLeaderboardQuery::new(); - /// let query_params = query.build(); - /// ``` - pub(crate) fn build(mut self) -> Vec<(String, String)> { - // For not pass "inf" to puery parameters. - if let Some(BeforeAfter::After(b)) = self.before_or_after { - if b.is_infinite() { - self.before_or_after = None; - } - } - let mut result = Vec::new(); - if let Some(b_a) = self.before_or_after.clone() { - match b_a { - BeforeAfter::Before(b) => result.push(("before".to_string(), b.to_string())), - BeforeAfter::After(b) => result.push(("after".to_string(), b.to_string())), - } - } - if let Some(l) = self.limit { - result.push(("limit".to_string(), l.to_string())); - } - if let Some(c) = self.country { - result.push(("country".to_string(), c)); - } - result - } - - /// Returns the default [`XPLeaderboardQuery`]. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::query::XPLeaderboardQuery; - /// let query = XPLeaderboardQuery::default(); - /// ``` - fn default() -> Self { - Self { - before_or_after: None, - limit: None, - country: None, - } - } - } - - /// The bound. - /// - /// The `before` and `after` parameters may not be combined, - /// so need to either set the parameter. - #[derive(Clone, Debug)] - pub enum BeforeAfter { - /// The lower bound. - /// Use this to paginate upwards. - /// Take the highest seen value and pass that back through this field to continue scrolling. - /// If set, the search order is reversed (returning the lowest items that match the query) - Before(f64), - /// Use this to paginate downwards. - /// Take the lowest seen value and pass that back through this field to continue scrolling. - After(f64), - } -} - pub mod stream { //! Features for streams. From 4430d00cb90a846d5ad5622a919e6bd98dd134d2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:40:47 +0900 Subject: [PATCH 065/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20tests=20?= =?UTF-8?q?for=20`client::query`=20module=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/client.rs b/src/client.rs index 8fc3c45..a4af19a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2248,26 +2248,6 @@ mod tests { let _ = Client::new(); } - #[test] - fn init_xp_query() { - let mut _query = query::XPLeaderboardQuery::new(); - _query.init(); - } - - #[test] - #[should_panic] - fn panic_invalid_limit_range_in_xp_query_with101() { - let mut _query = query::XPLeaderboardQuery::new(); - _query.limit(101); - } - - #[test] - #[should_panic] - fn panic_invalid_limit_range_in_xp_query_with0() { - let mut _query = query::XPLeaderboardQuery::new(); - _query.limit(101); - } - #[test] fn fortylines_as_str() { assert_eq!(stream::StreamType::FortyLines.as_str(), "40l"); From 55f2dfb769906cf668f3fe36d920414616ece515 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:42:36 +0900 Subject: [PATCH 066/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Delete:=20`xp=5Fle?= =?UTF-8?q?aderboard.rs`=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_xp_leaderboard` is removed. --- src/model/mod.rs | 1 - src/model/xp_leaderboard.rs | 268 ------------------------------------ 2 files changed, 269 deletions(-) delete mode 100644 src/model/xp_leaderboard.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index 0895434..f3ce6c9 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -16,4 +16,3 @@ pub mod stream; pub mod summary; pub mod user; pub mod user_records; -pub mod xp_leaderboard; diff --git a/src/model/xp_leaderboard.rs b/src/model/xp_leaderboard.rs deleted file mode 100644 index 8390c88..0000000 --- a/src/model/xp_leaderboard.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! The XP leaderboard data. - -use crate::{ - model::{ - cache::CacheData, - user::{Role, UserId}, - }, - util::{max_f64, to_unix_ts}, -}; -use serde::Deserialize; - -/// The response for the XP leaderboard. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct XPLeaderboardResponse { - /// Whether the request was successful. - #[serde(rename = "success")] - pub is_success: bool, - /// The reason the request failed. - pub error: Option, - /// Data about how this request was cached. - pub cache: Option, - /// Query parameters used to request. - /// - /// # Notes - /// - /// This field will never be returned to you in the `None` state. - /// So you can call the `unwrap`method on this value, for example. - #[serde(default = "none")] - pub query: Option, - /// The requested XP leaderboard data. - pub data: Option, -} - -impl XPLeaderboardResponse { - /// Whether all query parameters are default. - pub fn is_default_query(&self) -> bool { - if let Some(qp) = self.query.as_ref() { - qp.before.is_none() && qp.after.is_none() && qp.limit.is_none() && qp.country.is_none() - } else { - true - } - } - - /// Whether the leaderboard is reversed. - pub fn is_reversed(&self) -> bool { - self.query.as_ref().unwrap().before.is_some() - } - - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } -} - -impl AsRef for XPLeaderboardResponse { - fn as_ref(&self) -> &Self { - self - } -} - -/// Returns a `None`. -fn none() -> Option { - None -} - -/// A cache of query parameters used to the request. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct QueryCache { - /// The lower bound in XP. - /// Use this to paginate upwards. - /// Take the highest seen XP and pass that back through this field to continue scrolling. - /// If set, the search order is reversed (returning the lowest items that match the query) - pub before: Option, - /// The upper bound in XP. - /// Use this to paginate downwards. - /// Take the lowest seen XP and pass that back through this field to continue scrolling. - /// Infinite([`f64::INFINITY`]) by default. - pub after: Option, - /// The amount of entries to return. - /// Between 1 and 100. - /// 50 by default. - pub limit: Option, - /// The ISO 3166-1 country code to filter to. - pub country: Option, -} - -impl QueryCache { - /// Whether all query parameters are default. - pub fn is_default_query(&self) -> bool { - self.before.is_none() - && self.after.is_none() - && self.limit.is_none() - && self.country.is_none() - } -} - -/// A requested XP leaderboard data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct XPLeaderboardData { - /// An array of the matched users. - pub users: Vec, -} - -impl AsRef for XPLeaderboardData { - fn as_ref(&self) -> &Self { - self - } -} - -/// The matched user's data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct User { - /// The user's internal ID. - #[serde(rename = "_id")] - pub id: UserId, - /// The user's username. - #[serde(rename = "username")] - pub name: String, - /// The user's role. - pub role: Role, - /// When the user account was created. - /// If not set, this account was created before join dates were recorded. - #[serde(rename = "ts")] - pub created_at: Option, - /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. Some vanity flags exist. - pub country: Option, - /// Whether this user is currently supporting TETR.IO <3 - #[serde(rename = "supporter")] - pub is_supporter: Option, // EXCEPTION - /// Whether this user is a verified account. - #[serde(rename = "verified")] - pub is_verified: bool, - /// The user's XP in points. - pub xp: f64, - /// The amount of online games played by this user. - /// If the user has chosen to hide this statistic, it will be -1. - #[serde(rename = "gamesplayed")] - pub play_count: i32, - /// The amount of online games won by this user. - /// If the user has chosen to hide this statistic, it will be -1. - #[serde(rename = "gameswon")] - pub win_count: i32, - /// The amount of seconds this user spent playing, both on- and offline. - /// If the user has chosen to hide this statistic, it will be -1. - #[serde(rename = "gametime")] - pub play_time: f64, -} - -impl User { - /// Returns the level based on the user's xp. - pub fn level(&self) -> u32 { - let xp = self.xp; - // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 - ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() - as u32 - } - - /// Returns UNIX timestamp when the user's account created, if one exists. - pub fn account_created_at(&self) -> Option { - self.created_at.as_ref().map(|ts| to_unix_ts(ts)) - } - - /// Returns an `Option`. - /// - /// If user is displaying the country, - /// returns `Some(String)` with an image URL of the national flag based on the user's ISO 3166-1 country code. - /// If the user is not displaying the country, returns `None`. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } - - /// Whether the user is an anonymous. - pub fn is_anon(&self) -> bool { - self.role.is_anon() - } - - /// Whether the user is a bot. - pub fn is_bot(&self) -> bool { - self.role.is_bot() - } - - /// Whether the user is a SYSOP. - pub fn is_sysop(&self) -> bool { - self.role.is_sysop() - } - - /// Whether the user is an administrator. - pub fn is_admin(&self) -> bool { - self.role.is_admin() - } - - /// Whether the user is a moderator. - pub fn is_mod(&self) -> bool { - self.role.is_mod() - } - - /// Whether the user is a community moderator. - pub fn is_halfmod(&self) -> bool { - self.role.is_halfmod() - } - - /// Whether the user is banned. - pub fn is_banned(&self) -> bool { - self.role.is_banned() - } - - /// Whether the user is a supporter. - pub fn is_supporter(&self) -> bool { - self.is_supporter.unwrap_or(false) - } - - /// Whether the user is verified. - pub fn is_verified(&self) -> bool { - self.is_verified - } - - /// Whether the user is an anonymous. - #[deprecated(since = "0.3.5", note = "typo in function name. use `is_anon` instead")] - pub fn is_anonymous(&self) -> bool { - self.role.is_anon() - } - - /// Whether the user is an administrator. - #[deprecated( - since = "0.3.5", - note = "typo in function name. use `is_admin` instead" - )] - pub fn is_administrator(&self) -> bool { - self.role.is_admin() - } - - /// Whether the user is a moderator. - #[deprecated(since = "0.3.5", note = "typo in function name. use `is_mod` instead")] - pub fn is_moderator(&self) -> bool { - self.role.is_mod() - } -} - -impl AsRef for User { - fn as_ref(&self) -> &Self { - self - } -} From 99c370d6cdfd8d14076cd3d56b326d67e25f477f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:44:15 +0900 Subject: [PATCH 067/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20tests=20?= =?UTF-8?q?for=20`get=5Fxp=5Fleaderboard`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/client.rs | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/tests/client.rs b/tests/client.rs index 6effb2b..02c958e 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -16,48 +16,6 @@ fn get_server_activity_data() { let _ = tokio_test::block_on(Client::new().get_server_activity()); } -#[test] -fn get_xp_leaderboard_data() { - let _ = - tokio_test::block_on(Client::new().get_xp_leaderboard(query::XPLeaderboardQuery::new())); -} - -#[test] -fn get_xp_leaderboard_data_with_a_query() { - let _ = tokio_test::block_on( - Client::new().get_xp_leaderboard(query::XPLeaderboardQuery::new().limit(2)), - ); -} - -#[test] -fn get_xp_leaderboard_data_with_two_queries() { - let _ = tokio_test::block_on( - Client::new().get_xp_leaderboard(query::XPLeaderboardQuery::new().limit(2).before(23000.)), - ); -} - -#[test] -fn get_xp_leaderboard_data_with_three_queries() { - let _ = tokio_test::block_on( - Client::new().get_xp_leaderboard( - query::XPLeaderboardQuery::new() - .limit(2) - .country("us") - .after(13000.), - ), - ); -} - -#[test] -#[should_panic] -fn panic_if_invalid_limit_range_exhaustive_in_getting_xp_leaderboard() { - let q = query::XPLeaderboardQuery { - limit: Some(std::num::NonZeroU8::new(101).unwrap()), - ..query::XPLeaderboardQuery::new() - }; - let _ = tokio_test::block_on(Client::new().get_xp_leaderboard(q)); -} - #[test] fn get_stream_data() { let _ = tokio_test::block_on(Client::new().get_stream( From a299752344d23cb89186ba0d8245193ec94e1844 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:47:05 +0900 Subject: [PATCH 068/255] =?UTF-8?q?=F0=9F=94=A5=20Remove:=20`get=5Fstream`?= =?UTF-8?q?=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 85 --------------------------------------------------- 1 file changed, 85 deletions(-) diff --git a/src/client.rs b/src/client.rs index a4af19a..181fe0c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -14,7 +14,6 @@ use crate::{ searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, - stream::StreamResponse, summary::{ achievements::AchievementsResponse, blitz::BlitzResponse, @@ -790,90 +789,6 @@ impl Client { response(res).await } - /// Returns the stream model. - /// - /// # Arguments - /// - /// - `stream_type`: - /// - /// The type of Stream. - /// Currently - /// [`StreamType::FortyLines`](stream::StreamType::FortyLines), - /// [`StreamType::Blitz`](stream::StreamType::Blitz), - /// [`StreamType::Any`](stream::StreamType::Any), - /// or [`StreamType::League`](stream::StreamType::League). - /// - /// - `stream_context`: - /// - /// The context of the Stream. - /// Currently - /// [`StreamContext::Global`](stream::StreamContext::Global), - /// [`StreamContext::UserBest`](stream::StreamContext::UserBest), - /// or [`StreamContext::UserRecent`](stream::StreamContext::UserRecent). - /// - /// - `stream_identifier` (Optional): - /// - /// If applicable. - /// For example, in the case of "userbest" or "userrecent", the user ID. - /// - /// # Examples - /// - /// Getting the stream object: - /// - /// ```no_run - /// use tetr_ch::client::{ - /// Client, - /// stream::{StreamType, StreamContext} - /// }; - /// # use std::io; - /// - /// # async fn run() -> io::Result<()> { - /// let client = Client::new(); - /// - /// // Get the stream. - /// let user = client.get_stream( - /// // 40 LINES. - /// StreamType::FortyLines, - /// // User's best. - /// StreamContext::UserBest, - /// // User ID. - /// Some("621db46d1d638ea850be2aa0"), - /// ).await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// Go to [`stream::StreamType`] | [`stream::StreamContext`]. - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_stream( - self, - stream_type: stream::StreamType, - stream_context: stream::StreamContext, - stream_identifier: Option<&str>, - ) -> RspErr { - let stream_id = format!( - "{}_{}{}", - stream_type.as_str(), - stream_context.as_str(), - if let Some(i) = stream_identifier { - format!("_{}", i) - } else { - String::new() - } - ); - let url = format!("{}streams/{}", API_URL, stream_id.to_lowercase()); - let res = self.client.get(url).send().await; - response(res).await - } - /// Returns the latest news items in any stream. /// /// # Arguments From d6c9e744f7b03efc733fec40467f28befddcb8ec Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:49:47 +0900 Subject: [PATCH 069/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20`StreamT?= =?UTF-8?q?ype`=20enum=20and=20`StreamContext`=20enum=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_stream` method is removed. --- src/client.rs | 67 --------------------------------------------------- 1 file changed, 67 deletions(-) diff --git a/src/client.rs b/src/client.rs index 181fe0c..50f1443 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1144,73 +1144,6 @@ where pub mod stream { //! Features for streams. - /// Enum for the stream type. - pub enum StreamType { - /// 40 LINES - FortyLines, - /// BLITZ - Blitz, - /// Any - Any, - /// TETRA LEAGUE - League, - } - - impl StreamType { - /// Converts to a `&str`. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::stream::StreamType; - /// let forty_lines = StreamType::FortyLines; - /// let blitz = StreamType::Blitz; - /// let any = StreamType::Any; - /// let league = StreamType::League; - /// assert_eq!(forty_lines.as_str(), "40l"); - /// assert_eq!(blitz.as_str(), "blitz"); - /// assert_eq!(any.as_str(), "any"); - /// assert_eq!(league.as_str(), "league"); - /// ``` - pub(crate) fn as_str(&self) -> &str { - match self { - StreamType::FortyLines => "40l", - StreamType::Blitz => "blitz", - StreamType::Any => "any", - StreamType::League => "league", - } - } - } - - /// Enum for the stream context. - pub enum StreamContext { - Global, - UserBest, - UserRecent, - } - - impl StreamContext { - /// Converts to a `&str`. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::stream::StreamContext; - /// let global = StreamContext::Global; - /// let user_best = StreamContext::UserBest; - /// let user_recent = StreamContext::UserRecent; - /// assert_eq!(global.as_str(), "global"); - /// assert_eq!(user_best.as_str(), "user_best"); - /// assert_eq!(user_recent.as_str(), "user_recent"); - pub(crate) fn as_str(&self) -> &str { - match self { - StreamContext::Global => "global", - StreamContext::UserBest => "userbest", - StreamContext::UserRecent => "userrecent", - } - } - } - /// The news subject. pub enum NewsStream { /// Global news. From 73b1a8c610cec9fd223b76cec15aaf6936e9ecad Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:50:53 +0900 Subject: [PATCH 070/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20tests=20?= =?UTF-8?q?for=20`StreamType`=20enum=20and=20`StreamContext`=20enum=20[#64?= =?UTF-8?q?]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/client.rs b/src/client.rs index 50f1443..c17d041 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2095,39 +2095,4 @@ mod tests { fn create_a_new_client() { let _ = Client::new(); } - - #[test] - fn fortylines_as_str() { - assert_eq!(stream::StreamType::FortyLines.as_str(), "40l"); - } - - #[test] - fn blitz_as_str() { - assert_eq!(stream::StreamType::Blitz.as_str(), "blitz"); - } - - #[test] - fn any_as_str() { - assert_eq!(stream::StreamType::Any.as_str(), "any"); - } - - #[test] - fn league_as_str() { - assert_eq!(stream::StreamType::League.as_str(), "league"); - } - - #[test] - fn global_as_str() { - assert_eq!(stream::StreamContext::Global.as_str(), "global"); - } - - #[test] - fn userbest_as_str() { - assert_eq!(stream::StreamContext::UserBest.as_str(), "userbest"); - } - - #[test] - fn userrecent_as_str() { - assert_eq!(stream::StreamContext::UserRecent.as_str(), "userrecent"); - } } From 6a86b6266e9bde9bf68291ce07c0c355086a1bd2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 10 Nov 2024 23:56:24 +0900 Subject: [PATCH 071/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Delete:=20`stream.?= =?UTF-8?q?rs`=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_stream` method is removed. --- src/model/mod.rs | 1 - src/model/stream.rs | 63 --------------------------------------------- 2 files changed, 64 deletions(-) delete mode 100644 src/model/stream.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index f3ce6c9..4901b03 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -12,7 +12,6 @@ pub mod records_leaderboard; pub mod searched_user; pub mod server_activity; pub mod server_stats; -pub mod stream; pub mod summary; pub mod user; pub mod user_records; diff --git a/src/model/stream.rs b/src/model/stream.rs deleted file mode 100644 index 9ea84f9..0000000 --- a/src/model/stream.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Stream model. - -use crate::model::{cache::CacheData, record::Record}; -use serde::Deserialize; - -/// The response for the stream. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct StreamResponse { - /// Whether the request was successful. - #[serde(rename = "success")] - pub is_success: bool, - /// The reason the request failed. - pub error: Option, - /// Data about how this request was cached. - pub cache: Option, - /// The requested stream data. - pub data: Option, -} -impl StreamResponse { - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } -} - -impl AsRef for StreamResponse { - fn as_ref(&self) -> &Self { - self - } -} - -/// The requested stream data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct StreamData { - pub records: Vec, -} - -impl AsRef for StreamData { - fn as_ref(&self) -> &Self { - self - } -} From 142c24a92ae8c2090faf91c10532fea8a7aadefc Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 00:01:45 +0900 Subject: [PATCH 072/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Delete:=20`record.?= =?UTF-8?q?rs`=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because the `Client::get_stream` method is removed. --- src/model/mod.rs | 1 - src/model/record.rs | 526 -------------------------------------------- 2 files changed, 527 deletions(-) delete mode 100644 src/model/record.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index 4901b03..f28437e 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -7,7 +7,6 @@ pub mod latest_news; pub mod leaderboard; pub mod league; pub mod news; -pub mod record; pub mod records_leaderboard; pub mod searched_user; pub mod server_activity; diff --git a/src/model/record.rs b/src/model/record.rs deleted file mode 100644 index 07d8d9c..0000000 --- a/src/model/record.rs +++ /dev/null @@ -1,526 +0,0 @@ -//! The record data. - -use self::{multi_play_end_ctx::MultiPlayEndCtx, single_play_end_ctx::SinglePlayEndCtx}; -use crate::{model::user::UserId, util::to_unix_ts}; -use serde::Deserialize; - -/// The record data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct Record { - /// The Record's ID. - /// This is NOT the replay ID. - #[serde(rename = "_id")] - pub record_id: String, - /// The Stream this Record belongs to. - pub stream: String, - /// The ID of the associated replay. - /// This is NOT the Record's ID. - #[serde(rename = "replayid")] - pub replay_id: String, - /// The user who set this Record. - pub user: User, - /// The time this record was set. - #[serde(rename = "ts")] - pub recorded_at: String, - /// Whether this is a multiplayer replay. - #[serde(rename = "ismulti")] - pub is_multi: Option, - /// The state this replay finished with. - pub endcontext: EndContext, -} - -impl Record { - /// Returns the record URL. - pub fn record_url(&self) -> String { - format!("https://tetr.io/#r:{}", self.replay_id) - } - - /// Returns a UNIX timestamp when this record was recorded. - pub fn recorded_at(&self) -> i64 { - to_unix_ts(&self.recorded_at) - } -} - -impl AsRef for Record { - fn as_ref(&self) -> &Self { - self - } -} - -/// If [`Record::is_multi`] is true, this is the multiplayer end contexts. -/// Otherwise, this is the singleplayer end context. -#[derive(Clone, Debug, Deserialize)] -#[serde(untagged)] -pub enum EndContext { - SinglePlay(Box), - MultiPlay(Vec), -} - -impl EndContext { - /// Whether the end context is singleplay. - pub fn is_single_play(&self) -> bool { - matches!(self, EndContext::SinglePlay(_)) - } - - /// Whether the end context is multiplay. - pub fn is_multi_play(&self) -> bool { - matches!(self, EndContext::MultiPlay(_)) - } - - /// Converts from `EndContext` to [`Option`]. - /// - /// Returns `Some` if this is singleplay end context, - /// otherwise returns `None`. - pub fn single_play(self) -> Option { - match self { - EndContext::SinglePlay(s) => Some(*s), - EndContext::MultiPlay(_) => None, - } - } - - /// Converts from `EndContext` to [`Option`]. - /// - /// Returns `Some` if this is multiplay end context, - /// otherwise returns `None`. - pub fn multi_play(self) -> Option> { - match self { - EndContext::SinglePlay(_) => None, - EndContext::MultiPlay(m) => Some(m), - } - } -} - -impl AsRef for EndContext { - fn as_ref(&self) -> &Self { - self - } -} - -pub mod single_play_end_ctx { - use super::*; - - /// The state this singleplayer replay finished with. - /// - /// ***No information about the endcontext field is given in the TETRA CHANNEL API docs, - /// so the explanation of each content is a guess.** - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct SinglePlayEndCtx { - /// A seed for RNG. - pub seed: Option, - /// The number of cleared lines. - #[serde(rename = "lines")] - pub cleared_lines: Option, - /// - pub level_lines: Option, - /// - pub level_lines_needed: Option, - /// The number of keys presses. - pub inputs: Option, - /// The number of holds. - pub holds: Option, - /// - pub time: Option, - /// The record's score. - pub score: Option, - /// - #[serde(rename = "zenlevel")] - pub zen_level: Option, - /// - #[serde(rename = "zenprogress")] - pub zen_progress: Option, - /// The level of the record. - pub level: Option, - /// - pub combo: Option, - /// - #[serde(rename = "currentcombopower")] - pub current_combo_power: Option, - /// The number of maximum combo (zero indexed). - #[serde(rename = "topcombo")] - pub top_combo: Option, - /// - pub btb: Option, - /// The number of maximum Back To Back chain (zero indexed). - #[serde(rename = "topbtb")] - pub top_btb: Option, - /// - #[serde(rename = "currentbtbchainpower")] - pub current_btb_chain_power: Option, - /// The number of T-Spins. - #[serde(rename = "tspins")] - pub t_spins: Option, - /// The number of pieces places. - #[serde(rename = "piecesplaced")] - pub pieces_placed: Option, - /// How the lines was cleared. - pub clears: Option, - /// Garbage-related data. - pub garbage: Option, - /// The number of kills. - pub kills: Option, - /// The finesse data. - pub finesse: Option, - /// The time at the finished. - #[serde(rename = "finalTime")] - pub final_time: Option, - /// The game type. - #[serde(rename = "gametype")] - pub game_type: Option, - } - - impl SinglePlayEndCtx { - //! # Warning - //! - //! Calling these methods from a [`Record`] retrieved from other than method [`get_user_records`](crate::client::Client::get_user_records) is deprecated. - //! - //! These are because the docs for the [TETRA CHANNEL API](https://tetr.io/about/api/) are incomplete, - //! so we cannot guarantee which values are passed. - - /// Returns the PPS(Pieces Per Second) of this replay. - /// - /// Read the [warning](#warning) before using this method. - /// - /// # Panics - /// - /// Panics if necessary things is missing. - /// I can't predict when what will be missing. - pub fn pps(&self) -> f64 { - self.pieces_placed.unwrap() as f64 / (self.final_time.unwrap() / 1000.) - } - - /// Returns the KPP(Keys Per Piece) of this replay. - /// - /// Read the [warning](#warning) before using this method. - /// - /// # Panics - /// - /// Panics if necessary things is missing. - /// I can't predict when what will be missing. - pub fn kpp(&self) -> f64 { - self.inputs.unwrap() as f64 / self.pieces_placed.unwrap() as f64 - } - - /// Returns the KPS(Keys Per Second) of this replay. - /// - /// Read the [warning](#warning) before using this method. - /// - /// # Panics - /// - /// Panics if necessary things is missing. - /// I can't predict when what will be missing. - pub fn kps(&self) -> f64 { - self.inputs.unwrap() as f64 / (self.final_time.unwrap() / 1000.) - } - - /// Returns the LPM(Lines Per Minute) of this replay. - /// - /// Read the [warning](#warning) before using this method. - /// - /// # Panics - /// - /// Panics if necessary things is missing. - /// I can't predict when what will be missing. - pub fn lpm(&self) -> f64 { - self.cleared_lines.unwrap() as f64 / (self.final_time.unwrap() / 60000.) - } - - /// Returns the SPP(Score Per Piece) of this replay. - /// - /// Read the [warning](#warning) before using this method. - /// - /// # Panics - /// - /// Panics if necessary things is missing. - /// I can't predict when what will be missing. - pub fn spp(&self) -> f64 { - self.score.unwrap() as f64 / self.pieces_placed.unwrap() as f64 - } - - /// Returns the finesse rate of this replay. - /// - /// Read the [warning](#warning) before using this method. - /// - /// # Panics - /// - /// Panics if necessary things is missing. - /// I can't predict when what will be missing. - pub fn finesse_rate(&self) -> f64 { - self.clone().finesse.unwrap().perfect_pieces.unwrap() as f64 - / self.pieces_placed.unwrap() as f64 - * 100. - } - } - - impl AsRef for SinglePlayEndCtx { - fn as_ref(&self) -> &Self { - self - } - } - - /// - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct EndCtxTime { - /// - pub start: Option, - /// - pub zero: Option, - /// - pub locked: Option, - /// - pub prev: Option, - /// - #[serde(rename = "frameoffset")] - pub frame_offset: Option, - } - - impl AsRef for EndCtxTime { - fn as_ref(&self) -> &Self { - self - } - } - - /// How the lines was cleared. - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct EndCtxClears { - /// The number of cleared with Singles. - pub singles: Option, - /// The number of cleared with Doubles - pub doubles: Option, - /// The number of cleared with Triples - pub triples: Option, - /// The number of cleared with Quads - pub quads: Option, - /// The number of cleared with Pentas - pub pentas: Option, - /// The number of cleared with Realt T-Spins - #[serde(rename = "realtspins")] - pub realt_spins: Option, - /// The number of cleared with Mini T-Spins - #[serde(rename = "minitspins")] - pub mini_t_spins: Option, - /// The number of cleared with Mini T-Spin Singles - #[serde(rename = "minitspinsingles")] - pub mini_tss: Option, - /// The number of cleared with Mini T-Spin Doubles - #[serde(rename = "minitspindoubles")] - pub mini_tsd: Option, - /// The number of cleared with T-Spin Singles - #[serde(rename = "tspinsingles")] - pub tss: Option, - /// The number of cleared with T-Spin Doubles - #[serde(rename = "tspindoubles")] - pub tsd: Option, - /// The number of cleared with T-Spin Triples - #[serde(rename = "tspintriples")] - pub tst: Option, - /// The number of cleared with T-Spin Quads - #[serde(rename = "tspinquads")] - pub t_spin_quads: Option, - /// The number of cleared with T-Spin Pentas - #[serde(rename = "tspinpentas")] - pub t_spin_pentas: Option, - /// The number of cleared with All Clears - pub all_clears: Option, - } - - impl AsRef for EndCtxClears { - fn as_ref(&self) -> &Self { - self - } - } - - /// Garbage-related data. - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct EndCtxGarbage { - /// The number of garbage sent. - pub sent: Option, - /// The number of garbage received. - pub received: Option, - /// The number of garbage attacks. - #[serde(rename = "attack")] - pub attacks: Option, - /// The number of garbage cleared. - pub cleared: Option, - /// The number of garbage attacks. - #[deprecated( - since = "0.5.0", - note = "This field name is not appropriate. This field cannot be used anymore, so use `attacks` instead" - )] - pub attack: Option, - } - - impl AsRef for EndCtxGarbage { - fn as_ref(&self) -> &Self { - self - } - } - - /// About the finesse data. - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct EndCtxFinesse { - /// The number of maximum finesse chain (?) - pub combo: Option, - /// The num of finesse faults. - pub faults: Option, - /// The number of perfect finesses. - #[serde(rename = "perfectpieces")] - pub perfect_pieces: Option, - } - - impl AsRef for EndCtxFinesse { - fn as_ref(&self) -> &Self { - self - } - } -} - -pub mod multi_play_end_ctx { - use super::*; - - /// The state this multiplayer replay finished with. - /// - /// ***No information about the endcontext field is given in the TETRA CHANNEL API docs, - /// so the explanation of each content is a guess.** - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct MultiPlayEndCtx { - /// Who is finished with this state. - pub user: Option, - /// This user's handling settings. - pub handling: Option, - /// - #[serde(rename = "active")] - pub is_active: Option, - /// Whether this user is winner. - #[serde(rename = "success")] - pub is_success: Option, - /// The number of keys presses. - pub inputs: Option, - /// The number of pieces placed. - #[serde(rename = "piecesplaced")] - pub pieces_placed: Option, - /// This user's natural order in this record. - #[serde(rename = "naturalorder")] - pub natural_order: Option, - /// - pub score: Option, - /// The number of wins. - pub wins: Option, - /// The points data. - pub points: Option, - } - - impl AsRef for MultiPlayEndCtx { - fn as_ref(&self) -> &Self { - self - } - } - - /// This user's handling settings. - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct Handling { - /// ARR(Automatic Repeat Rate). - pub arr: Option, - /// DAS(Delayed Auto Shift). - pub das: Option, - /// DCD(DAS Cut Delay). - pub dcd: Option, - /// SDF(Soft Drop Factor). - pub sdf: Option, - /// Whether "accidental hard drops prevention" is enabled. - #[serde(rename = "safelock")] - pub enable_safe_lock: Option, - /// Whether "DAS cancellation when changing directions" is enabled. - #[serde(rename = "cancel")] - pub enable_das_cancel: Option, - } - - impl AsRef for Handling { - fn as_ref(&self) -> &Self { - self - } - } - - /// The points data. - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct Points { - /// The number of wins. - pub primary: Option, - /// APM(Attacks Per Minute). - pub secondary: Option, - /// PPS(Pieces Per Second). - pub tertiary: Option, - /// Extra data. - pub extra: Extra, - /// APM for each game. - #[serde(rename = "secondaryAvgTracking")] - pub secondary_avg_tracking: Option>, - /// PPS for each game. - #[serde(rename = "tertiaryAvgTracking")] - pub tertiary_avg_tracking: Option>, - /// Extra data for each game. - #[serde(rename = "extraAvgTracking")] - pub extra_avg_tracking: Option, - } - - impl AsRef for Points { - fn as_ref(&self) -> &Self { - self - } - } - - /// Extra data. - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct Extra { - /// VS score. - pub vs: Option, - } - - impl AsRef for Extra { - fn as_ref(&self) -> &Self { - self - } - } - - /// Extra data for each game. - #[derive(Clone, Debug, Deserialize)] - #[non_exhaustive] - pub struct ExtraAvgTracking { - /// VS score for each game. - #[serde(rename = "aggregatestats___vsscore")] - pub aggregate_stats_vs_score: Option>, - } - - impl AsRef for ExtraAvgTracking { - fn as_ref(&self) -> &Self { - self - } - } -} - -/// The user object. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct User { - /// The user's internal ID. - #[serde(rename = "_id")] - pub id: UserId, - /// The user's username. - #[serde(rename = "username")] - pub name: String, -} - -impl AsRef for User { - fn as_ref(&self) -> &Self { - self - } -} From 1864d0f3f64913903044f335d60d39b7e3bc48f5 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 00:03:07 +0900 Subject: [PATCH 073/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove:=20tests=20?= =?UTF-8?q?for=20`get=5Fstream`=20method=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/client.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/client.rs b/tests/client.rs index 02c958e..fc3925a 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -16,24 +16,6 @@ fn get_server_activity_data() { let _ = tokio_test::block_on(Client::new().get_server_activity()); } -#[test] -fn get_stream_data() { - let _ = tokio_test::block_on(Client::new().get_stream( - stream::StreamType::FortyLines, - stream::StreamContext::Global, - None, - )); -} - -#[test] -fn get_user_40l_best_stream_data() { - let _ = tokio_test::block_on(Client::new().get_stream( - stream::StreamType::FortyLines, - stream::StreamContext::UserBest, - Some("621db46d1d638ea850be2aa0"), - )); -} - #[test] fn get_latest_global_news_data() { let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Global, 3)); From 8d8acad340d7e5b4708c0160384d71300ca3bf6d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 00:47:18 +0900 Subject: [PATCH 074/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`client`=20modul?= =?UTF-8?q?e=20now=20has=20its=20own=20directory=20[#64]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{client.rs => client/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{client.rs => client/mod.rs} (100%) diff --git a/src/client.rs b/src/client/mod.rs similarity index 100% rename from src/client.rs rename to src/client/mod.rs From 86a3872c697dae2dd356c12c5c82563f0a1a97e8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 01:09:44 +0900 Subject: [PATCH 075/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`response`=20fun?= =?UTF-8?q?ction=20now=20has=20its=20own=20file=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/mod.rs | 36 ++++-------------------------------- src/client/response.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 32 deletions(-) create mode 100644 src/client/response.rs diff --git a/src/client/mod.rs b/src/client/mod.rs index c17d041..73f2631 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,7 +1,8 @@ //! Client for API requests. use crate::{ - error::{ResponseError, Status}, + client::response::response, + error::ResponseError, model::{ achievement_info::AchievementInfoResponse, labs::{ @@ -27,9 +28,7 @@ use crate::{ user_records::UserRecordsResponse, }, }; -use http::status::StatusCode; -use reqwest::{self, Error, Response}; -use serde::Deserialize; +use reqwest::{self}; const API_URL: &str = "https://ch.tetr.io/api/"; @@ -1112,34 +1111,7 @@ impl Client { } } -/// Receives `Result` and returns `Result`. -/// -/// # Examples -/// -/// ```ignore -/// let res = self.client.get(url).send().await; -/// response(res).await -/// ``` -async fn response(response: Result) -> RspErr -where - for<'de> T: Deserialize<'de>, -{ - match response { - Ok(r) => { - if !r.status().is_success() { - match StatusCode::from_u16(r.status().as_u16()) { - Ok(c) => return Err(ResponseError::HttpErr(Status::Valid(c))), - Err(e) => return Err(ResponseError::HttpErr(Status::Invalid(e))), - } - } - match r.json().await { - Ok(m) => Ok(m), - Err(e) => Err(ResponseError::DeserializeErr(e.to_string())), - } - } - Err(e) => Err(ResponseError::RequestErr(e.to_string())), - } -} +mod response; pub mod stream { //! Features for streams. diff --git a/src/client/response.rs b/src/client/response.rs new file mode 100644 index 0000000..e2ae5b0 --- /dev/null +++ b/src/client/response.rs @@ -0,0 +1,33 @@ +use crate::error::{ResponseError, Status}; +use http::StatusCode; +use reqwest::{Error, Response}; +use serde::Deserialize; + +/// Receives a `Result` and returns a `Result`. +/// +/// # Examples +/// +/// ```ignore +/// let res = self.client.get(url).send().await; +/// response(res).await +/// ``` +pub(super) async fn response(response: Result) -> Result +where + for<'de> T: Deserialize<'de>, +{ + match response { + Ok(r) => { + if !r.status().is_success() { + match StatusCode::from_u16(r.status().as_u16()) { + Ok(c) => return Err(ResponseError::HttpErr(Status::Valid(c))), + Err(e) => return Err(ResponseError::HttpErr(Status::Invalid(e))), + } + } + match r.json().await { + Ok(m) => Ok(m), + Err(e) => Err(ResponseError::DeserializeErr(e.to_string())), + } + } + Err(e) => Err(ResponseError::RequestErr(e.to_string())), + } +} From 692780921bc1e8b645faa68315a98dbb928460b2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 01:50:11 +0900 Subject: [PATCH 076/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=F0=9F=9A=9A=20Change?= =?UTF-8?q?:=20rename=20and=20move=20`stream`=20module=20to=20`param::news?= =?UTF-8?q?=5Fstream`=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/mod.rs | 38 +++------------------------------ src/client/param/mod.rs | 3 +++ src/client/param/news_stream.rs | 30 ++++++++++++++++++++++++++ tests/client.rs | 8 +++---- 4 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 src/client/param/mod.rs create mode 100644 src/client/param/news_stream.rs diff --git a/src/client/mod.rs b/src/client/mod.rs index 73f2631..fc2d0a8 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,7 +1,7 @@ //! Client for API requests. +use self::{param::news_stream::NewsStream, response::response}; use crate::{ - client::response::response, error::ResponseError, model::{ achievement_info::AchievementInfoResponse, @@ -896,7 +896,7 @@ impl Client { /// ``` pub async fn get_news_latest( self, - stream: stream::NewsStream, + stream: NewsStream, limit: u8, ) -> RspErr { if !(1..=100).contains(&limit) { @@ -1112,39 +1112,7 @@ impl Client { } mod response; - -pub mod stream { - //! Features for streams. - - /// The news subject. - pub enum NewsStream { - /// Global news. - Global, - /// News of the user. - /// The user ID is required. - User(String), - } - - impl NewsStream { - /// Converts into a parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::stream::NewsStream; - /// let global = NewsStream::Global; - /// let user = NewsStream::User("621db46d1d638ea850be2aa0".to_string()); - /// assert_eq!(global.to_param(), "global"); - /// assert_eq!(user.to_param(), "user_621db46d1d638ea850be2aa0"); - /// ``` - pub(crate) fn to_param(&self) -> String { - match self { - NewsStream::Global => "global".to_string(), - NewsStream::User(id) => format!("user_{}", id), - } - } - } -} +pub mod param; pub mod search_user { //! Features for searching users. diff --git a/src/client/param/mod.rs b/src/client/param/mod.rs new file mode 100644 index 0000000..6da4c4b --- /dev/null +++ b/src/client/param/mod.rs @@ -0,0 +1,3 @@ +//! Features for the parameters of the [`Client`](crate::client::Client) struct methods. + +pub mod news_stream; diff --git a/src/client/param/news_stream.rs b/src/client/param/news_stream.rs new file mode 100644 index 0000000..3b23398 --- /dev/null +++ b/src/client/param/news_stream.rs @@ -0,0 +1,30 @@ +//! Features for news streams. + +/// A news stream. +pub enum NewsStream { + /// A global news stream. + Global, + /// A news stream of the user. + /// Contains a user ID. + User(String), +} + +impl NewsStream { + /// Converts into a parameter string. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::stream::NewsStream; + /// let global = NewsStream::Global; + /// let user = NewsStream::User("621db46d1d638ea850be2aa0".to_string()); + /// assert_eq!(global.to_param(), "global"); + /// assert_eq!(user.to_param(), "user_621db46d1d638ea850be2aa0"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + NewsStream::Global => "global".to_string(), + NewsStream::User(id) => format!("user_{}", id), + } + } +} diff --git a/tests/client.rs b/tests/client.rs index fc3925a..77eee3e 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -1,4 +1,4 @@ -use tetr_ch::client::*; +use tetr_ch::client::{*, param::news_stream::*}; #[test] fn get_usr_data() { @@ -18,13 +18,13 @@ fn get_server_activity_data() { #[test] fn get_latest_global_news_data() { - let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Global, 3)); + let _ = tokio_test::block_on(Client::new().get_news_latest(NewsStream::Global, 3)); } #[test] fn get_latest_user_scale_news_data() { let _ = tokio_test::block_on(Client::new().get_news_latest( - stream::NewsStream::User("621db46d1d638ea850be2aa0".to_string()), + NewsStream::User("621db46d1d638ea850be2aa0".to_string()), 3, )); } @@ -32,5 +32,5 @@ fn get_latest_user_scale_news_data() { #[test] #[should_panic] fn panic_if_invalid_limit_range_in_getting_latest_news() { - let _ = tokio_test::block_on(Client::new().get_news_latest(stream::NewsStream::Global, 0)); + let _ = tokio_test::block_on(Client::new().get_news_latest(NewsStream::Global, 0)); } From c26253dd273056753ea389f368b91570375102ba Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 02:27:31 +0900 Subject: [PATCH 077/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`search=5Fuser`?= =?UTF-8?q?=20module=20to=20`param`=20module=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/mod.rs | 33 ++------------------------------- src/client/param/mod.rs | 1 + src/client/param/search_user.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 31 deletions(-) create mode 100644 src/client/param/search_user.rs diff --git a/src/client/mod.rs b/src/client/mod.rs index fc2d0a8..b88e6bb 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,6 +1,6 @@ //! Client for API requests. -use self::{param::news_stream::NewsStream, response::response}; +use self::{param::{news_stream::NewsStream, search_user::SocialConnection}, response::response}; use crate::{ error::ResponseError, model::{ @@ -950,7 +950,7 @@ impl Client { /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn search_user( self, - social_connection: search_user::SocialConnection, + social_connection: SocialConnection, ) -> RspErr { let url = format!("{}users/search/{}", API_URL, social_connection.to_param()); let res = self.client.get(url).send().await; @@ -1114,35 +1114,6 @@ impl Client { mod response; pub mod param; -pub mod search_user { - //! Features for searching users. - - /// The social connection. - /// - /// The API documentation says searching for the other social links will be added in the near future. - pub enum SocialConnection { - /// A Discord ID. - Discord(String), - } - - impl SocialConnection { - /// Converts into a parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::search_user::SocialConnection; - /// let discord_id = "724976600873041940".to_string(); - /// assert_eq!(SocialConnection::Discord(discord_id).to_param(), "discord:724976600873041940"); - /// ``` - pub(crate) fn to_param(&self) -> String { - match self { - SocialConnection::Discord(id) => format!("discord:{}", id), - } - } - } -} - pub mod leaderboard { //! Features for leaderboards. diff --git a/src/client/param/mod.rs b/src/client/param/mod.rs index 6da4c4b..63a53d8 100644 --- a/src/client/param/mod.rs +++ b/src/client/param/mod.rs @@ -1,3 +1,4 @@ //! Features for the parameters of the [`Client`](crate::client::Client) struct methods. pub mod news_stream; +pub mod search_user; diff --git a/src/client/param/search_user.rs b/src/client/param/search_user.rs new file mode 100644 index 0000000..5b98d04 --- /dev/null +++ b/src/client/param/search_user.rs @@ -0,0 +1,26 @@ +//! Features for the [`Client::search_user`](crate::client::Client::search_user) method. + +/// A social connection. +/// +/// [API document](https://tetr.io/about/api/#userssearchquery) says searching for the other social links will be added in the near future. +pub enum SocialConnection { + /// A Discord ID. + Discord(String), +} + +impl SocialConnection { + /// Converts into a parameter string. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::search_user::SocialConnection; + /// let connection = SocialConnection::Discord("724976600873041940".to_string()); + /// assert_eq!(connection.to_param(), "discord:724976600873041940"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + SocialConnection::Discord(id) => format!("discord:{}", id), + } + } +} From 29ee28d6d352f48c0ba6bf8c15b984c5a519b16c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 02:38:06 +0900 Subject: [PATCH 078/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=F0=9F=9A=9A=20Change?= =?UTF-8?q?:=20rename=20and=20move=20src/client/mod.rs=20to=20src/client.r?= =?UTF-8?q?s=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{client/mod.rs => client.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{client/mod.rs => client.rs} (100%) diff --git a/src/client/mod.rs b/src/client.rs similarity index 100% rename from src/client/mod.rs rename to src/client.rs From 82b06de8a6bf765370c94d533f371c5bacd3877f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 02:38:39 +0900 Subject: [PATCH 079/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 7 ++++-- src/client/param/news_stream.rs | 44 ++++++++++++++++----------------- tests/client.rs | 9 +++---- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/client.rs b/src/client.rs index b88e6bb..88103c5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,9 @@ //! Client for API requests. -use self::{param::{news_stream::NewsStream, search_user::SocialConnection}, response::response}; +use self::{ + param::{news_stream::NewsStream, search_user::SocialConnection}, + response::response, +}; use crate::{ error::ResponseError, model::{ @@ -1111,8 +1114,8 @@ impl Client { } } -mod response; pub mod param; +mod response; pub mod leaderboard { //! Features for leaderboards. diff --git a/src/client/param/news_stream.rs b/src/client/param/news_stream.rs index 3b23398..d35a9d3 100644 --- a/src/client/param/news_stream.rs +++ b/src/client/param/news_stream.rs @@ -2,29 +2,29 @@ /// A news stream. pub enum NewsStream { - /// A global news stream. - Global, - /// A news stream of the user. - /// Contains a user ID. - User(String), + /// A global news stream. + Global, + /// A news stream of the user. + /// Contains a user ID. + User(String), } impl NewsStream { - /// Converts into a parameter string. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::stream::NewsStream; - /// let global = NewsStream::Global; - /// let user = NewsStream::User("621db46d1d638ea850be2aa0".to_string()); - /// assert_eq!(global.to_param(), "global"); - /// assert_eq!(user.to_param(), "user_621db46d1d638ea850be2aa0"); - /// ``` - pub(crate) fn to_param(&self) -> String { - match self { - NewsStream::Global => "global".to_string(), - NewsStream::User(id) => format!("user_{}", id), - } - } + /// Converts into a parameter string. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::stream::NewsStream; + /// let global = NewsStream::Global; + /// let user = NewsStream::User("621db46d1d638ea850be2aa0".to_string()); + /// assert_eq!(global.to_param(), "global"); + /// assert_eq!(user.to_param(), "user_621db46d1d638ea850be2aa0"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + NewsStream::Global => "global".to_string(), + NewsStream::User(id) => format!("user_{}", id), + } + } } diff --git a/tests/client.rs b/tests/client.rs index 77eee3e..855e744 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -1,4 +1,4 @@ -use tetr_ch::client::{*, param::news_stream::*}; +use tetr_ch::client::{param::news_stream::*, *}; #[test] fn get_usr_data() { @@ -23,10 +23,9 @@ fn get_latest_global_news_data() { #[test] fn get_latest_user_scale_news_data() { - let _ = tokio_test::block_on(Client::new().get_news_latest( - NewsStream::User("621db46d1d638ea850be2aa0".to_string()), - 3, - )); + let _ = tokio_test::block_on( + Client::new().get_news_latest(NewsStream::User("621db46d1d638ea850be2aa0".to_string()), 3), + ); } #[test] From 32450daab8bf2bd3bab98784ec178d05a8de3e0e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 03:13:13 +0900 Subject: [PATCH 080/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20`client?= =?UTF-8?q?::leaderboard`=20modlue=20structure=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✏️ Rename: to `user_leaderboard` - 🚚 Move: to `param` module - ✏️ Rename: `LeaderboardSearchCriteria` to `SearchCriteria` --- src/client.rs | 343 ++------------------------- src/client/param/mod.rs | 1 + src/client/param/user_leaderboard.rs | 310 ++++++++++++++++++++++++ 3 files changed, 326 insertions(+), 328 deletions(-) create mode 100644 src/client/param/user_leaderboard.rs diff --git a/src/client.rs b/src/client.rs index 88103c5..4b690d9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ //! Client for API requests. use self::{ - param::{news_stream::NewsStream, search_user::SocialConnection}, + param::{news_stream::NewsStream, search_user::SocialConnection, user_leaderboard::{self, LeaderboardType}}, response::response, }; use crate::{ @@ -466,14 +466,14 @@ impl Client { /// ```no_run /// use tetr_ch::client::{ /// Client, - /// leaderboard::{LeaderboardType, LeaderboardSearchCriteria} + /// param::user_leaderboard::{self, LeaderboardType} /// }; /// # use std::io; /// /// # async fn run() -> io::Result<()> { /// let client = Client::new(); /// - /// let criteria = LeaderboardSearchCriteria::new() + /// let criteria = user_leaderboard::SearchCriteria::new() /// // Upper bound is `[15200, 0, 0]` /// .after([15200.,0.,0.]) /// // Three entries @@ -500,8 +500,8 @@ impl Client { /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_leaderboard( self, - leaderboard: leaderboard::LeaderboardType, - search_criteria: Option, + leaderboard: LeaderboardType, + search_criteria: Option, ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { @@ -531,14 +531,14 @@ impl Client { /// ```no_run /// use tetr_ch::client::{ /// Client, - /// leaderboard::LeaderboardSearchCriteria + /// param::user_leaderboard::{self, LeaderboardType} /// }; /// # use std::io; /// /// # async fn run() -> io::Result<()> { /// let client = Client::new(); /// - /// let criteria = LeaderboardSearchCriteria::new() + /// let criteria = user_leaderboard::SearchCriteria::new() /// // Upper bound is `[15200, 0, 0]` /// .after([15200.,0.,0.]) /// // Three entries @@ -566,7 +566,7 @@ impl Client { pub async fn get_historical_league_leaderboard( self, season: &str, - search_criteria: Option, + search_criteria: Option, ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { @@ -582,7 +582,7 @@ impl Client { let url = format!( "{}users/history/{}/{}", API_URL, - leaderboard::LeaderboardType::League.to_param(), + LeaderboardType::League.to_param(), season ); let res = self.client.get(url).query(&query_params).send().await; @@ -1117,319 +1117,6 @@ impl Client { pub mod param; mod response; -pub mod leaderboard { - //! Features for leaderboards. - - /// The leaderboard type. - pub enum LeaderboardType { - /// The TETRA LEAGUE leaderboard. - League, - /// The XP leaderboard. - Xp, - /// The Achievement Rating leaderboard. - Ar, - } - - impl LeaderboardType { - /// Converts into a parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::leaderboard::LeaderboardType; - /// assert_eq!(LeaderboardType::League.to_param(), "league"); - /// assert_eq!(LeaderboardType::Xp.to_param(), "xp"); - /// assert_eq!(LeaderboardType::Ar.to_param(), "ar"); - /// ``` - pub(crate) fn to_param(&self) -> String { - match self { - LeaderboardType::League => "league".to_string(), - LeaderboardType::Xp => "xp".to_string(), - LeaderboardType::Ar => "ar".to_string(), - } - } - } - - /// The search criteria for the leaderboard. - /// - /// # Examples - /// - /// ``` - /// use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// - /// // Default search criteria. - /// let c1 = LeaderboardSearchCriteria::new(); - /// - /// // Upper bound is `[15200, 0, 0]`, three entries, filter by Japan. - /// let c2 = LeaderboardSearchCriteria::new() - /// .after([15200., 0., 0.]) - /// .limit(3) - /// .country("jp"); - /// - /// // Lower bound is `[15200, 0, 0]`. - /// // Also the search order is reversed. - /// let c3 = LeaderboardSearchCriteria::new() - /// .before([15200., 0., 0.]); - /// - /// // You can initialize the search criteria to default as follows: - /// let mut c4 = LeaderboardSearchCriteria::new().country("us"); - /// c4.init(); - /// ``` - #[derive(Clone, Debug, Default)] - pub struct LeaderboardSearchCriteria { - /// The bound to paginate. - pub bound: Option, - /// The amount of entries to return, - /// between 1 and 100. 25 by default. - pub limit: Option, - /// The ISO 3166-1 country code to filter to. - /// Leave unset to not filter by country. - pub country: Option, - } - - impl LeaderboardSearchCriteria { - /// Creates a new [`LeaderboardSearchCriteria`]. - /// The values are set to default. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let criteria = LeaderboardSearchCriteria::new(); - /// ``` - pub fn new() -> Self { - Self::default() - } - - /// Initializes the search criteria. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let mut criteria = LeaderboardSearchCriteria::new().country("us"); - /// criteria.init(); - /// ``` - pub fn init(self) -> Self { - Self::default() - } - - /// Sets the upper bound. - /// - /// # Arguments - /// - /// - `bound`: The upper bound to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - /// - /// # Examples - /// - /// Sets the upper bound to `[10000.0, 0.0, 0.0]`. - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let mut criteria = LeaderboardSearchCriteria::new(); - /// criteria.after([10000.0, 0.0, 0.0]); - /// ``` - pub fn after(self, bound: [f64; 3]) -> Self { - Self { - bound: Some(Bound::After(bound)), - ..self - } - } - - /// Sets the lower bound. - /// - /// # Arguments - /// - /// - `bound`: The lower bound to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If use this, the search order is reversed - /// (returning the lowest items that match the query) - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - /// - /// # Examples - /// - /// Sets the lower bound to `[10000.0, 0.0, 0.0]`. - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let mut criteria = LeaderboardSearchCriteria::new(); - /// criteria.before([10000.0, 0.0, 0.0]); - /// ``` - pub fn before(self, bound: [f64; 3]) -> Self { - Self { - bound: Some(Bound::Before(bound)), - ..self - } - } - - /// Limits the amount of entries to return. - /// - /// # Arguments - /// - /// - `limit`: The amount of entries to return. - /// Between 1 and 100. 25 by default. - /// - /// # Examples - /// - /// Limits the amount of entries to return to `10`. - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let mut criteria = LeaderboardSearchCriteria::new(); - /// criteria.limit(10); - /// ``` - /// - /// # Panics - /// - /// Panics if the argument `limit` is not between `1` and `100`. - /// - /// ```should_panic - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let mut criteria = LeaderboardSearchCriteria::new(); - /// criteria.limit(0); - /// ``` - /// - /// ```should_panic - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let mut criteria = LeaderboardSearchCriteria::new(); - /// criteria.limit(101); - /// ``` - pub fn limit(self, limit: u8) -> Self { - if (1..=100).contains(&limit) { - Self { - limit: Some(limit), - ..self - } - } else { - panic!( - "The argument `limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); - } - } - - /// Sets the ISO 3166-1 country code to filter to. - /// - /// # Arguments - /// - /// - `country`: The ISO 3166-1 country code to filter to. - /// - /// # Examples - /// - /// Sets the country code to `jp`. - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let mut criteria = LeaderboardSearchCriteria::new(); - /// criteria.country("jp"); - /// ``` - pub fn country(self, country: &str) -> Self { - Self { - country: Some(country.to_owned().to_uppercase()), - ..self - } - } - - /// Whether the search criteria `limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let invalid_criteria = LeaderboardSearchCriteria { - /// limit: Some(0), - /// ..LeaderboardSearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - /// - /// ``` - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let invalid_criteria = LeaderboardSearchCriteria { - /// limit: Some(101), - /// ..LeaderboardSearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit { - !(1..=100).contains(&l) - } else { - false - } - } - - /// Builds the search criteria to `Vec<(String, String)>`. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::leaderboard::LeaderboardSearchCriteria; - /// let criteria = LeaderboardSearchCriteria::new(); - /// let query_params = criteria.build(); - /// ``` - pub(crate) fn build(self) -> Vec<(String, String)> { - let mut result = Vec::new(); - if let Some(b) = self.bound { - result.push(b.to_query_param()); - } - if let Some(l) = self.limit { - result.push(("limit".to_string(), l.to_string())); - } - if let Some(c) = self.country { - result.push(("country".to_string(), c)); - } - result - } - } - - /// The bound to paginate. - #[derive(Clone, Debug)] - pub enum Bound { - /// The upper bound. - /// Use this to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - After([f64; 3]), - /// The lower bound. - /// Use this to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If set, the search order is reversed - /// (returning the lowest items that match the query) - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - Before([f64; 3]), - } - - impl Bound { - /// Converts into a query parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::leaderboard::Bound; - /// let bound = Bound::After([12345.678, 0.0, 0.0]); - /// assert_eq!(bound.to_query_param(), ("after".to_string(), "12345.678:0:0".to_string())); - /// ``` - pub(crate) fn to_query_param(&self) -> (String, String) { - match self { - Bound::After(b) => ("after".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), - Bound::Before(b) => ("before".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), - } - } - } -} - pub mod user_record { //! Features for user records. @@ -1538,7 +1225,7 @@ pub mod user_record { #[derive(Clone, Debug, Default)] pub struct RecordSearchCriteria { /// The bound to paginate. - pub bound: Option, + pub bound: Option, /// The amount of entries to return, /// between 1 and 100. 25 by default. pub limit: Option, @@ -1592,7 +1279,7 @@ pub mod user_record { /// ``` pub fn after(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::leaderboard::Bound::After(bound)), + bound: Some(super::param::user_leaderboard::Bound::After(bound)), ..self } } @@ -1620,7 +1307,7 @@ pub mod user_record { /// ``` pub fn before(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::leaderboard::Bound::Before(bound)), + bound: Some(super::param::user_leaderboard::Bound::Before(bound)), ..self } } @@ -1816,7 +1503,7 @@ pub mod records_leaderboard { #[derive(Clone, Debug, Default)] pub struct RecordsLeaderboardSearchCriteria { /// The bound to paginate. - pub bound: Option, + pub bound: Option, /// The amount of entries to return, /// between 1 and 100. 25 by default. pub limit: Option, @@ -1870,7 +1557,7 @@ pub mod records_leaderboard { /// ``` pub fn after(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::leaderboard::Bound::After(bound)), + bound: Some(super::param::user_leaderboard::Bound::After(bound)), ..self } } @@ -1898,7 +1585,7 @@ pub mod records_leaderboard { /// ``` pub fn before(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::leaderboard::Bound::Before(bound)), + bound: Some(super::param::user_leaderboard::Bound::Before(bound)), ..self } } diff --git a/src/client/param/mod.rs b/src/client/param/mod.rs index 63a53d8..d036bf6 100644 --- a/src/client/param/mod.rs +++ b/src/client/param/mod.rs @@ -2,3 +2,4 @@ pub mod news_stream; pub mod search_user; +pub mod user_leaderboard; diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs new file mode 100644 index 0000000..a21daa8 --- /dev/null +++ b/src/client/param/user_leaderboard.rs @@ -0,0 +1,310 @@ +//! Features for user leaderboards. + +/// A user leaderboard type. +pub enum LeaderboardType { + /// A TETRA LEAGUE leaderboard. + League, + /// An XP leaderboard. + Xp, + /// An Achievement Rating leaderboard. + Ar, +} + +impl LeaderboardType { + /// Converts into a parameter string. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::leaderboard::LeaderboardType; + /// assert_eq!(LeaderboardType::League.to_param(), "league"); + /// assert_eq!(LeaderboardType::Xp.to_param(), "xp"); + /// assert_eq!(LeaderboardType::Ar.to_param(), "ar"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + LeaderboardType::League => "league".to_string(), + LeaderboardType::Xp => "xp".to_string(), + LeaderboardType::Ar => "ar".to_string(), + } + } +} + +/// A search criteria for user leaderboards. +/// +/// # Examples +/// +/// ``` +/// use tetr_ch::client::param::user_leaderboard::SearchCriteria; +/// +/// // Default search criteria. +/// let c1 = SearchCriteria::new(); +/// +/// // Upper bound is `[15200, 0, 0]`, three entries, filter by Japan. +/// let c2 = SearchCriteria::new() +/// .after([15200., 0., 0.]) +/// .limit(3) +/// .country("jp"); +/// +/// // Lower bound is `[15200, 0, 0]`. +/// // Also the search order is reversed. +/// let c3 = SearchCriteria::new() +/// .before([15200., 0., 0.]); +/// +/// // You can initialize the search criteria to default as follows: +/// let mut c4 = SearchCriteria::new().country("us"); +/// c4.init(); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct SearchCriteria { + /// The bound to paginate. + pub bound: Option, + /// The amount of entries to return, + /// between 1 and 100. 25 by default. + pub limit: Option, + /// The ISO 3166-1 country code to filter to. + /// Leave unset to not filter by country. + pub country: Option, +} + +impl SearchCriteria { + /// Creates a new [`SearchCriteria`]. + /// The values are set to default. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let criteria = SearchCriteria::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Initializes the search criteria. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let mut criteria = SearchCriteria::new().country("us"); + /// criteria.init(); + /// ``` + pub fn init(self) -> Self { + Self::default() + } + + /// Sets the upper bound. + /// + /// # Arguments + /// + /// - `bound`: The upper bound to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the upper bound to `[10000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.after([10000.0, 0.0, 0.0]); + /// ``` + pub fn after(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::After(bound)), + ..self + } + } + + /// Sets the lower bound. + /// + /// # Arguments + /// + /// - `bound`: The lower bound to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the lower bound to `[10000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.before([10000.0, 0.0, 0.0]); + /// ``` + pub fn before(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::Before(bound)), + ..self + } + } + + /// Limits the amount of entries to return. + /// + /// # Arguments + /// + /// - `limit`: The amount of entries to return. + /// Between 1 and 100. 25 by default. + /// + /// # Examples + /// + /// Limits the amount of entries to return to `10`. + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(10); + /// ``` + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between `1` and `100`. + /// + /// ```should_panic + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(0); + /// ``` + /// + /// ```should_panic + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(101); + /// ``` + pub fn limit(self, limit: u8) -> Self { + if (1..=100).contains(&limit) { + Self { + limit: Some(limit), + ..self + } + } else { + panic!( + "The argument `limit` must be between 1 and 100.\n\ + Received: {}", + limit + ); + } + } + + /// Sets the ISO 3166-1 country code to filter to. + /// + /// # Arguments + /// + /// - `country`: The ISO 3166-1 country code to filter to. + /// + /// # Examples + /// + /// Sets the country code to `jp`. + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.country("jp"); + /// ``` + pub fn country(self, country: &str) -> Self { + Self { + country: Some(country.to_owned().to_uppercase()), + ..self + } + } + + /// Whether the search criteria `limit` is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let invalid_criteria = SearchCriteria { + /// limit: Some(0), + /// ..SearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + /// + /// ``` + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let invalid_criteria = SearchCriteria { + /// limit: Some(101), + /// ..SearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + pub fn is_invalid_limit_range(&self) -> bool { + if let Some(l) = self.limit { + !(1..=100).contains(&l) + } else { + false + } + } + + /// Builds the search criteria to `Vec<(String, String)>`. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; + /// let criteria = SearchCriteria::new(); + /// let query_params = criteria.build(); + /// ``` + pub(crate) fn build(self) -> Vec<(String, String)> { + let mut result = Vec::new(); + if let Some(b) = self.bound { + result.push(b.to_query_param()); + } + if let Some(l) = self.limit { + result.push(("limit".to_string(), l.to_string())); + } + if let Some(c) = self.country { + result.push(("country".to_string(), c)); + } + result + } +} + +/// A bound to paginate. +#[derive(Clone, Debug)] +pub enum Bound { + /// A upper bound. + /// Use this to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + After([f64; 3]), + /// A lower bound. + /// Use this to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If set, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + Before([f64; 3]), +} + +impl Bound { + /// Converts into a query parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::user_leaderboard::Bound; + /// let bound = Bound::After([12345.678, 0.0, 0.0]); + /// assert_eq!(bound.to_query_param(), ("after".to_string(), "12345.678:0:0".to_string())); + /// ``` + pub(crate) fn to_query_param(&self) -> (String, String) { + match self { + Bound::After(b) => ("after".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), + Bound::Before(b) => ("before".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), + } + } +} From 8cce6cfcb4b6d6ea15134556cd9a4794f0d1456b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 03:20:15 +0900 Subject: [PATCH 081/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20fix=20compile=20?= =?UTF-8?q?errors=20in=20Doc-tests=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 +++--- src/client/param/news_stream.rs | 2 +- src/client/param/search_user.rs | 2 +- src/client/param/user_leaderboard.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client.rs b/src/client.rs index 4b690d9..e4f2615 100644 --- a/src/client.rs +++ b/src/client.rs @@ -850,7 +850,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::{Client, stream::NewsStream}; + /// use tetr_ch::client::{Client, param::news_stream::NewsStream}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { @@ -881,7 +881,7 @@ impl Client { /// Panics if the query parameter `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// use tetr_ch::client::{Client, stream::NewsStream}; + /// use tetr_ch::client::{Client, param::news_stream::NewsStream}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { @@ -927,7 +927,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::{Client, search_user::SocialConnection}; + /// use tetr_ch::client::{Client, param::search_user::SocialConnection}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { diff --git a/src/client/param/news_stream.rs b/src/client/param/news_stream.rs index d35a9d3..518497e 100644 --- a/src/client/param/news_stream.rs +++ b/src/client/param/news_stream.rs @@ -15,7 +15,7 @@ impl NewsStream { /// # Examples /// /// ```ignore - /// # use tetr_ch::client::stream::NewsStream; + /// # use tetr_ch::client::param::news_stream::NewsStream; /// let global = NewsStream::Global; /// let user = NewsStream::User("621db46d1d638ea850be2aa0".to_string()); /// assert_eq!(global.to_param(), "global"); diff --git a/src/client/param/search_user.rs b/src/client/param/search_user.rs index 5b98d04..8b2c86c 100644 --- a/src/client/param/search_user.rs +++ b/src/client/param/search_user.rs @@ -14,7 +14,7 @@ impl SocialConnection { /// # Examples /// /// ```ignore - /// # use tetr_ch::client::search_user::SocialConnection; + /// # use tetr_ch::client::param::search_user::SocialConnection; /// let connection = SocialConnection::Discord("724976600873041940".to_string()); /// assert_eq!(connection.to_param(), "discord:724976600873041940"); /// ``` diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index a21daa8..2a92387 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -16,7 +16,7 @@ impl LeaderboardType { /// # Examples /// /// ```ignore - /// # use tetr_ch::client::leaderboard::LeaderboardType; + /// # use tetr_ch::client::param::user_leaderboard::LeaderboardType; /// assert_eq!(LeaderboardType::League.to_param(), "league"); /// assert_eq!(LeaderboardType::Xp.to_param(), "xp"); /// assert_eq!(LeaderboardType::Ar.to_param(), "ar"); From 2895cce47a0e4b059dee745489c309e786c005e5 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 03:29:50 +0900 Subject: [PATCH 082/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`Bound`=20enum?= =?UTF-8?q?=20to=20`param::pagination`=20module=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 18 +++++++----- src/client/param/mod.rs | 1 + src/client/param/pagination.rs | 40 +++++++++++++++++++++++++++ src/client/param/user_leaderboard.rs | 41 ++-------------------------- 4 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 src/client/param/pagination.rs diff --git a/src/client.rs b/src/client.rs index e4f2615..fc98594 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ //! Client for API requests. use self::{ - param::{news_stream::NewsStream, search_user::SocialConnection, user_leaderboard::{self, LeaderboardType}}, + param::{news_stream::NewsStream, pagination::Bound, search_user::SocialConnection, user_leaderboard::{self, LeaderboardType}}, response::response, }; use crate::{ @@ -1120,6 +1120,8 @@ mod response; pub mod user_record { //! Features for user records. + use super::*; + /// The game mode of records. pub enum RecordGamemode { /// 40 LINES records. @@ -1225,7 +1227,7 @@ pub mod user_record { #[derive(Clone, Debug, Default)] pub struct RecordSearchCriteria { /// The bound to paginate. - pub bound: Option, + pub bound: Option, /// The amount of entries to return, /// between 1 and 100. 25 by default. pub limit: Option, @@ -1279,7 +1281,7 @@ pub mod user_record { /// ``` pub fn after(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::param::user_leaderboard::Bound::After(bound)), + bound: Some(Bound::After(bound)), ..self } } @@ -1307,7 +1309,7 @@ pub mod user_record { /// ``` pub fn before(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::param::user_leaderboard::Bound::Before(bound)), + bound: Some(Bound::Before(bound)), ..self } } @@ -1413,6 +1415,8 @@ pub mod user_record { pub mod records_leaderboard { //! Features for records leaderboards. + use super::*; + /// The records leaderboard ID. pub struct RecordsLeaderboardId { /// The game mode. e.g. `40l`. @@ -1503,7 +1507,7 @@ pub mod records_leaderboard { #[derive(Clone, Debug, Default)] pub struct RecordsLeaderboardSearchCriteria { /// The bound to paginate. - pub bound: Option, + pub bound: Option, /// The amount of entries to return, /// between 1 and 100. 25 by default. pub limit: Option, @@ -1557,7 +1561,7 @@ pub mod records_leaderboard { /// ``` pub fn after(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::param::user_leaderboard::Bound::After(bound)), + bound: Some(Bound::After(bound)), ..self } } @@ -1585,7 +1589,7 @@ pub mod records_leaderboard { /// ``` pub fn before(self, bound: [f64; 3]) -> Self { Self { - bound: Some(super::param::user_leaderboard::Bound::Before(bound)), + bound: Some(Bound::Before(bound)), ..self } } diff --git a/src/client/param/mod.rs b/src/client/param/mod.rs index d036bf6..ff43067 100644 --- a/src/client/param/mod.rs +++ b/src/client/param/mod.rs @@ -1,5 +1,6 @@ //! Features for the parameters of the [`Client`](crate::client::Client) struct methods. pub mod news_stream; +pub mod pagination; pub mod search_user; pub mod user_leaderboard; diff --git a/src/client/param/pagination.rs b/src/client/param/pagination.rs new file mode 100644 index 0000000..3b4918c --- /dev/null +++ b/src/client/param/pagination.rs @@ -0,0 +1,40 @@ +//! Features for pagination. + +/// A bound to paginate. +#[derive(Clone, Debug)] +pub enum Bound { + /// A upper bound. + /// Use this to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + After([f64; 3]), + /// A lower bound. + /// Use this to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If set, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + Before([f64; 3]), +} + +impl Bound { + /// Converts into a query parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::user_leaderboard::Bound; + /// let bound = Bound::After([12345.678, 0.0, 0.0]); + /// assert_eq!(bound.to_query_param(), ("after".to_string(), "12345.678:0:0".to_string())); + /// ``` + pub(crate) fn to_query_param(&self) -> (String, String) { + match self { + Bound::After(b) => ("after".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), + Bound::Before(b) => ("before".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), + } + } +} diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 2a92387..69728c6 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -1,5 +1,7 @@ //! Features for user leaderboards. +use super::pagination::Bound; + /// A user leaderboard type. pub enum LeaderboardType { /// A TETRA LEAGUE leaderboard. @@ -269,42 +271,3 @@ impl SearchCriteria { result } } - -/// A bound to paginate. -#[derive(Clone, Debug)] -pub enum Bound { - /// A upper bound. - /// Use this to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - After([f64; 3]), - /// A lower bound. - /// Use this to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If set, the search order is reversed - /// (returning the lowest items that match the query) - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - Before([f64; 3]), -} - -impl Bound { - /// Converts into a query parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::param::user_leaderboard::Bound; - /// let bound = Bound::After([12345.678, 0.0, 0.0]); - /// assert_eq!(bound.to_query_param(), ("after".to_string(), "12345.678:0:0".to_string())); - /// ``` - pub(crate) fn to_query_param(&self) -> (String, String) { - match self { - Bound::After(b) => ("after".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), - Bound::Before(b) => ("before".to_string(), format!("{}:{}:{}", b[0], b[1], b[2])), - } - } -} From b37c49926cccbd956822ef757d81af32a8b6a6e4 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 03:30:35 +0900 Subject: [PATCH 083/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index fc98594..471969c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,12 @@ //! Client for API requests. use self::{ - param::{news_stream::NewsStream, pagination::Bound, search_user::SocialConnection, user_leaderboard::{self, LeaderboardType}}, + param::{ + news_stream::NewsStream, + pagination::Bound, + search_user::SocialConnection, + user_leaderboard::{self, LeaderboardType}, + }, response::response, }; use crate::{ From c66c91a07a8c9774ad362c2473207921ea75cd69 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 03:52:46 +0900 Subject: [PATCH 084/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20`client?= =?UTF-8?q?::user=5Frecord`=20module=20structure=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✏️ Rename: to `record` - πŸ“¦ Move: to `param` module - ✏️ Rename: `RecordGamemode` to `Gamemode` - ✏️ Rename: `RecordSearchCriteria` to `SearchCriteria` --- src/client.rs | 320 ++----------------------------------- src/client/param/mod.rs | 1 + src/client/param/record.rs | 292 +++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+), 307 deletions(-) create mode 100644 src/client/param/record.rs diff --git a/src/client.rs b/src/client.rs index 471969c..b99168b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,6 +4,7 @@ use self::{ param::{ news_stream::NewsStream, pagination::Bound, + record::{self, Gamemode}, search_user::SocialConnection, user_leaderboard::{self, LeaderboardType}, }, @@ -608,7 +609,7 @@ impl Client { /// ```no_run /// use tetr_ch::client::{ /// Client, - /// user_record::{RecordGamemode, LeaderboardType, RecordSearchCriteria} + /// param::record::{self, Gamemode, LeaderboardType} /// }; /// # use std::io; /// @@ -616,7 +617,7 @@ impl Client { /// let client = Client::new(); /// /// // Set the search criteria. - /// let criteria = RecordSearchCriteria::new() + /// let criteria = record::SearchCriteria::new() /// // Upper bound is `[500000, 0, 0]` /// .after([500000.,0.,0.]) /// // Three entries @@ -625,7 +626,7 @@ impl Client { /// // Get the User Records. /// let user = client.get_user_records( /// "rinrin-rs", - /// RecordGamemode::FortyLines, + /// Gamemode::FortyLines, /// LeaderboardType::Top, /// Some(criteria) /// ).await?; @@ -644,9 +645,9 @@ impl Client { pub async fn get_user_records( self, user: &str, - gamemode: user_record::RecordGamemode, - leaderboard: user_record::LeaderboardType, - search_criteria: Option, + gamemode: Gamemode, + leaderboard: record::LeaderboardType, + search_criteria: Option, ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { @@ -756,7 +757,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::{Client, user_record::RecordGamemode}; + /// use tetr_ch::client::{param::record::Gamemode, Client}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { @@ -765,7 +766,7 @@ impl Client { /// // Get the User Record. /// let user = client.search_record( /// "621db46d1d638ea850be2aa0", - /// RecordGamemode::Blitz, + /// Gamemode::Blitz, /// 1680053762145 /// ).await?; /// # Ok(()) @@ -783,7 +784,7 @@ impl Client { pub async fn search_record( self, user_id: &str, - gamemode: user_record::RecordGamemode, + gamemode: Gamemode, timestamp: i64, ) -> RspErr { let query_params = [ @@ -975,7 +976,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::{Client, user_record::RecordGamemode}; + /// use tetr_ch::client::{param::record::Gamemode, Client}; /// # use std::io; /// /// # async fn run() -> io::Result<()> { @@ -984,7 +985,7 @@ impl Client { /// // Get the Labs Scoreflow. /// let user = client.get_labs_scoreflow( /// "rinrin-rs", - /// RecordGamemode::FortyLines + /// Gamemode::FortyLines /// ).await?; /// # Ok(()) /// # } @@ -1001,7 +1002,7 @@ impl Client { pub async fn get_labs_scoreflow( self, user: &str, - gamemode: user_record::RecordGamemode, + gamemode: Gamemode, ) -> RspErr { let url = format!( "{}labs/scoreflow/{}/{}", @@ -1122,301 +1123,6 @@ impl Client { pub mod param; mod response; -pub mod user_record { - //! Features for user records. - - use super::*; - - /// The game mode of records. - pub enum RecordGamemode { - /// 40 LINES records. - FortyLines, - /// BLITZ records. - Blitz, - /// QUICK PLAY records. - Zenith, - /// EXPERT QUICK PLAY records. - ZenithEx, - /// TETRA LEAGUE history. - League, - } - - impl RecordGamemode { - /// Converts into a parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::user_record::RecordGamemode; - /// let forty_lines = RecordGamemode::FortyLines; - /// let blitz = RecordGamemode::Blitz; - /// let zenith = RecordGamemode::Zenith; - /// let zenith_ex = RecordGamemode::ZenithEx; - /// let league = RecordGamemode::League; - /// assert_eq!(forty_lines.to_param(), "40l"); - /// assert_eq!(blitz.to_param(), "blitz"); - /// assert_eq!(zenith.to_param(), "zenith"); - /// assert_eq!(zenith_ex.to_param(), "zenithex"); - /// assert_eq!(league.to_param(), "league"); - /// ``` - pub(crate) fn to_param(&self) -> String { - match self { - RecordGamemode::FortyLines => "40l", - RecordGamemode::Blitz => "blitz", - RecordGamemode::Zenith => "zenith", - RecordGamemode::ZenithEx => "zenithex", - RecordGamemode::League => "league", - } - .to_string() - } - } - - /// The leaderboard type. - pub enum LeaderboardType { - /// The top scores. - Top, - /// The most recently placed records. - Recent, - /// The top scores (Personal Bests only). - Progression, - } - - impl LeaderboardType { - /// Converts into a parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::user_record::LeaderboardType; - /// let top = LeaderboardType::Top; - /// let recent = LeaderboardType::Recent; - /// let progression = LeaderboardType::Progression; - /// assert_eq!(top.to_param(), "top"); - /// assert_eq!(recent.to_param(), "recent"); - /// assert_eq!(progression.to_param(), "progression"); - /// ``` - pub(crate) fn to_param(&self) -> String { - match self { - LeaderboardType::Top => "top", - LeaderboardType::Recent => "recent", - LeaderboardType::Progression => "progression", - } - .to_string() - } - } - - /// The search criteria for the user records. - /// - /// # Examples - /// - /// ``` - /// use tetr_ch::client::user_record::RecordSearchCriteria; - /// - /// // Default search criteria. - /// let c1 = RecordSearchCriteria::new(); - /// - /// // Upper bound is `[500000, 0, 0]`, three entries. - /// let c2 = RecordSearchCriteria::new() - /// .after([500000., 0., 0.]) - /// .limit(3); - /// - /// // Lower bound is `[500000, 0, 0]`. - /// // Also the search order is reversed. - /// let c3 = RecordSearchCriteria::new() - /// .before([500000., 0., 0.]); - /// - /// // You can initialize the search criteria to default as follows: - /// let mut c4 = RecordSearchCriteria::new().limit(10); - /// c4.init(); - /// ``` - #[derive(Clone, Debug, Default)] - pub struct RecordSearchCriteria { - /// The bound to paginate. - pub bound: Option, - /// The amount of entries to return, - /// between 1 and 100. 25 by default. - pub limit: Option, - } - - impl RecordSearchCriteria { - /// Creates a new [`RecordSearchCriteria`]. - /// The values are set to default. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let criteria = RecordSearchCriteria::new(); - /// ``` - pub fn new() -> Self { - Self::default() - } - - /// Initializes the search criteria. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let mut criteria = RecordSearchCriteria::new(); - /// criteria.init(); - /// ``` - pub fn init(self) -> Self { - Self::default() - } - - /// Sets the upper bound. - /// - /// # Arguments - /// - /// - `bound`: The upper bound to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - /// - /// # Examples - /// - /// Sets the upper bound to `[500000.0, 0.0, 0.0]`. - /// - /// ``` - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let mut criteria = RecordSearchCriteria::new(); - /// criteria.after([500000.0, 0.0, 0.0]); - /// ``` - pub fn after(self, bound: [f64; 3]) -> Self { - Self { - bound: Some(Bound::After(bound)), - ..self - } - } - - /// Sets the lower bound. - /// - /// # Arguments - /// - /// - `bound`: The lower bound to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If use this, the search order is reversed - /// (returning the lowest items that match the query) - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - /// - /// # Examples - /// - /// Sets the lower bound to `[500000.0, 0.0, 0.0]`. - /// - /// ``` - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let mut criteria = RecordSearchCriteria::new(); - /// criteria.before([500000.0, 0.0, 0.0]); - /// ``` - pub fn before(self, bound: [f64; 3]) -> Self { - Self { - bound: Some(Bound::Before(bound)), - ..self - } - } - - /// Limits the amount of entries to return. - /// - /// # Arguments - /// - /// - `limit`: The amount of entries to return. - /// Between 1 and 100. 25 by default. - /// - /// # Examples - /// - /// Limits the amount of entries to return to `10`. - /// - /// ``` - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let mut criteria = RecordSearchCriteria::new(); - /// criteria.limit(10); - /// ``` - /// - /// # Panics - /// - /// Panics if the argument `limit` is not between `1` and `100`. - /// - /// ```should_panic - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let mut criteria = RecordSearchCriteria::new(); - /// criteria.limit(0); - /// ``` - /// - /// ```should_panic - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let mut criteria = RecordSearchCriteria::new(); - /// criteria.limit(101); - /// ``` - pub fn limit(self, limit: u8) -> Self { - if (1..=100).contains(&limit) { - Self { - limit: Some(limit), - ..self - } - } else { - panic!( - "The argument `limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); - } - } - - /// Whether the search criteria `limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let invalid_criteria = RecordSearchCriteria { - /// limit: Some(0), - /// ..RecordSearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - /// - /// ``` - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let invalid_criteria = RecordSearchCriteria { - /// limit: Some(101), - /// ..RecordSearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit { - !(1..=100).contains(&l) - } else { - false - } - } - - /// Builds the search criteria to `Vec<(String, String)>`. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::user_record::RecordSearchCriteria; - /// let criteria = RecordSearchCriteria::new(); - /// let query_params = criteria.build(); - /// ``` - pub(crate) fn build(self) -> Vec<(String, String)> { - let mut result = Vec::new(); - if let Some(b) = self.bound { - result.push(b.to_query_param()); - } - if let Some(l) = self.limit { - result.push(("limit".to_string(), l.to_string())); - } - result - } - } -} - pub mod records_leaderboard { //! Features for records leaderboards. diff --git a/src/client/param/mod.rs b/src/client/param/mod.rs index ff43067..294cc72 100644 --- a/src/client/param/mod.rs +++ b/src/client/param/mod.rs @@ -2,5 +2,6 @@ pub mod news_stream; pub mod pagination; +pub mod record; pub mod search_user; pub mod user_leaderboard; diff --git a/src/client/param/record.rs b/src/client/param/record.rs new file mode 100644 index 0000000..288ec4c --- /dev/null +++ b/src/client/param/record.rs @@ -0,0 +1,292 @@ +//! Features for records. + +use super::pagination::Bound; + +/// A game mode of a record. +pub enum Gamemode { + /// 40 LINES. + FortyLines, + /// BLITZ. + Blitz, + /// QUICK PLAY. + Zenith, + /// EXPERT QUICK PLAY. + ZenithEx, + /// TETRA LEAGUE history. + League, +} + +impl Gamemode { + /// Converts into a parameter string. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::record::Gamemode; + /// let forty_lines = Gamemode::FortyLines; + /// let blitz = Gamemode::Blitz; + /// let zenith = Gamemode::Zenith; + /// let zenith_ex = Gamemode::ZenithEx; + /// let league = Gamemode::League; + /// assert_eq!(forty_lines.to_param(), "40l"); + /// assert_eq!(blitz.to_param(), "blitz"); + /// assert_eq!(zenith.to_param(), "zenith"); + /// assert_eq!(zenith_ex.to_param(), "zenithex"); + /// assert_eq!(league.to_param(), "league"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + Gamemode::FortyLines => "40l", + Gamemode::Blitz => "blitz", + Gamemode::Zenith => "zenith", + Gamemode::ZenithEx => "zenithex", + Gamemode::League => "league", + } + .to_string() + } +} + +/// A record leaderboard type. +pub enum LeaderboardType { + /// Top scores. + Top, + /// Most recently placed records. + Recent, + /// Top scores (Personal Bests only). + Progression, +} + +impl LeaderboardType { + /// Converts into a parameter string. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::record::LeaderboardType; + /// let top = LeaderboardType::Top; + /// let recent = LeaderboardType::Recent; + /// let progression = LeaderboardType::Progression; + /// assert_eq!(top.to_param(), "top"); + /// assert_eq!(recent.to_param(), "recent"); + /// assert_eq!(progression.to_param(), "progression"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match self { + LeaderboardType::Top => "top", + LeaderboardType::Recent => "recent", + LeaderboardType::Progression => "progression", + } + .to_string() + } +} + +/// A search criteria for user records. +/// +/// # Examples +/// +/// ``` +/// use tetr_ch::client::param::record::SearchCriteria; +/// +/// // Default search criteria. +/// let c1 = SearchCriteria::new(); +/// +/// // Upper bound is `[500000, 0, 0]`, three entries. +/// let c2 = SearchCriteria::new() +/// .after([500000., 0., 0.]) +/// .limit(3); +/// +/// // Lower bound is `[500000, 0, 0]`. +/// // Also the search order is reversed. +/// let c3 = SearchCriteria::new() +/// .before([500000., 0., 0.]); +/// +/// // You can initialize the search criteria to default as follows: +/// let mut c4 = SearchCriteria::new().limit(10); +/// c4.init(); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct SearchCriteria { + /// The bound to paginate. + pub bound: Option, + /// The amount of entries to return, + /// between 1 and 100. 25 by default. + pub limit: Option, +} + +impl SearchCriteria { + /// Creates a new [`SearchCriteria`]. + /// The values are set to default. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let criteria = SearchCriteria::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Initializes the search criteria. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.init(); + /// ``` + pub fn init(self) -> Self { + Self::default() + } + + /// Sets the upper bound. + /// + /// # Arguments + /// + /// - `bound`: The upper bound to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the upper bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.after([500000.0, 0.0, 0.0]); + /// ``` + pub fn after(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::After(bound)), + ..self + } + } + + /// Sets the lower bound. + /// + /// # Arguments + /// + /// - `bound`: The lower bound to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the lower bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.before([500000.0, 0.0, 0.0]); + /// ``` + pub fn before(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::Before(bound)), + ..self + } + } + + /// Limits the amount of entries to return. + /// + /// # Arguments + /// + /// - `limit`: The amount of entries to return. + /// Between 1 and 100. 25 by default. + /// + /// # Examples + /// + /// Limits the amount of entries to return to `10`. + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(10); + /// ``` + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between `1` and `100`. + /// + /// ```should_panic + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(0); + /// ``` + /// + /// ```should_panic + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(101); + /// ``` + pub fn limit(self, limit: u8) -> Self { + if (1..=100).contains(&limit) { + Self { + limit: Some(limit), + ..self + } + } else { + panic!( + "The argument `limit` must be between 1 and 100.\n\ + Received: {}", + limit + ); + } + } + + /// Whether the search criteria `limit` is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let invalid_criteria = SearchCriteria { + /// limit: Some(0), + /// ..SearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let invalid_criteria = SearchCriteria { + /// limit: Some(101), + /// ..SearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + pub fn is_invalid_limit_range(&self) -> bool { + if let Some(l) = self.limit { + !(1..=100).contains(&l) + } else { + false + } + } + + /// Builds the search criteria to `Vec<(String, String)>`. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let criteria = SearchCriteria::new(); + /// let query_params = criteria.build(); + /// ``` + pub(crate) fn build(self) -> Vec<(String, String)> { + let mut result = Vec::new(); + if let Some(b) = self.bound { + result.push(b.to_query_param()); + } + if let Some(l) = self.limit { + result.push(("limit".to_string(), l.to_string())); + } + result + } +} From a1deb51cbe900619de672457884680ac017f43b9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 04:10:40 +0900 Subject: [PATCH 085/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20`record?= =?UTF-8?q?s=5Fleaderboard`=20module=20structure=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✏️ Rename: to `record_leaderboard` - 🚚 Move: to `param` module - ✏️ Rename: `RecordsLeaderboardSearchCriteria` struct to `SearchCriteria` --- src/client.rs | 294 +------------------------ src/client/param/mod.rs | 1 + src/client/param/record_leaderboard.rs | 278 +++++++++++++++++++++++ 3 files changed, 284 insertions(+), 289 deletions(-) create mode 100644 src/client/param/record_leaderboard.rs diff --git a/src/client.rs b/src/client.rs index b99168b..8d7723b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,8 +3,8 @@ use self::{ param::{ news_stream::NewsStream, - pagination::Bound, record::{self, Gamemode}, + record_leaderboard::{self, RecordsLeaderboardId}, search_user::SocialConnection, user_leaderboard::{self, LeaderboardType}, }, @@ -683,11 +683,7 @@ impl Client { /// ```no_run /// use tetr_ch::client::{ /// Client, - /// records_leaderboard::{ - /// RecordsLeaderboardId, - /// RecordsLeaderboardSearchCriteria, - /// Scope - /// } + /// param::record_leaderboard::{self, RecordsLeaderboardId, Scope} /// }; /// # use std::io; /// @@ -695,7 +691,7 @@ impl Client { /// let client = Client::new(); /// /// // Set the search criteria. - /// let criteria = RecordsLeaderboardSearchCriteria::new() + /// let criteria = record_leaderboard::SearchCriteria::new() /// // Upper bound is `[500000, 0, 0]` /// .after([500000.,0.,0.]) /// // Three entries @@ -724,8 +720,8 @@ impl Client { /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_records_leaderboard( self, - leaderboard: records_leaderboard::RecordsLeaderboardId, - search_criteria: Option, + leaderboard: RecordsLeaderboardId, + search_criteria: Option, ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { @@ -1123,286 +1119,6 @@ impl Client { pub mod param; mod response; -pub mod records_leaderboard { - //! Features for records leaderboards. - - use super::*; - - /// The records leaderboard ID. - pub struct RecordsLeaderboardId { - /// The game mode. e.g. `40l`. - pub gamemode: String, - /// The scope. - pub scope: Scope, - /// An optional Revolution ID. e.g. `@2024w31`. - pub revolution_id: Option, - } - - impl RecordsLeaderboardId { - /// Creates a new [`RecordsLeaderboardId`]. - /// - /// # Arguments - /// - /// - `gamemode`: The game mode. e.g. `40l`. - /// - `scope`: The scope. ether [`Scope::Global`] or [`Scope::Country`]. - /// - `revolution_id`: An optional Revolution ID. e.g. `@2024w31`. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::{RecordsLeaderboardId, Scope}; - /// let id = RecordsLeaderboardId::new("40l", Scope::Global, None); - /// ``` - pub fn new(gamemode: &str, scope: Scope, revolution_id: Option<&str>) -> Self { - Self { - gamemode: gamemode.to_owned(), - scope, - revolution_id: revolution_id.map(|s| s.to_owned()), - } - } - - /// Converts into a parameter. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::records_leaderboard::{RecordsLeaderboardId, Scope}; - /// let id1 = RecordsLeaderboardId::new("40l", Scope::Global, None); - /// let id2 = RecordsLeaderboardId::new("blitz", Scope::Country("JP".to_string()), None); - /// let id3 = RecordsLeaderboardId::new("zenith", Scope::Global, Some("@2024w31")); - /// assert_eq!(id1.to_param(), "40l_global"); - /// assert_eq!(id2.to_param(), "blitz_country_JP"); - /// assert_eq!(id3.to_param(), "zenith_global@2024w31"); - /// ``` - pub(crate) fn to_param(&self) -> String { - match &self.scope { - Scope::Global => format!("{}_global", self.gamemode), - Scope::Country(c) => format!("{}_country_{}", self.gamemode, c.to_uppercase()), - } - } - } - - /// The scope of the records leaderboard. - pub enum Scope { - /// The global scope. - Global, - /// The country scope. - /// e.g. `JP`. - Country(String), - } - - /// The search criteria for the records leaderboard. - /// - /// # Examples - /// - /// ``` - /// use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// - /// // Default search criteria. - /// let c1 = RecordsLeaderboardSearchCriteria::new(); - /// - /// // Upper bound is `[500000, 0, 0]`, three entries. - /// let c2 = RecordsLeaderboardSearchCriteria::new() - /// .after([500000., 0., 0.]) - /// .limit(3); - /// - /// // Lower bound is `[500000, 0, 0]`. - /// // Also the search order is reversed. - /// let c3 = RecordsLeaderboardSearchCriteria::new() - /// .before([500000., 0., 0.]); - /// - /// // You can initialize the search criteria to default as follows: - /// let mut c4 = RecordsLeaderboardSearchCriteria::new().limit(10); - /// c4.init(); - /// ``` - #[derive(Clone, Debug, Default)] - pub struct RecordsLeaderboardSearchCriteria { - /// The bound to paginate. - pub bound: Option, - /// The amount of entries to return, - /// between 1 and 100. 25 by default. - pub limit: Option, - } - - impl RecordsLeaderboardSearchCriteria { - /// Creates a new [`RecordsLeaderboardSearchCriteria`]. - /// The values are set to default. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let criteria = RecordsLeaderboardSearchCriteria::new(); - /// ``` - pub fn new() -> Self { - Self::default() - } - - /// Initializes the search criteria. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); - /// criteria.init(); - /// ``` - pub fn init(self) -> Self { - Self::default() - } - - /// Sets the upper bound. - /// - /// # Arguments - /// - /// - `bound`: The upper bound to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - /// - /// # Examples - /// - /// Sets the upper bound to `[500000.0, 0.0, 0.0]`. - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); - /// criteria.after([500000.0, 0.0, 0.0]); - /// ``` - pub fn after(self, bound: [f64; 3]) -> Self { - Self { - bound: Some(Bound::After(bound)), - ..self - } - } - - /// Sets the lower bound. - /// - /// # Arguments - /// - /// - `bound`: The lower bound to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If use this, the search order is reversed - /// (returning the lowest items that match the query) - /// - /// A **prisecter** is consisting of three floats. - /// The `prisecter` field in a response data allows you to continue paginating. - /// - /// # Examples - /// - /// Sets the lower bound to `[500000.0, 0.0, 0.0]`. - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); - /// criteria.before([500000.0, 0.0, 0.0]); - /// ``` - pub fn before(self, bound: [f64; 3]) -> Self { - Self { - bound: Some(Bound::Before(bound)), - ..self - } - } - - /// Limits the amount of entries to return. - /// - /// # Arguments - /// - /// - `limit`: The amount of entries to return. - /// Between 1 and 100. 25 by default. - /// - /// # Examples - /// - /// Limits the amount of entries to return to `10`. - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); - /// criteria.limit(10); - /// ``` - /// - /// # Panics - /// - /// Panics if the argument `limit` is not between `1` and `100`. - /// - /// ```should_panic - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); - /// criteria.limit(0); - /// ``` - /// - /// ```should_panic - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let mut criteria = RecordsLeaderboardSearchCriteria::new(); - /// criteria.limit(101); - /// ``` - pub fn limit(self, limit: u8) -> Self { - if (1..=100).contains(&limit) { - Self { - limit: Some(limit), - ..self - } - } else { - panic!( - "The argument `limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); - } - } - - /// Whether the search criteria `limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let invalid_criteria = RecordsLeaderboardSearchCriteria { - /// limit: Some(0), - /// ..RecordsLeaderboardSearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - /// - /// ``` - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let invalid_criteria = RecordsLeaderboardSearchCriteria { - /// limit: Some(101), - /// ..RecordsLeaderboardSearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit { - !(1..=100).contains(&l) - } else { - false - } - } - - /// Builds the search criteria to `Vec<(String, String)>`. - /// - /// # Examples - /// - /// ```ignore - /// # use tetr_ch::client::records_leaderboard::RecordsLeaderboardSearchCriteria; - /// let criteria = RecordsLeaderboardSearchCriteria::new(); - /// let query_params = criteria.build(); - /// ``` - pub(crate) fn build(self) -> Vec<(String, String)> { - let mut result = Vec::new(); - if let Some(b) = self.bound { - result.push(b.to_query_param()); - } - if let Some(l) = self.limit { - result.push(("limit".to_string(), l.to_string())); - } - result - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/client/param/mod.rs b/src/client/param/mod.rs index 294cc72..2316f7b 100644 --- a/src/client/param/mod.rs +++ b/src/client/param/mod.rs @@ -3,5 +3,6 @@ pub mod news_stream; pub mod pagination; pub mod record; +pub mod record_leaderboard; pub mod search_user; pub mod user_leaderboard; diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs new file mode 100644 index 0000000..e869f26 --- /dev/null +++ b/src/client/param/record_leaderboard.rs @@ -0,0 +1,278 @@ +//! Features for record leaderboards. + +use super::pagination::Bound; + +/// A record leaderboard ID. +pub struct RecordsLeaderboardId { + /// The game mode. e.g. `40l`. + pub gamemode: String, + /// The scope. + pub scope: Scope, + /// The optional Revolution ID. e.g. `@2024w31`. + pub revolution_id: Option, +} + +impl RecordsLeaderboardId { + /// Creates a new [`RecordsLeaderboardId`]. + /// + /// # Arguments + /// + /// - `gamemode`: The game mode. e.g. `40l`. + /// - `scope`: The scope. ether [`Scope::Global`] or [`Scope::Country`]. + /// - `revolution_id`: The optional Revolution ID. e.g. `@2024w31`. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::record_leaderboard::{RecordsLeaderboardId, Scope}; + /// let id = RecordsLeaderboardId::new("40l", Scope::Global, None); + /// ``` + pub fn new(gamemode: &str, scope: Scope, revolution_id: Option<&str>) -> Self { + Self { + gamemode: gamemode.to_owned(), + scope, + revolution_id: revolution_id.map(|s| s.to_owned()), + } + } + + /// Converts into a parameter. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::record_leaderboard::{RecordsLeaderboardId, Scope}; + /// let id1 = RecordsLeaderboardId::new("40l", Scope::Global, None); + /// let id2 = RecordsLeaderboardId::new("blitz", Scope::Country("JP".to_string()), None); + /// let id3 = RecordsLeaderboardId::new("zenith", Scope::Global, Some("@2024w31")); + /// assert_eq!(id1.to_param(), "40l_global"); + /// assert_eq!(id2.to_param(), "blitz_country_JP"); + /// assert_eq!(id3.to_param(), "zenith_global@2024w31"); + /// ``` + pub(crate) fn to_param(&self) -> String { + match &self.scope { + Scope::Global => format!("{}_global", self.gamemode), + Scope::Country(c) => format!("{}_country_{}", self.gamemode, c.to_uppercase()), + } + } +} + +/// A scope of record leaderboards. +pub enum Scope { + /// Global scope. + Global, + /// Country scope. + /// Contains a country code. + /// e.g. `JP`. + Country(String), +} + +/// A search criteria for the records leaderboard. +/// +/// # Examples +/// +/// ``` +/// use tetr_ch::client::param::record_leaderboard::SearchCriteria; +/// +/// // Default search criteria. +/// let c1 = SearchCriteria::new(); +/// +/// // Upper bound is `[500000, 0, 0]`, three entries. +/// let c2 = SearchCriteria::new() +/// .after([500000., 0., 0.]) +/// .limit(3); +/// +/// // Lower bound is `[500000, 0, 0]`. +/// // Also the search order is reversed. +/// let c3 = SearchCriteria::new() +/// .before([500000., 0., 0.]); +/// +/// // You can initialize the search criteria to default as follows: +/// let mut c4 = SearchCriteria::new().limit(10); +/// c4.init(); +/// ``` +#[derive(Clone, Debug, Default)] +pub struct SearchCriteria { + /// The bound to paginate. + pub bound: Option, + /// The amount of entries to return, + /// between 1 and 100. 25 by default. + pub limit: Option, +} + +impl SearchCriteria { + /// Creates a new [`SearchCriteria`]. + /// The values are set to default. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let criteria = SearchCriteria::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Initializes the search criteria. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.init(); + /// ``` + pub fn init(self) -> Self { + Self::default() + } + + /// Sets the upper bound. + /// + /// # Arguments + /// + /// - `bound`: The upper bound to paginate downwards: + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the upper bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.after([500000.0, 0.0, 0.0]); + /// ``` + pub fn after(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::After(bound)), + ..self + } + } + + /// Sets the lower bound. + /// + /// # Arguments + /// + /// - `bound`: The lower bound to paginate upwards: + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) + /// + /// A **prisecter** is consisting of three floats. + /// The `prisecter` field in a response data allows you to continue paginating. + /// + /// # Examples + /// + /// Sets the lower bound to `[500000.0, 0.0, 0.0]`. + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.before([500000.0, 0.0, 0.0]); + /// ``` + pub fn before(self, bound: [f64; 3]) -> Self { + Self { + bound: Some(Bound::Before(bound)), + ..self + } + } + + /// Limits the amount of entries to return. + /// + /// # Arguments + /// + /// - `limit`: The amount of entries to return. + /// Between 1 and 100. 25 by default. + /// + /// # Examples + /// + /// Limits the amount of entries to return to `10`. + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(10); + /// ``` + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between `1` and `100`. + /// + /// ```should_panic + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(0); + /// ``` + /// + /// ```should_panic + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let mut criteria = SearchCriteria::new(); + /// criteria.limit(101); + /// ``` + pub fn limit(self, limit: u8) -> Self { + if (1..=100).contains(&limit) { + Self { + limit: Some(limit), + ..self + } + } else { + panic!( + "The argument `limit` must be between 1 and 100.\n\ + Received: {}", + limit + ); + } + } + + /// Whether the search criteria `limit` is out of bounds. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let invalid_criteria = SearchCriteria { + /// limit: Some(0), + /// ..SearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + /// + /// ``` + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let invalid_criteria = SearchCriteria { + /// limit: Some(101), + /// ..SearchCriteria::new() + /// }; + /// assert!(invalid_criteria.is_invalid_limit_range()); + /// ``` + pub fn is_invalid_limit_range(&self) -> bool { + if let Some(l) = self.limit { + !(1..=100).contains(&l) + } else { + false + } + } + + /// Builds the search criteria to `Vec<(String, String)>`. + /// + /// # Examples + /// + /// ```ignore + /// # use tetr_ch::client::param::record::SearchCriteria; + /// let criteria = SearchCriteria::new(); + /// let query_params = criteria.build(); + /// ``` + pub(crate) fn build(self) -> Vec<(String, String)> { + let mut result = Vec::new(); + if let Some(b) = self.bound { + result.push(b.to_query_param()); + } + if let Some(l) = self.limit { + result.push(("limit".to_string(), l.to_string())); + } + result + } +} From aa6d793e24f18ee9ba4eee58075d14fa3bc6cce3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 04:25:13 +0900 Subject: [PATCH 086/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`error`=20module?= =?UTF-8?q?=20to=20`client`=20module=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 49 +++++++++++++++++++------------------- src/{ => client}/error.rs | 6 ++--- src/client/response.rs | 2 +- src/lib.rs | 1 - src/model/searched_user.rs | 3 +-- src/model/user.rs | 3 +-- 6 files changed, 30 insertions(+), 34 deletions(-) rename src/{ => client}/error.rs (94%) diff --git a/src/client.rs b/src/client.rs index 8d7723b..d955b19 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,6 +1,7 @@ //! Client for API requests. use self::{ + error::ResponseError, param::{ news_stream::NewsStream, record::{self, Gamemode}, @@ -10,32 +11,29 @@ use self::{ }, response::response, }; -use crate::{ - error::ResponseError, - model::{ - achievement_info::AchievementInfoResponse, - labs::{ - league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, - scoreflow::LabsScoreflowResponse, - }, - leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, - news::{NewsAllResponse, NewsLatestResponse}, - records_leaderboard::RecordsLeaderboardResponse, - searched_user::SearchedUserResponse, - server_activity::ServerActivityResponse, - server_stats::ServerStatsResponse, - summary::{ - achievements::AchievementsResponse, - blitz::BlitzResponse, - forty_lines::FortyLinesResponse, - league::LeagueResponse, - zen::ZenResponse, - zenith::{ZenithExResponse, ZenithResponse}, - AllSummariesResponse, - }, - user::UserResponse, - user_records::UserRecordsResponse, +use crate::model::{ + achievement_info::AchievementInfoResponse, + labs::{ + league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, + scoreflow::LabsScoreflowResponse, + }, + leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, + news::{NewsAllResponse, NewsLatestResponse}, + records_leaderboard::RecordsLeaderboardResponse, + searched_user::SearchedUserResponse, + server_activity::ServerActivityResponse, + server_stats::ServerStatsResponse, + summary::{ + achievements::AchievementsResponse, + blitz::BlitzResponse, + forty_lines::FortyLinesResponse, + league::LeagueResponse, + zen::ZenResponse, + zenith::{ZenithExResponse, ZenithResponse}, + AllSummariesResponse, }, + user::UserResponse, + user_records::UserRecordsResponse, }; use reqwest::{self}; @@ -1116,6 +1114,7 @@ impl Client { } } +pub mod error; pub mod param; mod response; diff --git a/src/error.rs b/src/client/error.rs similarity index 94% rename from src/error.rs rename to src/client/error.rs index dcc36dd..f34116e 100644 --- a/src/error.rs +++ b/src/client/error.rs @@ -1,4 +1,4 @@ -//! Error enum for the tetr-ch-rs. +//! An error enum for the response handling. use http::status::{InvalidStatusCode, StatusCode}; use std::error::Error; @@ -10,7 +10,7 @@ pub enum ResponseError { /// When there are some mismatches in the API docs, /// or when this library is defective. DeserializeErr(String), - /// When redirect loop was detected or redirect limit was exhausted. + /// When the request is invalid. RequestErr(String), /// When the HTTP request fails. HttpErr(Status), @@ -40,7 +40,7 @@ impl From for std::io::Error { } } -/// HTTP status codes. +/// A HTTP status code. #[derive(Debug)] pub enum Status { /// If the status code greater or equal to 100 but less than 600. diff --git a/src/client/response.rs b/src/client/response.rs index e2ae5b0..40d96cf 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,4 +1,4 @@ -use crate::error::{ResponseError, Status}; +use super::error::{ResponseError, Status}; use http::StatusCode; use reqwest::{Error, Response}; use serde::Deserialize; diff --git a/src/lib.rs b/src/lib.rs index e930515..d7e7fa6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,6 @@ pub mod client; pub mod constants; -pub mod error; pub mod model; pub(crate) mod util; diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index 16c6819..ff1501f 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -1,8 +1,7 @@ //! The Searched User model. use crate::{ - client::Client, - error::ResponseError, + client::{error::ResponseError, Client}, model::{ cache::CacheData, user::{UserId, UserResponse}, diff --git a/src/model/user.rs b/src/model/user.rs index a252210..0bacb7f 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,8 +1,7 @@ //! The User Info models. use crate::{ - client::Client, - error::ResponseError, + client::{error::ResponseError, Client}, model::cache::CacheData, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; From df83551fcad59606717e64720354303740b74c70 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 04:31:35 +0900 Subject: [PATCH 087/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=F0=9F=9A=9A=20Change?= =?UTF-8?q?:=20rename=20and=20move=20`src/util/mod.rs`=20to=20`src/util.rs?= =?UTF-8?q?`=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{util/mod.rs => util.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{util/mod.rs => util.rs} (100%) diff --git a/src/util/mod.rs b/src/util.rs similarity index 100% rename from src/util/mod.rs rename to src/util.rs From 2fa83f2bbebdc712864ba52b42dc1f239d56201a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 04:40:36 +0900 Subject: [PATCH 088/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`Rank`=20enum=20?= =?UTF-8?q?to=20`league=5Frank`=20module=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 2 +- src/model/{league.rs => league_rank.rs} | 248 +----------------------- src/model/mod.rs | 2 +- src/model/news.rs | 2 +- src/model/summary/league.rs | 2 +- src/model/summary/record.rs | 2 +- 6 files changed, 13 insertions(+), 245 deletions(-) rename src/model/{league.rs => league_rank.rs} (67%) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 94b9e7b..063c501 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -3,7 +3,7 @@ use crate::{ model::{ cache::CacheData, - league::Rank, + league_rank::Rank, user::{AchievementRatingCounts, Role, UserId}, }, util::{max_f64, to_unix_ts}, diff --git a/src/model/league.rs b/src/model/league_rank.rs similarity index 67% rename from src/model/league.rs rename to src/model/league_rank.rs index 9e3bfb9..af0e918 100644 --- a/src/model/league.rs +++ b/src/model/league_rank.rs @@ -1,139 +1,9 @@ -//! TETRA LEAGUE related objects. +//! Ranks in TETRA LEAGUE. use serde::Deserialize; use std::fmt::{self, Display, Formatter}; -/// The user's TETRA LEAGUE data. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct LeagueData { - /// The amount of TETRA LEAGUE games played by this user. - #[serde(rename = "gamesplayed")] - pub play_count: u32, - /// The amount of TETRA LEAGUE games won by this user. - #[serde(rename = "gameswon")] - pub win_count: u32, - /// This user's TR (Tetra Rating), or -1 if less than 10 games were played. - pub rating: f64, - /// This user's letter rank. Z is unranked. - pub rank: Rank, - /// This user's highest achieved rank this season. - #[serde(rename = "bestrank")] - pub best_rank: Option, - /// This user's position in global leaderboards, or -1 if not applicable. - pub standing: i32, - /// This user's position in local leaderboards, or -1 if not applicable. - pub standing_local: i32, - /// The next rank this user can achieve, - /// if they win more games, or `None` if unranked (or the best rank). - pub next_rank: Option, - /// The previous rank this user can achieve, - /// if they lose more games, or `None` if unranked (or the worst rank). - pub prev_rank: Option, - /// The position of the best player in the user's current rank, surpass them to go up a rank. - /// -1 if unranked (or the best rank). - pub next_at: i32, - /// The position of the worst player in the user's current rank, dip below them to go down a rank. - /// -1 if unranked (or the worst rank). - pub prev_at: i32, - /// This user's percentile position (0 is best, 1 is worst). - pub percentile: f64, - /// This user's percentile rank, or Z if not applicable. - pub percentile_rank: Rank, - /// This user's Glicko-2 rating. - pub glicko: Option, - /// This user's Glicko-2 Rating Deviation. - /// If over 100, this user is unranked. - pub rd: Option, - /// This user's average APM (attack per minute) over the last 10 games. - pub apm: Option, - /// This user's average PPS (pieces per second) over the last 10 games. - pub pps: Option, - /// This user's average VS (versus score) over the last 10 games. - pub vs: Option, - /// Whether this user's RD is rising (has not played in the last week). - #[serde(rename = "decaying")] - pub is_decaying: bool, -} - -impl LeagueData { - /// Returns an icon URL of the user's rank. - /// If the user is unranked, returns ?-rank(z) icon URL. - /// If the user has no rank, returns `None`. - pub fn rank_icon_url(&self) -> Option { - if 10 <= self.play_count { - Some(self.rank.icon_url()) - } else { - None - } - } - - /// Returns a rank color. (Hex color codes) - /// If the user has no rank, returns `None`. - pub fn rank_color(&self) -> Option { - if 10 <= self.play_count { - Some(self.rank.color()) - } else { - None - } - } - - /// Returns an icon URL of the user's percentile rank. - /// If not applicable, returns `None`. - pub fn percentile_rank_icon_url(&self) -> Option { - let pr = &self.percentile_rank; - if !pr.is_unranked() { - Some(pr.icon_url()) - } else { - None - } - } - - /// Returns a percentile rank color. (Hex color codes) - /// If not applicable, returns `None`. - pub fn percentile_rank_color(&self) -> Option { - let pr = &self.percentile_rank; - if !pr.is_unranked() { - Some(pr.color()) - } else { - None - } - } - - /// Returns an icon URL of the user's highest achieved rank. - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_icon_url(&self) -> Option { - self.best_rank.as_ref().map(|br| br.icon_url()) - } - - /// Returns a highest achieved rank color. (Hex color codes) - /// If the user has no highest achieved rank, returns `None`. - pub fn best_rank_color(&self) -> Option { - self.best_rank.as_ref().map(|br| br.color()) - } - - /// Returns the user's progress percentage in the rank. - /// Returns `None` if there is no user's position in global leaderboards. - pub fn rank_progress(&self) -> Option { - let current_standing = self.standing as f64; - let prev_at = self.prev_at as f64; - let next_at = self.next_at as f64; - - if prev_at < 0. || next_at < 0. { - return None; - } - - Some((current_standing - prev_at) / (next_at - prev_at) * 100.) - } -} - -impl AsRef for LeagueData { - fn as_ref(&self) -> &Self { - self - } -} - -/// The TETRA LEAGUE rank. +/// A rank in TETRA LEAGUE. #[derive(Clone, Debug, Deserialize)] pub enum Rank { /// The D rank. @@ -201,7 +71,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::league::Rank; + /// # use tetr_ch::model::league_rank::Rank; /// assert_eq!(Rank::D.as_str(), "D"); /// assert_eq!(Rank::DPlus.as_str(), "D+"); /// assert_eq!(Rank::CMinus.as_str(), "C-"); @@ -246,12 +116,12 @@ impl Rank { } } - /// Whether the rank is unranked(Z). + /// Whether the rank is unranked (Z rank). /// /// # Examples /// /// ``` - /// # use tetr_ch::model::league::Rank; + /// # use tetr_ch::model::league_rank::Rank; /// assert!(!Rank::D.is_unranked()); /// assert!(!Rank::A.is_unranked()); /// assert!(!Rank::X.is_unranked()); @@ -266,7 +136,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::league::Rank; + /// # use tetr_ch::model::league_rank::Rank; /// assert_eq!(Rank::D.icon_url(), "https://tetr.io/res/league-ranks/d.png"); /// assert_eq!(Rank::DPlus.icon_url(), "https://tetr.io/res/league-ranks/d+.png"); /// assert_eq!(Rank::CMinus.icon_url(), "https://tetr.io/res/league-ranks/c-.png"); @@ -291,12 +161,12 @@ impl Rank { format!("https://tetr.io/res/league-ranks/{}.png", self) } - /// Returns the rank color. (Hex color codes) + /// Returns the rank color (hex color code). /// /// # Examples /// /// ``` - /// # use tetr_ch::model::league::Rank; + /// # use tetr_ch::model::league_rank::Rank; /// assert_eq!(Rank::D.color(), 0x907591); /// assert_eq!(Rank::DPlus.color(), 0x8e6091); /// assert_eq!(Rank::CMinus.color(), 0x79558c); @@ -458,84 +328,6 @@ impl Display for Rank { mod tests { use super::*; - #[test] - fn get_rank_icon_url_from_league_data() { - let league_data_ranked = LeagueData { - rank: Rank::D, - play_count: 10, - ..default_league_data() - }; - let league_data_unranked = LeagueData { - play_count: 9, - ..default_league_data() - }; - assert_eq!( - league_data_ranked.rank_icon_url(), - Some("https://tetr.io/res/league-ranks/d.png".to_string()) - ); - assert_eq!(league_data_unranked.rank_icon_url(), None); - } - - #[test] - fn get_rank_color_from_league_data() { - let league_data = LeagueData { - rank: Rank::A, - play_count: 10, - ..default_league_data() - }; - assert_eq!(league_data.rank_color(), Some(0x46ad51)); - } - - #[test] - fn get_percentile_rank_icon_url_from_league_data() { - let league_data_ranked = LeagueData { - percentile_rank: Rank::X, - ..default_league_data() - }; - let league_data_unranked = LeagueData { - percentile_rank: Rank::Z, - ..default_league_data() - }; - assert_eq!( - league_data_ranked.percentile_rank_icon_url(), - Some("https://tetr.io/res/league-ranks/x.png".to_string()) - ); - assert_eq!(league_data_unranked.percentile_rank_icon_url(), None); - } - - #[test] - fn get_percentile_rank_color_from_league_data() { - let league_data = LeagueData { - percentile_rank: Rank::DPlus, - ..default_league_data() - }; - assert_eq!(league_data.percentile_rank_color(), Option::Some(0x8e6091)); - } - - #[test] - fn get_rank_progress() { - let league_data_unranked = LeagueData { - prev_at: -1, - next_at: -1, - ..default_league_data() - }; - let league_data_ranked = LeagueData { - standing: 600, - prev_at: 2000, - next_at: 400, - ..default_league_data() - }; - assert_eq!(league_data_unranked.rank_progress(), None); - assert_eq!(league_data_ranked.rank_progress(), Some(87.5)); - } - - #[test] - fn league_data_as_ref() { - let league_data = default_league_data(); - let _a = league_data.as_ref(); - let _b = league_data; - } - #[test] fn ranks_as_str() { let rank_d = Rank::D; @@ -643,28 +435,4 @@ mod tests { let _a = rank.as_ref(); let _b = rank; } - - fn default_league_data() -> LeagueData { - LeagueData { - play_count: 0, - win_count: 0, - rating: 0., - rank: Rank::Z, - best_rank: None, - standing: 0, - standing_local: 0, - next_rank: Some(Rank::Z), - prev_rank: Some(Rank::Z), - next_at: 0, - prev_at: 0, - percentile: 0., - percentile_rank: Rank::Z, - glicko: Some(0.), - rd: Some(0.), - apm: Some(0.), - pps: Some(0.), - vs: Some(0.), - is_decaying: false, - } - } } diff --git a/src/model/mod.rs b/src/model/mod.rs index f28437e..2289785 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,7 +5,7 @@ pub mod cache; pub mod labs; pub mod latest_news; pub mod leaderboard; -pub mod league; +pub mod league_rank; pub mod news; pub mod records_leaderboard; pub mod searched_user; diff --git a/src/model/news.rs b/src/model/news.rs index 8aeea11..8627219 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -1,6 +1,6 @@ //! The All Latest News models. -use crate::model::{cache::CacheData, league::Rank}; +use crate::model::{cache::CacheData, league_rank::Rank}; use serde::Deserialize; /// The response for the All Latest News data. diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 26dcc3c..46bed26 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -1,6 +1,6 @@ //! The User Summary TETRA LEAGUE models. -use crate::model::{cache::CacheData, league::Rank}; +use crate::model::{cache::CacheData, league_rank::Rank}; use serde::Deserialize; use std::collections::HashMap; diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index afeb757..e26034f 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -1,7 +1,7 @@ //! The Record Data models. use crate::{ - model::{leaderboard::Prisecter, league::Rank, user::UserId}, + model::{leaderboard::Prisecter, league_rank::Rank, user::UserId}, util::to_unix_ts, }; use serde::Deserialize; From b77e67f2aa492d2d826292a07b43945d57041db4 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 04:54:18 +0900 Subject: [PATCH 089/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`RspErr`=20ty?= =?UTF-8?q?pe=20alias=20to=20`error`=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 4 +--- src/client/error.rs | 2 ++ src/client/response.rs | 4 ++-- src/model/searched_user.rs | 4 +--- src/model/user.rs | 4 +--- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/client.rs b/src/client.rs index d955b19..803a628 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ //! Client for API requests. use self::{ - error::ResponseError, + error::RspErr, param::{ news_stream::NewsStream, record::{self, Gamemode}, @@ -64,8 +64,6 @@ pub struct Client { client: reqwest::Client, } -type RspErr = Result; - impl Client { /// Create a new [`Client`]. /// diff --git a/src/client/error.rs b/src/client/error.rs index f34116e..664b1a7 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -49,6 +49,8 @@ pub enum Status { Invalid(InvalidStatusCode), } +pub(crate) type RspErr = Result; + #[cfg(test)] mod tests { use super::*; diff --git a/src/client/response.rs b/src/client/response.rs index 40d96cf..4f381df 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,4 +1,4 @@ -use super::error::{ResponseError, Status}; +use super::error::{ResponseError, RspErr, Status}; use http::StatusCode; use reqwest::{Error, Response}; use serde::Deserialize; @@ -11,7 +11,7 @@ use serde::Deserialize; /// let res = self.client.get(url).send().await; /// response(res).await /// ``` -pub(super) async fn response(response: Result) -> Result +pub(super) async fn response(response: Result) -> RspErr where for<'de> T: Deserialize<'de>, { diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index ff1501f..dd9537b 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -1,7 +1,7 @@ //! The Searched User model. use crate::{ - client::{error::ResponseError, Client}, + client::{error::RspErr, Client}, model::{ cache::CacheData, user::{UserId, UserResponse}, @@ -25,8 +25,6 @@ pub struct SearchedUserResponse { pub data: Option, } -type RspErr = Result; - impl SearchedUserResponse { /// Gets the user's data. /// Returns `None` if the user was not found. diff --git a/src/model/user.rs b/src/model/user.rs index 0bacb7f..e7d756d 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,7 +1,7 @@ //! The User Info models. use crate::{ - client::{error::ResponseError, Client}, + client::{error::RspErr, Client}, model::cache::CacheData, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; @@ -790,8 +790,6 @@ impl AsRef for AchievementRatingCounts { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] pub struct UserId(pub String); -type RspErr = Result; - impl UserId { /// Returns the user's internal ID. pub fn id(&self) -> &str { From dc726384d0616b0fd8b23676c92bf3b5b6b32864 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 05:01:33 +0900 Subject: [PATCH 090/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`Role`=20enum=20?= =?UTF-8?q?to=20`role`=20module=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 6 +- src/model/leaderboard.rs | 3 +- src/model/mod.rs | 1 + src/model/role.rs | 116 ++++++++++++++++++++++++++++++++++ src/model/user.rs | 115 +-------------------------------- 5 files changed, 123 insertions(+), 118 deletions(-) create mode 100644 src/model/role.rs diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 70304c9..f06948e 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -1,10 +1,10 @@ //! The Achievement Info models. -use crate::model::{cache::CacheData, summary::achievements::Achievement, user::UserId}; +use crate::model::{ + cache::CacheData, role::Role, summary::achievements::Achievement, user::UserId, +}; use serde::Deserialize; -use super::user::Role; - /// The response for the Achievement Info data. /// /// Data about the achievement itself, its cutoffs, and its leaderboard. diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 063c501..5d822b5 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -4,7 +4,8 @@ use crate::{ model::{ cache::CacheData, league_rank::Rank, - user::{AchievementRatingCounts, Role, UserId}, + role::Role, + user::{AchievementRatingCounts, UserId}, }, util::{max_f64, to_unix_ts}, }; diff --git a/src/model/mod.rs b/src/model/mod.rs index 2289785..3599553 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -8,6 +8,7 @@ pub mod leaderboard; pub mod league_rank; pub mod news; pub mod records_leaderboard; +pub mod role; pub mod searched_user; pub mod server_activity; pub mod server_stats; diff --git a/src/model/role.rs b/src/model/role.rs new file mode 100644 index 0000000..1c7ed09 --- /dev/null +++ b/src/model/role.rs @@ -0,0 +1,116 @@ +//! A model for user roles. + +use serde::Deserialize; + +/// A user role. +#[derive(Clone, Debug, Deserialize)] +pub enum Role { + /// A normal user. + #[serde(rename = "user")] + User, + /// An anonymous user. + #[serde(rename = "anon")] + Anon, + /// A bot. + #[serde(rename = "bot")] + Bot, + /// A SYSOP. + #[serde(rename = "sysop")] + Sysop, + /// An administrator. + #[serde(rename = "admin")] + Admin, + /// A moderator. + #[serde(rename = "mod")] + Mod, + /// A community moderator. + #[serde(rename = "halfmod")] + Halfmod, + /// A banned user. + #[serde(rename = "banned")] + Banned, + /// A hidden user. + #[serde(rename = "hidden")] + Hidden, +} + +impl Role { + /// Whether the user is an anonymous. + pub fn is_anon(&self) -> bool { + matches!(self, Role::Anon) + } + + /// Whether the user is a bot. + pub fn is_bot(&self) -> bool { + matches!(self, Role::Bot) + } + + /// Whether the user is a SYSOP. + pub fn is_sysop(&self) -> bool { + matches!(self, Role::Sysop) + } + + /// Whether the user is an administrator. + pub fn is_admin(&self) -> bool { + matches!(self, Role::Admin) + } + + /// Whether the user is a moderator. + pub fn is_mod(&self) -> bool { + matches!(self, Role::Mod) + } + + /// Whether the user is a community moderator. + pub fn is_halfmod(&self) -> bool { + matches!(self, Role::Halfmod) + } + + /// Whether the user is banned. + pub fn is_banned(&self) -> bool { + matches!(self, Role::Banned) + } + + /// Whether the user is hidden. + pub fn is_hidden(&self) -> bool { + matches!(self, Role::Hidden) + } +} + +impl AsRef for Role { + fn as_ref(&self) -> &Self { + self + } +} + +impl ToString for Role { + /// Converts to a `String`. + /// + /// # Examples + /// + /// ``` + /// # use tetr_ch::model::role::Role; + /// assert_eq!(Role::User.to_string(), "User"); + /// assert_eq!(Role::Anon.to_string(), "Anonymous"); + /// assert_eq!(Role::Bot.to_string(), "Bot"); + /// assert_eq!(Role::Sysop.to_string(), "SYSOP"); + /// assert_eq!(Role::Admin.to_string(), "Administrator"); + /// assert_eq!(Role::Mod.to_string(), "Moderator"); + /// assert_eq!(Role::Halfmod.to_string(), "Community moderator"); + /// assert_eq!(Role::Banned.to_string(), "Banned user"); + /// assert_eq!(Role::Hidden.to_string(), "Hidden user"); + /// ``` + fn to_string(&self) -> String { + match self { + Role::User => "User", + Role::Anon => "Anonymous", + Role::Bot => "Bot", + Role::Sysop => "SYSOP", + Role::Admin => "Administrator", + Role::Mod => "Moderator", + Role::Halfmod => "Community moderator", + Role::Banned => "Banned user", + Role::Hidden => "Hidden user", + } + .to_string() + } +} diff --git a/src/model/user.rs b/src/model/user.rs index e7d756d..df75ae9 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -2,7 +2,7 @@ use crate::{ client::{error::RspErr, Client}, - model::cache::CacheData, + model::{cache::CacheData, role::Role}, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; use serde::Deserialize; @@ -481,119 +481,6 @@ impl AsRef for User { } } -/// The user's role. -#[derive(Clone, Debug, Deserialize)] -pub enum Role { - /// The normal user. - #[serde(rename = "user")] - User, - /// The anonymous user. - #[serde(rename = "anon")] - Anon, - /// The bot. - #[serde(rename = "bot")] - Bot, - /// The SYSOP. - #[serde(rename = "sysop")] - Sysop, - /// The administrator. - #[serde(rename = "admin")] - Admin, - /// The moderator. - #[serde(rename = "mod")] - Mod, - /// The community moderator. - #[serde(rename = "halfmod")] - Halfmod, - /// The banned user. - #[serde(rename = "banned")] - Banned, - /// The hidden user. - #[serde(rename = "hidden")] - Hidden, -} - -impl Role { - /// Whether the user is an anonymous. - pub fn is_anon(&self) -> bool { - matches!(self, Role::Anon) - } - - /// Whether the user is a bot. - pub fn is_bot(&self) -> bool { - matches!(self, Role::Bot) - } - - /// Whether the user is a SYSOP. - pub fn is_sysop(&self) -> bool { - matches!(self, Role::Sysop) - } - - /// Whether the user is an administrator. - pub fn is_admin(&self) -> bool { - matches!(self, Role::Admin) - } - - /// Whether the user is a moderator. - pub fn is_mod(&self) -> bool { - matches!(self, Role::Mod) - } - - /// Whether the user is a community moderator. - pub fn is_halfmod(&self) -> bool { - matches!(self, Role::Halfmod) - } - - /// Whether the user is banned. - pub fn is_banned(&self) -> bool { - matches!(self, Role::Banned) - } - - /// Whether the user is hidden. - pub fn is_hidden(&self) -> bool { - matches!(self, Role::Hidden) - } -} - -impl AsRef for Role { - fn as_ref(&self) -> &Self { - self - } -} - -impl ToString for Role { - /// Converts the given value to a `String`. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::model::user::Role; - /// assert_eq!(Role::User.to_string(), "User"); - /// assert_eq!(Role::Anon.to_string(), "Anonymous"); - /// assert_eq!(Role::Bot.to_string(), "Bot"); - /// assert_eq!(Role::Sysop.to_string(), "SYSOP"); - /// assert_eq!(Role::Admin.to_string(), "Administrator"); - /// assert_eq!(Role::Mod.to_string(), "Moderator"); - /// assert_eq!(Role::Halfmod.to_string(), "Community moderator"); - /// assert_eq!(Role::Banned.to_string(), "Banned user"); - /// assert_eq!(Role::Hidden.to_string(), "Hidden user"); - /// ``` - fn to_string(&self) -> String { - match self { - Role::User => "User", - Role::Anon => "Anonymous", - Role::Bot => "Bot", - Role::Sysop => "SYSOP", - Role::Admin => "Administrator", - Role::Mod => "Moderator", - Role::Halfmod => "Community moderator", - Role::Banned => "Banned user", - Role::Hidden => "Hidden user", - } - .to_string() - } -} - /// The user's badges. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] From 753dcfde6643ad8c550ba21e0a3361387de05b0e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 05:10:18 +0900 Subject: [PATCH 091/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`Prisecter`=20st?= =?UTF-8?q?ruct=20to=20`pagination`=20module=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/pagination.rs | 31 +++++++++++++++++++++++++++++++ src/model/leaderboard.rs | 30 +----------------------------- src/model/summary/record.rs | 3 ++- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/client/param/pagination.rs b/src/client/param/pagination.rs index 3b4918c..c143961 100644 --- a/src/client/param/pagination.rs +++ b/src/client/param/pagination.rs @@ -1,5 +1,36 @@ //! Features for pagination. +use serde::Deserialize; + +/// A prisecter. +/// +/// A **prisecter** is consisting of three floats. +/// It allows you to continue paginating. +#[derive(Clone, Debug, Deserialize)] +pub struct Prisecter { + /// The primary sort key. + pub pri: f64, + /// The secondary sort key. + pub sec: f64, + /// The tertiary sort key. + pub ter: f64, +} + +impl Prisecter { + /// Converts to an array. + /// + /// This array can be used as a bound for the next search. + pub fn to_array(&self) -> [f64; 3] { + [self.pri, self.sec, self.ter] + } +} + +impl AsRef for Prisecter { + fn as_ref(&self) -> &Self { + self + } +} + /// A bound to paginate. #[derive(Clone, Debug)] pub enum Bound { diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 5d822b5..5154fc6 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -1,6 +1,7 @@ //! The User Leaderboard models. use crate::{ + client::param::pagination::Prisecter, model::{ cache::CacheData, league_rank::Rank, @@ -214,35 +215,6 @@ impl AsRef for League { } } -/// A prisecter. -/// -/// A **prisecter** is consisting of three floats. -/// It allows you to continue paginating. -#[derive(Clone, Debug, Deserialize)] -pub struct Prisecter { - /// The primary sort key. - pub pri: f64, - /// The secondary sort key. - pub sec: f64, - /// The tertiary sort key. - pub ter: f64, -} - -impl Prisecter { - /// Converts this prisecter to an array. - /// - /// This array can be used as a bound for the next search. - pub fn to_array(&self) -> [f64; 3] { - [self.pri, self.sec, self.ter] - } -} - -impl AsRef for Prisecter { - fn as_ref(&self) -> &Self { - self - } -} - /// The response for the Historical User Leaderboard data. /// /// An array of historical user blobs fulfilling the search criteria. diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index e26034f..da055c9 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -1,7 +1,8 @@ //! The Record Data models. use crate::{ - model::{leaderboard::Prisecter, league_rank::Rank, user::UserId}, + client::param::pagination::Prisecter, + model::{league_rank::Rank, user::UserId}, util::to_unix_ts, }; use serde::Deserialize; From 648ca3f74e4f8380aa24cca704b75449add056ed Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 05:12:56 +0900 Subject: [PATCH 092/255] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Delete:=20`latest?= =?UTF-8?q?=5Fnews.rs`=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/latest_news.rs | 456 --------------------------------------- src/model/mod.rs | 1 - 2 files changed, 457 deletions(-) delete mode 100644 src/model/latest_news.rs diff --git a/src/model/latest_news.rs b/src/model/latest_news.rs deleted file mode 100644 index a67b338..0000000 --- a/src/model/latest_news.rs +++ /dev/null @@ -1,456 +0,0 @@ -//! Latest news model. - -use crate::{model::cache::CacheData, util::to_unix_ts}; -use serde::Deserialize; - -/// The response for the latest news. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct LatestNewsResponse { - /// Whether the request was successful. - #[serde(rename = "success")] - pub is_success: bool, - /// The reason the request failed. - pub error: Option, - /// Data about how this request was cached. - pub cache: Option, - /// The requested latest news data. - pub data: Option, -} - -impl LatestNewsResponse { - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Examples - /// - /// ```ignore - /// // An example. - /// assert_eq!(cache_data.cached_at(), 1596317823); - /// ``` - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Examples - /// - /// ```ignore - /// // An example. - /// assert_eq!(cache_data.cached_until(), 1661710844); - /// ``` - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_until(), - None => panic!("There is no cache data."), - } - } -} - -impl AsRef for LatestNewsResponse { - fn as_ref(&self) -> &Self { - self - } -} - -/// The requested latest news. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct LatestNews { - /// The latest news items. - pub news: Vec, -} - -impl AsRef for LatestNews { - fn as_ref(&self) -> &Self { - self - } -} - -/// A news item. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct News { - /// The item's internal ID. - #[serde(rename = "_id")] - pub id: String, - /// The item's stream. - pub stream: String, - /// The item's type. - /// - /// Currently(August 2022) it has the following types: - /// - `leaderboard`: - /// When a user's new personal best enters a global leaderboard. - /// Seen in the global stream only. - /// - `personalbest` - /// When a user gets a personal best. - /// Seen in user streams only. - /// - `badge` - /// When a user gets a badge. - /// Seen in user streams only. - /// - `rankup` - /// When a user gets a new top rank in TETRA LEAGUE. - /// Seen in user streams only. - /// - `supporter` - /// When a user gets TETR.IO Supporter. - /// Seen in user streams only. - /// - `supporter_gift` - /// When a user is gifted TETR.IO Supporter. - /// Seen in user streams only. - #[serde(rename = "type")] - pub _type: String, - /// The item's records. - pub data: NewsData, - /// The item's creation date. - #[serde(rename = "ts")] - pub creation_at: String, -} - -impl News { - /// ~~Returns an icon URL of the TETRA LEAGUE rank.~~ - /// ~~If the user is unranked, returns ?-rank(z) icon URL.~~ - /// - /// ~~# Panics~~ - /// - /// ~~Panics if the stream is not type([`Self::_type`]) `rankup`.~~ - /// - /// This function does not currently work. - /// See [here](`NewsData::rank`) for the reason. - #[deprecated] - pub fn rank_icon_url(&self) /*-> String*/ - { - /*if let Some(rank) = self.data.rank.as_ref() { - if let Ok(_) = rank.parse::() { - panic!("This stream is not type `rankup`") - } else { - format!("https://tetr.io/res/league-ranks/{}.png", rank) - } - } else { - panic!("This stream is not type `rankup`") - }*/ - } - - /// Returns an badge URL. - /// - /// # Panics - /// - /// Panics if the stream is not type([`Self::_type`]) `badge`. - pub fn badge_icon_url(&self) -> String { - if let Some(i) = self.data.badge_id.as_ref() { - format!("https://tetr.io/res/badges/{}.png", i) - } else { - panic!("This stream is not type `badge`") - } - } - - /// Returns a UNIX timestamp when the item was created. - /// - /// # Examples - /// - /// ```ignore - /// // An example. - /// assert_eq!(news.creation_at(), 1658856923); - /// ``` - pub fn creation_at(&self) -> i64 { - to_unix_ts(&self.creation_at) - } -} - -impl AsRef for News { - fn as_ref(&self) -> &Self { - self - } -} - -/// The item's records. -/// -/// Which fields are valid depends on the [`News::_type`]. -/// Currently(August 2022) it has the following types: -/// - `leaderboard` -/// - `personalbest` -/// - `badge` -/// - `rankup` -/// - `supporter` -/// - `supporter_gift` -/// -/// And defined as optional even if the field is currently(August 2022) valid for all types. -/// This is for backward compatibility. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct NewsData { - /// The username of the player. - /// - /// Valid for types: - /// `leaderboard`, `personalbest`, `badge`, `rankup`, `supporter`, `supporter_gift` - pub username: Option, - /// The game mode played. - /// - /// Valid for types: - /// `leaderboard`, `personalbest` - #[serde(rename = "gametype")] - pub game_type: Option, - /// - ~~The global rank achieved. (leaderboard)~~ - /// - ~~The new rank. (rankup)~~ - /// - /// ~~Valid for types:~~ - /// ~~`leaderboard`,~~ - /// ~~`rankup`~~ - /// - /// This field is currently(August 2022) too dynamic. - /// So the developer(Rinrin.rs) was not able to deal it. - #[serde(default = "none", rename = "_")] - pub rank: Option<()>, - /// The result (score or time) achieved. - /// - /// Valid for types: - /// `leaderboard`, `personalbest` - pub result: Option, - /// The replay's shortID. - /// - /// Valid for types: - /// `leaderboard`, `personalbest` - #[serde(rename = "replayid")] - pub replay_id: Option, - /// The badge's internal ID, - /// and the filename of the badge icon (all PNGs within /res/badges/) - /// - /// Valid for types: - /// `badge` - #[serde(rename = "type")] - pub badge_id: Option, - /// The badge's label. - /// - /// Valid for types: - /// `badge` - pub badge_label: Option, -} - -impl AsRef for NewsData { - fn as_ref(&self) -> &Self { - self - } -} - -fn none() -> Option<()> { - None -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn get_cached_at_and_cached_until() { - use super::*; - let news = LatestNewsResponse { - is_success: true, - error: None, - cache: Some(crate::model::cache::CacheData { - status: "hit".to_string(), - cached_at: 1661710769000, - cached_until: 1661710844000, - }), - data: None, - }; - assert_eq!(news.cached_at(), 1661710769); - assert_eq!(news.cached_until(), 1661710844); - } - - #[test] - #[should_panic] - fn panic_if_no_cache_1() { - use super::*; - let news = LatestNewsResponse { - is_success: false, - error: None, - cache: None, - data: None, - }; - news.cached_at(); - } - - #[test] - #[should_panic] - fn panic_if_no_cache_2() { - use super::*; - let news = LatestNewsResponse { - is_success: false, - error: None, - cache: None, - data: None, - }; - news.cached_until(); - } - - #[test] - fn latest_news_response_as_ref() { - let latest_news_response = LatestNewsResponse { - is_success: true, - error: None, - cache: Some(crate::model::cache::CacheData { - status: "hit".to_string(), - cached_at: 1661710769000, - cached_until: 1661710844000, - }), - data: None, - }; - let _latest_news_response2 = latest_news_response.as_ref(); - let _latest_news_response3 = latest_news_response; - } - - #[test] - fn latest_news_as_ref() { - let latest_news = LatestNews { news: Vec::new() }; - let _latest_news2 = latest_news.as_ref(); - let _latest_news3 = latest_news; - } - - #[test] - //#[ignore] - #[allow(deprecated)] - fn get_rank_icon_but_deprecated() { - /*let news = News { - _id: "-".to_string(), - stream: "-".to_string(), - _type: "rankup".to_string(), - data: NewsData { - rank: Some("ss".to_string()), - username: None, - gametype: None, - result: None, - replayid: None, - _type: None, - label: None, - }, - ts: "-".to_string(), - }; - assert_eq!(news.rank_icon_url(), "https://tetr.io/res/league-ranks/SS.png");*/ - let news = News { - id: "-".to_string(), - stream: "-".to_string(), - _type: "-".to_string(), - data: NewsData { - rank: None, - username: None, - game_type: None, - result: None, - replay_id: None, - badge_id: None, - badge_label: None, - }, - creation_at: "-".to_string(), - }; - news.rank_icon_url(); - } - - #[test] - fn get_badge_icon() { - let news = News { - id: "-".to_string(), - stream: "-".to_string(), - _type: "-".to_string(), - data: NewsData { - rank: None, - username: None, - game_type: None, - result: None, - replay_id: None, - badge_id: Some("secretgrade".to_string()), - badge_label: Some("Achieved the full Secret Grade".to_string()), - }, - creation_at: "-".to_string(), - }; - assert_eq!( - news.badge_icon_url(), - "https://tetr.io/res/badges/secretgrade.png" - ); - } - - #[test] - #[should_panic] - fn panic_if_no_badge_id() { - let news = News { - id: "-".to_string(), - stream: "-".to_string(), - _type: "-".to_string(), - data: NewsData { - rank: None, - username: None, - game_type: None, - result: None, - replay_id: None, - badge_id: None, - badge_label: None, - }, - creation_at: "-".to_string(), - }; - news.badge_icon_url(); - } - - #[test] - fn get_creation_at() { - let news = News { - id: "-".to_string(), - stream: "-".to_string(), - _type: "-".to_string(), - data: NewsData { - rank: None, - username: None, - game_type: None, - result: None, - replay_id: None, - badge_id: None, - badge_label: None, - }, - creation_at: "2022-07-26T17:35:23.988Z".to_string(), - }; - assert_eq!(news.creation_at(), 1658856923); - } - - #[test] - fn news_as_ref() { - let news = News { - id: "-".to_string(), - stream: "-".to_string(), - _type: "-".to_string(), - data: NewsData { - rank: None, - username: None, - game_type: None, - result: None, - replay_id: None, - badge_id: None, - badge_label: None, - }, - creation_at: "-".to_string(), - }; - let _news2 = news.as_ref(); - let _news3 = news; - } - - #[test] - fn news_data_as_ref() { - let news_data = NewsData { - rank: None, - username: None, - game_type: None, - result: None, - replay_id: None, - badge_id: None, - badge_label: None, - }; - let _news_data2 = news_data.as_ref(); - let _news_data3 = news_data; - } -} diff --git a/src/model/mod.rs b/src/model/mod.rs index 3599553..4d6e87a 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -3,7 +3,6 @@ pub mod achievement_info; pub mod cache; pub mod labs; -pub mod latest_news; pub mod leaderboard; pub mod league_rank; pub mod news; From b9cf1db9301b4410d7f70b0915e58c37ca408628 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 05:20:25 +0900 Subject: [PATCH 093/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`Achievement`=20?= =?UTF-8?q?struct=20to=20`achievement`=20module=20[#63]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement.rs | 89 +++++++++++++++++++++++++++++++ src/model/achievement_info.rs | 4 +- src/model/mod.rs | 1 + src/model/summary/achievements.rs | 88 +----------------------------- src/model/summary/mod.rs | 4 +- 5 files changed, 94 insertions(+), 92 deletions(-) create mode 100644 src/model/achievement.rs diff --git a/src/model/achievement.rs b/src/model/achievement.rs new file mode 100644 index 0000000..ac68276 --- /dev/null +++ b/src/model/achievement.rs @@ -0,0 +1,89 @@ +//! A model for achievements. + +use serde::Deserialize; + +/// An achievement. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct Achievement { + /// The Achievement ID, for every type of achievement. + #[serde(rename = "k")] + pub id: u32, + /// The category of the achievement. + pub category: String, + /// The primary name of the achievement. + pub name: String, + /// The objective of the achievement. + pub object: String, + /// The flavor text of the achievement. + pub desc: String, + /// The order of this achievement in its category. + #[serde(rename = "o")] + pub order: Option, // EXCEPTION + /// The rank type of this achievement. + /// + /// - 1 = PERCENTILE β€” ranked by percentile cutoffs (5% Diamond, 10% Platinum, 30% Gold, 50% Silver, 70% Bronze) + /// - 2 = ISSUE β€” always has the ISSUED rank + /// - 3 = ZENITH β€” ranked by QUICK PLAY floors + /// - 4 = PERCENTILELAX β€” ranked by percentile cutoffs (5% Diamond, 20% Platinum, 60% Gold, 100% Silver) + /// - 5 = PERCENTILEVLAX β€” ranked by percentile cutoffs (20% Diamond, 50% Platinum, 100% Gold) + /// - 6 = PERCENTILEMLAX β€” ranked by percentile cutoffs (10% Diamond, 20% Platinum, 50% Gold, 100% Silver) + #[serde(rename = "rt")] + pub rank_type: u32, + /// The value type of this achievement: + /// + /// - 0 = NONE β€” [`Achievement::value`] is `None` + /// - 1 = NUMBER β€” [`Achievement::value`] is a positive number + /// - 2 = TIME β€” [`Achievement::value`] is a positive amount of milliseconds + /// - 3 = TIME_INV β€” [`Achievement::value`] is a negative amount of milliseconds; negate it before displaying + /// - 4 = FLOOR β€” [`Achievement::value`] is an altitude, A is a floor number + /// - 5 = ISSUE β€” [`Achievement::value`] is the negative time of issue + /// - 6 = NUMBER_INV β€” [`Achievement::value`] is a negative number; negate it before displaying + #[serde(rename = "vt")] + pub value_type: u32, + /// The AR type of this achievement: + /// + /// - 0 = UNRANKED β€” no AR is given + /// - 1 = RANKED β€” AR is given for medal ranks + /// - 2 = COMPETITIVE β€” AR is given for medal ranks and leaderboard positions + #[serde(rename = "art")] + pub ar_type: u32, + /// The minimum score required to obtain the achievement. + pub min: i64, + /// The amount of decimal placed to show. + pub deci: u32, + /// Whether this achievement is usually not shown. + #[serde(rename = "hidden")] + pub is_hidden: bool, + /// The achieved score. + #[serde(rename = "v")] + pub value: Option, + /// Additional data (see [`Achievement::value_type`]). + #[serde(rename = "a")] + pub additional: Option, + /// The time the achievement was updated. + #[serde(rename = "t")] + pub time: Option, + /// The zero-indexed position in the achievement's leaderboards. + #[serde(rename = "pos")] + pub position: Option, + /// The total amount of players who have this achievement + /// (with a value of min or higher). + pub total: Option, + /// The rank of the achievement. + /// + /// - 0 = NONE, + /// - 1 = BRONZE, + /// - 2 = SILVER, + /// - 3 = GOLD, + /// - 4 = PLATINUM, + /// - 5 = DIAMOND, + /// - 100 = ISSUED + pub rank: Option, +} + +impl AsRef for Achievement { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index f06948e..4268c25 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -1,8 +1,6 @@ //! The Achievement Info models. -use crate::model::{ - cache::CacheData, role::Role, summary::achievements::Achievement, user::UserId, -}; +use crate::model::{achievement::Achievement, cache::CacheData, role::Role, user::UserId}; use serde::Deserialize; /// The response for the Achievement Info data. diff --git a/src/model/mod.rs b/src/model/mod.rs index 4d6e87a..7cd0fda 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,5 +1,6 @@ //! Easy-to-use models of the various objects returned by the API. +pub mod achievement; pub mod achievement_info; pub mod cache; pub mod labs; diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs index 1f41cba..05857b3 100644 --- a/src/model/summary/achievements.rs +++ b/src/model/summary/achievements.rs @@ -1,6 +1,6 @@ //! The User Summary Achievements models. -use crate::model::cache::CacheData; +use crate::model::{achievement::Achievement, cache::CacheData}; use serde::Deserialize; /// The response for the User Summary Achievements data. @@ -24,89 +24,3 @@ impl AsRef for AchievementsResponse { self } } - -/// An object containing information about a user's achievement. -#[derive(Clone, Debug, Deserialize)] -#[non_exhaustive] -pub struct Achievement { - /// The Achievement ID, for every type of achievement. - #[serde(rename = "k")] - pub id: u32, - /// The category of the achievement. - pub category: String, - /// The primary name of the achievement. - pub name: String, - /// The objective of the achievement. - pub object: String, - /// The flavor text of the achievement. - pub desc: String, - /// The order of this achievement in its category. - #[serde(rename = "o")] - pub order: Option, // EXCEPTION - /// The rank type of this achievement. - /// - /// - 1 = PERCENTILE β€” ranked by percentile cutoffs (5% Diamond, 10% Platinum, 30% Gold, 50% Silver, 70% Bronze) - /// - 2 = ISSUE β€” always has the ISSUED rank - /// - 3 = ZENITH β€” ranked by QUICK PLAY floors - /// - 4 = PERCENTILELAX β€” ranked by percentile cutoffs (5% Diamond, 20% Platinum, 60% Gold, 100% Silver) - /// - 5 = PERCENTILEVLAX β€” ranked by percentile cutoffs (20% Diamond, 50% Platinum, 100% Gold) - /// - 6 = PERCENTILEMLAX β€” ranked by percentile cutoffs (10% Diamond, 20% Platinum, 50% Gold, 100% Silver) - #[serde(rename = "rt")] - pub rank_type: u32, - /// The value type of this achievement: - /// - /// - 0 = NONE β€” [`Achievement::value`] is `None` - /// - 1 = NUMBER β€” [`Achievement::value`] is a positive number - /// - 2 = TIME β€” [`Achievement::value`] is a positive amount of milliseconds - /// - 3 = TIME_INV β€” [`Achievement::value`] is a negative amount of milliseconds; negate it before displaying - /// - 4 = FLOOR β€” [`Achievement::value`] is an altitude, A is a floor number - /// - 5 = ISSUE β€” [`Achievement::value`] is the negative time of issue - /// - 6 = NUMBER_INV β€” [`Achievement::value`] is a negative number; negate it before displaying - #[serde(rename = "vt")] - pub value_type: u32, - /// The AR type of this achievement: - /// - /// - 0 = UNRANKED β€” no AR is given - /// - 1 = RANKED β€” AR is given for medal ranks - /// - 2 = COMPETITIVE β€” AR is given for medal ranks and leaderboard positions - #[serde(rename = "art")] - pub ar_type: u32, - /// The minimum score required to obtain the achievement. - pub min: i64, - /// The amount of decimal placed to show. - pub deci: u32, - /// Whether this achievement is usually not shown. - #[serde(rename = "hidden")] - pub is_hidden: bool, - /// The achieved score. - #[serde(rename = "v")] - pub value: Option, - /// Additional data (see [`Achievement::value_type`]). - #[serde(rename = "a")] - pub additional: Option, - /// The time the achievement was updated. - #[serde(rename = "t")] - pub time: Option, - /// The zero-indexed position in the achievement's leaderboards. - #[serde(rename = "pos")] - pub position: Option, - /// The total amount of players who have this achievement - /// (with a value of min or higher). - pub total: Option, - /// The rank of the achievement. - /// - /// - 0 = NONE, - /// - 1 = BRONZE, - /// - 2 = SILVER, - /// - 3 = GOLD, - /// - 4 = PLATINUM, - /// - 5 = DIAMOND, - /// - 100 = ISSUED - pub rank: Option, -} - -impl AsRef for Achievement { - fn as_ref(&self) -> &Self { - self - } -} diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index e75fd6c..a56ed48 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,6 +1,6 @@ //! Easy-to-use models of the various objects returned by the User Summaries API endpoint. -use crate::model::cache::CacheData; +use crate::model::{achievement::Achievement, cache::CacheData}; use serde::Deserialize; pub mod achievements; @@ -52,5 +52,5 @@ pub struct AllSummaries { /// The user's ZEN summary data. pub zen: zen::Zen, /// The user's achievements. - pub achievements: Vec, + pub achievements: Vec, } From 99344844f0254774fda2535ea0bbe6d2d354a033 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 11 Nov 2024 06:38:19 +0900 Subject: [PATCH 094/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20Server=20Statistics=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/server_stats.rs | 96 ++++++++++++--------------------------- 1 file changed, 30 insertions(+), 66 deletions(-) diff --git a/src/model/server_stats.rs b/src/model/server_stats.rs index dd70f3a..19ef464 100644 --- a/src/model/server_stats.rs +++ b/src/model/server_stats.rs @@ -19,72 +19,6 @@ pub struct ServerStatsResponse { pub data: Option, } -impl ServerStatsResponse { - /// Returns the amount of registered players. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn registered_players(&self) -> u64 { - self.get_server_stats().registered_players() - } - - /// Returns the average amount of pieces placed per second. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn avg_pieces_per_second(&self) -> f64 { - self.get_server_stats().avg_pieces_per_second() - } - - /// Returns the average amount of keys pressed per second. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn avg_keys_per_second(&self) -> f64 { - self.get_server_stats().avg_keys_per_second() - } - - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns the reference to the [`&ServerStats`](crate::model::server_stats::ServerStats). - /// - /// # Panics - /// - /// Panics if the request was not successful. - fn get_server_stats(&self) -> &ServerStats { - if let Some(d) = self.data.as_ref() { - d - } else { - panic!("There is no server stats object because the request was not successful.") - } - } -} - impl AsRef for ServerStatsResponse { fn as_ref(&self) -> &Self { self @@ -149,6 +83,36 @@ impl ServerStats { self.user_count - self.anon_count } + /// Returns the amount of minutes spent playing across all users. + /// including both off- and online modes. 1*60 + pub fn play_time_minutes(&self) -> f64 { + self.play_time / 60. + } + + /// Returns the amount of hours spent playing across all users. + /// including both off- and online modes. + pub fn play_time_hours(&self) -> f64 { + self.play_time / 3600. + } + + /// Returns the amount of days spent playing across all users. + /// including both off- and online modes. + pub fn play_time_days(&self) -> f64 { + self.play_time / 86400. + } + + /// Returns the amount of months spent playing across all users. + /// including both off- and online modes. + pub fn play_time_months(&self) -> f64 { + self.play_time / 2628000. + } + + /// Returns the amount of years spent playing across all users. + /// including both off- and online modes. + pub fn play_time_years(&self) -> f64 { + self.play_time / 31536000.0 + } + /// Returns the average amount of pieces placed per second. pub fn avg_pieces_per_second(&self) -> f64 { self.pieces_place_count as f64 / self.play_time From 25425e660f01c221272dbb645f59ecbe94c8eb49 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 17:22:20 +0900 Subject: [PATCH 095/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20Server=20Activity=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/server_activity.rs | 83 +++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/model/server_activity.rs b/src/model/server_activity.rs index f658e3a..756648a 100644 --- a/src/model/server_activity.rs +++ b/src/model/server_activity.rs @@ -21,45 +21,6 @@ pub struct ServerActivityResponse { pub data: Option, } -impl ServerActivityResponse { - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns the `&ServerActivity`. - /// - /// # Panics - /// - /// Panics if the request was not successful. - fn _get_server_activity(&self) -> &ServerActivity { - if let Some(d) = self.data.as_ref() { - d - } else { - panic!("There is no server activity object because the request was not successful.") - } - } -} - impl AsRef for ServerActivityResponse { fn as_ref(&self) -> &Self { self @@ -75,6 +36,50 @@ pub struct ServerActivity { pub activity: Vec, } +impl ServerActivity { + /// Get the peak point of the activity. + /// + /// If the activity is empty, `None` is returned. + pub fn peak(&self) -> Option { + self.activity.iter().max().copied() + } + + /// Get the index of the peak point of the activity. + /// + /// If several points are equally maximum, the first one is returned. + /// If the activity is empty, `None` is returned. + pub fn peak_index(&self) -> Option { + self.activity.iter().enumerate().max_by_key(|(_, &v)| v).map(|(i, _)| i) + } + + /// Get the trough point of the activity. + /// + /// If the activity is empty, `None` is returned. + pub fn trough(&self) -> Option { + self.activity.iter().min().copied() + } + + /// Get the index of the trough point of the activity. + /// + /// If several points are equally minimum, the first one is returned. + /// If the activity is empty, `None` is returned. + pub fn trough_index(&self) -> Option { + self.activity.iter().enumerate().min_by_key(|(_, &v)| v).map(|(i, _)| i) + } + + /// Get the average of the activity. + /// + /// If the activity is empty, `None` is returned. + pub fn average(&self) -> Option { + let len = self.activity.len() as f64; + if 0.0 < len { + Some(self.activity.iter().sum::() as f64 / len) + } else { + None + } + } +} + impl AsRef for ServerActivity { fn as_ref(&self) -> &Self { self From 2b4273466c5a3f3d59bd397c3f00beb8d73644f9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 19:52:34 +0900 Subject: [PATCH 096/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20models=20for=20user=20roles=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/role.rs | 45 +++++++++++++++++---------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/model/role.rs b/src/model/role.rs index 1c7ed09..565f159 100644 --- a/src/model/role.rs +++ b/src/model/role.rs @@ -1,6 +1,7 @@ //! A model for user roles. use serde::Deserialize; +use std::fmt; /// A user role. #[derive(Clone, Debug, Deserialize)] @@ -35,6 +36,11 @@ pub enum Role { } impl Role { + /// Whether the user is a normal user. + pub fn is_normal_user(&self) -> bool { + matches!(self, Role::User) + } + /// Whether the user is an anonymous. pub fn is_anon(&self) -> bool { matches!(self, Role::Anon) @@ -82,35 +88,18 @@ impl AsRef for Role { } } -impl ToString for Role { - /// Converts to a `String`. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::model::role::Role; - /// assert_eq!(Role::User.to_string(), "User"); - /// assert_eq!(Role::Anon.to_string(), "Anonymous"); - /// assert_eq!(Role::Bot.to_string(), "Bot"); - /// assert_eq!(Role::Sysop.to_string(), "SYSOP"); - /// assert_eq!(Role::Admin.to_string(), "Administrator"); - /// assert_eq!(Role::Mod.to_string(), "Moderator"); - /// assert_eq!(Role::Halfmod.to_string(), "Community moderator"); - /// assert_eq!(Role::Banned.to_string(), "Banned user"); - /// assert_eq!(Role::Hidden.to_string(), "Hidden user"); - /// ``` - fn to_string(&self) -> String { +impl fmt::Display for Role { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Role::User => "User", - Role::Anon => "Anonymous", - Role::Bot => "Bot", - Role::Sysop => "SYSOP", - Role::Admin => "Administrator", - Role::Mod => "Moderator", - Role::Halfmod => "Community moderator", - Role::Banned => "Banned user", - Role::Hidden => "Hidden user", + Role::User => write!(f, "User"), + Role::Anon => write!(f, "Anonymous"), + Role::Bot => write!(f, "Bot"), + Role::Sysop => write!(f, "SYSOP"), + Role::Admin => write!(f, "Administrator"), + Role::Mod => write!(f, "Moderator"), + Role::Halfmod => write!(f, "Community moderator"), + Role::Banned => write!(f, "Banned user"), + Role::Hidden => write!(f, "Hidden user"), } - .to_string() } } From 7fa9ed77329dbefc76918a92d6d1c8774241cc0e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 19:54:07 +0900 Subject: [PATCH 097/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20User=20Info=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 405 ++++++++++------------------------------------ 1 file changed, 81 insertions(+), 324 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index df75ae9..ee53753 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -6,7 +6,7 @@ use crate::{ util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; use serde::Deserialize; -use std::fmt::{self, Display, Formatter}; +use std::fmt; /// The response for User Info data. /// An object describing the user in detail. @@ -24,245 +24,6 @@ pub struct UserResponse { pub data: Option, } -impl UserResponse { - /// Returns the UNIX timestamp when the user's account created, if one exists. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn account_created_at(&self) -> Option { - self.get_user().created_at.as_ref().map(|ts| to_unix_ts(ts)) - } - - /// Returns the level based on the user's xp. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn level(&self) -> u32 { - let xp = self.get_user().xp; - // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 - ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() - as u32 - } - - /// Returns the user's avatar URL. - /// If the user has no avatar, returns anon's. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn face(&self) -> String { - let default = "https://tetr.io/res/avatar.png".to_string(); - if let Some(ar) = self.get_user().avatar_revision { - if ar == 0 { - return default; - } - format!( - "https://tetr.io/user-content/avatars/{}.jpg?rv={}", - self.get_user().id, - ar - ) - } else { - default - } - } - - /// Returns the user's banner URL. - /// If the user has no banner, returns `None`. - /// - /// ***Even if the user is not currently a supporter, - /// the URL may be returned if the banner was once set.** - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn banner(&self) -> Option { - if let Some(br) = self.get_user().banner_revision { - if br == 0 { - return None; - } - Some(format!( - "https://tetr.io/user-content/banners/{}.jpg?rv={}", - self.get_user().id, - br - )) - } else { - None - } - } - - /// Whether the user has at least one badge. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn has_badges(&self) -> bool { - !self.get_user().badges.is_empty() - } - - /// Whether the user is an anonymous. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_anon(&self) -> bool { - self.get_user().role.is_anon() - } - - /// Whether the user is a bot. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_bot(&self) -> bool { - self.get_user().role.is_bot() - } - - /// Whether the user is a SYSOP. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_sysop(&self) -> bool { - self.get_user().role.is_sysop() - } - - /// Whether the user is an administrator. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_admin(&self) -> bool { - self.get_user().role.is_admin() - } - - /// Whether the user is a moderator, - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_mod(&self) -> bool { - self.get_user().role.is_mod() - } - - /// Whether the user is a community moderator. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_halfmod(&self) -> bool { - self.get_user().role.is_halfmod() - } - - /// Whether the user is banned. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_banned(&self) -> bool { - self.get_user().role.is_banned() - } - - /// Whether the user is hidden. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_hidden(&self) -> bool { - self.get_user().role.is_hidden() - } - - /// Whether the user is bad standing. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_badstanding(&self) -> bool { - self.get_user().is_badstanding.unwrap_or(false) - } - - /// Whether the user is a supporter. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn is_supporter(&self) -> bool { - self.get_user().is_supporter.unwrap_or(false) - } - - /// Returns the user's profile URL. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.get_user().username) - } - - /// Returns an `Option`. - /// - /// If user is displaying the country, - /// returns `Some(String)` with an image URL of the national flag based on the user's ISO 3166-1 country code. - /// If the user is not displaying the country, returns `None`. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn national_flag_url(&self) -> Option { - self.get_user() - .country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } - - /// Returns the badges count. - /// - /// # Panics - /// - /// Panics if the request was not successful. - pub fn badges_count(&self) -> usize { - self.get_user().badges.len() - } - - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns the [`&User`](crate::model::user::User). - /// - /// # Panics - /// - /// Panics if the request was not successful. - fn get_user(&self) -> &User { - if let Some(d) = self.data.as_ref() { - d - } else { - panic!("There is no user object because the request was not successful.") - } - } -} - impl AsRef for UserResponse { fn as_ref(&self) -> &Self { self @@ -306,10 +67,10 @@ pub struct User { /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. /// Some vanity flags exist. pub country: Option, - /// Whether this user currently has a bad standing (recently banned). + /// Whether the user currently has a bad standing (recently banned). #[serde(rename = "badstanding")] pub is_badstanding: Option, - /// Whether this user is currently supporting TETR.IO <3 + /// Whether the user is currently supporting TETR.IO <3 #[serde(rename = "supporter")] pub is_supporter: Option, // EXCEPTION /// An indicator of their total amount supported, @@ -330,9 +91,7 @@ pub struct User { /// This user's third party connections. pub connections: Connections, /// The amount of players who have added this user to their friends list. - /// - /// ***This field is optional but the API documentation does not mention it.** - pub friend_count: Option, + pub friend_count: Option, // EXCEPTION // This user's distinguishment banner. pub distinguishment: Option, /// This user's featured achievements. @@ -347,12 +106,7 @@ pub struct User { } impl User { - /// Returns UNIX timestamp when the user's account created, if one exists. - pub fn account_created_at(&self) -> Option { - self.created_at.as_ref().map(|ts| to_unix_ts(ts)) - } - - /// Returns the level based on the user's xp. + /// Returns the level of the user. pub fn level(&self) -> u32 { let xp = self.xp; // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 @@ -360,119 +114,120 @@ impl User { as u32 } - /// Returns the user's avatar URL. - /// If the user has no avatar, returns anon's. - pub fn face(&self) -> String { - let default = "https://tetr.io/res/avatar.png".to_string(); - if let Some(ar) = self.avatar_revision { - if ar == 0 { - return default; - } - format!( - "https://tetr.io/user-content/avatars/{}.jpg?rv={}", - self.id, ar - ) - } else { - default - } - } - - /// Returns the user's banner URL. - /// If the user has no banner, returns `None`. - /// - /// ***Even if the user is not currently a supporter, - /// the URL may be returned if the banner was once set.** - pub fn banner(&self) -> Option { - if let Some(br) = self.banner_revision { - if br == 0 { - return None; - } - Some(format!( - "https://tetr.io/user-content/banners/{}.jpg?rv={}", - self.id, br - )) - } else { - None - } + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) } - /// Whether the user has at least one badge. - pub fn has_badge(&self) -> bool { - !self.badges.is_empty() + /// Whether the user is a normal user. + pub fn is_normal_user(&self) -> bool { + self.role.is_normal_user() } - /// Whether this user is an anonymous. + /// Whether the user is an anonymous. pub fn is_anon(&self) -> bool { self.role.is_anon() } - /// Whether this user is a bot. + /// Whether the user is a bot. pub fn is_bot(&self) -> bool { self.role.is_bot() } - /// Whether this user is a SYSOP. + /// Whether the user is a SYSOP. pub fn is_sysop(&self) -> bool { self.role.is_sysop() } - /// Whether this user is an administrator. + /// Whether the user is an administrator. pub fn is_admin(&self) -> bool { self.role.is_admin() } - /// Whether this user is a moderator. + /// Whether the user is a moderator. pub fn is_mod(&self) -> bool { self.role.is_mod() } - /// Whether this user is a community moderator. + /// Whether the user is a community moderator. pub fn is_halfmod(&self) -> bool { self.role.is_halfmod() } - /// Whether this user is banned. + /// Whether the user is banned. pub fn is_banned(&self) -> bool { self.role.is_banned() } - /// Whether this user is hidden. + /// Whether the user is hidden. pub fn is_hidden(&self) -> bool { self.role.is_hidden() } - /// Whether this user is bad standing. - pub fn is_badstanding(&self) -> bool { - self.is_badstanding.unwrap_or(false) + /// Returns an UNIX timestamp when the user's account created. + /// + /// If the account was created before join dates were recorded, `None` is returned. + pub fn created_at(&self) -> Option { + self.created_at.as_ref().map(|ts| to_unix_ts(ts)) } - /// Whether this user is a supporter. - pub fn is_supporter(&self) -> bool { - self.is_supporter.unwrap_or(false) + /// Whether the user has any badges. + pub fn has_badge(&self) -> bool { + !self.badges.is_empty() } - /// Returns the user's profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) + /// Returns the number of badges the user has. + pub fn badge_count(&self) -> usize { + self.badges.len() } - /// Returns an i + /// Returns the user's avatar URL. + /// + /// If the user does not have an avatar, the anonymous's avatar URL is returned. + pub fn avatar_url(&self) -> String { + let default = "https://tetr.io/res/avatar.png".to_string(); + if let Some(ar) = self.avatar_revision { + if ar == 0 { + return default; + } + format!( + "https://tetr.io/user-content/avatars/{}.jpg?rv={}", + self.id, ar + ) + } else { + default + } + } - /// Returns an `Option`. + /// Returns the user's banner URL. /// - /// If user is displaying the country, - /// returns `Some(String)` with an image URL of the national flag based on the user's ISO 3166-1 country code. - /// If the user is not displaying the country, returns `None`. + /// If the user does not have a banner, `None` is returned. + /// + /// ***Ignore the returned value if the user is not a supporter. + /// Because even if the user is not currently a supporter, + /// `Some` may be returned if the banner was once set.** + pub fn banner_url(&self) -> Option { + if let Some(br) = self.banner_revision { + if br == 0 { + return None; + } + Some(format!( + "https://tetr.io/user-content/banners/{}.jpg?rv={}", + self.id, br + )) + } else { + None + } + } + + /// Returns the national flag URL of the user's country. + /// + /// If the user's country is hidden or unknown, `None` is returned. pub fn national_flag_url(&self) -> Option { self.country .as_ref() .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) } - - /// Returns the badges count. - pub fn badges_count(&self) -> usize { - self.badges.len() - } } impl AsRef for User { @@ -498,9 +253,7 @@ pub struct Badge { /// The badge's label, shown when hovered. pub label: String, /// Extra flavor text for the badge, shown when hovered. - /// - /// ***This field is optional but the API documentation does not mention it.** - pub desc: Option, + pub desc: Option, // EXCEPTION /// The badge's timestamp, if shown. /// /// Why it uses `deserialize_with` attribute? @@ -514,12 +267,12 @@ pub struct Badge { } impl Badge { - /// Returns the formatted badge icon URL. + /// Returns the badge icon URL. pub fn badge_icon_url(&self) -> String { format!("https://tetr.io/res/badges/{}.png", self.id) } - /// Returns a UNIX timestamp when this badge was achieved. + /// Returns a UNIX timestamp when the badge was achieved. pub fn received_at(&self) -> Option { self.received_at.as_ref().map(|ts| to_unix_ts(ts)) } @@ -679,6 +432,10 @@ pub struct UserId(pub String); impl UserId { /// Returns the user's internal ID. + #[deprecated( + since = "0.6.0", + note = "please use the `.to_string()` method instead" + )] pub fn id(&self) -> &str { &self.0 } @@ -694,7 +451,7 @@ impl UserId { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user(&self) -> RspErr { - Client::new().get_user(self.id()).await + Client::new().get_user(&self.to_string()).await } } @@ -704,8 +461,8 @@ impl AsRef for UserId { } } -impl Display for UserId { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.id()) +impl fmt::Display for UserId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) } } From e85ee02a8b0fd82900043b437d75c24ee2b669a3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 19:54:58 +0900 Subject: [PATCH 098/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/server_activity.rs | 12 ++++++++++-- src/model/user.rs | 7 ++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/model/server_activity.rs b/src/model/server_activity.rs index 756648a..ddd9aa3 100644 --- a/src/model/server_activity.rs +++ b/src/model/server_activity.rs @@ -49,7 +49,11 @@ impl ServerActivity { /// If several points are equally maximum, the first one is returned. /// If the activity is empty, `None` is returned. pub fn peak_index(&self) -> Option { - self.activity.iter().enumerate().max_by_key(|(_, &v)| v).map(|(i, _)| i) + self.activity + .iter() + .enumerate() + .max_by_key(|(_, &v)| v) + .map(|(i, _)| i) } /// Get the trough point of the activity. @@ -64,7 +68,11 @@ impl ServerActivity { /// If several points are equally minimum, the first one is returned. /// If the activity is empty, `None` is returned. pub fn trough_index(&self) -> Option { - self.activity.iter().enumerate().min_by_key(|(_, &v)| v).map(|(i, _)| i) + self.activity + .iter() + .enumerate() + .min_by_key(|(_, &v)| v) + .map(|(i, _)| i) } /// Get the average of the activity. diff --git a/src/model/user.rs b/src/model/user.rs index ee53753..91fc8b5 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -253,7 +253,7 @@ pub struct Badge { /// The badge's label, shown when hovered. pub label: String, /// Extra flavor text for the badge, shown when hovered. - pub desc: Option, // EXCEPTION + pub desc: Option, // EXCEPTION /// The badge's timestamp, if shown. /// /// Why it uses `deserialize_with` attribute? @@ -432,10 +432,7 @@ pub struct UserId(pub String); impl UserId { /// Returns the user's internal ID. - #[deprecated( - since = "0.6.0", - note = "please use the `.to_string()` method instead" - )] + #[deprecated(since = "0.6.0", note = "please use the `.to_string()` method instead")] pub fn id(&self) -> &str { &self.0 } From e090f8bdfb5ad8f519fa934de4215f0e81ebdef4 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 19:56:02 +0900 Subject: [PATCH 099/255] =?UTF-8?q?=F0=9F=8E=A8=20Improve:=20use=20`to=5Fs?= =?UTF-8?q?tring`=20method=20instead=20of=20deprecated=20`UserId::id`=20me?= =?UTF-8?q?thod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/searched_user.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index dd9537b..0b2d24f 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -39,7 +39,7 @@ impl SearchedUserResponse { /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user(&self) -> Option> { if let Some(u) = &self.data { - Some(Client::new().get_user(u.user.id.id()).await) + Some(Client::new().get_user(&u.user.id.to_string()).await) } else { None } @@ -105,7 +105,7 @@ impl UserData { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user(&self) -> RspErr { - Client::new().get_user(self.user.id.id()).await + Client::new().get_user(&self.user.id.to_string()).await } /// Returns the user's profile URL. @@ -144,7 +144,7 @@ impl UserInfo { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user(&self) -> RspErr { - Client::new().get_user(self.id.id()).await + Client::new().get_user(&self.id.to_string()).await } /// Returns the user's profile URL. From fd20f3b719af043a1c107e3890cba97790d054f9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 20:43:58 +0900 Subject: [PATCH 100/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20Record=20Data=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 122 ++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 4 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index da055c9..44833c2 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -1,8 +1,11 @@ //! The Record Data models. use crate::{ - client::param::pagination::Prisecter, - model::{league_rank::Rank, user::UserId}, + client::{error::RspErr, param::pagination::Prisecter}, + model::{ + league_rank::Rank, + user::{UserId, UserResponse}, + }, util::to_unix_ts, }; use serde::Deserialize; @@ -68,12 +71,12 @@ pub struct Record { } impl Record { - /// Returns the URL to the replay. + /// Returns the replay URL. pub fn replay_url(&self) -> String { format!("https://tetr.io/#R:{}", self.replay_id) } - /// Returns a UNIX timestamp when this record was submitted. + /// Returns a UNIX timestamp when the record was submitted. pub fn submitted_at(&self) -> i64 { to_unix_ts(&self.submitted_at) } @@ -104,6 +107,75 @@ pub struct User { pub is_supporter: bool, } +impl User { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + self.id.get_user().await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } + + /// Returns the user's avatar URL. + /// + /// If the user does not have an avatar, the anonymous's avatar URL is returned. + pub fn avatar_url(&self) -> String { + let default = "https://tetr.io/res/avatar.png".to_string(); + if let Some(ar) = self.avatar_revision { + if ar == 0 { + return default; + } + format!( + "https://tetr.io/user-content/avatars/{}.jpg?rv={}", + self.id, ar + ) + } else { + default + } + } + + /// Returns the user's banner URL. + /// + /// If the user does not have a banner, `None` is returned. + /// + /// ***Ignore the returned value if the user is not a supporter. + /// Because even if the user is not currently a supporter, + /// `Some` may be returned if the banner was once set.** + pub fn banner_url(&self) -> Option { + if let Some(br) = self.banner_revision { + if br == 0 { + return None; + } + Some(format!( + "https://tetr.io/user-content/banners/{}.jpg?rv={}", + self.id, br + )) + } else { + None + } + } + + /// Returns the national flag URL of the user's country. + /// + /// If the user's country is hidden or unknown, `None` is returned. + pub fn national_flag_url(&self) -> Option { + self.country + .as_ref() + .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) + } +} + impl AsRef for User { fn as_ref(&self) -> &Self { self @@ -207,6 +279,27 @@ pub struct PlayerStats { pub stats: serde_json::Value, } +impl PlayerStats { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + self.id.get_user().await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } +} + impl AsRef for PlayerStats { fn as_ref(&self) -> &Self { self @@ -234,6 +327,27 @@ pub struct PlayerStatsRound { pub stats: serde_json::Value, } +impl PlayerStatsRound { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + self.id.get_user().await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } +} + impl AsRef for PlayerStatsRound { fn as_ref(&self) -> &Self { self From f81105ef7f7903a40b52c3a2fc976a65c49fb7e0 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 20:48:37 +0900 Subject: [PATCH 101/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20Searched=20User=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/searched_user.rs | 61 ++++---------------------------------- 1 file changed, 5 insertions(+), 56 deletions(-) diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index 0b2d24f..a7b1406 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -25,56 +25,6 @@ pub struct SearchedUserResponse { pub data: Option, } -impl SearchedUserResponse { - /// Gets the user's data. - /// Returns `None` if the user was not found. - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - pub async fn get_user(&self) -> Option> { - if let Some(u) = &self.data { - Some(Client::new().get_user(&u.user.id.to_string()).await) - } else { - None - } - } - - /// Returns the user's profile URL or `None` if the user was not found. - pub fn profile_url(&self) -> Option { - self.data.as_ref().map(|u| u.profile_url()) - } - - /// Returns a UNIX timestamp when this resource was cached. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_at(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } - - /// Returns a UNIX timestamp when this resource's cache expires. - /// - /// # Panics - /// - /// Panics if there is no cache data. - pub fn cached_until(&self) -> i64 { - match self.cache.as_ref() { - Some(c) => c.cached_at(), - None => panic!("There is no cache data."), - } - } -} - impl AsRef for SearchedUserResponse { fn as_ref(&self) -> &Self { self @@ -94,7 +44,7 @@ pub struct UserData { } impl UserData { - /// Gets the user's data. + /// Gets the User Info data. /// /// # Errors /// @@ -105,7 +55,7 @@ impl UserData { /// /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.user.id.to_string()).await + self.user.get_user().await } /// Returns the user's profile URL. @@ -128,8 +78,7 @@ pub struct UserInfo { #[serde(rename = "_id")] pub id: UserId, /// The user's username. - #[serde(rename = "username")] - pub name: String, + pub username: String, } impl UserInfo { @@ -147,9 +96,9 @@ impl UserInfo { Client::new().get_user(&self.id.to_string()).await } - /// Returns the user's profile URL. + /// Returns the user's TETRA CHANNEL profile URL. pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.name) + format!("https://ch.tetr.io/u/{}", self.username) } } From 23bbe9a05ec92cb803a78768a252dec54f00e7c2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 21:16:05 +0900 Subject: [PATCH 102/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20Latest=20News=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 208 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 3 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index 8627219..76b815e 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -1,6 +1,9 @@ -//! The All Latest News models. +//! The Latest News models. -use crate::model::{cache::CacheData, league_rank::Rank}; +use crate::{ + client::{error::RspErr, Client}, + model::{cache::CacheData, league_rank::Rank, user::{UserId, UserResponse}}, util::to_unix_ts +}; use serde::Deserialize; /// The response for the All Latest News data. @@ -46,7 +49,7 @@ impl AsRef for NewsItems { pub struct News { /// The item's internal ID. #[serde(rename = "_id")] - pub id: String, + pub id: UserId, /// The item's stream. pub stream: String, /// The item's type. @@ -58,6 +61,27 @@ pub struct News { pub created_at: String, } +impl News { + /// Gets the user's data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + self.id.get_user().await + } + + /// Returns an UNIX timestamp when the news item was created. + pub fn created_at(&self) -> i64 { + to_unix_ts(&self.created_at) + } +} + impl AsRef for News { fn as_ref(&self) -> &Self { self @@ -92,6 +116,43 @@ pub enum NewsData { Unknown(serde_json::Value), } +impl NewsData { + /// Whether the news data is a leaderboard news. + pub fn is_leaderboard_news(&self) -> bool { + matches!(self, Self::LeaderboardNews(_)) + } + + /// Whether the news data is a personal best news. + pub fn is_personal_best_news(&self) -> bool { + matches!(self, Self::PersonalBestNews(_)) + } + + /// Whether the news data is a badge news. + pub fn is_badge_news(&self) -> bool { + matches!(self, Self::BadgeNews(_)) + } + + /// Whether the news data is a rank up news. + pub fn is_rank_up_news(&self) -> bool { + matches!(self, Self::RankUpNews(_)) + } + + /// Whether the news data is a supporter news. + pub fn is_supporter_news(&self) -> bool { + matches!(self, Self::SupporterNews(_)) + } + + /// Whether the news data is a supporter gift news. + pub fn is_supporter_gift_news(&self) -> bool { + matches!(self, Self::SupporterGiftNews(_)) + } + + /// Whether the news data is an unknown news type. + pub fn is_unknown(&self) -> bool { + matches!(self, Self::Unknown(_)) + } +} + impl AsRef for NewsData { fn as_ref(&self) -> &Self { self @@ -115,6 +176,32 @@ pub struct LeaderboardNews { pub replay_id: String, } +impl LeaderboardNews { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + Client::new().get_user(&self.username).await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } + + /// Returns the replay URL. + pub fn replay_url(&self) -> String { + format!("https://tetr.io/#R:{}", self.replay_id) + } +} + impl AsRef for LeaderboardNews { fn as_ref(&self) -> &Self { self @@ -136,6 +223,32 @@ pub struct PersonalBestNews { pub replay_id: String, } +impl PersonalBestNews { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + Client::new().get_user(&self.username).await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } + + /// Returns the replay URL. + pub fn replay_url(&self) -> String { + format!("https://tetr.io/#R:{}", self.replay_id) + } +} + impl AsRef for PersonalBestNews { fn as_ref(&self) -> &Self { self @@ -155,6 +268,32 @@ pub struct BadgeNews { pub label: String, } +impl BadgeNews { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + Client::new().get_user(&self.username).await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } + + /// Returns the badge icon URL. + pub fn badge_icon_url(&self) -> String { + format!("https://tetr.io/res/badges/{}.png", self.r#type) + } +} + impl AsRef for BadgeNews { fn as_ref(&self) -> &Self { self @@ -171,6 +310,27 @@ pub struct RankUpNews { pub rank: Rank, } +impl RankUpNews { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + Client::new().get_user(&self.username).await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } +} + impl AsRef for RankUpNews { fn as_ref(&self) -> &Self { self @@ -185,6 +345,27 @@ pub struct SupporterNews { pub username: String, } +impl SupporterNews { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + Client::new().get_user(&self.username).await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } +} + impl AsRef for SupporterNews { fn as_ref(&self) -> &Self { self @@ -199,6 +380,27 @@ pub struct SupporterGiftNews { pub username: String, } +impl SupporterGiftNews { + /// Gets the User Info data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + Client::new().get_user(&self.username).await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } +} + /// The response for the Latest News data. /// /// The latest news items in the stream. From 72a3e44fcb550d6e9560055152fc85f6cabebee2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 22:08:08 +0900 Subject: [PATCH 103/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20models=20for=20Ranks=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/league_rank.rs | 78 ++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/model/league_rank.rs b/src/model/league_rank.rs index af0e918..742ac1c 100644 --- a/src/model/league_rank.rs +++ b/src/model/league_rank.rs @@ -72,27 +72,27 @@ impl Rank { /// /// ``` /// # use tetr_ch::model::league_rank::Rank; - /// assert_eq!(Rank::D.as_str(), "D"); - /// assert_eq!(Rank::DPlus.as_str(), "D+"); - /// assert_eq!(Rank::CMinus.as_str(), "C-"); - /// assert_eq!(Rank::C.as_str(), "C"); - /// assert_eq!(Rank::CPlus.as_str(), "C+"); - /// assert_eq!(Rank::BMinus.as_str(), "B-"); - /// assert_eq!(Rank::B.as_str(), "B"); - /// assert_eq!(Rank::BPlus.as_str(), "B+"); - /// assert_eq!(Rank::AMinus.as_str(), "A-"); - /// assert_eq!(Rank::A.as_str(), "A"); - /// assert_eq!(Rank::APlus.as_str(), "A+"); - /// assert_eq!(Rank::SMinus.as_str(), "S-"); - /// assert_eq!(Rank::S.as_str(), "S"); - /// assert_eq!(Rank::SPlus.as_str(), "S+"); - /// assert_eq!(Rank::SS.as_str(), "SS"); - /// assert_eq!(Rank::U.as_str(), "U"); - /// assert_eq!(Rank::X.as_str(), "X"); - /// assert_eq!(Rank::XPlus.as_str(), "X+"); - /// assert_eq!(Rank::Z.as_str(), "Unranked"); + /// assert_eq!(Rank::D.name(), "D"); + /// assert_eq!(Rank::DPlus.name(), "D+"); + /// assert_eq!(Rank::CMinus.name(), "C-"); + /// assert_eq!(Rank::C.name(), "C"); + /// assert_eq!(Rank::CPlus.name(), "C+"); + /// assert_eq!(Rank::BMinus.name(), "B-"); + /// assert_eq!(Rank::B.name(), "B"); + /// assert_eq!(Rank::BPlus.name(), "B+"); + /// assert_eq!(Rank::AMinus.name(), "A-"); + /// assert_eq!(Rank::A.name(), "A"); + /// assert_eq!(Rank::APlus.name(), "A+"); + /// assert_eq!(Rank::SMinus.name(), "S-"); + /// assert_eq!(Rank::S.name(), "S"); + /// assert_eq!(Rank::SPlus.name(), "S+"); + /// assert_eq!(Rank::SS.name(), "SS"); + /// assert_eq!(Rank::U.name(), "U"); + /// assert_eq!(Rank::X.name(), "X"); + /// assert_eq!(Rank::XPlus.name(), "X+"); + /// assert_eq!(Rank::Z.name(), "Unranked"); /// ``` - pub fn as_str(&self) -> &str { + pub fn name(&self) -> &str { match self { Rank::D => "D", Rank::DPlus => "D+", @@ -349,25 +349,25 @@ mod tests { let rank_x = Rank::X; let rank_x_plus = Rank::XPlus; let rank_z = Rank::Z; - assert_eq!(rank_d.as_str(), "D"); - assert_eq!(rank_d_plus.as_str(), "D+"); - assert_eq!(rank_c_minus.as_str(), "C-"); - assert_eq!(rank_c.as_str(), "C"); - assert_eq!(rank_c_plus.as_str(), "C+"); - assert_eq!(rank_b_minus.as_str(), "B-"); - assert_eq!(rank_b.as_str(), "B"); - assert_eq!(rank_b_plus.as_str(), "B+"); - assert_eq!(rank_a_minus.as_str(), "A-"); - assert_eq!(rank_a.as_str(), "A"); - assert_eq!(rank_a_plus.as_str(), "A+"); - assert_eq!(rank_s_minus.as_str(), "S-"); - assert_eq!(rank_s.as_str(), "S"); - assert_eq!(rank_s_plus.as_str(), "S+"); - assert_eq!(rank_ss.as_str(), "SS"); - assert_eq!(rank_u.as_str(), "U"); - assert_eq!(rank_x.as_str(), "X"); - assert_eq!(rank_x_plus.as_str(), "X+"); - assert_eq!(rank_z.as_str(), "Unranked"); + assert_eq!(rank_d.name(), "D"); + assert_eq!(rank_d_plus.name(), "D+"); + assert_eq!(rank_c_minus.name(), "C-"); + assert_eq!(rank_c.name(), "C"); + assert_eq!(rank_c_plus.name(), "C+"); + assert_eq!(rank_b_minus.name(), "B-"); + assert_eq!(rank_b.name(), "B"); + assert_eq!(rank_b_plus.name(), "B+"); + assert_eq!(rank_a_minus.name(), "A-"); + assert_eq!(rank_a.name(), "A"); + assert_eq!(rank_a_plus.name(), "A+"); + assert_eq!(rank_s_minus.name(), "S-"); + assert_eq!(rank_s.name(), "S"); + assert_eq!(rank_s_plus.name(), "S+"); + assert_eq!(rank_ss.name(), "SS"); + assert_eq!(rank_u.name(), "U"); + assert_eq!(rank_x.name(), "X"); + assert_eq!(rank_x_plus.name(), "X+"); + assert_eq!(rank_z.name(), "Unranked"); } #[test] From c29867c6885715640e2137d8d750c6ae3367f4b2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 22:22:12 +0900 Subject: [PATCH 104/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20User=20Leaderboard=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 97 +++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 5154fc6..e4f13db 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -1,12 +1,12 @@ //! The User Leaderboard models. use crate::{ - client::param::pagination::Prisecter, + client::{error::RspErr, param::pagination::Prisecter}, model::{ cache::CacheData, league_rank::Rank, role::Role, - user::{AchievementRatingCounts, UserId}, + user::{AchievementRatingCounts, UserId, UserResponse}, }, util::{max_f64, to_unix_ts}, }; @@ -101,72 +101,94 @@ pub struct LeaderboardEntry { } impl LeaderboardEntry { - /// Whether this user is an anonymous. + /// Gets the user's data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + self.id.get_user().await + } + + /// Returns the level of the user. + pub fn level(&self) -> u32 { + let xp = self.xp; + // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 + ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() + as u32 + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } + + /// Whether the user is a normal user. + pub fn is_normal_user(&self) -> bool { + self.role.is_normal_user() + } + + /// Whether the user is an anonymous. pub fn is_anon(&self) -> bool { self.role.is_anon() } - /// Whether this user is a bot. + /// Whether the user is a bot. pub fn is_bot(&self) -> bool { self.role.is_bot() } - /// Whether this user is a SYSOP. + /// Whether the user is a SYSOP. pub fn is_sysop(&self) -> bool { self.role.is_sysop() } - /// Whether this user is an administrator. + /// Whether the user is an administrator. pub fn is_admin(&self) -> bool { self.role.is_admin() } - /// Whether this user is a moderator. + /// Whether the user is a moderator. pub fn is_mod(&self) -> bool { self.role.is_mod() } - /// Whether this user is a community moderator. + /// Whether the user is a community moderator. pub fn is_halfmod(&self) -> bool { self.role.is_halfmod() } - /// Whether this user is banned. + /// Whether the user is banned. pub fn is_banned(&self) -> bool { self.role.is_banned() } - /// Whether this user is hidden. + /// Whether the user is hidden. pub fn is_hidden(&self) -> bool { self.role.is_hidden() } - /// Returns an UNIX timestamp of when the account was created. + /// Returns a UNIX timestamp when the user account was created. + /// /// If this account was created before join dates were recorded, - /// returns `None`. + /// `None` is returned. pub fn account_created_at(&self) -> Option { self.account_created_at.as_ref().map(|ts| to_unix_ts(ts)) } - /// Returns the level based on the user's xp. - pub fn level(&self) -> u32 { - let xp = self.xp; - // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 - ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() - as u32 - } - /// Returns the national flag URL of the user's country. + /// + /// If the user's country is hidden or unknown, `None` is returned. pub fn national_flag_url(&self) -> Option { self.country .as_ref() .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) } - - /// Whether this user is a supporter. - pub fn is_supporter(&self) -> bool { - self.is_supporter.unwrap_or(false) - } } impl AsRef for LeaderboardEntry { @@ -303,6 +325,31 @@ pub struct HistoricalEntry { pub prisecter: Prisecter, } +impl HistoricalEntry { + /// Gets the user's data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + self.id.get_user().await + } + + /// Returns the national flag URL of the user's country. + /// + /// If the user's country is hidden or unknown, `None` is returned. + pub fn national_flag_url(&self) -> Option { + self.country + .as_ref() + .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) + } +} + impl AsRef for HistoricalEntry { fn as_ref(&self) -> &Self { self From a8798b70a738ad31903a684e43cd0a5c98606fe8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 22:26:50 +0900 Subject: [PATCH 105/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20cache-related=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/cache.rs b/src/model/cache.rs index e3b6968..97e2d96 100644 --- a/src/model/cache.rs +++ b/src/model/cache.rs @@ -1,4 +1,4 @@ -//! The cache-related data. +//! The cache-related models. use serde::Deserialize; From ba52bed8b6d811386a40e6b786eddcedfb5ce75d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 22:40:06 +0900 Subject: [PATCH 106/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20Achievement=20Info=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 77 ++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 4268c25..a97ee42 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -1,6 +1,6 @@ //! The Achievement Info models. -use crate::model::{achievement::Achievement, cache::CacheData, role::Role, user::UserId}; +use crate::{client::error::RspErr, model::{achievement::Achievement, cache::CacheData, role::Role, user::{UserId, UserResponse}}}; use serde::Deserialize; /// The response for the Achievement Info data. @@ -86,6 +86,81 @@ pub struct User { pub country: Option, } +impl User { + /// Gets the user's data. + /// + /// # Errors + /// + /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, + /// or when this library is defective. + /// + /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. + /// + /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + pub async fn get_user(&self) -> RspErr { + self.id.get_user().await + } + + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } + + /// Whether the user is a normal user. + pub fn is_normal_user(&self) -> bool { + self.role.is_normal_user() + } + + /// Whether the user is an anonymous. + pub fn is_anon(&self) -> bool { + self.role.is_anon() + } + + /// Whether the user is a bot. + pub fn is_bot(&self) -> bool { + self.role.is_bot() + } + + /// Whether the user is a SYSOP. + pub fn is_sysop(&self) -> bool { + self.role.is_sysop() + } + + /// Whether the user is an administrator. + pub fn is_admin(&self) -> bool { + self.role.is_admin() + } + + /// Whether the user is a moderator. + pub fn is_mod(&self) -> bool { + self.role.is_mod() + } + + /// Whether the user is a community moderator. + pub fn is_halfmod(&self) -> bool { + self.role.is_halfmod() + } + + /// Whether the user is banned. + pub fn is_banned(&self) -> bool { + self.role.is_banned() + } + + /// Whether the user is hidden. + pub fn is_hidden(&self) -> bool { + self.role.is_hidden() + } + + /// Returns the national flag URL of the user's country. + /// + /// If the user's country is not public, `None` is returned. + pub fn national_flag_url(&self) -> Option { + self.country + .as_ref() + .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) + } +} + impl AsRef for User { fn as_ref(&self) -> &Self { self From b2ed04d66dbc6afa608237f9de2e950a257a504e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 22:40:48 +0900 Subject: [PATCH 107/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 10 +++++++++- src/model/news.rs | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index a97ee42..133456c 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -1,6 +1,14 @@ //! The Achievement Info models. -use crate::{client::error::RspErr, model::{achievement::Achievement, cache::CacheData, role::Role, user::{UserId, UserResponse}}}; +use crate::{ + client::error::RspErr, + model::{ + achievement::Achievement, + cache::CacheData, + role::Role, + user::{UserId, UserResponse}, + }, +}; use serde::Deserialize; /// The response for the Achievement Info data. diff --git a/src/model/news.rs b/src/model/news.rs index 76b815e..8939e35 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -2,7 +2,12 @@ use crate::{ client::{error::RspErr, Client}, - model::{cache::CacheData, league_rank::Rank, user::{UserId, UserResponse}}, util::to_unix_ts + model::{ + cache::CacheData, + league_rank::Rank, + user::{UserId, UserResponse}, + }, + util::to_unix_ts, }; use serde::Deserialize; From 5f69186b400a87e03d987810fb6bf3c60c521755 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 15 Nov 2024 23:32:51 +0900 Subject: [PATCH 108/255] =?UTF-8?q?=E2=9C=A8=20Add:=20useful=20methods=20f?= =?UTF-8?q?or=20User=20Summary=20TETRA=20LEAGUE=20models=20[#67]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/league.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 46bed26..855109b 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -86,6 +86,29 @@ pub struct League { pub past: HashMap, } +impl League { + /// Returns the user's progress percentage in the rank. + /// + /// But there are cases where values less than 0 or greater than 100 are returned, + /// because the rank boundaries are fluctuating. + /// (e.g. `-88.5` `104.9`, `-0.0`) + /// + /// If there is no user's position in global leaderboards, + /// `None` is returned. + pub fn rank_progress(&self) -> Option { + if let (Some(standing), Some(prev_at), Some(next_at)) = (self.standing, self.prev_at, self.next_at) { + if prev_at < 0 || next_at < 0 { + return None; + } + let current_standing = standing as f64; + let prev_at = prev_at as f64; + let next_at = next_at as f64; + return Some((current_standing - prev_at) / (next_at - prev_at) * 100.); + } + return None; + } +} + impl AsRef for League { fn as_ref(&self) -> &Self { self @@ -134,6 +157,15 @@ pub struct PastSeason { pub vs: f64, } +impl PastSeason { + /// Returns the national flag URL of the user's country. + pub fn national_flag_url(&self) -> Option { + self.country + .as_ref() + .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) + } +} + impl AsRef for PastSeason { fn as_ref(&self) -> &Self { self From 814e437b58dbdf989a506b014ab03065f7c483fa Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 16 Nov 2024 00:15:20 +0900 Subject: [PATCH 109/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`util.?= =?UTF-8?q?rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.rs b/src/util.rs index e77dedd..bd78ee4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,10 +1,10 @@ -//! Utilities for the tetr-ch-rs. +//! Utilities for tetr-ch-rs. use chrono::DateTime; use serde::Deserialize; use serde_json::Value; -/// Parses a RFC 3339 and ISO 8601 date to UNIX timestamp as `i64`. +/// Parses an RFC 3339 and ISO 8601 date and time string into a UNIX timestamp. pub(crate) fn to_unix_ts(ts: &str) -> i64 { match DateTime::parse_from_rfc3339(ts) { Ok(dt) => dt.timestamp(), From 5c93bfe6503261c353f7731f432ed268896025e2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 16 Nov 2024 00:26:04 +0900 Subject: [PATCH 110/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`const?= =?UTF-8?q?ants.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index f257113..a1d2b81 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,10 +2,10 @@ #[deprecated( since = "0.2.0", - note = "use the implemented constants of tetr_ch::model::league::Rank" + note = "use the implemented constants in `tetr_ch::model::league_rank::Rank`" )] pub mod rank_col { - //! The colors for each rank + //! The colors of the ranks in TETRA LEAGUE. /// The D rank color. /// #907591 @@ -64,7 +64,7 @@ pub mod rank_col { /// The XX rank color. /// #ff8fff pub const XX: u32 = 0xff8fff; - /// The unranked(Z rank) color. + /// The unranked (Z rank) color. /// #767671 pub const Z: u32 = 0x767671; } From 5d0dddb9d27993bc1bbda6ce376f757eb2da4ec2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 22:38:12 +0900 Subject: [PATCH 111/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`clien?= =?UTF-8?q?t.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 607 +++++++++++++++++++++----------------------------- 1 file changed, 256 insertions(+), 351 deletions(-) diff --git a/src/client.rs b/src/client.rs index 803a628..c8b4102 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,4 @@ -//! Client for API requests. +//! A module for the [`Client`] struct and supporting types. use self::{ error::RspErr, @@ -39,25 +39,25 @@ use reqwest::{self}; const API_URL: &str = "https://ch.tetr.io/api/"; -/// Client for API requests. +/// A client for API requests. /// /// # Examples /// -/// Creating a Client instance and getting some objects: +/// Creating a new [`Client`] instance and getting information about the user "RINRIN-RS". /// /// ```no_run /// use tetr_ch::client::Client; -/// # use std::io; /// -/// # async fn run() -> io::Result<()> { +/// # async fn run() -> std::io::Result<()> { +/// // Create a new client. /// let client = Client::new(); -/// // For example, get information for user `RINRIN-RS`. +/// // Get the user information. /// let user = client.get_user("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` /// -/// [See more examples](https://github.com/Rinrin0413/tetr-ch-rs/examples/) +/// [See more examples](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples) #[non_exhaustive] #[derive(Default)] pub struct Client { @@ -65,16 +65,30 @@ pub struct Client { } impl Client { - /// Create a new [`Client`]. + //! # Errors + //! + //! The `get_*` methods and `search_*` methods return a `Result`. + //! + //! - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + //! if the request failed. + //! - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + //! if the response did not match the expected format but the HTTP request succeeded. + //! There may be defectives in this wrapper or the TETRA CHANNEL API document. + //! - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + //! if the HTTP request failed and the response did not match the expected format. + //! Even if the HTTP request failed, + //! it may be possible to deserialize the response containing an error message, + //! so the deserialization will be tried before returning this error. + + /// Creates a new [`Client`]. /// /// # Examples /// - /// Creating a Client instance: - /// /// ``` - /// use tetr_ch::client; + /// use tetr_ch::client::Client; /// - /// let client = client::Client::new(); + /// // Create a new client. + /// let client = Client::new(); /// ``` pub fn new() -> Self { Self { @@ -82,264 +96,212 @@ impl Client { } } - /// Returns the object describing the user in detail. + /// Gets the detailed information about the specified user. + /// + /// About the endpoint "User Info", + /// see the [API document](https://tetr.io/about/api/#usersuser). + /// + /// # Arguments + /// + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Info. + /// // Get the information about the user "RINRIN-RS". /// let user = client.get_user("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user(self, user: &str) -> RspErr { let url = format!("{}users/{}", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns some statistics about the TETR.IO. + /// Gets some statistics about the TETR.IO. + /// + /// About the endpoint "Server Statistics", + /// see the [API document](https://tetr.io/about/api/#generalstats). /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the Server Statistics. + /// // Get the statistics. /// let user = client.get_server_stats().await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_server_stats(self) -> RspErr { let url = format!("{}general/stats", API_URL); let res = self.client.get(url).send().await; response(res).await } - /// Returns an array of user activity over the last 2 days. + /// Gets the array of the user activity over the last 2 days. + /// + /// About the endpoint "Server Activity", + /// see the [API document](https://tetr.io/about/api/#generalactivity). /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the Server Activity. + /// // Get the activity. /// let user = client.get_server_activity().await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_server_activity(self) -> RspErr { let url = format!("{}general/activity", API_URL); let res = self.client.get(url).send().await; response(res).await } - /// Returns the object containing all the user's summaries in one. + /// Gets all the summaries of the specified user. /// - /// ***consider whether you really need this. + /// ***Consider whether you really need to use this method. /// If you only collect data for one or two game modes, - /// use the individual summaries' methods instead.** + /// use the methods for the individual summaries instead.** + /// + /// About the endpoint "User Summaries", + /// see the [API document](https://tetr.io/about/api/#usersusersummaries). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get All the User Summaries. + /// // Get all the summaries of the user "RINRIN-RS". /// let user = client.get_user_all_summaries("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_all_summaries(self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns the object describing a summary of the user's 40 LINES games. + /// Gets the summary of the specified user's 40 LINES games. + /// + /// About the endpoint "User Summary: 40 LINES", + /// see the [API document](https://tetr.io/about/api/#usersusersummaries40l). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Summary 40 LINES. + /// // Get the summary of the 40 LINES games of the user "RINRIN-RS". /// let user = client.get_user_40l("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_40l(self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/40l", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns the object describing a summary of the user's BLITZ games. + /// Gets the summary of the specified user's BLITZ games. + /// + /// About the endpoint "User Summary: BLITZ", + /// see the [API document](https://tetr.io/about/api/#usersusersummariesblitz). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Summary BLITZ. + /// // Get the summary of the BLITZ games of the user "RINRIN-RS". /// let user = client.get_user_blitz("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_blitz(self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/blitz", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns the object describing a summary of the user's QUICK PLAY games. + /// Gets the summary of the specified user's QUICK PLAY games. + /// + /// About the endpoint "User Summary: QUICK PLAY", + /// see the [API document](https://tetr.io/about/api/#usersusersummarieszenith). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Summary QUICK PLAY. + /// // Get the summary of the QUICK PLAY games of the user "RINRIN-RS". /// let user = client.get_user_zenith("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_zenith(self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/zenith", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns the object describing a summary of the user's EXPERT QUICK PLAY games. + /// Gets the summary of the specified user's EXPERT QUICK PLAY games. + /// + /// About the endpoint "User Summary: EXPERT QUICK PLAY", + /// see the [API document](https://tetr.io/about/api/#usersusersummarieszenithex). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Summary EXPERT QUICK PLAY. + /// // Get the summary of the EXPERT QUICK PLAY games of the user "RINRIN-RS". /// let user = client.get_user_zenith_ex("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_zenith_ex(self, user: &str) -> RspErr { let url = format!( "{}users/{}/summaries/zenithex", @@ -350,102 +312,81 @@ impl Client { response(res).await } - /// Returns the object describing a summary of the user's TETRA LEAGUE standing. + /// Gets the summary of the specified user's TETRA LEAGUE standing. + /// + /// About the endpoint "User Summary: TETRA LEAGUE", + /// see the [API document](https://tetr.io/about/api/#usersusersummariesleague). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Summary TETRA LEAGUE. + /// // Get the summary of the TETRA LEAGUE standing of the user "RINRIN-RS". /// let user = client.get_user_league("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_league(self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/league", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns the object describing a summary of the user's ZEN progress. + /// Gets the summary of the specified user's ZEN progress. + /// + /// About the endpoint "User Summary: ZEN", + /// see the [API document](https://tetr.io/about/api/#usersusersummarieszen). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Summary ZEN. + /// // Get the summary of the ZEN progress of the user "RINRIN-RS". /// let user = client.get_user_zen("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_zen(self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/zen", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns the object containing all the user's achievements. + /// Gets all the achievements of the specified user. + /// + /// About the endpoint "User Summary: Achievements", + /// see the [API document](https://tetr.io/about/api/#usersusersummariesachievements). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the User Summary Achievements. + /// // Get all the achievements of the user "RINRIN-RS". /// let user = client.get_user_achievements("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_achievements(self, user: &str) -> RspErr { let url = format!( "{}users/{}/summaries/achievements", @@ -456,23 +397,31 @@ impl Client { response(res).await } - /// Returns the array of users fulfilling the search criteria. + /// Gets the user leaderboard fulfilling the search criteria. + /// + /// About the endpoint "User Leaderboard", + /// see the [API document](https://tetr.io/about/api/#usersbyleaderboard). /// /// # Arguments /// - /// - `leaderboard`: The leaderboard to sort users by. - /// - `search_criteria`: The search criteria to filter users by. + /// - `leaderboard` - The user leaderboard type. + /// - `search_criteria` - The search criteria to filter users by. /// /// # Examples /// + /// Gets the TETRA LEAGUE leaderboard with the following criteria: + /// + /// - Upper bound is `[15200, 0, 0]` + /// - Three entries + /// - Filter by Japan + /// /// ```no_run /// use tetr_ch::client::{ /// Client, /// param::user_leaderboard::{self, LeaderboardType} /// }; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// /// let criteria = user_leaderboard::SearchCriteria::new() @@ -483,7 +432,7 @@ impl Client { /// // Filter by Japan /// .country("jp"); /// - /// // Get the User Leaderboard. + /// // Get the user leaderboard. /// let user = client.get_leaderboard( /// LeaderboardType::League, /// Some(criteria) @@ -491,15 +440,6 @@ impl Client { /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_leaderboard( self, leaderboard: LeaderboardType, @@ -521,23 +461,32 @@ impl Client { response(res).await } - /// Returns the array of historical user blobs fulfilling the search criteria. + /// Gets the array of the historical user blobs fulfilling the search criteria. + /// + /// About the endpoint "Historical User Leaderboard", + /// see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). /// /// # Arguments /// - /// - `season`: The season to look up. (e.g. `"1"`) - /// - `search_criteria`: The search criteria to filter users by. + /// - `season` - The season to look up. (e.g. `"1"`) + /// - `search_criteria` - The search criteria to filter users by. /// /// # Examples /// + /// Gets the array of the historical user blobs with the following criteria: + /// + /// - Season 1 + /// - Upper bound is `[15200, 0, 0]` + /// - Three entries + /// - Filter by Japan + /// /// ```no_run /// use tetr_ch::client::{ /// Client, /// param::user_leaderboard::{self, LeaderboardType} /// }; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// /// let criteria = user_leaderboard::SearchCriteria::new() @@ -548,23 +497,15 @@ impl Client { /// // Filter by Japan /// .country("jp"); /// - /// // Get the User Leaderboard. + /// // Get the array. /// let user = client.get_historical_league_leaderboard( + /// // Season 1 /// "1", /// Some(criteria) /// ).await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_historical_league_leaderboard( self, season: &str, @@ -591,25 +532,33 @@ impl Client { response(res).await } - /// Returns the list of Records fulfilling the search criteria. + /// Gets the personal record leaderboard of the specified user, + /// fulfilling the search criteria. + /// + /// About the endpoint "User Personal Records", + /// see the [API document](https://tetr.io/about/api/#usersuserrecordsgamemodeleaderboard). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. - /// - `gamemode`: The game mode to look up. - /// - `leaderboard`: The personal leaderboard to look up. - /// - `search_criteria`: The search criteria to filter records by. + /// - `user` - The username or user ID to look up. + /// - `gamemode` - The game mode to look up. + /// - `leaderboard` - The personal leaderboard to look up. + /// - `search_criteria` - The search criteria to filter records by. /// /// # Examples /// + /// Gets the personal top score leaderboard of the 40 LINES records of the user "RINRIN-RS" with the following criteria: + /// + /// - Upper bound is `[500000, 0, 0]` + /// - Three entries + /// /// ```no_run /// use tetr_ch::client::{ /// Client, /// param::record::{self, Gamemode, LeaderboardType} /// }; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// /// // Set the search criteria. @@ -619,25 +568,19 @@ impl Client { /// // Three entries /// .limit(3); /// - /// // Get the User Records. + /// // Get the leaderboard. /// let user = client.get_user_records( + /// // Record of the user "RINRIN-RS" /// "rinrin-rs", + /// // 40 LINES /// Gamemode::FortyLines, + /// // Top score leaderboard /// LeaderboardType::Top, /// Some(criteria) /// ).await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_user_records( self, user: &str, @@ -667,23 +610,33 @@ impl Client { response(res).await } - /// Returns the list of Records fulfilling the search criteria. + /// Gets the record leaderboard fulfilling the search criteria. + /// + /// About the endpoint "Records Leaderboard", + /// see the [API document](https://tetr.io/about/api/#recordsleaderboard). /// /// # Arguments /// - /// - `leaderboard`: The leaderboard to look up. - /// - `search_criteria`: The search criteria to filter records by. + /// - `leaderboard` - The record leaderboard ID to look up. + /// - `search_criteria` - The search criteria to filter records by. /// /// # Examples /// + /// Gets the record leaderboard with the following criteria: + /// + /// - Upper bound is `[500000, 0, 0]` + /// - Three entries + /// - Game mode: `blitz` (BLITZ) + /// - Scope: `JP` (Japan) + /// - Revolution ID: `@2024w31` + /// /// ```no_run /// use tetr_ch::client::{ /// Client, /// param::record_leaderboard::{self, RecordsLeaderboardId, Scope} /// }; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// /// // Set the search criteria. @@ -693,11 +646,15 @@ impl Client { /// // Three entries /// .limit(3); /// - /// // Get the Records Leaderboard. + /// // Get the record leaderboard. /// let user = client.get_records_leaderboard( + /// // Record leaderboard ID: `blitz_country_JP@2024w31` /// RecordsLeaderboardId::new( + /// // Game mode: `blitz` (BLITZ) /// "blitz", + /// // Scope: `JP` (Japan) /// Scope::Country("JP".to_string()), + /// // Revolution ID: `@2024w31` /// Some("@2024w31") /// ), /// Some(criteria) @@ -705,15 +662,6 @@ impl Client { /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_records_leaderboard( self, leaderboard: RecordsLeaderboardId, @@ -735,44 +683,46 @@ impl Client { response(res).await } - /// Searches for a record. + /// Searches for a record of the specified user with the specified timestamp. /// /// Only one record is returned. /// It is generally not possible for a player to play the same gamemode twice in a millisecond. /// + /// About the endpoint "Record Search", + /// see the [API document](https://tetr.io/about/api/#recordsreverse). + /// /// # Arguments /// - /// - `user_id`: The user ID to look up. - /// - `gamemode`: The game mode to look up. - /// - `timestamp`: The timestamp of the record to find. + /// - `user_id` - The user ID to look up. + /// - `gamemode` - The game mode to look up. + /// - `timestamp` - The timestamp of the record to find. /// /// # Examples /// + /// Gets a record with the following criteria: + /// + /// - User ID: `621db46d1d638ea850be2aa0` + /// - Gamemode: `blitz` (BLITZ) + /// - Timestamp: `1680053762145` (`2023-03-29T01:36:02.145Z`) + /// /// ```no_run /// use tetr_ch::client::{param::record::Gamemode, Client}; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Get the User Record. + /// // Get a record. /// let user = client.search_record( + /// // User ID: `621db46d1d638ea850be2aa0` /// "621db46d1d638ea850be2aa0", + /// // Gamemode: `blitz` (BLITZ) /// Gamemode::Blitz, + /// // Timestamp: `1680053762145` (`2023-03-29T01:36:02.145Z`) /// 1680053762145 /// ).await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn search_record( self, user_id: &str, @@ -789,36 +739,29 @@ impl Client { response(res).await } - /// Returns the latest news items in any stream. + /// Gets the latest news items in any stream. + /// + /// About the endpoint "All Latest News", + /// see the [API document](https://tetr.io/about/api/#newsall). /// /// # Arguments /// - /// - `limit`:The amount of entries to return, + /// - `limit` - The amount of entries to return, /// between 1 and 100. 25 by default. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Get the All Latest News. + /// // Get three latest news. /// let user = client.get_news_all(Some(3)).await?; /// # Ok(()) /// # } /// ``` - /// - /// # Error - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_news_all(self, limit: Option) -> RspErr { let mut query_param = Vec::new(); if let Some(l) = limit { @@ -837,57 +780,51 @@ impl Client { response(res).await } - /// Returns latest news items in the stream. + /// Gets the latest news items in the specified stream. /// - /// # Arguments + /// About the endpoint "Latest News", + /// see the [API document](https://tetr.io/about/api/#newsstream). /// - /// - `stream`: The news stream to look up. + /// # Arguments /// - /// - `limit`: The amount of entries to return, between 1 and 100. + /// - `stream` - The news stream to look up. + /// - `limit` - The amount of entries to return, between 1 and 100. /// /// # Examples /// + /// Gets three latest news of the user `621db46d1d638ea850be2aa0`. + /// /// ```no_run /// use tetr_ch::client::{Client, param::news_stream::NewsStream}; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// /// // Get the latest news. /// let user = client.get_news_latest( - /// // News of the user `621db46d1d638ea850be2aa0`. + /// // News of the user `621db46d1d638ea850be2aa0` /// NewsStream::User("621db46d1d638ea850be2aa0".to_string()), - /// // three news. + /// // Three news /// 3, /// ).await?; /// # Ok(()) /// # } /// ``` /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. - /// /// # Panics /// /// Panics if the query parameter `limit` is not between 1 and 100. /// /// ```should_panic,no_run /// use tetr_ch::client::{Client, param::news_stream::NewsStream}; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// /// let user = client.get_news_latest( + /// // Global news /// NewsStream::Global, - /// // 101 news. (not allowed) + /// // 101 news (not allowed) /// 101, /// ).await?; /// # Ok(()) @@ -913,26 +850,28 @@ impl Client { response(res).await } - /// Searches for a TETR.IO user account by the social account. + /// Searches for a TETR.IO user account by the social connection. /// - /// # Arguments + /// About the endpoint "User Search", + /// see the [API document](https://tetr.io/about/api/#userssearchquery). /// - /// - `social_connection`: + /// # Arguments /// - /// The social connection to look up. - /// This argument requires a [`search_user::SocialConnection`]. + /// - `social_connection` - The social connection to look up. /// /// # Examples /// + /// Searches for a account by Discord ID `724976600873041940`. + /// /// ```no_run /// use tetr_ch::client::{Client, param::search_user::SocialConnection}; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Search for a TETR.IO user account. + /// // Search for a account. /// let user = client.search_user( + /// // By Discord ID `724976600873041940` /// SocialConnection::Discord("724976600873041940".to_string()) /// ).await?; /// # Ok(()) @@ -940,15 +879,6 @@ impl Client { /// /// # tokio_test::block_on(run()); /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn search_user( self, social_connection: SocialConnection, @@ -958,39 +888,36 @@ impl Client { response(res).await } - /// Returns the condensed graph of all of the user's records in the gamemode. + /// Gets the condensed graph of all of the specified user's records in the specified gamemode. + /// + /// About the endpoint "Labs Scoreflow", + /// see the [API document](https://tetr.io/about/api/#labsscoreflowusergamemode). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. - /// - `gamemode`: The game mode to look up. + /// - `user` - The username or user ID to look up. + /// - `gamemode` - The game mode to look up. /// /// # Examples /// + /// Gets the graph of the 40 LINES records of the user `RINRIN-RS`. + /// /// ```no_run /// use tetr_ch::client::{param::record::Gamemode, Client}; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Get the Labs Scoreflow. + /// // Get the graph of the records. /// let user = client.get_labs_scoreflow( + /// // Records of the user "RINRIN-RS" /// "rinrin-rs", + /// // 40 LINES records /// Gamemode::FortyLines /// ).await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_labs_scoreflow( self, user: &str, @@ -1006,102 +933,80 @@ impl Client { response(res).await } - /// Returns the condensed graph of all of the user's matches in TETRA LEAGUE. + /// Gets the condensed graph of all of the specified user's matches in TETRA LEAGUE. + /// + /// About the endpoint "Labs Leagueflow, + /// see the [API document](https://tetr.io/about/api/#labsleagueflowuser). /// /// # Arguments /// - /// - `user`: The username or user ID to look up. + /// - `user` - The username or user ID to look up. /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Get the Labs Leagueflow. + /// // Get the graph of the matches of the user `RINRIN-RS` in TETRA LEAGUE. /// let user = client.get_labs_leagueflow("rinrin-rs").await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_labs_leagueflow(self, user: &str) -> RspErr { let url = format!("{}labs/leagueflow/{}", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await } - /// Returns the view over all TETRA LEAGUE ranks and their metadata. + /// Gets the view over all TETRA LEAGUE ranks and their metadata. + /// + /// About the endpoint "Labs League Ranks", + /// see the [API document](https://tetr.io/about/api/#labsleagueranks). /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Get the Labs League Ranks. + /// // Get the view over all TETRA LEAGUE ranks and their metadata. /// let user = client.get_labs_league_ranks().await?; /// # Ok(()) /// # } /// ``` - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_labs_league_ranks(self) -> RspErr { let url = format!("{}labs/league_ranks", API_URL); let res = self.client.get(url).send().await; response(res).await } - /// Returns the data about the achievement itself, its cutoffs, and its leaderboard. + /// Gets the data about the specified achievement itself, its cutoffs, and its leaderboard. + /// + /// About the endpoint "Achievement Info", + /// see the [API document](https://tetr.io/about/api/#achievementsk). /// /// # Arguments /// - /// - `achievement_id`: The achievement ID to look up. + /// - `achievement_id` - The achievement ID to look up. (e.g. `"15"`) /// /// # Examples /// /// ```no_run /// use tetr_ch::client::Client; - /// # use std::io; /// - /// # async fn run() -> io::Result<()> { + /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Get the Achievement Info. + /// // Get the data about the achievement "15". /// let user = client.get_achievement_info("15").await?; /// # Ok(()) /// # } /// ``` - /// - /// - /// # Errors - /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. pub async fn get_achievement_info( self, achievement_id: &str, From ff7c30b39f202000a6a6b053e834b932a033423f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 22:39:34 +0900 Subject: [PATCH 112/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`error?= =?UTF-8?q?.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/error.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/client/error.rs b/src/client/error.rs index 664b1a7..edf3e40 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -1,4 +1,4 @@ -//! An error enum for the response handling. +//! A module for the error related types for the [`client`](crate::client) module. use http::status::{InvalidStatusCode, StatusCode}; use std::error::Error; @@ -7,12 +7,17 @@ use std::fmt; /// A enum for the response handling errors. #[derive(Debug)] pub enum ResponseError { - /// When there are some mismatches in the API docs, - /// or when this library is defective. - DeserializeErr(String), - /// When the request is invalid. + /// The request failed. RequestErr(String), - /// When the HTTP request fails. + /// The response did not match the expected format but the HTTP request succeeded. + /// + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + DeserializeErr(String), + /// The HTTP request failed and the response did not match the expected format. + /// + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. HttpErr(Status), } @@ -43,8 +48,10 @@ impl From for std::io::Error { /// A HTTP status code. #[derive(Debug)] pub enum Status { + /// A valid HTTP status code. /// If the status code greater or equal to 100 but less than 600. Valid(StatusCode), + /// An invalid HTTP status code. /// If the status code less than 100 or greater than 599. Invalid(InvalidStatusCode), } From a53e61f3b15d8b67f7931d4b0ad3df987ca0f59f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 23:06:20 +0900 Subject: [PATCH 113/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`src/m?= =?UTF-8?q?odel/mod.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/mod.rs b/src/model/mod.rs index 7cd0fda..427b597 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,4 +1,4 @@ -//! Easy-to-use models of the various objects returned by the API. +//! Easy-to-use models of the various objects received from the API. pub mod achievement; pub mod achievement_info; From a1c12e94109f0d6a83237c28b0a326dc3c4b8a50 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 23:20:35 +0900 Subject: [PATCH 114/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`user.?= =?UTF-8?q?rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index 91fc8b5..80cea42 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,4 +1,7 @@ -//! The User Info models. +//! Models for the endpoint "User Info", and its related types. +//! +//! About the endpoint "User Info", +//! see the [API document](https://tetr.io/about/api/#usersuser). use crate::{ client::{error::RspErr, Client}, @@ -8,8 +11,7 @@ use crate::{ use serde::Deserialize; use std::fmt; -/// The response for User Info data. -/// An object describing the user in detail. +/// A struct for the response for the endpoint "User Info". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct UserResponse { @@ -30,7 +32,7 @@ impl AsRef for UserResponse { } } -/// The User Info data. +/// A struct that describes a user in detail. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct User { @@ -236,7 +238,7 @@ impl AsRef for User { } } -/// The user's badges. +/// A user's badge. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Badge { @@ -284,7 +286,7 @@ impl AsRef for Badge { } } -/// This user's third party connections. +/// A user's third party connections. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Connections { @@ -333,7 +335,7 @@ impl AsRef for Connections { } } -/// This user's connection. +/// A user's connection. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Connection { @@ -351,7 +353,7 @@ impl AsRef for Connection { } } -/// This user's distinguishment banner. +/// A user's distinguishment banner. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Distinguishment { @@ -378,7 +380,7 @@ impl AsRef for Distinguishment { } } -/// The breakdown of the source of this user's Achievement Rating. +/// A breakdown of the source of a user's Achievement Rating. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct AchievementRatingCounts { @@ -426,7 +428,7 @@ impl AsRef for AchievementRatingCounts { } } -/// The user's internal ID. +/// A user's internal ID. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] pub struct UserId(pub String); @@ -437,16 +439,20 @@ impl UserId { &self.0 } - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.to_string()).await } From 5169c9af8af860eb2d22d546781544750aeace50 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 23:38:17 +0900 Subject: [PATCH 115/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`user?= =?UTF-8?q?=5Frecords.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user_records.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/model/user_records.rs b/src/model/user_records.rs index 85a7639..470af43 100644 --- a/src/model/user_records.rs +++ b/src/model/user_records.rs @@ -1,11 +1,12 @@ -//! The User Personal Records models. +//! Models for the endpoint "User Personal Records". +//! +//! About the endpoint "User Personal Records", +//! see the [API document](https://tetr.io/about/api/#usersuserrecordsgamemodeleaderboard). use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; -/// The response for the User Personal Records data. -/// -/// A list of Records fulfilling the search criteria. +/// A struct for the response for the endpoint "User Personal Records". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct UserRecordsResponse { @@ -26,7 +27,7 @@ impl AsRef for UserRecordsResponse { } } -/// The User Personal Records data. +/// An array of user personal records. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct UserRecords { From cc15009cd22fc1af243e28294ff4ee74683cbf35 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 23:41:25 +0900 Subject: [PATCH 116/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`serve?= =?UTF-8?q?r=5Fstats.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/server_stats.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/model/server_stats.rs b/src/model/server_stats.rs index 19ef464..d0d936c 100644 --- a/src/model/server_stats.rs +++ b/src/model/server_stats.rs @@ -1,10 +1,12 @@ -//! The Server Statistics model. +//! Models for the endpoint "Server Statistics". +//! +//! About the endpoint "Server Statistics", +//! see the [API document](https://tetr.io/about/api/#generalstats). use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the Server Statistics data. -/// Some statistics about the TETR.IO. +/// A struct for the response for the endpoint "Server Statistics". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ServerStatsResponse { @@ -25,7 +27,7 @@ impl AsRef for ServerStatsResponse { } } -/// The Server Statistics data. +/// Server Statistics about the TETR.IO. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ServerStats { From 9ca0f7c418b32a7f243d164745d8264d5a47eb3b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 23:45:11 +0900 Subject: [PATCH 117/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`serve?= =?UTF-8?q?r=5Factivity.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/server_activity.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/model/server_activity.rs b/src/model/server_activity.rs index ddd9aa3..f1c2679 100644 --- a/src/model/server_activity.rs +++ b/src/model/server_activity.rs @@ -1,12 +1,12 @@ -//! The Server Activity model. +//! Models for the endpoint "Server Activity". +//! +//! About the endpoint "Server Activity", +//! see the [API document](https://tetr.io/about/api/#generalactivity). use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the Server Activity data. -/// -/// An array of user activity over the last 2 days. -/// A user is seen as active if they logged in or received XP within the last 30 minutes. +/// A struct for the response for the endpoint "Server Activity". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ServerActivityResponse { @@ -27,24 +27,24 @@ impl AsRef for ServerActivityResponse { } } -/// The Server Activity data. +/// An array of user activity over the last 2 days. +/// A user is seen as active if they logged in or received XP within the last 30 minutes. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ServerActivity { - /// An array of plot points, - /// newest points first. + /// The array of plot points, newest points first. pub activity: Vec, } impl ServerActivity { - /// Get the peak point of the activity. + /// Returns the peak point of the activity. /// /// If the activity is empty, `None` is returned. pub fn peak(&self) -> Option { self.activity.iter().max().copied() } - /// Get the index of the peak point of the activity. + /// Returns the index of the peak point of the activity. /// /// If several points are equally maximum, the first one is returned. /// If the activity is empty, `None` is returned. @@ -56,14 +56,14 @@ impl ServerActivity { .map(|(i, _)| i) } - /// Get the trough point of the activity. + /// Returns the trough point of the activity. /// /// If the activity is empty, `None` is returned. pub fn trough(&self) -> Option { self.activity.iter().min().copied() } - /// Get the index of the trough point of the activity. + /// Returns the index of the trough point of the activity. /// /// If several points are equally minimum, the first one is returned. /// If the activity is empty, `None` is returned. @@ -75,7 +75,7 @@ impl ServerActivity { .map(|(i, _)| i) } - /// Get the average of the activity. + /// Returns the average of the activity. /// /// If the activity is empty, `None` is returned. pub fn average(&self) -> Option { From 343b09c6593efed4e55a026dc874880b467fd1c9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 23:52:05 +0900 Subject: [PATCH 118/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`searc?= =?UTF-8?q?hed=5Fuser.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/searched_user.rs | 48 +++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index a7b1406..2db314e 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -1,4 +1,7 @@ -//! The Searched User model. +//! Models for the endpoint "User Search". +//! +//! About the endpoint "User Search", +//! see the [API document](https://tetr.io/about/api/#userssearchquery). use crate::{ client::{error::RspErr, Client}, @@ -9,8 +12,7 @@ use crate::{ }; use serde::Deserialize; -/// The response for the Searched User data. -/// An object describing the user found. +/// A struct for the response for the endpoint "User Search". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct SearchedUserResponse { @@ -31,7 +33,7 @@ impl AsRef for SearchedUserResponse { } } -/// The Searched User data. +/// A searched user. /// /// Only one user is contained. /// Generally, you won't see two users with the same social linked, though, @@ -44,16 +46,20 @@ pub struct UserData { } impl UserData { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.user.get_user().await } @@ -70,7 +76,7 @@ impl AsRef for UserData { } } -/// The user information (TETRA.IO user account). +/// A user information (TETRA.IO user account). #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct UserInfo { @@ -82,16 +88,20 @@ pub struct UserInfo { } impl UserInfo { - /// Gets the user's data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.id.to_string()).await } From 586b26233a57a13cdccd1e29e68e2873046d0138 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 19 Nov 2024 23:54:47 +0900 Subject: [PATCH 119/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`recor?= =?UTF-8?q?ds=5Fleaderboard.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/records_leaderboard.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/model/records_leaderboard.rs b/src/model/records_leaderboard.rs index 5c0c887..4f54f18 100644 --- a/src/model/records_leaderboard.rs +++ b/src/model/records_leaderboard.rs @@ -1,11 +1,12 @@ -//! The Records Leaderboard models. +//! Models for the endpoint "Records Leaderboard". +//! +//! About the endpoint "Records Leaderboard", +//! see the [API document](https://tetr.io/about/api/#recordsleaderboard). use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; -/// The response for the Records Leaderboard data. -/// -/// A list of Records fulfilling the search criteria. +/// An struct for the response for the endpoint "Records Leaderboard". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct RecordsLeaderboardResponse { @@ -26,7 +27,7 @@ impl AsRef for RecordsLeaderboardResponse { } } -/// The Records Leaderboard data. +/// An array of records. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct RecordsLeaderboard { From 31a4621e2577fee27a4a3bb94726863444b1cba5 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:09:18 +0900 Subject: [PATCH 120/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`news.?= =?UTF-8?q?rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 160 +++++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 65 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index 8939e35..dc7cc1e 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -1,4 +1,9 @@ -//! The Latest News models. +//! Models for the TETRA NEWS endpoints. +//! +//! - About the endpoint "All Latest News", +//! see the [API document](https://tetr.io/about/api/#newsall). +//! - About the endpoint "Latest News", +//! see the [API document](https://tetr.io/about/api/#newsstream). use crate::{ client::{error::RspErr, Client}, @@ -11,9 +16,7 @@ use crate::{ }; use serde::Deserialize; -/// The response for the All Latest News data. -/// -/// The latest news items in any stream. +/// A struct for the response for the endpoint "All Latest News". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct NewsAllResponse { @@ -34,7 +37,7 @@ impl AsRef for NewsAllResponse { } } -/// The All Latest News data. +/// Latest news items. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct NewsItems { @@ -67,16 +70,20 @@ pub struct News { } impl News { - /// Gets the user's data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.id.get_user().await } @@ -93,11 +100,12 @@ impl AsRef for News { } } -/// The data of a news item. +/// A news data. /// /// News data may be stored in different enumerators depending on the type of news item. /// -/// ***New news types may be added at any moment.** +/// ***New news types may be added at any moment.** +/// For more details, see the [API document](https://tetr.io/about/api/#newsdata). #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] #[non_exhaustive] @@ -164,7 +172,7 @@ impl AsRef for NewsData { } } -/// The data of a leaderboard news item. +/// A data of a leaderboard news item. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LeaderboardNews { @@ -182,16 +190,20 @@ pub struct LeaderboardNews { } impl LeaderboardNews { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.username).await } @@ -213,7 +225,7 @@ impl AsRef for LeaderboardNews { } } -/// The data of a personal best news item. +/// A data of a personal best news item. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct PersonalBestNews { @@ -229,16 +241,20 @@ pub struct PersonalBestNews { } impl PersonalBestNews { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.username).await } @@ -260,7 +276,7 @@ impl AsRef for PersonalBestNews { } } -/// The data of a badge news item. +/// A data of a badge news item. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct BadgeNews { @@ -274,16 +290,20 @@ pub struct BadgeNews { } impl BadgeNews { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.username).await } @@ -305,7 +325,7 @@ impl AsRef for BadgeNews { } } -/// The data of a rank up news item. +/// A data of a rank up news item. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct RankUpNews { @@ -316,16 +336,20 @@ pub struct RankUpNews { } impl RankUpNews { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.username).await } @@ -342,7 +366,7 @@ impl AsRef for RankUpNews { } } -/// The data of a supporter news item. +/// A data of a supporter news item. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct SupporterNews { @@ -351,16 +375,20 @@ pub struct SupporterNews { } impl SupporterNews { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.username).await } @@ -377,7 +405,7 @@ impl AsRef for SupporterNews { } } -/// The data of a supporter gift news item. +/// A data of a supporter gift news item. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct SupporterGiftNews { @@ -386,16 +414,20 @@ pub struct SupporterGiftNews { } impl SupporterGiftNews { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.username).await } @@ -406,9 +438,7 @@ impl SupporterGiftNews { } } -/// The response for the Latest News data. -/// -/// The latest news items in the stream. +/// A struct for the response for the endpoint "Latest News". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct NewsLatestResponse { From 4ed46ac26be8cdb327eb955a5e4b64a4e64a7263 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:14:42 +0900 Subject: [PATCH 121/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`leagu?= =?UTF-8?q?e=5Frank.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/league_rank.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/model/league_rank.rs b/src/model/league_rank.rs index 742ac1c..de46b9b 100644 --- a/src/model/league_rank.rs +++ b/src/model/league_rank.rs @@ -1,63 +1,63 @@ -//! Ranks in TETRA LEAGUE. +//! A model for the ranks in TETRA LEAGUE. use serde::Deserialize; use std::fmt::{self, Display, Formatter}; -/// A rank in TETRA LEAGUE. +/// A enum for the ranks in TETRA LEAGUE. #[derive(Clone, Debug, Deserialize)] pub enum Rank { - /// The D rank. + /// D rank. #[serde(rename = "d")] D, - /// The D+ rank. + /// D+ rank. #[serde(rename = "d+")] DPlus, - /// The C- rank. + /// C- rank. #[serde(rename = "c-")] CMinus, - /// The C rank. + /// C rank. #[serde(rename = "c")] C, - /// The C+ rank. + /// C+ rank. #[serde(rename = "c+")] CPlus, - /// The B- rank. + /// B- rank. #[serde(rename = "b-")] BMinus, - /// The B rank. + /// B rank. #[serde(rename = "b")] B, - /// The B+ rank. + /// B+ rank. #[serde(rename = "b+")] BPlus, - /// The A- rank. + /// A- rank. #[serde(rename = "a-")] AMinus, - /// The A rank. + /// A rank. #[serde(rename = "a")] A, - /// The A+ rank. + /// A+ rank. #[serde(rename = "a+")] APlus, - /// The S- rank. + /// S- rank. #[serde(rename = "s-")] SMinus, - /// The S rank. + /// S rank. #[serde(rename = "s")] S, - /// The S+ rank. + /// S+ rank. #[serde(rename = "s+")] SPlus, - /// The SS rank. + /// SS rank. #[serde(rename = "ss")] SS, - /// The U rank. + /// U rank. #[serde(rename = "u")] U, - /// The X rank. + /// X rank. #[serde(rename = "x")] X, - /// The X+ rank. + /// X+ rank. #[serde(rename = "x+")] XPlus, /// Unranked. From cd92b923e8cc519ee7147c1c95ebc7bf860be918 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:20:31 +0900 Subject: [PATCH 122/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`role.?= =?UTF-8?q?rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/role.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/role.rs b/src/model/role.rs index 565f159..ea903d4 100644 --- a/src/model/role.rs +++ b/src/model/role.rs @@ -1,4 +1,4 @@ -//! A model for user roles. +//! A model for the user roles. use serde::Deserialize; use std::fmt; From 1fecd143250cfb84cb8de75acf05ffb53f354f0a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:32:39 +0900 Subject: [PATCH 123/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`leade?= =?UTF-8?q?rboard.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 61 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index e4f13db..071dfdb 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -1,4 +1,9 @@ -//! The User Leaderboard models. +//! Models for the endpoints "User Leaderboard", "Historical User Leaderboard". +//! +//! - About the endpoint "User Leaderboard", +//! see the [API document](https://tetr.io/about/api/#usersbyleaderboard). +//! - About the endpoint "Historical User Leaderboard", +//! see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). use crate::{ client::{error::RspErr, param::pagination::Prisecter}, @@ -12,9 +17,7 @@ use crate::{ }; use serde::Deserialize; -/// The response for the User Leaderboard data. -/// -/// An array of users fulfilling the search criteria. +/// A struct for the response for the endpoint "User Leaderboard". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LeaderboardResponse { @@ -35,7 +38,7 @@ impl AsRef for LeaderboardResponse { } } -/// The User Leaderboard data. +/// An array of users. (user leaderboard) #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Leaderboard { @@ -49,7 +52,7 @@ impl AsRef for Leaderboard { } } -/// An entry in the User Leaderboard. +/// An entry as a user. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LeaderboardEntry { @@ -101,16 +104,20 @@ pub struct LeaderboardEntry { } impl LeaderboardEntry { - /// Gets the user's data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.id.get_user().await } @@ -197,7 +204,7 @@ impl AsRef for LeaderboardEntry { } } -/// The user's current TETRA LEAGUE standing. +/// A user's current TETRA LEAGUE standing. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct League { @@ -237,9 +244,7 @@ impl AsRef for League { } } -/// The response for the Historical User Leaderboard data. -/// -/// An array of historical user blobs fulfilling the search criteria. +/// A struct for the response for the endpoint "Historical User Leaderboard". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct HistoricalLeaderboardResponse { @@ -260,7 +265,7 @@ impl AsRef for HistoricalLeaderboardResponse { } } -/// The Historical User Leaderboard data. +/// An array of historical user blobs. (user leaderboard) #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct HistoricalLeaderboard { @@ -274,7 +279,7 @@ impl AsRef for HistoricalLeaderboard { } } -/// An entry in the Historical User Leaderboard. +/// An entry as a historical user blobs. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct HistoricalEntry { @@ -326,16 +331,20 @@ pub struct HistoricalEntry { } impl HistoricalEntry { - /// Gets the user's data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.id.get_user().await } From 7b3ff21d7d0d84a0141b4bb5cd907b67944ca469 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:34:55 +0900 Subject: [PATCH 124/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`cache?= =?UTF-8?q?.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/cache.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/model/cache.rs b/src/model/cache.rs index 97e2d96..15dc83b 100644 --- a/src/model/cache.rs +++ b/src/model/cache.rs @@ -1,8 +1,10 @@ -//! The cache-related models. +//! A model for the cache data. +//! +//! For more details, see the [API document](https://tetr.io/about/api/#cachedata). use serde::Deserialize; -/// Data about how this request was cached. +/// Data about how a request was cached. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct CacheData { From fb1cbd4739798caea61564febb7e104ffc5518d3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:37:02 +0900 Subject: [PATCH 125/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`achie?= =?UTF-8?q?vement.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/model/achievement.rs b/src/model/achievement.rs index ac68276..f9bbb67 100644 --- a/src/model/achievement.rs +++ b/src/model/achievement.rs @@ -1,4 +1,6 @@ -//! A model for achievements. +//! A model for the achievement data. +//! +//! For more details, see the [API document](https://tetr.io/about/api/#achievementdata). use serde::Deserialize; From 7145ab69aac11c18ab8a4f81b6f6e661d3f7c30a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:40:56 +0900 Subject: [PATCH 126/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`achie?= =?UTF-8?q?vement=5Finfo.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 133456c..df505ab 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -1,4 +1,7 @@ -//! The Achievement Info models. +//! Models for the endpoint "Achievement Info". +//! +//! About the endpoint "Achievement Info", +//! see the [API document](https://tetr.io/about/api/#achievementsk). use crate::{ client::error::RspErr, @@ -11,9 +14,7 @@ use crate::{ }; use serde::Deserialize; -/// The response for the Achievement Info data. -/// -/// Data about the achievement itself, its cutoffs, and its leaderboard. +/// A struct for the response for the endpoint "Achievement Info". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct AchievementInfoResponse { @@ -34,7 +35,7 @@ impl AsRef for AchievementInfoResponse { } } -/// The Achievement Info data. +/// Data about an achievement itself, its cutoffs, and its leaderboard. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct AchievementInfo { @@ -95,16 +96,20 @@ pub struct User { } impl User { - /// Gets the user's data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.id.get_user().await } From fc50b223ba7cfc3b72a86d7ef093552e64019fd6 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:47:03 +0900 Subject: [PATCH 127/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`zenit?= =?UTF-8?q?h.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/zenith.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/model/summary/zenith.rs b/src/model/summary/zenith.rs index 1ae5ffe..54e1bc4 100644 --- a/src/model/summary/zenith.rs +++ b/src/model/summary/zenith.rs @@ -1,10 +1,14 @@ -//! The User Summaries QUICK PLAY, EXPERT QUICK PLAY models. +//! Models for the endpoints "User Summary: QUICK PLAY", "User Summary: EXPERT QUICK PLAY". +//! +//! - About the endpoint "User Summary: QUICK PLAY", +//! see the [API document](https://tetr.io/about/api/#usersusersummarieszenith). +//! - About the endpoint "User Summary: EXPERT QUICK PLAY", +//! see the [API document](https://tetr.io/about/api/#usersusersummarieszenithex). use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; -/// The response for the User Summary QUICK PLAY data. -/// An object describing a summary of the user's QUICK PLAY games. +/// A struct for the response for the endpoint "User Summary: QUICK PLAY". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ZenithResponse { @@ -25,7 +29,7 @@ impl AsRef for ZenithResponse { } } -/// The User Summary QUICK PLAY data. +/// A struct that describes a summary of a user's QUICK PLAY or EXPERT QUICK PLAY games. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Zenith { @@ -51,7 +55,7 @@ impl AsRef for Zenith { } } -/// The user's career best QUICK PLAY data. +/// A user's career best QUICK PLAY data. /// /// Career bests are only updated on revolve time /// (when the week changes, which is 12AM on Monday, UTC). @@ -74,8 +78,7 @@ impl AsRef for ZenithBest { } } -/// The response for the User Summary EXPERT QUICK PLAY data. -/// An object describing a summary of the user's EXPERT QUICK PLAY games. +/// A struct for the response for the endpoint "User Summary: EXPERT QUICK PLAY". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ZenithExResponse { From a9cced0ed4f096c2cde78ed95054d4dfcee4b00d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:50:03 +0900 Subject: [PATCH 128/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`zen.r?= =?UTF-8?q?s`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/zen.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/model/summary/zen.rs b/src/model/summary/zen.rs index 5dda43c..6a712ff 100644 --- a/src/model/summary/zen.rs +++ b/src/model/summary/zen.rs @@ -1,10 +1,12 @@ -//! The User Summary ZEN models. +//! Models for the endpoint "User Summary: ZEN". +//! +//! About the endpoint "User Summary: ZEN", +//! see the [API document](https://tetr.io/about/api/#usersusersummarieszen). use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the User Summary ZEN data. -/// An object describing a summary of the user's ZEN progress. +/// A struct for the response for the endpoint "User Summary: ZEN". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct ZenResponse { @@ -25,13 +27,13 @@ impl AsRef for ZenResponse { } } -/// The User Summary ZEN data. +/// A struct that describes a summary of a user's ZEN progress. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Zen { - /// The user's level. + /// The user's ZEN level. pub level: u32, - /// The user's score. + /// The user's ZEN score. pub score: f64, } From 8102984645c7fa18c4c01088812eb8c57a57e852 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 00:56:50 +0900 Subject: [PATCH 129/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`recor?= =?UTF-8?q?d.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 84 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 44833c2..b81132f 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -1,4 +1,6 @@ -//! The Record Data models. +//! Models for the record data. +//! +//! For more details, see the [API document](https://tetr.io/about/api/#recorddata). use crate::{ client::{error::RspErr, param::pagination::Prisecter}, @@ -11,11 +13,11 @@ use crate::{ use serde::Deserialize; use std::collections::HashMap; -/// The record data. -/// Achieved scores and matches. +/// A record data. +/// Includes achieved scores and matches. /// -/// ***This structure may be changed drastically at any time. -/// See the [official API documentation](https://tetr.io/about/api/#recorddata) for more information.** +/// ***This structure may be changed drastically at any time.** +/// For more details, see the [API document](https://tetr.io/about/api/#recorddata). #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Record { @@ -88,7 +90,7 @@ impl AsRef for Record { } } -/// The User owning the Record. +/// A User owning a Record. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct User { @@ -108,16 +110,20 @@ pub struct User { } impl User { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.id.get_user().await } @@ -182,7 +188,7 @@ impl AsRef for User { } } -/// The results of a Record. +/// Results of a Record. /// /// If [`Record::other_users`] is empty, this is [`SinglePlayer`](`Results::SinglePlayer`). /// Otherwise, this is [`MultiPlayer`](`Results::MultiPlayer`). @@ -193,11 +199,11 @@ impl AsRef for User { #[serde(untagged)] #[non_exhaustive] pub enum Results { - /// The results for a single-player games. + /// Results for a single-player games. SinglePlayer(SinglePlayerResults), - /// The results for a multi-player games. + /// Results for a multi-player games. MultiPlayer(MultiPlayerResults), - /// An unknown result type. + /// Unknown structure. Unknown(serde_json::Value), } @@ -224,7 +230,7 @@ impl AsRef for Results { } } -/// The results for a single-player games. +/// Results for a single-player games. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct SinglePlayerResults { @@ -245,7 +251,7 @@ impl AsRef for SinglePlayerResults { } } -/// The results of a multi-player games. +/// Results of a multi-player games. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct MultiPlayerResults { @@ -261,7 +267,7 @@ impl AsRef for MultiPlayerResults { } } -/// The stats of a player in a multi-player game. +/// Stats of a player in a multi-player game. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct PlayerStats { @@ -280,16 +286,20 @@ pub struct PlayerStats { } impl PlayerStats { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.id.get_user().await } @@ -306,7 +316,7 @@ impl AsRef for PlayerStats { } } -/// The stats of a round in a multi-player game. +/// Stats of a round in a multi-player game. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct PlayerStatsRound { @@ -328,16 +338,20 @@ pub struct PlayerStatsRound { } impl PlayerStatsRound { - /// Gets the User Info data. + /// Gets the detailed information about the user. /// /// # Errors /// - /// Returns a [`ResponseError::DeserializeErr`] if there are some mismatches in the API docs, - /// or when this library is defective. - /// - /// Returns a [`ResponseError::RequestErr`] redirect loop was detected or redirect limit was exhausted. - /// - /// Returns a [`ResponseError::HttpErr`] if the HTTP request fails. + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user(&self) -> RspErr { self.id.get_user().await } From 49439812898218de3f96d0d5dbda730fa682953f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:00:45 +0900 Subject: [PATCH 130/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`src/m?= =?UTF-8?q?odel/summary/mod.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index a56ed48..d6f53d6 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,4 +1,4 @@ -//! Easy-to-use models of the various objects returned by the User Summaries API endpoint. +//! Easy-to-use models of the various objects received from the User Summaries API endpoints. use crate::model::{achievement::Achievement, cache::CacheData}; use serde::Deserialize; @@ -11,8 +11,7 @@ pub mod record; pub mod zen; pub mod zenith; -/// The response for the User Summary All data. -/// An object containing all the user's summaries in one. +/// A struct for the response for the endpoint "User Summary: All". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct AllSummariesResponse { @@ -33,7 +32,7 @@ impl AsRef for AllSummariesResponse { } } -/// All the User Summary data. +/// A struct that contains all summaries of a user in one. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct AllSummaries { From 44ddbe3c75fd57a67dce9bc30ec26c279162a193 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:04:50 +0900 Subject: [PATCH 131/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`leagu?= =?UTF-8?q?e.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/league.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 855109b..b85dd7a 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -1,11 +1,13 @@ -//! The User Summary TETRA LEAGUE models. +//! Models for the endpoint "User Summary: TETRA LEAGUE". +//! +//! About the endpoint "User Summary: TETRA LEAGUE", +//! see the [API document](https://tetr.io/about/api/#usersusersummariesleague). use crate::model::{cache::CacheData, league_rank::Rank}; use serde::Deserialize; use std::collections::HashMap; -/// The response for the User Summary TETRA LEAGUE data. -/// An object describing a summary of the user's TETRA LEAGUE standing. +/// A struct for the response for the endpoint "User Summary: TETRA LEAGUE". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LeagueResponse { @@ -26,7 +28,7 @@ impl AsRef for LeagueResponse { } } -/// The User Summary TETRA LEAGUE data. +/// A struct that describes a summary of a user's TETRA LEAGUE standing. /// /// Season information is only saved if the user had finished placements in the season, /// and was not banned or hidden. @@ -96,7 +98,9 @@ impl League { /// If there is no user's position in global leaderboards, /// `None` is returned. pub fn rank_progress(&self) -> Option { - if let (Some(standing), Some(prev_at), Some(next_at)) = (self.standing, self.prev_at, self.next_at) { + if let (Some(standing), Some(prev_at), Some(next_at)) = + (self.standing, self.prev_at, self.next_at) + { if prev_at < 0 || next_at < 0 { return None; } @@ -105,7 +109,7 @@ impl League { let next_at = next_at as f64; return Some((current_standing - prev_at) / (next_at - prev_at) * 100.); } - return None; + None } } From 82c3b1301a98a2c27bf185bdf8a4f915d7a7cfe7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:06:22 +0900 Subject: [PATCH 132/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`forty?= =?UTF-8?q?=5Flines.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/forty_lines.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/model/summary/forty_lines.rs b/src/model/summary/forty_lines.rs index bd2cb44..ed3630d 100644 --- a/src/model/summary/forty_lines.rs +++ b/src/model/summary/forty_lines.rs @@ -1,10 +1,12 @@ -//! The User Summary 40 LINES models. +//! Models for the endpoint "User Summary: 40 LINES". +//! +//! About the endpoint "User Summary: 40 LINES", +//! see the [API document](https://tetr.io/about/api/#usersusersummaries40l). use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; -/// The response for the User Summary 40 LINES data. -/// An object describing a summary of the user's 40 LINES games. +/// A struct for the response for the endpoint "User Summary: 40 LINES". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct FortyLinesResponse { @@ -25,7 +27,7 @@ impl AsRef for FortyLinesResponse { } } -/// The User Summary 40 LINES data. +/// A struct that describes a summary of a user's 40 LINES games. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct FortyLines { From e02f96892baa51473a5a49f76e6ddf3a5d7aff54 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:07:17 +0900 Subject: [PATCH 133/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`blitz?= =?UTF-8?q?.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/blitz.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/model/summary/blitz.rs b/src/model/summary/blitz.rs index 6da5d85..66c57c9 100644 --- a/src/model/summary/blitz.rs +++ b/src/model/summary/blitz.rs @@ -1,10 +1,12 @@ -//! The User Summary BLITZ models. +//! Models for the endpoint "User Summary: BLITZ". +//! +//! About the endpoint "User Summary: BLITZ", +//! see the [API document](https://tetr.io/about/api/#usersusersummariesblitz). use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; -/// The response for the User Summary BLITZ data. -/// An object describing a summary of the user's BLITZ games. +/// A struct for the response for the endpoint "User Summary: BLITZ". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct BlitzResponse { @@ -25,7 +27,7 @@ impl AsRef for BlitzResponse { } } -/// The User Summary BLITZ data. +/// A struct that describes a summary of a user's BLITZ games. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Blitz { From 5e91149b1d87589361f6ded817372787f72b6609 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:08:31 +0900 Subject: [PATCH 134/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`achie?= =?UTF-8?q?vements.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/achievements.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs index 05857b3..c9bd732 100644 --- a/src/model/summary/achievements.rs +++ b/src/model/summary/achievements.rs @@ -1,10 +1,12 @@ -//! The User Summary Achievements models. +//! A model for the endpoint "User Summary: Achievements". +//! +//! About the endpoint "User Summary: Achievements", +//! see the [API document](https://tetr.io/about/api/#usersusersummariesachievements). use crate::model::{achievement::Achievement, cache::CacheData}; use serde::Deserialize; -/// The response for the User Summary Achievements data. -/// An object containing all the user's achievements. +/// A struct for the response for the endpoint "User Summary: Achievements". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct AchievementsResponse { From 68c70a247079283554e021d0e0ec717340d8b4fb Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:09:27 +0900 Subject: [PATCH 135/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`src/m?= =?UTF-8?q?odel/labs/mod.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/labs/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/labs/mod.rs b/src/model/labs/mod.rs index 851833d..4813091 100644 --- a/src/model/labs/mod.rs +++ b/src/model/labs/mod.rs @@ -1,4 +1,4 @@ -//! Easy-to-use models of the various objects returned by the Labs API endpoint. +//! Easy-to-use models of the various objects received from the Labs API endpoint. pub mod league_ranks; pub mod leagueflow; From 4c8c78d2a14e35b356985c0cb03f2d460d27e107 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:11:07 +0900 Subject: [PATCH 136/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`score?= =?UTF-8?q?flow.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/labs/scoreflow.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/model/labs/scoreflow.rs b/src/model/labs/scoreflow.rs index e4b5fef..38ced76 100644 --- a/src/model/labs/scoreflow.rs +++ b/src/model/labs/scoreflow.rs @@ -1,11 +1,12 @@ -//! The Labs Scoreflow models. +//! Models for the endpoint "Labs Scoreflow". +//! +//! About the endpoint "Labs Scoreflow", +//! see the [API document](https://tetr.io/about/api/#labsscoreflowusergamemode). use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the Labs Scoreflow data. -/// -/// A condensed graph of all of the user's records in the gamemode. +/// A struct for the response for the endpoint "Labs Scoreflow". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LabsScoreflowResponse { @@ -26,7 +27,7 @@ impl AsRef for LabsScoreflowResponse { } } -/// The Labs Scoreflow data. +/// A condensed graph of all of a user's records in a gamemode. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LabsScoreflow { From 49f7cf63a8c983abbaaa1c6813b61d461ce1b732 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:12:39 +0900 Subject: [PATCH 137/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`leagu?= =?UTF-8?q?eflow.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/labs/leagueflow.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/model/labs/leagueflow.rs b/src/model/labs/leagueflow.rs index b84c1b7..e4e9913 100644 --- a/src/model/labs/leagueflow.rs +++ b/src/model/labs/leagueflow.rs @@ -1,9 +1,12 @@ -//! The Labs Leagueflow models. +//! Models for the endpoint "Labs Leagueflow". +//! +//! About the endpoint "Labs Leagueflow", +//! see the [API document](https://tetr.io/about/api/#labsleagueflowuser). use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the Labs Leagueflow data. +/// A struct for the response for the endpoint "Labs Leagueflow". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LabsLeagueflowResponse { @@ -24,7 +27,7 @@ impl AsRef for LabsLeagueflowResponse { } } -/// The Labs Leagueflow data. +/// A condensed graph of all of a user's matches in TETRA LEAGUE. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LabsLeagueflow { From 9d23b4c56a56fa20b999b15b978650d5f86dc7b9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:19:08 +0900 Subject: [PATCH 138/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20`leagu?= =?UTF-8?q?e=5Franks.rs`=20[#69]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/labs/league_ranks.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/model/labs/league_ranks.rs b/src/model/labs/league_ranks.rs index f51e48e..1ad1582 100644 --- a/src/model/labs/league_ranks.rs +++ b/src/model/labs/league_ranks.rs @@ -1,9 +1,12 @@ -//! The Labs League Ranks models. +//! Models for the endpoint "Labs League Ranks". +//! +//! About the endpoint "Labs League Ranks", +//! see the [API document](https://tetr.io/about/api/#labsleagueranks). use crate::model::cache::CacheData; use serde::Deserialize; -/// The response for the Labs League Ranks data. +/// A struct for the response for the endpoint "Labs League Ranks". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LabsLeagueRanksResponse { @@ -24,7 +27,7 @@ impl AsRef for LabsLeagueRanksResponse { } } -/// The Labs League Ranks data. +/// A view over all TETRA LEAGUE ranks and their metadata. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LabsLeagueRanks { @@ -47,9 +50,10 @@ impl AsRef for LabsLeagueRanks { } } -/// The data point for the Labs League Ranks data. +/// A data point. /// -/// If there are any unwrapped ranks, please create an Issue on GitHub. +/// If there are any unwrapped ranks, +/// please [create an Issue on GitHub](https://github.com/Rinrin0413/tetr-ch-rs/issues/new). #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct LeagueRanksData { @@ -117,7 +121,7 @@ impl AsRef for LeagueRanksData { } } -/// The data for a rank. +/// A rank's data. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct RankData { From 3e4639d1a68b7dbd818e8f73293be34c32a12e3a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:58:01 +0900 Subject: [PATCH 139/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`SearchedRecordRespo?= =?UTF-8?q?nse`=20struct=20[#71]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/mod.rs | 1 + src/model/searched_record.rs | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/model/searched_record.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index 427b597..90f6115 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -9,6 +9,7 @@ pub mod league_rank; pub mod news; pub mod records_leaderboard; pub mod role; +pub mod searched_record; pub mod searched_user; pub mod server_activity; pub mod server_stats; diff --git a/src/model/searched_record.rs b/src/model/searched_record.rs new file mode 100644 index 0000000..b78cfc9 --- /dev/null +++ b/src/model/searched_record.rs @@ -0,0 +1,25 @@ +//! A model for the endpoint "Record Search". +//! +//! About the endpoint "Record Search", +//! see the [API document](https://tetr.io/about/api/#recordsreverse). + +use crate::model::{ + cache::CacheData, + summary::record::Record, +}; +use serde::Deserialize; + +/// A struct for the response for the endpoint "Record Search". +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct SearchedRecordResponse { + /// Whether the request was successful. + #[serde(rename = "success")] + pub is_success: bool, + /// The reason the request failed. + pub error: Option, + /// Data about how this request was cached. + pub cache: Option, + /// The requested data. + pub data: Option, +} From 4603056b5d2ab2295d3558a70ca9765213bd90c3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 01:58:27 +0900 Subject: [PATCH 140/255] =?UTF-8?q?=F0=9F=A9=B9=20Fix:=20`search=5Frecord`?= =?UTF-8?q?=20method=20now=20returns=20`SearchedRecordResponse`=20model=20?= =?UTF-8?q?instead=20of=20`serde=5Fjson::Value`=20[#71]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 3 ++- src/model/searched_record.rs | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client.rs b/src/client.rs index c8b4102..8b7ccc7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -20,6 +20,7 @@ use crate::model::{ leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, news::{NewsAllResponse, NewsLatestResponse}, records_leaderboard::RecordsLeaderboardResponse, + searched_record::SearchedRecordResponse, searched_user::SearchedUserResponse, server_activity::ServerActivityResponse, server_stats::ServerStatsResponse, @@ -728,7 +729,7 @@ impl Client { user_id: &str, gamemode: Gamemode, timestamp: i64, - ) -> RspErr { + ) -> RspErr { let query_params = [ ("user", user_id.to_string()), ("gamemode", gamemode.to_param()), diff --git a/src/model/searched_record.rs b/src/model/searched_record.rs index b78cfc9..9b77b25 100644 --- a/src/model/searched_record.rs +++ b/src/model/searched_record.rs @@ -3,10 +3,7 @@ //! About the endpoint "Record Search", //! see the [API document](https://tetr.io/about/api/#recordsreverse). -use crate::model::{ - cache::CacheData, - summary::record::Record, -}; +use crate::model::{cache::CacheData, summary::record::Record}; use serde::Deserialize; /// A struct for the response for the endpoint "Record Search". From c133cbab7d0a21a9b7597d59064256def451c0e3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 02:49:55 +0900 Subject: [PATCH 141/255] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20`RecordsLeaderboa?= =?UTF-8?q?rdId::to=5Fparam`=20method=20no=20longer=20ignores=20`revolutio?= =?UTF-8?q?n=5Fid`=20field=20[#73]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/record_leaderboard.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index e869f26..3c6f933 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -49,10 +49,12 @@ impl RecordsLeaderboardId { /// assert_eq!(id3.to_param(), "zenith_global@2024w31"); /// ``` pub(crate) fn to_param(&self) -> String { - match &self.scope { - Scope::Global => format!("{}_global", self.gamemode), - Scope::Country(c) => format!("{}_country_{}", self.gamemode, c.to_uppercase()), - } + let scope = match &self.scope { + Scope::Global => "global".to_string(), + Scope::Country(c) => format!("country_{}", c.to_uppercase()), + }; + let revolution_id = self.revolution_id.as_deref().unwrap_or(""); + format!("{}_{}{}", self.gamemode, scope, revolution_id) } } From 42f3430c9cef782db6c289a7df5ebb79c77ce322 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 03:11:49 +0900 Subject: [PATCH 142/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20example=20for=20?= =?UTF-8?q?`get=5Frecords=5Fleaderboard`=20method=20[#73]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client.rs b/src/client.rs index 8b7ccc7..c85c709 100644 --- a/src/client.rs +++ b/src/client.rs @@ -627,7 +627,7 @@ impl Client { /// /// - Upper bound is `[500000, 0, 0]` /// - Three entries - /// - Game mode: `blitz` (BLITZ) + /// - Game mode: `zenith` (QUICK PLAY) /// - Scope: `JP` (Japan) /// - Revolution ID: `@2024w31` /// @@ -649,10 +649,10 @@ impl Client { /// /// // Get the record leaderboard. /// let user = client.get_records_leaderboard( - /// // Record leaderboard ID: `blitz_country_JP@2024w31` + /// // Record leaderboard ID: `zenith_country_JP@2024w31` /// RecordsLeaderboardId::new( - /// // Game mode: `blitz` (BLITZ) - /// "blitz", + /// // Game mode: `zenith` (QUICK PLAY) + /// "zenith", /// // Scope: `JP` (Japan) /// Scope::Country("JP".to_string()), /// // Revolution ID: `@2024w31` From c5603ec1e058d6e3d2f22419753a824df6ae987c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 03:58:46 +0900 Subject: [PATCH 143/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20optio?= =?UTF-8?q?n=20of=20`get=5Fnews=5Fall`=20method=20is=20now=20mandatory=20[?= =?UTF-8?q?#75]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/client.rs b/src/client.rs index c85c709..75c1816 100644 --- a/src/client.rs +++ b/src/client.rs @@ -747,8 +747,7 @@ impl Client { /// /// # Arguments /// - /// - `limit` - The amount of entries to return, - /// between 1 and 100. 25 by default. + /// - `limit` - The amount of entries to return, between 1 and 100. /// /// # Examples /// @@ -759,25 +758,26 @@ impl Client { /// let client = Client::new(); /// /// // Get three latest news. - /// let user = client.get_news_all(Some(3)).await?; + /// let user = client.get_news_all(3).await?; /// # Ok(()) /// # } /// ``` - pub async fn get_news_all(self, limit: Option) -> RspErr { - let mut query_param = Vec::new(); - if let Some(l) = limit { - if !(1..=100).contains(&l) { - // !(1 <= limit && limit <= 100) - panic!( - "The query parameter`limit` must be between 1 and 100.\n\ - Received: {}", - l - ); - } - query_param.push(("limit", l.to_string())); + pub async fn get_news_all(self, limit: u8) -> RspErr { + if !(1..=100).contains(&limit) { + // !(1 <= limit && limit <= 100) + panic!( + "The query parameter`limit` must be between 1 and 100.\n\ + Received: {}", + limit + ); } let url = format!("{}news/", API_URL); - let res = self.client.get(url).query(&query_param).send().await; + let res = self + .client + .get(url) + .query(&[("limit", limit.to_string())]) + .send() + .await; response(res).await } From e8b7eab5baec167de6935a5325974a08d6812de8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 05:05:19 +0900 Subject: [PATCH 144/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20write=20"Panics"?= =?UTF-8?q?=20sections=20in=20some=20docs=20[#77]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 139 +++++++++++++++++++++++-- src/client/param/record.rs | 6 +- src/client/param/record_leaderboard.rs | 6 +- src/client/param/user_leaderboard.rs | 6 +- src/model/leaderboard.rs | 4 + src/model/news.rs | 4 + src/model/summary/record.rs | 4 + src/model/user.rs | 8 ++ src/util.rs | 4 + 9 files changed, 160 insertions(+), 21 deletions(-) diff --git a/src/client.rs b/src/client.rs index 75c1816..70aa0db 100644 --- a/src/client.rs +++ b/src/client.rs @@ -441,6 +441,32 @@ impl Client { /// # Ok(()) /// # } /// ``` + /// + /// # Panics + /// + /// Panics if the search criteria `limit` is not between 1 and 100. + /// + /// ```should_panic,no_run + /// # use tetr_ch::client::{ + /// # Client, + /// # param::user_leaderboard::{self, LeaderboardType} + /// # }; + /// # async fn run() -> std::io::Result<()> { + /// # let client = Client::new(); + /// let criteria = user_leaderboard::SearchCriteria { + /// // 101 entries (out of bounds) + /// limit: Some(101), + /// ..Default::default() + /// }; + /// + /// // Panics! + /// let user = client.get_leaderboard( + /// LeaderboardType::League, + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` pub async fn get_leaderboard( self, leaderboard: LeaderboardType, @@ -507,6 +533,32 @@ impl Client { /// # Ok(()) /// # } /// ``` + /// + /// # Panics + /// + /// Panics if the search criteria `limit` is not between 1 and 100. + /// + /// ```should_panic,no_run + /// # use tetr_ch::client::{ + /// # Client, + /// # param::user_leaderboard::{self, LeaderboardType} + /// # }; + /// # async fn run() -> std::io::Result<()> { + /// # let client = Client::new(); + /// let criteria = user_leaderboard::SearchCriteria { + /// // 101 entries (out of bounds) + /// limit: Some(101), + /// ..Default::default() + /// }; + /// + /// // Panics! + /// let user = client.get_historical_league_leaderboard( + /// "1", + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` pub async fn get_historical_league_leaderboard( self, season: &str, @@ -582,6 +634,34 @@ impl Client { /// # Ok(()) /// # } /// ``` + /// + /// # Panics + /// + /// Panics if the search criteria `limit` is not between 1 and 100. + /// + /// ```should_panic,no_run + /// # use tetr_ch::client::{ + /// # Client, + /// # param::record::{self, Gamemode, LeaderboardType} + /// # }; + /// # async fn run() -> std::io::Result<()> { + /// # let client = Client::new(); + /// let criteria = record::SearchCriteria { + /// // 101 entries (out of bounds) + /// limit: Some(101), + /// ..Default::default() + /// }; + /// + /// // Panics! + /// let user = client.get_user_records( + /// "rinrin-rs", + /// Gamemode::FortyLines, + /// LeaderboardType::Top, + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` pub async fn get_user_records( self, user: &str, @@ -663,6 +743,36 @@ impl Client { /// # Ok(()) /// # } /// ``` + /// + /// # Panics + /// + /// Panics if the search criteria `limit` is not between 1 and 100. + /// + /// ```should_panic,no_run + /// # use tetr_ch::client::{ + /// # Client, + /// # param::record_leaderboard::{self, RecordsLeaderboardId, Scope} + /// # }; + /// # async fn run() -> std::io::Result<()> { + /// # let client = Client::new(); + /// let criteria = record_leaderboard::SearchCriteria { + /// // 101 entries (out of bounds) + /// limit: Some(101), + /// ..Default::default() + /// }; + /// + /// // Panics! + /// let user = client.get_records_leaderboard( + /// RecordsLeaderboardId::new( + /// "zenith", + /// Scope::Global, + /// None + /// ), + /// Some(criteria) + /// ).await?; + /// # Ok(()) + /// # } + /// ``` pub async fn get_records_leaderboard( self, leaderboard: RecordsLeaderboardId, @@ -762,6 +872,21 @@ impl Client { /// # Ok(()) /// # } /// ``` + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between 1 and 100. + /// + /// ```should_panic,no_run + /// # use tetr_ch::client::Client; + /// # async fn run() -> std::io::Result<()> { + /// # let client = Client::new(); + /// // Panics! + /// // Because the limit is 101 (out of bounds) + /// let user = client.get_news_all(101).await?; + /// # Ok(()) + /// # } + /// ``` pub async fn get_news_all(self, limit: u8) -> RspErr { if !(1..=100).contains(&limit) { // !(1 <= limit && limit <= 100) @@ -814,24 +939,20 @@ impl Client { /// /// # Panics /// - /// Panics if the query parameter `limit` is not between 1 and 100. + /// Panics if the argument `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// use tetr_ch::client::{Client, param::news_stream::NewsStream}; - /// + /// # use tetr_ch::client::{Client, param::news_stream::NewsStream}; /// # async fn run() -> std::io::Result<()> { - /// let client = Client::new(); - /// + /// # let client = Client::new(); + /// // Panics! /// let user = client.get_news_latest( - /// // Global news /// NewsStream::Global, - /// // 101 news (not allowed) + /// // 101 news (out of bounds) /// 101, /// ).await?; /// # Ok(()) /// # } - /// - /// # tokio_test::block_on(run()); /// ``` pub async fn get_news_latest( self, diff --git a/src/client/param/record.rs b/src/client/param/record.rs index 288ec4c..bbcd5a9 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -217,14 +217,12 @@ impl SearchCriteria { /// /// ```should_panic /// # use tetr_ch::client::param::record::SearchCriteria; - /// let mut criteria = SearchCriteria::new(); - /// criteria.limit(0); + /// let mut criteria = SearchCriteria::new().limit(0); /// ``` /// /// ```should_panic /// # use tetr_ch::client::param::record::SearchCriteria; - /// let mut criteria = SearchCriteria::new(); - /// criteria.limit(101); + /// let mut criteria = SearchCriteria::new().limit(101); /// ``` pub fn limit(self, limit: u8) -> Self { if (1..=100).contains(&limit) { diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index 3c6f933..25880d8 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -205,14 +205,12 @@ impl SearchCriteria { /// /// ```should_panic /// # use tetr_ch::client::param::record::SearchCriteria; - /// let mut criteria = SearchCriteria::new(); - /// criteria.limit(0); + /// let criteria = SearchCriteria::new().limit(0); /// ``` /// /// ```should_panic /// # use tetr_ch::client::param::record::SearchCriteria; - /// let mut criteria = SearchCriteria::new(); - /// criteria.limit(101); + /// let criteria = SearchCriteria::new().limit(101); /// ``` pub fn limit(self, limit: u8) -> Self { if (1..=100).contains(&limit) { diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 69728c6..9163e86 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -173,14 +173,12 @@ impl SearchCriteria { /// /// ```should_panic /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; - /// let mut criteria = SearchCriteria::new(); - /// criteria.limit(0); + /// let mut criteria = SearchCriteria::new().limit(0); /// ``` /// /// ```should_panic /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; - /// let mut criteria = SearchCriteria::new(); - /// criteria.limit(101); + /// let mut criteria = SearchCriteria::new().limit(101); /// ``` pub fn limit(self, limit: u8) -> Self { if (1..=100).contains(&limit) { diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 071dfdb..ac5f84d 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -184,6 +184,10 @@ impl LeaderboardEntry { /// /// If this account was created before join dates were recorded, /// `None` is returned. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. pub fn account_created_at(&self) -> Option { self.account_created_at.as_ref().map(|ts| to_unix_ts(ts)) } diff --git a/src/model/news.rs b/src/model/news.rs index dc7cc1e..f89573c 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -89,6 +89,10 @@ impl News { } /// Returns an UNIX timestamp when the news item was created. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. pub fn created_at(&self) -> i64 { to_unix_ts(&self.created_at) } diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index b81132f..ddc58e9 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -79,6 +79,10 @@ impl Record { } /// Returns a UNIX timestamp when the record was submitted. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. pub fn submitted_at(&self) -> i64 { to_unix_ts(&self.submitted_at) } diff --git a/src/model/user.rs b/src/model/user.rs index 80cea42..496a72d 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -169,6 +169,10 @@ impl User { /// Returns an UNIX timestamp when the user's account created. /// /// If the account was created before join dates were recorded, `None` is returned. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. pub fn created_at(&self) -> Option { self.created_at.as_ref().map(|ts| to_unix_ts(ts)) } @@ -275,6 +279,10 @@ impl Badge { } /// Returns a UNIX timestamp when the badge was achieved. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. pub fn received_at(&self) -> Option { self.received_at.as_ref().map(|ts| to_unix_ts(ts)) } diff --git a/src/util.rs b/src/util.rs index bd78ee4..7d91036 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,6 +5,10 @@ use serde::Deserialize; use serde_json::Value; /// Parses an RFC 3339 and ISO 8601 date and time string into a UNIX timestamp. +/// +/// # Panics +/// +/// Panics if failed to parse the given string. pub(crate) fn to_unix_ts(ts: &str) -> i64 { match DateTime::parse_from_rfc3339(ts) { Ok(dt) => dt.timestamp(), From da62c75a287bc765f917a8267d75f7dc5536c6ef Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 05:56:16 +0900 Subject: [PATCH 145/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20`to=5Fu?= =?UTF-8?q?nix=5Fts`=20function=20now=20uses=20`expect`=20method=20instead?= =?UTF-8?q?=20of=20`match`=20expression=20[#79]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/util.rs b/src/util.rs index 7d91036..0c4069b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -10,10 +10,9 @@ use serde_json::Value; /// /// Panics if failed to parse the given string. pub(crate) fn to_unix_ts(ts: &str) -> i64 { - match DateTime::parse_from_rfc3339(ts) { - Ok(dt) => dt.timestamp(), - Err(e) => panic!("{}", e), - } + DateTime::parse_from_rfc3339(ts) + .expect("Failed to parse the given string.") + .timestamp() } /// Compares and returns the maximum of two 64bit floats`. From b40512e58e738bee183b51e7aade9958e6203770 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 06:04:20 +0900 Subject: [PATCH 146/255] =?UTF-8?q?=E2=9C=A8=20Add:=20util=20function=20`v?= =?UTF-8?q?alidate=5Flimit`=20[#79]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/util.rs b/src/util.rs index 0c4069b..63a1b94 100644 --- a/src/util.rs +++ b/src/util.rs @@ -42,6 +42,18 @@ where } } +/// # Panics +/// +/// Panics with a message "The limit must be between 1 and 100, but got X." +/// if the given value is not between 1 and 100. +pub(crate) fn validate_limit(value: u8) { + assert!( + (1..=100).contains(&value), + "The limit must be between 1 and 100, but got {}.", + value + ); +} + #[cfg(test)] mod tests { use super::*; From abe7ca50069fc3c5d00456f9c6de08c12fc3c5fc Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 06:05:09 +0900 Subject: [PATCH 147/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20replace?= =?UTF-8?q?=20panic=20calls=20with=20`validate=5Flimit`=20utility=20functi?= =?UTF-8?q?on=20for=20limit=20validation=20[#79]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 99 +++++++++----------------- src/client/param/record.rs | 46 +++--------- src/client/param/record_leaderboard.rs | 46 +++--------- src/client/param/user_leaderboard.rs | 46 +++--------- 4 files changed, 62 insertions(+), 175 deletions(-) diff --git a/src/client.rs b/src/client.rs index 70aa0db..71830f4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,30 +11,33 @@ use self::{ }, response::response, }; -use crate::model::{ - achievement_info::AchievementInfoResponse, - labs::{ - league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, - scoreflow::LabsScoreflowResponse, +use crate::{ + model::{ + achievement_info::AchievementInfoResponse, + labs::{ + league_ranks::LabsLeagueRanksResponse, leagueflow::LabsLeagueflowResponse, + scoreflow::LabsScoreflowResponse, + }, + leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, + news::{NewsAllResponse, NewsLatestResponse}, + records_leaderboard::RecordsLeaderboardResponse, + searched_record::SearchedRecordResponse, + searched_user::SearchedUserResponse, + server_activity::ServerActivityResponse, + server_stats::ServerStatsResponse, + summary::{ + achievements::AchievementsResponse, + blitz::BlitzResponse, + forty_lines::FortyLinesResponse, + league::LeagueResponse, + zen::ZenResponse, + zenith::{ZenithExResponse, ZenithResponse}, + AllSummariesResponse, + }, + user::UserResponse, + user_records::UserRecordsResponse, }, - leaderboard::{HistoricalLeaderboardResponse, LeaderboardResponse}, - news::{NewsAllResponse, NewsLatestResponse}, - records_leaderboard::RecordsLeaderboardResponse, - searched_record::SearchedRecordResponse, - searched_user::SearchedUserResponse, - server_activity::ServerActivityResponse, - server_stats::ServerStatsResponse, - summary::{ - achievements::AchievementsResponse, - blitz::BlitzResponse, - forty_lines::FortyLinesResponse, - league::LeagueResponse, - zen::ZenResponse, - zenith::{ZenithExResponse, ZenithResponse}, - AllSummariesResponse, - }, - user::UserResponse, - user_records::UserRecordsResponse, + util::validate_limit, }; use reqwest::{self}; @@ -474,13 +477,7 @@ impl Client { ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { - if criteria.is_invalid_limit_range() { - panic!( - "The query parameter`limit` must be between 0 and 100.\n\ - Received: {}", - criteria.limit.unwrap() - ); - } + criteria.validate_limit(); query_params = criteria.build(); } let url = format!("{}users/by/{}", API_URL, leaderboard.to_param()); @@ -566,13 +563,7 @@ impl Client { ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { - if criteria.is_invalid_limit_range() { - panic!( - "The query parameter`limit` must be between 0 and 100.\n\ - Received: {}", - criteria.limit.unwrap() - ); - } + criteria.validate_limit(); query_params = criteria.build(); } let url = format!( @@ -671,13 +662,7 @@ impl Client { ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { - if criteria.is_invalid_limit_range() { - panic!( - "The query parameter`limit` must be between 0 and 100.\n\ - Received: {}", - criteria.limit.unwrap() - ); - } + criteria.validate_limit(); query_params = criteria.build(); } let url = format!( @@ -780,13 +765,7 @@ impl Client { ) -> RspErr { let mut query_params = Vec::new(); if let Some(criteria) = search_criteria { - if criteria.is_invalid_limit_range() { - panic!( - "The query parameter`limit` must be between 0 and 100.\n\ - Received: {}", - criteria.limit.unwrap() - ); - } + criteria.validate_limit(); query_params = criteria.build(); } let url = format!("{}records/{}", API_URL, leaderboard.to_param()); @@ -888,14 +867,7 @@ impl Client { /// # } /// ``` pub async fn get_news_all(self, limit: u8) -> RspErr { - if !(1..=100).contains(&limit) { - // !(1 <= limit && limit <= 100) - panic!( - "The query parameter`limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); - } + validate_limit(limit); let url = format!("{}news/", API_URL); let res = self .client @@ -959,14 +931,7 @@ impl Client { stream: NewsStream, limit: u8, ) -> RspErr { - if !(1..=100).contains(&limit) { - // !(1 <= limit && limit <= 100) - panic!( - "The query parameter`limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); - } + validate_limit(limit); let url = format!("{}news/{}", API_URL, stream.to_param()); let res = self.client.get(url).query(&[("limit", limit)]).send().await; response(res).await diff --git a/src/client/param/record.rs b/src/client/param/record.rs index bbcd5a9..7346791 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -1,6 +1,7 @@ //! Features for records. use super::pagination::Bound; +use crate::util::validate_limit; /// A game mode of a record. pub enum Gamemode { @@ -225,46 +226,19 @@ impl SearchCriteria { /// let mut criteria = SearchCriteria::new().limit(101); /// ``` pub fn limit(self, limit: u8) -> Self { - if (1..=100).contains(&limit) { - Self { - limit: Some(limit), - ..self - } - } else { - panic!( - "The argument `limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); + validate_limit(limit); + Self { + limit: Some(limit), + ..self } } - /// Whether the search criteria `limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::param::record::SearchCriteria; - /// let invalid_criteria = SearchCriteria { - /// limit: Some(0), - /// ..SearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` + /// # Panics /// - /// ``` - /// # use tetr_ch::client::param::record::SearchCriteria; - /// let invalid_criteria = SearchCriteria { - /// limit: Some(101), - /// ..SearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit { - !(1..=100).contains(&l) - } else { - false + /// Panics if the limit is not between 1 and 100. + pub(crate) fn validate_limit(&self) { + if let Some(self_limit) = self.limit { + validate_limit(self_limit) } } diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index 25880d8..b026f1d 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -1,6 +1,7 @@ //! Features for record leaderboards. use super::pagination::Bound; +use crate::util::validate_limit; /// A record leaderboard ID. pub struct RecordsLeaderboardId { @@ -213,46 +214,19 @@ impl SearchCriteria { /// let criteria = SearchCriteria::new().limit(101); /// ``` pub fn limit(self, limit: u8) -> Self { - if (1..=100).contains(&limit) { - Self { - limit: Some(limit), - ..self - } - } else { - panic!( - "The argument `limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); + validate_limit(limit); + Self { + limit: Some(limit), + ..self } } - /// Whether the search criteria `limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::param::record::SearchCriteria; - /// let invalid_criteria = SearchCriteria { - /// limit: Some(0), - /// ..SearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` + /// # Panics /// - /// ``` - /// # use tetr_ch::client::param::record::SearchCriteria; - /// let invalid_criteria = SearchCriteria { - /// limit: Some(101), - /// ..SearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit { - !(1..=100).contains(&l) - } else { - false + /// Panics if the limit is not between 1 and 100. + pub(crate) fn validate_limit(&self) { + if let Some(self_limit) = self.limit { + validate_limit(self_limit) } } diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 9163e86..d2d3cda 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -1,6 +1,7 @@ //! Features for user leaderboards. use super::pagination::Bound; +use crate::util::validate_limit; /// A user leaderboard type. pub enum LeaderboardType { @@ -181,17 +182,10 @@ impl SearchCriteria { /// let mut criteria = SearchCriteria::new().limit(101); /// ``` pub fn limit(self, limit: u8) -> Self { - if (1..=100).contains(&limit) { - Self { - limit: Some(limit), - ..self - } - } else { - panic!( - "The argument `limit` must be between 1 and 100.\n\ - Received: {}", - limit - ); + validate_limit(limit); + Self { + limit: Some(limit), + ..self } } @@ -217,32 +211,12 @@ impl SearchCriteria { } } - /// Whether the search criteria `limit` is out of bounds. - /// - /// # Examples - /// - /// ``` - /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; - /// let invalid_criteria = SearchCriteria { - /// limit: Some(0), - /// ..SearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` + /// # Panics /// - /// ``` - /// # use tetr_ch::client::param::user_leaderboard::SearchCriteria; - /// let invalid_criteria = SearchCriteria { - /// limit: Some(101), - /// ..SearchCriteria::new() - /// }; - /// assert!(invalid_criteria.is_invalid_limit_range()); - /// ``` - pub fn is_invalid_limit_range(&self) -> bool { - if let Some(l) = self.limit { - !(1..=100).contains(&l) - } else { - false + /// Panics if the limit is not between 1 and 100. + pub(crate) fn validate_limit(&self) { + if let Some(self_limit) = self.limit { + validate_limit(self_limit) } } From d087289475f66d21f2e5935ec05d1d10877fc500 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 14:12:48 +0900 Subject: [PATCH 148/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Improve:=20dese?= =?UTF-8?q?rialization=20is=20now=20tried=20before=20returning=20HTTP=20er?= =?UTF-8?q?ror=20[#82]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/error.rs | 2 +- src/client/response.rs | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/client/error.rs b/src/client/error.rs index edf3e40..0e7290b 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -15,7 +15,7 @@ pub enum ResponseError { DeserializeErr(String), /// The HTTP request failed and the response did not match the expected format. /// - /// Even if the HTTP request failed, + /// Even if the HTTP status code is not within 200-299. /// it may be possible to deserialize the response containing an error message, /// so the deserialization will be tried before returning this error. HttpErr(Status), diff --git a/src/client/response.rs b/src/client/response.rs index 4f381df..b254d28 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -15,17 +15,25 @@ pub(super) async fn response(response: Result) -> RspErr where for<'de> T: Deserialize<'de>, { + // Whether the request succeeded or not. match response { Ok(r) => { - if !r.status().is_success() { - match StatusCode::from_u16(r.status().as_u16()) { - Ok(c) => return Err(ResponseError::HttpErr(Status::Valid(c))), - Err(e) => return Err(ResponseError::HttpErr(Status::Invalid(e))), - } - } + let status = r.status(); + let is_success = status.is_success(); + // Whether the response is an expected structure or not. match r.json().await { Ok(m) => Ok(m), - Err(e) => Err(ResponseError::DeserializeErr(e.to_string())), + Err(e) => { + // Whether the status code is within 200-299 or not. + if is_success { + Err(ResponseError::DeserializeErr(e.to_string())) + } else { + match StatusCode::from_u16(status.as_u16()) { + Ok(c) => Err(ResponseError::HttpErr(Status::Valid(c))), + Err(e) => Err(ResponseError::HttpErr(Status::Invalid(e))), + } + } + } } } Err(e) => Err(ResponseError::RequestErr(e.to_string())), From 8c9534c094c2ec7d3b3df98da7a484ccc2639e41 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 14:36:08 +0900 Subject: [PATCH 149/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`ErrorResponse`=20mo?= =?UTF-8?q?del=20[#84]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/error_response.rs | 19 +++++++++++++++++++ src/model/mod.rs | 1 + 2 files changed, 20 insertions(+) create mode 100644 src/model/error_response.rs diff --git a/src/model/error_response.rs b/src/model/error_response.rs new file mode 100644 index 0000000..a1a6bd6 --- /dev/null +++ b/src/model/error_response.rs @@ -0,0 +1,19 @@ +//! A model for the error response. + +use serde::Deserialize; + +/// An error response. +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct ErrorResponse { + /// The error message. + /// + /// e.g. "No such user! | Either you mistyped something, or the account no longer exists." + pub msg: Option, +} + +impl AsRef for ErrorResponse { + fn as_ref(&self) -> &Self { + self + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 90f6115..8e51b70 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -3,6 +3,7 @@ pub mod achievement; pub mod achievement_info; pub mod cache; +pub mod error_response; pub mod labs; pub mod leaderboard; pub mod league_rank; From 6cc38f8e323745d51b4b266b9c5b922d6dced65d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 14:46:07 +0900 Subject: [PATCH 150/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20repla?= =?UTF-8?q?ce=20`String`=20error=20type=20with=20`ErrorResponse`=20model?= =?UTF-8?q?=20in=20all=20response=20models=20[#84]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 3 ++- src/model/labs/league_ranks.rs | 4 ++-- src/model/labs/leagueflow.rs | 4 ++-- src/model/labs/scoreflow.rs | 4 ++-- src/model/leaderboard.rs | 5 +++-- src/model/news.rs | 5 +++-- src/model/records_leaderboard.rs | 4 ++-- src/model/searched_record.rs | 4 ++-- src/model/searched_user.rs | 3 ++- src/model/server_activity.rs | 4 ++-- src/model/server_stats.rs | 4 ++-- src/model/summary/achievements.rs | 4 ++-- src/model/summary/blitz.rs | 4 ++-- src/model/summary/forty_lines.rs | 4 ++-- src/model/summary/league.rs | 4 ++-- src/model/summary/mod.rs | 4 ++-- src/model/summary/zen.rs | 4 ++-- src/model/summary/zenith.rs | 6 +++--- src/model/user.rs | 4 ++-- src/model/user_records.rs | 4 ++-- 20 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index df505ab..04c23ee 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -8,6 +8,7 @@ use crate::{ model::{ achievement::Achievement, cache::CacheData, + error_response::ErrorResponse, role::Role, user::{UserId, UserResponse}, }, @@ -22,7 +23,7 @@ pub struct AchievementInfoResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/labs/league_ranks.rs b/src/model/labs/league_ranks.rs index 1ad1582..62b066f 100644 --- a/src/model/labs/league_ranks.rs +++ b/src/model/labs/league_ranks.rs @@ -3,7 +3,7 @@ //! About the endpoint "Labs League Ranks", //! see the [API document](https://tetr.io/about/api/#labsleagueranks). -use crate::model::cache::CacheData; +use crate::model::{cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; /// A struct for the response for the endpoint "Labs League Ranks". @@ -14,7 +14,7 @@ pub struct LabsLeagueRanksResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/labs/leagueflow.rs b/src/model/labs/leagueflow.rs index e4e9913..4f8fde1 100644 --- a/src/model/labs/leagueflow.rs +++ b/src/model/labs/leagueflow.rs @@ -3,7 +3,7 @@ //! About the endpoint "Labs Leagueflow", //! see the [API document](https://tetr.io/about/api/#labsleagueflowuser). -use crate::model::cache::CacheData; +use crate::model::{cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; /// A struct for the response for the endpoint "Labs Leagueflow". @@ -14,7 +14,7 @@ pub struct LabsLeagueflowResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/labs/scoreflow.rs b/src/model/labs/scoreflow.rs index 38ced76..adf9cce 100644 --- a/src/model/labs/scoreflow.rs +++ b/src/model/labs/scoreflow.rs @@ -3,7 +3,7 @@ //! About the endpoint "Labs Scoreflow", //! see the [API document](https://tetr.io/about/api/#labsscoreflowusergamemode). -use crate::model::cache::CacheData; +use crate::model::{cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; /// A struct for the response for the endpoint "Labs Scoreflow". @@ -14,7 +14,7 @@ pub struct LabsScoreflowResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index ac5f84d..6780f75 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -9,6 +9,7 @@ use crate::{ client::{error::RspErr, param::pagination::Prisecter}, model::{ cache::CacheData, + error_response::ErrorResponse, league_rank::Rank, role::Role, user::{AchievementRatingCounts, UserId, UserResponse}, @@ -25,7 +26,7 @@ pub struct LeaderboardResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. @@ -256,7 +257,7 @@ pub struct HistoricalLeaderboardResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/news.rs b/src/model/news.rs index f89573c..c826a0e 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -9,6 +9,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ cache::CacheData, + error_response::ErrorResponse, league_rank::Rank, user::{UserId, UserResponse}, }, @@ -24,7 +25,7 @@ pub struct NewsAllResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. @@ -450,7 +451,7 @@ pub struct NewsLatestResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/records_leaderboard.rs b/src/model/records_leaderboard.rs index 4f54f18..0c1a8fc 100644 --- a/src/model/records_leaderboard.rs +++ b/src/model/records_leaderboard.rs @@ -3,7 +3,7 @@ //! About the endpoint "Records Leaderboard", //! see the [API document](https://tetr.io/about/api/#recordsleaderboard). -use crate::model::{cache::CacheData, summary::record::Record}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; use serde::Deserialize; /// An struct for the response for the endpoint "Records Leaderboard". @@ -14,7 +14,7 @@ pub struct RecordsLeaderboardResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/searched_record.rs b/src/model/searched_record.rs index 9b77b25..5d07fc9 100644 --- a/src/model/searched_record.rs +++ b/src/model/searched_record.rs @@ -3,7 +3,7 @@ //! About the endpoint "Record Search", //! see the [API document](https://tetr.io/about/api/#recordsreverse). -use crate::model::{cache::CacheData, summary::record::Record}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; use serde::Deserialize; /// A struct for the response for the endpoint "Record Search". @@ -14,7 +14,7 @@ pub struct SearchedRecordResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index 2db314e..738e7d1 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -7,6 +7,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ cache::CacheData, + error_response::ErrorResponse, user::{UserId, UserResponse}, }, }; @@ -20,7 +21,7 @@ pub struct SearchedUserResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/server_activity.rs b/src/model/server_activity.rs index f1c2679..68c57b4 100644 --- a/src/model/server_activity.rs +++ b/src/model/server_activity.rs @@ -3,7 +3,7 @@ //! About the endpoint "Server Activity", //! see the [API document](https://tetr.io/about/api/#generalactivity). -use crate::model::cache::CacheData; +use crate::model::{cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; /// A struct for the response for the endpoint "Server Activity". @@ -14,7 +14,7 @@ pub struct ServerActivityResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/server_stats.rs b/src/model/server_stats.rs index d0d936c..da0ce95 100644 --- a/src/model/server_stats.rs +++ b/src/model/server_stats.rs @@ -3,7 +3,7 @@ //! About the endpoint "Server Statistics", //! see the [API document](https://tetr.io/about/api/#generalstats). -use crate::model::cache::CacheData; +use crate::model::{cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; /// A struct for the response for the endpoint "Server Statistics". @@ -14,7 +14,7 @@ pub struct ServerStatsResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs index c9bd732..bfbcf7f 100644 --- a/src/model/summary/achievements.rs +++ b/src/model/summary/achievements.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Summary: Achievements", //! see the [API document](https://tetr.io/about/api/#usersusersummariesachievements). -use crate::model::{achievement::Achievement, cache::CacheData}; +use crate::model::{achievement::Achievement, cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; /// A struct for the response for the endpoint "User Summary: Achievements". @@ -14,7 +14,7 @@ pub struct AchievementsResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/summary/blitz.rs b/src/model/summary/blitz.rs index 66c57c9..848b762 100644 --- a/src/model/summary/blitz.rs +++ b/src/model/summary/blitz.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Summary: BLITZ", //! see the [API document](https://tetr.io/about/api/#usersusersummariesblitz). -use crate::model::{cache::CacheData, summary::record::Record}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; use serde::Deserialize; /// A struct for the response for the endpoint "User Summary: BLITZ". @@ -14,7 +14,7 @@ pub struct BlitzResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/summary/forty_lines.rs b/src/model/summary/forty_lines.rs index ed3630d..a2d54d8 100644 --- a/src/model/summary/forty_lines.rs +++ b/src/model/summary/forty_lines.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Summary: 40 LINES", //! see the [API document](https://tetr.io/about/api/#usersusersummaries40l). -use crate::model::{cache::CacheData, summary::record::Record}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; use serde::Deserialize; /// A struct for the response for the endpoint "User Summary: 40 LINES". @@ -14,7 +14,7 @@ pub struct FortyLinesResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index b85dd7a..4882c73 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Summary: TETRA LEAGUE", //! see the [API document](https://tetr.io/about/api/#usersusersummariesleague). -use crate::model::{cache::CacheData, league_rank::Rank}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, league_rank::Rank}; use serde::Deserialize; use std::collections::HashMap; @@ -15,7 +15,7 @@ pub struct LeagueResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index d6f53d6..2d7c84b 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,6 +1,6 @@ //! Easy-to-use models of the various objects received from the User Summaries API endpoints. -use crate::model::{achievement::Achievement, cache::CacheData}; +use crate::model::{achievement::Achievement, cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; pub mod achievements; @@ -19,7 +19,7 @@ pub struct AllSummariesResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/summary/zen.rs b/src/model/summary/zen.rs index 6a712ff..0d1aa34 100644 --- a/src/model/summary/zen.rs +++ b/src/model/summary/zen.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Summary: ZEN", //! see the [API document](https://tetr.io/about/api/#usersusersummarieszen). -use crate::model::cache::CacheData; +use crate::model::{cache::CacheData, error_response::ErrorResponse}; use serde::Deserialize; /// A struct for the response for the endpoint "User Summary: ZEN". @@ -14,7 +14,7 @@ pub struct ZenResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/summary/zenith.rs b/src/model/summary/zenith.rs index 54e1bc4..3a6bba1 100644 --- a/src/model/summary/zenith.rs +++ b/src/model/summary/zenith.rs @@ -5,7 +5,7 @@ //! - About the endpoint "User Summary: EXPERT QUICK PLAY", //! see the [API document](https://tetr.io/about/api/#usersusersummarieszenithex). -use crate::model::{cache::CacheData, summary::record::Record}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; use serde::Deserialize; /// A struct for the response for the endpoint "User Summary: QUICK PLAY". @@ -16,7 +16,7 @@ pub struct ZenithResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. @@ -86,7 +86,7 @@ pub struct ZenithExResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/user.rs b/src/model/user.rs index 496a72d..e46dc9e 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -5,7 +5,7 @@ use crate::{ client::{error::RspErr, Client}, - model::{cache::CacheData, role::Role}, + model::{cache::CacheData, error_response::ErrorResponse, role::Role}, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; use serde::Deserialize; @@ -19,7 +19,7 @@ pub struct UserResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. diff --git a/src/model/user_records.rs b/src/model/user_records.rs index 470af43..c7c819c 100644 --- a/src/model/user_records.rs +++ b/src/model/user_records.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Personal Records", //! see the [API document](https://tetr.io/about/api/#usersuserrecordsgamemodeleaderboard). -use crate::model::{cache::CacheData, summary::record::Record}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; use serde::Deserialize; /// A struct for the response for the endpoint "User Personal Records". @@ -14,7 +14,7 @@ pub struct UserRecordsResponse { #[serde(rename = "success")] pub is_success: bool, /// The reason the request failed. - pub error: Option, + pub error: Option, /// Data about how this request was cached. pub cache: Option, /// The requested data. From 0aebbfbbd0334adaaa10d52dfb9bb83a53ecc8d7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 15:57:38 +0900 Subject: [PATCH 151/255] =?UTF-8?q?=E2=9C=A8=20Add:=202=20fields=20to=20`E?= =?UTF-8?q?rrorResponse`=20model=20[#84]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/error_response.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/model/error_response.rs b/src/model/error_response.rs index a1a6bd6..1adebe3 100644 --- a/src/model/error_response.rs +++ b/src/model/error_response.rs @@ -10,6 +10,8 @@ pub struct ErrorResponse { /// /// e.g. "No such user! | Either you mistyped something, or the account no longer exists." pub msg: Option, + pub key: Option, + pub context: Option, } impl AsRef for ErrorResponse { From 66b297317d267541612654b3730f58283491f6a9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 16:08:24 +0900 Subject: [PATCH 152/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20argume?= =?UTF-8?q?nt=20documentation=20for=20consistency=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/record.rs | 6 +++--- src/client/param/record_leaderboard.rs | 12 ++++++------ src/client/param/user_leaderboard.rs | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/client/param/record.rs b/src/client/param/record.rs index 7346791..956fef1 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -145,7 +145,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `bound`: The upper bound to paginate downwards: + /// - `bound` - The upper bound to paginate downwards: /// take the lowest seen prisecter and pass that back through this field to continue scrolling. /// /// A **prisecter** is consisting of three floats. @@ -171,7 +171,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `bound`: The lower bound to paginate upwards: + /// - `bound` - The lower bound to paginate upwards: /// take the highest seen prisecter and pass that back through this field to continue scrolling. /// If use this, the search order is reversed /// (returning the lowest items that match the query) @@ -199,7 +199,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `limit`: The amount of entries to return. + /// - `limit` - The amount of entries to return. /// Between 1 and 100. 25 by default. /// /// # Examples diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index b026f1d..8daca2c 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -18,9 +18,9 @@ impl RecordsLeaderboardId { /// /// # Arguments /// - /// - `gamemode`: The game mode. e.g. `40l`. - /// - `scope`: The scope. ether [`Scope::Global`] or [`Scope::Country`]. - /// - `revolution_id`: The optional Revolution ID. e.g. `@2024w31`. + /// - `gamemode` - The game mode. e.g. `40l`. + /// - `scope` - The scope. ether [`Scope::Global`] or [`Scope::Country`]. + /// - `revolution_id` - The optional Revolution ID. e.g. `@2024w31`. /// /// # Examples /// @@ -133,7 +133,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `bound`: The upper bound to paginate downwards: + /// - `bound` - The upper bound to paginate downwards: /// take the lowest seen prisecter and pass that back through this field to continue scrolling. /// /// A **prisecter** is consisting of three floats. @@ -159,7 +159,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `bound`: The lower bound to paginate upwards: + /// - `bound` - The lower bound to paginate upwards: /// take the highest seen prisecter and pass that back through this field to continue scrolling. /// If use this, the search order is reversed /// (returning the lowest items that match the query) @@ -187,7 +187,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `limit`: The amount of entries to return. + /// - `limit` - The amount of entries to return. /// Between 1 and 100. 25 by default. /// /// # Examples diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index d2d3cda..b0650cc 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -101,7 +101,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `bound`: The upper bound to paginate downwards: + /// - `bound` -: The upper bound to paginate downwards: /// take the lowest seen prisecter and pass that back through this field to continue scrolling. /// /// A **prisecter** is consisting of three floats. @@ -127,7 +127,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `bound`: The lower bound to paginate upwards: + /// - `bound` - The lower bound to paginate upwards: /// take the highest seen prisecter and pass that back through this field to continue scrolling. /// If use this, the search order is reversed /// (returning the lowest items that match the query) @@ -155,7 +155,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `limit`: The amount of entries to return. + /// - `limit` - The amount of entries to return. /// Between 1 and 100. 25 by default. /// /// # Examples @@ -193,7 +193,7 @@ impl SearchCriteria { /// /// # Arguments /// - /// - `country`: The ISO 3166-1 country code to filter to. + /// - `country` - The ISO 3166-1 country code to filter to. /// /// # Examples /// From 4c0e4573bbd09464784efc9699dd84f4a6ae2d10 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 20 Nov 2024 17:06:42 +0900 Subject: [PATCH 153/255] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20there=20were=20ca?= =?UTF-8?q?ses=20country=20option=20in=20`user=5Fleaderboard::SearchCriter?= =?UTF-8?q?ia`=20is=20ignored=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/user_leaderboard.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index b0650cc..82e54dd 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -206,7 +206,7 @@ impl SearchCriteria { /// ``` pub fn country(self, country: &str) -> Self { Self { - country: Some(country.to_owned().to_uppercase()), + country: Some(country.to_owned()), ..self } } @@ -238,7 +238,7 @@ impl SearchCriteria { result.push(("limit".to_string(), l.to_string())); } if let Some(c) = self.country { - result.push(("country".to_string(), c)); + result.push(("country".to_string(), c.to_uppercase())); } result } From d728153636de3b1abfed2ae0340e626500930b54 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 24 Nov 2024 23:10:59 +0900 Subject: [PATCH 154/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20fix=20typos=20in?= =?UTF-8?q?=20doc=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 2 +- src/model/records_leaderboard.rs | 2 +- src/model/user.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index c826a0e..6d1a668 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -89,7 +89,7 @@ impl News { self.id.get_user().await } - /// Returns an UNIX timestamp when the news item was created. + /// Returns a UNIX timestamp when the news item was created. /// /// # Panics /// diff --git a/src/model/records_leaderboard.rs b/src/model/records_leaderboard.rs index 0c1a8fc..134dc3a 100644 --- a/src/model/records_leaderboard.rs +++ b/src/model/records_leaderboard.rs @@ -6,7 +6,7 @@ use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; use serde::Deserialize; -/// An struct for the response for the endpoint "Records Leaderboard". +/// A struct for the response for the endpoint "Records Leaderboard". #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct RecordsLeaderboardResponse { diff --git a/src/model/user.rs b/src/model/user.rs index e46dc9e..2b6d407 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -166,7 +166,7 @@ impl User { self.role.is_hidden() } - /// Returns an UNIX timestamp when the user's account created. + /// Returns a UNIX timestamp when the user's account created. /// /// If the account was created before join dates were recorded, `None` is returned. /// From 78b3db57ff59d07e5720d857c1a6f02be72296c2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 24 Nov 2024 23:16:42 +0900 Subject: [PATCH 155/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20fix=20doc=20for?= =?UTF-8?q?=20`Distinguishment`=20struct=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index 2b6d407..4da5870 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -365,21 +365,15 @@ impl AsRef for Connection { #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] pub struct Distinguishment { - // The type of distinguishment banner. + /// The type of distinguishment banner. #[serde(rename = "type")] pub _type: String, /// The detail of distinguishment banner. - /// - /// ***This field is not documented in the API documentation.** - pub detail: Option, + pub detail: Option, // EXCEPTION /// The header of distinguishment banner. - /// - /// ***This field is not documented in the API documentation.** - pub header: Option, - /// the footer of distinguishment banner. - /// - /// ***This field is not documented in the API documentation.** - pub footer: Option, + pub header: Option, // EXCEPTION + /// The footer of distinguishment banner. + pub footer: Option, // EXCEPTION } impl AsRef for Distinguishment { From b361c0fb6faee7cb1613b07b5fb7559ed52f15c7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sun, 24 Nov 2024 23:25:22 +0900 Subject: [PATCH 156/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20XX=20?= =?UTF-8?q?rank=20color=20constants=20are=20now=20deprecated=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.rs | 7 ++++--- src/model/league_rank.rs | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index a1d2b81..90fd0fc 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -61,10 +61,11 @@ pub mod rank_col { /// The X+ rank color. /// #a763ea pub const X_PLUS: u32 = 0xa763ea; - /// The XX rank color. - /// #ff8fff - pub const XX: u32 = 0xff8fff; /// The unranked (Z rank) color. /// #767671 pub const Z: u32 = 0x767671; + /// The XX rank color. + /// #ff8fff + #[deprecated(since = "0.6.0", note = "this is not official rank")] + pub const XX: u32 = 0xff8fff; } diff --git a/src/model/league_rank.rs b/src/model/league_rank.rs index de46b9b..f35fdfc 100644 --- a/src/model/league_rank.rs +++ b/src/model/league_rank.rs @@ -283,13 +283,14 @@ impl Rank { /// #a763ea pub const X_PLUS_COL: u32 = 0xa763ea; - /// The XX rank color. - /// #ff8fff - pub const XX_COL: u32 = 0xff8fff; - /// The unranked(Z rank) color. /// #767671 pub const Z_COL: u32 = 0x767671; + + /// The XX rank color. + /// #ff8fff + #[deprecated(since = "0.6.0", note = "this is not official rank")] + pub const XX_COL: u32 = 0xff8fff; } impl AsRef for Rank { From 3584015d9880f54626b7e871de344f55f9371eb6 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 00:37:12 +0900 Subject: [PATCH 157/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Improve:=20use?= =?UTF-8?q?=20`#[serde(default)]`=20instead=20of=20`Option`=20[#87]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 1 + src/model/leaderboard.rs | 3 ++- src/model/summary/record.rs | 1 + src/model/user.rs | 6 ++++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 04c23ee..2f7ff8a 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -91,6 +91,7 @@ pub struct User { pub role: Role, /// Whether the user is supporting TETR.IO. #[serde(rename = "supporter")] + #[serde(default)] // If the field is missing, it is false. pub is_supporter: bool, /// The user's country, if public. pub country: Option, diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 6780f75..a0c06b2 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -75,7 +75,8 @@ pub struct LeaderboardEntry { pub country: Option, /// Whether this user is currently supporting TETR.IO <3 #[serde(rename = "supporter")] - pub is_supporter: Option, // EXCEPTION + #[serde(default)] // If the field is missing, it is false. + pub is_supporter: bool, /// This user's current TETRA LEAGUE standing. pub league: League, /// The amount of online games played by this user. diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index ddc58e9..322f0e0 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -110,6 +110,7 @@ pub struct User { pub country: Option, /// Whether the user is supporting TETR.IO. #[serde(rename = "supporter")] + #[serde(default)] // If the field is missing, it is false. pub is_supporter: bool, } diff --git a/src/model/user.rs b/src/model/user.rs index 4da5870..0ff3b91 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -71,10 +71,12 @@ pub struct User { pub country: Option, /// Whether the user currently has a bad standing (recently banned). #[serde(rename = "badstanding")] - pub is_badstanding: Option, + #[serde(default)] // If the field is missing, it is false. + pub is_badstanding: bool, /// Whether the user is currently supporting TETR.IO <3 #[serde(rename = "supporter")] - pub is_supporter: Option, // EXCEPTION + #[serde(default)] // If the field is missing, it is false. + pub is_supporter: bool, /// An indicator of their total amount supported, /// between 0 and 4 inclusive. pub supporter_tier: u8, From 7e01fa31736176a940772dbe9ed38f36f8c8112a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 01:16:02 +0900 Subject: [PATCH 158/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20`Resp?= =?UTF-8?q?onseError`=20enum's=20enumerators=20now=20have=20appropriate=20?= =?UTF-8?q?types=20[#89]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/error.rs | 56 ++++++------------------------------------ src/client/response.rs | 12 +++------ 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/src/client/error.rs b/src/client/error.rs index 0e7290b..ba2d61c 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -1,40 +1,33 @@ //! A module for the error related types for the [`client`](crate::client) module. -use http::status::{InvalidStatusCode, StatusCode}; -use std::error::Error; +use http::status::StatusCode; use std::fmt; /// A enum for the response handling errors. #[derive(Debug)] pub enum ResponseError { /// The request failed. - RequestErr(String), + RequestErr(reqwest::Error), /// The response did not match the expected format but the HTTP request succeeded. /// /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - DeserializeErr(String), + DeserializeErr(reqwest::Error), /// The HTTP request failed and the response did not match the expected format. /// /// Even if the HTTP status code is not within 200-299. /// it may be possible to deserialize the response containing an error message, /// so the deserialization will be tried before returning this error. - HttpErr(Status), + HttpErr(StatusCode), } -impl Error for ResponseError {} +impl std::error::Error for ResponseError {} impl fmt::Display for ResponseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ResponseError::DeserializeErr(msg) => write!(f, "{}", msg), - ResponseError::RequestErr(msg) => write!(f, "{}", msg), - ResponseError::HttpErr(status) => { - if let Status::Valid(sc) = status { - write!(f, "HTTP error {}", sc) - } else { - write!(f, "HTTP error (Invalid HTTP status code)") - } - } + ResponseError::RequestErr(err) => write!(f, "{}", err), + ResponseError::HttpErr(status) => write!(f, "{}", status), } } } @@ -45,40 +38,7 @@ impl From for std::io::Error { } } -/// A HTTP status code. -#[derive(Debug)] -pub enum Status { - /// A valid HTTP status code. - /// If the status code greater or equal to 100 but less than 600. - Valid(StatusCode), - /// An invalid HTTP status code. - /// If the status code less than 100 or greater than 599. - Invalid(InvalidStatusCode), -} - pub(crate) type RspErr = Result; #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn response_errors_to_string() { - let de_err = ResponseError::DeserializeErr("Deserialize error".to_string()); - let req_err = ResponseError::RequestErr("Request error".to_string()); - let http_err = ResponseError::HttpErr(Status::Valid(http::StatusCode::SERVICE_UNAVAILABLE)); - let invalid_http_err = - ResponseError::HttpErr(Status::Invalid(if let Err(isc) = StatusCode::from_u16(0) { - isc - } else { - unreachable!() - })); - assert_eq!(de_err.to_string(), "Deserialize error"); - assert_eq!(req_err.to_string(), "Request error"); - assert_eq!(http_err.to_string(), "HTTP error 503 Service Unavailable"); - assert_eq!( - invalid_http_err.to_string(), - "HTTP error (Invalid HTTP status code)" - ); - } -} +mod tests {} diff --git a/src/client/response.rs b/src/client/response.rs index b254d28..d110e61 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,5 +1,4 @@ -use super::error::{ResponseError, RspErr, Status}; -use http::StatusCode; +use super::error::{ResponseError, RspErr}; use reqwest::{Error, Response}; use serde::Deserialize; @@ -26,16 +25,13 @@ where Err(e) => { // Whether the status code is within 200-299 or not. if is_success { - Err(ResponseError::DeserializeErr(e.to_string())) + Err(ResponseError::DeserializeErr(e)) } else { - match StatusCode::from_u16(status.as_u16()) { - Ok(c) => Err(ResponseError::HttpErr(Status::Valid(c))), - Err(e) => Err(ResponseError::HttpErr(Status::Invalid(e))), - } + Err(ResponseError::HttpErr(status)) } } } } - Err(e) => Err(ResponseError::RequestErr(e.to_string())), + Err(e) => Err(ResponseError::RequestErr(e)), } } From 0564eb3af9a0c00e356b709f9094e32862e2561b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 01:38:40 +0900 Subject: [PATCH 159/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20use=20"API=20doc?= =?UTF-8?q?ument"=20instead=20of=20`API=20documentation`=20for=20consisten?= =?UTF-8?q?cy=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 322f0e0..a6eabe0 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -199,7 +199,7 @@ impl AsRef for User { /// Otherwise, this is [`MultiPlayer`](`Results::MultiPlayer`). /// /// ***This structure may be changed drastically at any time. -/// See the [official API documentation](https://tetr.io/about/api/#recorddata) for more information.** +/// See the [official API document](https://tetr.io/about/api/#recorddata) for more information.** #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] #[non_exhaustive] From 74bb9a2984e818ec0f7c33af81641c4c5e1b74f8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 01:50:13 +0900 Subject: [PATCH 160/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20fix=20indefinite?= =?UTF-8?q?=20articles=20that=20are=20"a"=20but=20should=20be=20"an"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 4 ++-- src/client/error.rs | 2 +- src/client/param/pagination.rs | 2 +- src/model/achievement.rs | 2 +- src/model/league_rank.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client.rs b/src/client.rs index 71830f4..9e9c7e6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -948,7 +948,7 @@ impl Client { /// /// # Examples /// - /// Searches for a account by Discord ID `724976600873041940`. + /// Searches for an account by Discord ID `724976600873041940`. /// /// ```no_run /// use tetr_ch::client::{Client, param::search_user::SocialConnection}; @@ -956,7 +956,7 @@ impl Client { /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); /// - /// // Search for a account. + /// // Search for an account. /// let user = client.search_user( /// // By Discord ID `724976600873041940` /// SocialConnection::Discord("724976600873041940".to_string()) diff --git a/src/client/error.rs b/src/client/error.rs index ba2d61c..d2dd5a0 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -3,7 +3,7 @@ use http::status::StatusCode; use std::fmt; -/// A enum for the response handling errors. +/// An enum for the response handling errors. #[derive(Debug)] pub enum ResponseError { /// The request failed. diff --git a/src/client/param/pagination.rs b/src/client/param/pagination.rs index c143961..a6a2ee6 100644 --- a/src/client/param/pagination.rs +++ b/src/client/param/pagination.rs @@ -34,7 +34,7 @@ impl AsRef for Prisecter { /// A bound to paginate. #[derive(Clone, Debug)] pub enum Bound { - /// A upper bound. + /// An upper bound. /// Use this to paginate downwards: /// take the lowest seen prisecter and pass that back through this field to continue scrolling. /// diff --git a/src/model/achievement.rs b/src/model/achievement.rs index f9bbb67..3410a8c 100644 --- a/src/model/achievement.rs +++ b/src/model/achievement.rs @@ -38,7 +38,7 @@ pub struct Achievement { /// - 1 = NUMBER β€” [`Achievement::value`] is a positive number /// - 2 = TIME β€” [`Achievement::value`] is a positive amount of milliseconds /// - 3 = TIME_INV β€” [`Achievement::value`] is a negative amount of milliseconds; negate it before displaying - /// - 4 = FLOOR β€” [`Achievement::value`] is an altitude, A is a floor number + /// - 4 = FLOOR β€” [`Achievement::value`] is an altitude, [`Achievement::additional`] is a floor number /// - 5 = ISSUE β€” [`Achievement::value`] is the negative time of issue /// - 6 = NUMBER_INV β€” [`Achievement::value`] is a negative number; negate it before displaying #[serde(rename = "vt")] diff --git a/src/model/league_rank.rs b/src/model/league_rank.rs index f35fdfc..af039dc 100644 --- a/src/model/league_rank.rs +++ b/src/model/league_rank.rs @@ -3,7 +3,7 @@ use serde::Deserialize; use std::fmt::{self, Display, Formatter}; -/// A enum for the ranks in TETRA LEAGUE. +/// An enum for the ranks in TETRA LEAGUE. #[derive(Clone, Debug, Deserialize)] pub enum Rank { /// D rank. From 84021b474c6d72780bfc2f7b6951f2acb6462519 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 02:38:51 +0900 Subject: [PATCH 161/255] =?UTF-8?q?=F0=9F=A9=B9=20Fix:=20`UserData::user`?= =?UTF-8?q?=20field=20was=20not=20optional=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/searched_user.rs | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index 738e7d1..5b564e6 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -43,32 +43,7 @@ impl AsRef for SearchedUserResponse { #[non_exhaustive] pub struct UserData { /// The user information (TETRA.IO user account). - pub user: UserInfo, -} - -impl UserData { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.user.get_user().await - } - - /// Returns the user's profile URL. - pub fn profile_url(&self) -> String { - self.user.profile_url() - } + pub user: Option, } impl AsRef for UserData { From 442f904d17c24d985afc0942802756ea20c165e1 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:11:49 +0900 Subject: [PATCH 162/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`BadgeNe?= =?UTF-8?q?ws::type`=20filed=20to=20`id`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index 6d1a668..274a71a 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -289,7 +289,8 @@ pub struct BadgeNews { pub username: String, /// The badge's internal ID, and the filename of the badge icon /// (all PNGs within `/res/badges/`) - pub r#type: String, + #[serde(rename = "type")] + pub id: String, /// The badge's label. pub label: String, } @@ -320,7 +321,7 @@ impl BadgeNews { /// Returns the badge icon URL. pub fn badge_icon_url(&self) -> String { - format!("https://tetr.io/res/badges/{}.png", self.r#type) + format!("https://tetr.io/res/badges/{}.png", self.id) } } From 131909a91f1f733a1c939db346e7fe7bc6c690a7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:15:16 +0900 Subject: [PATCH 163/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Remove:=20`leaderb?= =?UTF-8?q?oard::LeaderboardEntry`=20struct=20to=20`LeaderboardUser`=20[#9?= =?UTF-8?q?1]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index a0c06b2..4c700d0 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -44,7 +44,7 @@ impl AsRef for LeaderboardResponse { #[non_exhaustive] pub struct Leaderboard { /// The matched users. - pub entries: Vec, + pub entries: Vec, } impl AsRef for Leaderboard { @@ -53,10 +53,11 @@ impl AsRef for Leaderboard { } } -/// An entry as a user. +/// User data in a user leaderboard. +/// This is used as an entry in the [`Leaderboard`] struct, #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct LeaderboardEntry { +pub struct LeaderboardUser { /// The user's internal ID. #[serde(rename = "_id")] pub id: UserId, @@ -105,7 +106,7 @@ pub struct LeaderboardEntry { pub prisecter: Prisecter, } -impl LeaderboardEntry { +impl LeaderboardUser { /// Gets the detailed information about the user. /// /// # Errors @@ -204,7 +205,7 @@ impl LeaderboardEntry { } } -impl AsRef for LeaderboardEntry { +impl AsRef for LeaderboardUser { fn as_ref(&self) -> &Self { self } From 625329ca8e1c53aca6e1fe0bac4dec5638cd40d5 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:24:00 +0900 Subject: [PATCH 164/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`leaderb?= =?UTF-8?q?oard::League`=20struct=20to=20`PartialLeagueData`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 4c700d0..0ea54e7 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -79,7 +79,7 @@ pub struct LeaderboardUser { #[serde(default)] // If the field is missing, it is false. pub is_supporter: bool, /// This user's current TETRA LEAGUE standing. - pub league: League, + pub league: PartialLeagueData, /// The amount of online games played by this user. /// If the user has chosen to hide this statistic, it will be -1. #[serde(rename = "gamesplayed")] @@ -211,10 +211,11 @@ impl AsRef for LeaderboardUser { } } -/// A user's current TETRA LEAGUE standing. +/// Partial summary of a user's TETRA LEAGUE standing. +/// This is used in the [`LeaderboardUser`] struct, #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct League { +pub struct PartialLeagueData { /// The amount of TETRA LEAGUE games played by this user. #[serde(rename = "gamesplayed")] pub games_played: u32, @@ -245,7 +246,7 @@ pub struct League { pub is_decaying: bool, } -impl AsRef for League { +impl AsRef for PartialLeagueData { fn as_ref(&self) -> &Self { self } From 0ee48536a2ee74f2eb71e9646f8a1c4d781b37e7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:26:03 +0900 Subject: [PATCH 165/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`Histori?= =?UTF-8?q?calEntry`=20struct=20to=20`PastUserWithPrisecter`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 0ea54e7..fe542a0 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -278,7 +278,7 @@ impl AsRef for HistoricalLeaderboardResponse { #[non_exhaustive] pub struct HistoricalLeaderboard { /// The matched historical user blobs. - pub entries: Vec, + pub entries: Vec, } impl AsRef for HistoricalLeaderboard { @@ -287,10 +287,11 @@ impl AsRef for HistoricalLeaderboard { } } -/// An entry as a historical user blobs. +/// Past season final placement information of a user, with a [`Prisecter`]. +/// This is used as an entry in the [`HistoricalLeaderboard`] struct, #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct HistoricalEntry { +pub struct PastUserWithPrisecter { /// The user's internal ID. #[serde(rename = "_id")] pub id: UserId, @@ -338,7 +339,7 @@ pub struct HistoricalEntry { pub prisecter: Prisecter, } -impl HistoricalEntry { +impl PastUserWithPrisecter { /// Gets the detailed information about the user. /// /// # Errors @@ -367,7 +368,7 @@ impl HistoricalEntry { } } -impl AsRef for HistoricalEntry { +impl AsRef for PastUserWithPrisecter { fn as_ref(&self) -> &Self { self } From 9774bf99319e2a667a9cbef3534d1beb22130722 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:41:41 +0900 Subject: [PATCH 166/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`Achieve?= =?UTF-8?q?mentLeaderboardEntry`=20struct=20to=20`AchievementLeaderboardUs?= =?UTF-8?q?er`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 2f7ff8a..2dd2fee 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -43,7 +43,7 @@ pub struct AchievementInfo { /// The achievement info. pub achievement: Achievement, /// The entries in the achievement's leaderboard. - pub leaderboard: Vec, + pub leaderboard: Vec, /// Scores required to obtain the achievement: pub cutoffs: Cutoffs, } @@ -54,10 +54,10 @@ impl AsRef for AchievementInfo { } } -/// An entry in an achievement's leaderboard. +/// User's achievement data in an achievement's leaderboard. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct AchievementLeaderboardEntry { +pub struct AchievementLeaderboardUser { /// The user owning the achievement. #[serde(rename = "u")] pub user: User, @@ -72,7 +72,7 @@ pub struct AchievementLeaderboardEntry { pub last_updated_at: String, } -impl AsRef for AchievementLeaderboardEntry { +impl AsRef for AchievementLeaderboardUser { fn as_ref(&self) -> &Self { self } From 242abb483bf0d9a5ae6355bd34f6871c11bb75a3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:44:45 +0900 Subject: [PATCH 167/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`achieve?= =?UTF-8?q?ment=5Finfo::User`=20struct=20to=20`PartialUser`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 2dd2fee..486361f 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -60,7 +60,7 @@ impl AsRef for AchievementInfo { pub struct AchievementLeaderboardUser { /// The user owning the achievement. #[serde(rename = "u")] - pub user: User, + pub user: PartialUser, /// The achieved score in the achievement. #[serde(rename = "v")] pub value: f64, @@ -78,10 +78,11 @@ impl AsRef for AchievementLeaderboardUser { } } -/// A user owning an achievement. +/// Partial information about a user. +/// This is used in the [`AchievementLeaderboardUser`] struct. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct User { +pub struct PartialUser { /// The user's internal ID. #[serde(rename = "_id")] pub id: UserId, @@ -97,7 +98,7 @@ pub struct User { pub country: Option, } -impl User { +impl PartialUser { /// Gets the detailed information about the user. /// /// # Errors @@ -176,7 +177,7 @@ impl User { } } -impl AsRef for User { +impl AsRef for PartialUser { fn as_ref(&self) -> &Self { self } From 4c966154f1a5dd9de7e03669fadfe4b8f940bc30 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:50:33 +0900 Subject: [PATCH 168/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`league:?= =?UTF-8?q?:League`=20struct=20to=20`LeagueData`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/league.rs | 8 ++++---- src/model/summary/mod.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 4882c73..b39b39d 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -19,7 +19,7 @@ pub struct LeagueResponse { /// Data about how this request was cached. pub cache: Option, /// The requested data. - pub data: Option, + pub data: Option, } impl AsRef for LeagueResponse { @@ -34,7 +34,7 @@ impl AsRef for LeagueResponse { /// and was not banned or hidden. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct League { +pub struct LeagueData { /// The amount of TETRA LEAGUE games played by this user. #[serde(rename = "gamesplayed")] pub games_played: u32, @@ -88,7 +88,7 @@ pub struct League { pub past: HashMap, } -impl League { +impl LeagueData { /// Returns the user's progress percentage in the rank. /// /// But there are cases where values less than 0 or greater than 100 are returned, @@ -113,7 +113,7 @@ impl League { } } -impl AsRef for League { +impl AsRef for LeagueData { fn as_ref(&self) -> &Self { self } diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index 2d7c84b..a199570 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -47,7 +47,7 @@ pub struct AllSummaries { #[serde(rename = "zenithex")] pub zenith_ex: zenith::Zenith, /// The user's TETRA LEAGUE summary data. - pub league: league::League, + pub league: league::LeagueData, /// The user's ZEN summary data. pub zen: zen::Zen, /// The user's achievements. From f41e0756c3d87c99551cf404fa0a8c0653efa9bd Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:54:38 +0900 Subject: [PATCH 169/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`PastSea?= =?UTF-8?q?son`=20struct=20to=20`PastUser`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/league.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index b39b39d..c8b9e78 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -85,7 +85,7 @@ pub struct LeagueData { /// dip below them to go down a rank. -1 if unranked (or the worst rank). pub prev_at: Option, /// An object mapping past season IDs to past season final placement information. - pub past: HashMap, + pub past: HashMap, } impl LeagueData { @@ -119,10 +119,10 @@ impl AsRef for LeagueData { } } -/// Past season final placement information. +/// Past season final placement information of a user. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct PastSeason { +pub struct PastUser { /// The season ID. pub season: String, /// The username the user had at the time. @@ -161,7 +161,7 @@ pub struct PastSeason { pub vs: f64, } -impl PastSeason { +impl PastUser { /// Returns the national flag URL of the user's country. pub fn national_flag_url(&self) -> Option { self.country @@ -170,7 +170,7 @@ impl PastSeason { } } -impl AsRef for PastSeason { +impl AsRef for PastUser { fn as_ref(&self) -> &Self { self } From 04771b9c9c849aa61fe434900fcdd1f43d9284f4 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 03:56:31 +0900 Subject: [PATCH 170/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`record:?= =?UTF-8?q?:User`=20struct=20to=20`PartialUser`=20[#01]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index a6eabe0..2264827 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -45,13 +45,13 @@ pub struct Record { /// If revolved away, the revolution it belongs to. pub revolution: Option, /// The user owning the Record. - pub user: Option, // EXCEPTION + pub user: Option, // EXCEPTION /// Other users mentioned in the Record. /// /// If not empty, this is a multiplayer game /// (this changes the enumerator of the [`Record::results`] field). #[serde(rename = "otherusers")] - pub other_users: Vec, + pub other_users: Vec, /// The leaderboards this Record is mentioned in. /// /// e.g. `["40l_global", "40l_country_JP"]` @@ -94,10 +94,11 @@ impl AsRef for Record { } } -/// A User owning a Record. +/// Partial information about a user. +/// This is used in the [`Record`] struct. #[derive(Clone, Debug, Deserialize)] #[non_exhaustive] -pub struct User { +pub struct PartialUser { /// The user's user ID. pub id: UserId, /// The user's username. @@ -114,7 +115,7 @@ pub struct User { pub is_supporter: bool, } -impl User { +impl PartialUser { /// Gets the detailed information about the user. /// /// # Errors @@ -187,7 +188,7 @@ impl User { } } -impl AsRef for User { +impl AsRef for PartialUser { fn as_ref(&self) -> &Self { self } From 32d3f5d6527e0ca494435f3f01879b215c036285 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 04:05:00 +0900 Subject: [PATCH 171/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`LeagueD?= =?UTF-8?q?ata::decaying`=20field=20to=20`is=5Fdecaying`=20[#91]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/league.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index c8b9e78..77d21b0 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -47,7 +47,7 @@ pub struct LeagueData { /// If over 100, this user is unranked. pub rd: Option, /// Whether this user's RD is rising (has not played in the last week). - pub decaying: bool, + pub is_decaying: bool, /// This user's TR (Tetra Rating), or -1 if less than 10 games were played. pub tr: f64, /// This user's GLIXARE score (a % chance of beating an average player), From f56961e0d1903852828362fc31598a7d77b793ce Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 04:09:17 +0900 Subject: [PATCH 172/255] =?UTF-8?q?=F0=9F=A9=B9=20Fix:=20`News::id`=20fiel?= =?UTF-8?q?d=20type=20is=20no=20longer=20`UserId`=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index 274a71a..a83bc8c 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -11,7 +11,7 @@ use crate::{ cache::CacheData, error_response::ErrorResponse, league_rank::Rank, - user::{UserId, UserResponse}, + user::UserResponse, }, util::to_unix_ts, }; @@ -58,7 +58,7 @@ impl AsRef for NewsItems { pub struct News { /// The item's internal ID. #[serde(rename = "_id")] - pub id: UserId, + pub id: String, /// The item's stream. pub stream: String, /// The item's type. @@ -71,24 +71,6 @@ pub struct News { } impl News { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.id.get_user().await - } - /// Returns a UNIX timestamp when the news item was created. /// /// # Panics From 3805acad2cfe2c3bca0aee3ba7411809036c715a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 04:12:34 +0900 Subject: [PATCH 173/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Improve:=20reor?= =?UTF-8?q?der=20`Client`=20struct's=20methods=20for=20better=20a11y=20in?= =?UTF-8?q?=20doc=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 100 +++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/client.rs b/src/client.rs index 9e9c7e6..5735c56 100644 --- a/src/client.rs +++ b/src/client.rs @@ -127,48 +127,40 @@ impl Client { response(res).await } - /// Gets some statistics about the TETR.IO. - /// - /// About the endpoint "Server Statistics", - /// see the [API document](https://tetr.io/about/api/#generalstats). - /// - /// # Examples + /// Searches for a TETR.IO user account by the social connection. /// - /// ```no_run - /// use tetr_ch::client::Client; + /// About the endpoint "User Search", + /// see the [API document](https://tetr.io/about/api/#userssearchquery). /// - /// # async fn run() -> std::io::Result<()> { - /// let client = Client::new(); - /// // Get the statistics. - /// let user = client.get_server_stats().await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn get_server_stats(self) -> RspErr { - let url = format!("{}general/stats", API_URL); - let res = self.client.get(url).send().await; - response(res).await - } - - /// Gets the array of the user activity over the last 2 days. + /// # Arguments /// - /// About the endpoint "Server Activity", - /// see the [API document](https://tetr.io/about/api/#generalactivity). + /// - `social_connection` - The social connection to look up. /// /// # Examples /// + /// Searches for an account by Discord ID `724976600873041940`. + /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::client::{Client, param::search_user::SocialConnection}; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// // Get the activity. - /// let user = client.get_server_activity().await?; + /// + /// // Search for an account. + /// let user = client.search_user( + /// // By Discord ID `724976600873041940` + /// SocialConnection::Discord("724976600873041940".to_string()) + /// ).await?; /// # Ok(()) /// # } + /// + /// # tokio_test::block_on(run()); /// ``` - pub async fn get_server_activity(self) -> RspErr { - let url = format!("{}general/activity", API_URL); + pub async fn search_user( + self, + social_connection: SocialConnection, + ) -> RspErr { + let url = format!("{}users/search/{}", API_URL, social_connection.to_param()); let res = self.client.get(url).send().await; response(res).await } @@ -937,40 +929,48 @@ impl Client { response(res).await } - /// Searches for a TETR.IO user account by the social connection. + /// Gets some statistics about the TETR.IO. /// - /// About the endpoint "User Search", - /// see the [API document](https://tetr.io/about/api/#userssearchquery). + /// About the endpoint "Server Statistics", + /// see the [API document](https://tetr.io/about/api/#generalstats). /// - /// # Arguments + /// # Examples /// - /// - `social_connection` - The social connection to look up. + /// ```no_run + /// use tetr_ch::client::Client; /// - /// # Examples + /// # async fn run() -> std::io::Result<()> { + /// let client = Client::new(); + /// // Get the statistics. + /// let user = client.get_server_stats().await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn get_server_stats(self) -> RspErr { + let url = format!("{}general/stats", API_URL); + let res = self.client.get(url).send().await; + response(res).await + } + + /// Gets the array of the user activity over the last 2 days. /// - /// Searches for an account by Discord ID `724976600873041940`. + /// About the endpoint "Server Activity", + /// see the [API document](https://tetr.io/about/api/#generalactivity). + /// + /// # Examples /// /// ```no_run - /// use tetr_ch::client::{Client, param::search_user::SocialConnection}; + /// use tetr_ch::client::Client; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); - /// - /// // Search for an account. - /// let user = client.search_user( - /// // By Discord ID `724976600873041940` - /// SocialConnection::Discord("724976600873041940".to_string()) - /// ).await?; + /// // Get the activity. + /// let user = client.get_server_activity().await?; /// # Ok(()) /// # } - /// - /// # tokio_test::block_on(run()); /// ``` - pub async fn search_user( - self, - social_connection: SocialConnection, - ) -> RspErr { - let url = format!("{}users/search/{}", API_URL, social_connection.to_param()); + pub async fn get_server_activity(self) -> RspErr { + let url = format!("{}general/activity", API_URL); let res = self.client.get(url).send().await; response(res).await } From 6519108dabb1cbf91f13cfbfea8672b1154cadc3 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 25 Nov 2024 04:17:03 +0900 Subject: [PATCH 174/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Improve:=20reor?= =?UTF-8?q?der=20`UserId`=20struct's=20methods=20for=20better=20a11y=20in?= =?UTF-8?q?=20doc=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index 0ff3b91..36aa697 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -437,12 +437,6 @@ impl AsRef for AchievementRatingCounts { pub struct UserId(pub String); impl UserId { - /// Returns the user's internal ID. - #[deprecated(since = "0.6.0", note = "please use the `.to_string()` method instead")] - pub fn id(&self) -> &str { - &self.0 - } - /// Gets the detailed information about the user. /// /// # Errors @@ -460,6 +454,12 @@ impl UserId { pub async fn get_user(&self) -> RspErr { Client::new().get_user(&self.to_string()).await } + + /// Returns the user's internal ID. + #[deprecated(since = "0.6.0", note = "please use the `.to_string()` method instead")] + pub fn id(&self) -> &str { + &self.0 + } } impl AsRef for UserId { From ec3f44772172460230cf2339f45139ce382d1002 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 09:50:11 +0900 Subject: [PATCH 175/255] =?UTF-8?q?=E2=9C=A8=20Add:=20utility=20model=20`T?= =?UTF-8?q?imestamp`=20and=20use=20it=20for=20`User::created=5Fat`=20field?= =?UTF-8?q?=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/mod.rs | 1 + src/model/user.rs | 6 +++--- src/model/util/mod.rs | 3 +++ src/model/util/timestamp.rs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/model/util/mod.rs create mode 100644 src/model/util/timestamp.rs diff --git a/src/model/mod.rs b/src/model/mod.rs index 8e51b70..af1e159 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -17,3 +17,4 @@ pub mod server_stats; pub mod summary; pub mod user; pub mod user_records; +pub mod util; diff --git a/src/model/user.rs b/src/model/user.rs index 36aa697..91fb6cf 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -5,7 +5,7 @@ use crate::{ client::{error::RspErr, Client}, - model::{cache::CacheData, error_response::ErrorResponse, role::Role}, + model::{cache::CacheData, error_response::ErrorResponse, role::Role, util::timestamp::Timestamp}, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; use serde::Deserialize; @@ -46,7 +46,7 @@ pub struct User { /// When the user account was created. /// If not set, this account was created before join dates were recorded. #[serde(rename = "ts")] - pub created_at: Option, + pub created_at: Option, /// If this user is a bot, the bot's operator. #[serde(rename = "botmaster")] pub bot_master: Option, @@ -176,7 +176,7 @@ impl User { /// /// Panics if failed to parse the timestamp. pub fn created_at(&self) -> Option { - self.created_at.as_ref().map(|ts| to_unix_ts(ts)) + self.created_at.as_ref().map(|ts| ts.unix_ts()) } /// Whether the user has any badges. diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs new file mode 100644 index 0000000..e03cc9e --- /dev/null +++ b/src/model/util/mod.rs @@ -0,0 +1,3 @@ +//! Utility models for the models. + +pub mod timestamp; diff --git a/src/model/util/timestamp.rs b/src/model/util/timestamp.rs new file mode 100644 index 0000000..b54b905 --- /dev/null +++ b/src/model/util/timestamp.rs @@ -0,0 +1,33 @@ +//! A model for timestamp. + +use crate::util::to_unix_ts; +use serde::Deserialize; +use std::fmt; + +/// A timestamp string. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub struct Timestamp(String); + +impl Timestamp { + /// Returns the UNIX timestamp. + /// + /// # Panics + /// + /// Panics if failed to parse the given string. + pub fn unix_ts(&self) -> i64 { + to_unix_ts(&self.0) + } +} + +impl AsRef for Timestamp { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} From f67bb540cbc5425cdf8affa45f4c095945472197 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 09:55:05 +0900 Subject: [PATCH 176/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 5 +---- src/model/user.rs | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index a83bc8c..7c89c1f 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -8,10 +8,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ - cache::CacheData, - error_response::ErrorResponse, - league_rank::Rank, - user::UserResponse, + cache::CacheData, error_response::ErrorResponse, league_rank::Rank, user::UserResponse, }, util::to_unix_ts, }; diff --git a/src/model/user.rs b/src/model/user.rs index 91fb6cf..45bb5fa 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -5,7 +5,9 @@ use crate::{ client::{error::RspErr, Client}, - model::{cache::CacheData, error_response::ErrorResponse, role::Role, util::timestamp::Timestamp}, + model::{ + cache::CacheData, error_response::ErrorResponse, role::Role, util::timestamp::Timestamp, + }, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; use serde::Deserialize; From f8e3aed53f0a432b66c21c56411b8ae7c8b44134 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 10:10:43 +0900 Subject: [PATCH 177/255] =?UTF-8?q?=E2=9C=A8=20Add:=20utility=20model=20`B?= =?UTF-8?q?adgeId`=20and=20use=20it=20for=20`Badge::id`=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 9 ++++++--- src/model/util/badge_id.rs | 28 ++++++++++++++++++++++++++++ src/model/util/mod.rs | 1 + 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/model/util/badge_id.rs diff --git a/src/model/user.rs b/src/model/user.rs index 45bb5fa..ca84062 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -6,7 +6,10 @@ use crate::{ client::{error::RspErr, Client}, model::{ - cache::CacheData, error_response::ErrorResponse, role::Role, util::timestamp::Timestamp, + cache::CacheData, + error_response::ErrorResponse, + role::Role, + util::{badge_id::BadgeId, timestamp::Timestamp}, }, util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, }; @@ -256,7 +259,7 @@ pub struct Badge { /// Note that badge IDs may include forward slashes. /// Please do not encode them! /// Follow the folder structure. - pub id: String, + pub id: BadgeId, /// The badge's group ID. /// If multiple badges have the same group ID, they are rendered together. pub group: Option, @@ -279,7 +282,7 @@ pub struct Badge { impl Badge { /// Returns the badge icon URL. pub fn badge_icon_url(&self) -> String { - format!("https://tetr.io/res/badges/{}.png", self.id) + self.id.icon_url() } /// Returns a UNIX timestamp when the badge was achieved. diff --git a/src/model/util/badge_id.rs b/src/model/util/badge_id.rs new file mode 100644 index 0000000..a77953d --- /dev/null +++ b/src/model/util/badge_id.rs @@ -0,0 +1,28 @@ +//! A model for the badge's internal IDs. + +use serde::Deserialize; +use std::fmt; + +/// A badge's internal ID. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub struct BadgeId(String); + +impl BadgeId { + /// Returns the badge icon URL. + pub fn icon_url(&self) -> String { + format!("https://tetr.io/res/badges/{}.png", self.0) + } +} + +impl AsRef for BadgeId { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for BadgeId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index e03cc9e..0441008 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -1,3 +1,4 @@ //! Utility models for the models. +pub mod badge_id; pub mod timestamp; From 788d0b59d13bf1cab1810b6b2f44bd77e044b575 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 10:20:16 +0900 Subject: [PATCH 178/255] =?UTF-8?q?=E2=9C=A8=20Add:=20private=20method=20`?= =?UTF-8?q?Timestamp::new`=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/util/timestamp.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/model/util/timestamp.rs b/src/model/util/timestamp.rs index b54b905..7787a4b 100644 --- a/src/model/util/timestamp.rs +++ b/src/model/util/timestamp.rs @@ -10,6 +10,11 @@ use std::fmt; pub struct Timestamp(String); impl Timestamp { + /// Creates a new `Timestamp`. + pub(crate) fn new(ts: String) -> Self { + Self(ts) + } + /// Returns the UNIX timestamp. /// /// # Panics From 9514d230f01aa009e5c54dabc14bf26880c028dd Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 10:21:01 +0900 Subject: [PATCH 179/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`Timestamp`=20for=20`Badge::received=5Fat`?= =?UTF-8?q?=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 6 +++--- src/util.rs | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/model/user.rs b/src/model/user.rs index ca84062..9854521 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -11,7 +11,7 @@ use crate::{ role::Role, util::{badge_id::BadgeId, timestamp::Timestamp}, }, - util::{deserialize_from_non_str_to_none, max_f64, to_unix_ts}, + util::{deserialize_from_non_str_to_none, max_f64}, }; use serde::Deserialize; use std::fmt; @@ -276,7 +276,7 @@ pub struct Badge { deserialize_with = "deserialize_from_non_str_to_none", default )] - pub received_at: Option, + pub received_at: Option, } impl Badge { @@ -291,7 +291,7 @@ impl Badge { /// /// Panics if failed to parse the timestamp. pub fn received_at(&self) -> Option { - self.received_at.as_ref().map(|ts| to_unix_ts(ts)) + self.received_at.as_ref().map(|ts| ts.unix_ts()) } } diff --git a/src/util.rs b/src/util.rs index 63a1b94..865cb76 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,6 @@ //! Utilities for tetr-ch-rs. +use crate::model::util::timestamp::Timestamp; use chrono::DateTime; use serde::Deserialize; use serde_json::Value; @@ -24,19 +25,19 @@ pub(crate) fn max_f64(v1: f64, v2: f64) -> f64 { } } -/// Deserialize from the given value to `Option`. +/// Deserializes from the given value to `Option`. /// -/// If the given value is string, returns `Some(String)`. +/// If the given value is string, returns `Some(Timestamp)`. /// Otherwise, returns `None`. pub(crate) fn deserialize_from_non_str_to_none<'de, D>( deserializer: D, -) -> Result, D::Error> +) -> Result, D::Error> where D: serde::Deserializer<'de>, { let value: Value = Deserialize::deserialize(deserializer)?; if let Some(received_at) = value.as_str() { - Ok(Some(received_at.to_owned())) + Ok(Some(Timestamp::new(received_at.to_owned()))) } else { Ok(None) } From 074b9b110902c4401798c01ce40fdaabb33521d6 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 10:28:13 +0900 Subject: [PATCH 180/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`UserId`=20model?= =?UTF-8?q?=20to=20`crate::model::util::user=5Fid`=20module=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 7 ++--- src/model/leaderboard.rs | 3 ++- src/model/searched_user.rs | 4 +-- src/model/summary/record.rs | 5 +--- src/model/user.rs | 46 +------------------------------- src/model/util/mod.rs | 1 + src/model/util/user_id.rs | 50 +++++++++++++++++++++++++++++++++++ 7 files changed, 58 insertions(+), 58 deletions(-) create mode 100644 src/model/util/user_id.rs diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 486361f..ef37612 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -6,11 +6,8 @@ use crate::{ client::error::RspErr, model::{ - achievement::Achievement, - cache::CacheData, - error_response::ErrorResponse, - role::Role, - user::{UserId, UserResponse}, + achievement::Achievement, cache::CacheData, error_response::ErrorResponse, role::Role, + user::UserResponse, util::user_id::UserId, }, }; use serde::Deserialize; diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index fe542a0..ebf4c48 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -12,7 +12,8 @@ use crate::{ error_response::ErrorResponse, league_rank::Rank, role::Role, - user::{AchievementRatingCounts, UserId, UserResponse}, + user::{AchievementRatingCounts, UserResponse}, + util::user_id::UserId, }, util::{max_f64, to_unix_ts}, }; diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index 5b564e6..f2f60ba 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -6,9 +6,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ - cache::CacheData, - error_response::ErrorResponse, - user::{UserId, UserResponse}, + cache::CacheData, error_response::ErrorResponse, user::UserResponse, util::user_id::UserId, }, }; use serde::Deserialize; diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 2264827..56bb1be 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -4,10 +4,7 @@ use crate::{ client::{error::RspErr, param::pagination::Prisecter}, - model::{ - league_rank::Rank, - user::{UserId, UserResponse}, - }, + model::{league_rank::Rank, user::UserResponse, util::user_id::UserId}, util::to_unix_ts, }; use serde::Deserialize; diff --git a/src/model/user.rs b/src/model/user.rs index 9854521..5e2256b 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -4,17 +4,15 @@ //! see the [API document](https://tetr.io/about/api/#usersuser). use crate::{ - client::{error::RspErr, Client}, model::{ cache::CacheData, error_response::ErrorResponse, role::Role, - util::{badge_id::BadgeId, timestamp::Timestamp}, + util::{badge_id::BadgeId, timestamp::Timestamp, user_id::UserId}, }, util::{deserialize_from_non_str_to_none, max_f64}, }; use serde::Deserialize; -use std::fmt; /// A struct for the response for the endpoint "User Info". #[derive(Clone, Debug, Deserialize)] @@ -436,45 +434,3 @@ impl AsRef for AchievementRatingCounts { self } } - -/// A user's internal ID. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] -pub struct UserId(pub String); - -impl UserId { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.to_string()).await - } - - /// Returns the user's internal ID. - #[deprecated(since = "0.6.0", note = "please use the `.to_string()` method instead")] - pub fn id(&self) -> &str { - &self.0 - } -} - -impl AsRef for UserId { - fn as_ref(&self) -> &Self { - self - } -} - -impl fmt::Display for UserId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index 0441008..cd84db7 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -2,3 +2,4 @@ pub mod badge_id; pub mod timestamp; +pub mod user_id; diff --git a/src/model/util/user_id.rs b/src/model/util/user_id.rs new file mode 100644 index 0000000..a59230e --- /dev/null +++ b/src/model/util/user_id.rs @@ -0,0 +1,50 @@ +//! A model for the user IDs, + +use crate::{ + client::{error::RspErr, Client}, + model::user::UserResponse, +}; +use serde::Deserialize; +use std::fmt; + +/// A user's internal ID. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +pub struct UserId(String); + +impl UserId { + /// Gets the detailed information about the user. + /// + /// # Errors + /// + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. + pub async fn get_user(&self) -> RspErr { + Client::new().get_user(&self.to_string()).await + } + + /// Returns the user's internal ID. + #[deprecated(since = "0.6.0", note = "please use the `.to_string()` method instead")] + pub fn id(&self) -> &str { + &self.0 + } +} + +impl AsRef for UserId { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for UserId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} From 51ea97d759701141f4a7bb6a37ff12122fb8d818 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 12:26:11 +0900 Subject: [PATCH 181/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20`get?= =?UTF-8?q?=5Fnews=5Flatest::stream`=20argument=20is=20now=20generic=20typ?= =?UTF-8?q?e=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 6 +++--- src/client/param/news_stream.rs | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/client.rs b/src/client.rs index 5735c56..1d3b612 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,7 +3,7 @@ use self::{ error::RspErr, param::{ - news_stream::NewsStream, + news_stream::ToNewsStreamParam, record::{self, Gamemode}, record_leaderboard::{self, RecordsLeaderboardId}, search_user::SocialConnection, @@ -918,9 +918,9 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_news_latest( + pub async fn get_news_latest( self, - stream: NewsStream, + stream: S, limit: u8, ) -> RspErr { validate_limit(limit); diff --git a/src/client/param/news_stream.rs b/src/client/param/news_stream.rs index 518497e..ea138ae 100644 --- a/src/client/param/news_stream.rs +++ b/src/client/param/news_stream.rs @@ -9,7 +9,7 @@ pub enum NewsStream { User(String), } -impl NewsStream { +impl ToNewsStreamParam for NewsStream { /// Converts into a parameter string. /// /// # Examples @@ -21,10 +21,15 @@ impl NewsStream { /// assert_eq!(global.to_param(), "global"); /// assert_eq!(user.to_param(), "user_621db46d1d638ea850be2aa0"); /// ``` - pub(crate) fn to_param(&self) -> String { + fn to_param(self) -> String { match self { NewsStream::Global => "global".to_string(), NewsStream::User(id) => format!("user_{}", id), } } } + +pub trait ToNewsStreamParam { + /// Converts into a parameter string. + fn to_param(self) -> String; +} From c463ccebf906fb998750525bba1d564e63110015 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 12:34:57 +0900 Subject: [PATCH 182/255] =?UTF-8?q?=E2=9C=A8=20Add:=20utility=20model=20`N?= =?UTF-8?q?ewsStream`=20and=20use=20it=20for=20`News::stream`=20field=20[#?= =?UTF-8?q?93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 3 +- src/model/util/mod.rs | 1 + src/model/util/news_stream.rs | 70 +++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/model/util/news_stream.rs diff --git a/src/model/news.rs b/src/model/news.rs index 7c89c1f..9896c5b 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -9,6 +9,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ cache::CacheData, error_response::ErrorResponse, league_rank::Rank, user::UserResponse, + util::news_stream::NewsStream, }, util::to_unix_ts, }; @@ -57,7 +58,7 @@ pub struct News { #[serde(rename = "_id")] pub id: String, /// The item's stream. - pub stream: String, + pub stream: NewsStream, /// The item's type. pub r#type: String, /// The item's records. diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index cd84db7..2794f4e 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -1,5 +1,6 @@ //! Utility models for the models. pub mod badge_id; +pub mod news_stream; pub mod timestamp; pub mod user_id; diff --git a/src/model/util/news_stream.rs b/src/model/util/news_stream.rs new file mode 100644 index 0000000..21b5620 --- /dev/null +++ b/src/model/util/news_stream.rs @@ -0,0 +1,70 @@ +//! A model for news streams. + +use crate::{ + client::{error::RspErr, param::news_stream::ToNewsStreamParam, Client}, + model::news::NewsLatestResponse, +}; +use serde::Deserialize; +use std::fmt; + +/// A news stream. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub struct NewsStream(String); + +impl NewsStream { + /// Gets the latest news items in the stream. + /// Calls the [`Client::get_news_latest`](crate::client::Client::get_news_latest) method. + /// + /// # Arguments + /// + /// - `limit` - The amount of entries to return, between 1 and 100. + /// + /// # Panics + /// + /// Panics if the argument `limit` is not between 1 and 100. + /// + /// # Errors + /// + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. + pub async fn get_news_items(self, limit: u8) -> RspErr { + Client::new().get_news_latest(self, limit).await + } + + /// Whether the stream is the global news stream. + pub fn is_global_steam(&self) -> bool { + self.0 == "global" + } + + /// Whether the stream is a news stream of a user. + pub fn is_user_steam(&self) -> bool { + self.0.starts_with("user_") + } +} + +impl AsRef for NewsStream { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for NewsStream { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl ToNewsStreamParam for NewsStream { + fn to_param(self) -> String { + self.0 + } +} From 49faa22ef2732c5c4e508cb149c3da6b8e99142e Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 12:42:43 +0900 Subject: [PATCH 183/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`Timestamp`=20for=20`News::created=5Fat`?= =?UTF-8?q?=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index 9896c5b..4d43050 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -9,9 +9,8 @@ use crate::{ client::{error::RspErr, Client}, model::{ cache::CacheData, error_response::ErrorResponse, league_rank::Rank, user::UserResponse, - util::news_stream::NewsStream, - }, - util::to_unix_ts, + util::{news_stream::NewsStream, timestamp::Timestamp}, + } }; use serde::Deserialize; @@ -65,7 +64,7 @@ pub struct News { pub data: NewsData, /// The item's creation date. #[serde(rename = "ts")] - pub created_at: String, + pub created_at: Timestamp, } impl News { @@ -75,7 +74,7 @@ impl News { /// /// Panics if failed to parse the timestamp. pub fn created_at(&self) -> i64 { - to_unix_ts(&self.created_at) + self.created_at.unix_ts() } } From 213dd0c3397c5d9e2bf16d1ed8cfe56dc7d37e5f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 13:38:41 +0900 Subject: [PATCH 184/255] =?UTF-8?q?=E2=9C=A8=20Add:=20utility=20model=20`G?= =?UTF-8?q?amemode`=20and=20use=20it=20for=20`LeaderboardNews::Gamemode`?= =?UTF-8?q?=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 4 ++-- src/model/util/gamemode.rs | 37 +++++++++++++++++++++++++++++++++++++ src/model/util/mod.rs | 1 + 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/model/util/gamemode.rs diff --git a/src/model/news.rs b/src/model/news.rs index 4d43050..4cac9d0 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -9,7 +9,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ cache::CacheData, error_response::ErrorResponse, league_rank::Rank, user::UserResponse, - util::{news_stream::NewsStream, timestamp::Timestamp}, + util::{gamemode::Gamemode, news_stream::NewsStream, timestamp::Timestamp}, } }; use serde::Deserialize; @@ -163,7 +163,7 @@ pub struct LeaderboardNews { /// The username of the person who got the leaderboard spot. pub username: String, /// The game mode played. - pub gametype: String, + pub gametype: Gamemode, /// The global rank achieved. pub rank: u32, /// The result (score or time) achieved. diff --git a/src/model/util/gamemode.rs b/src/model/util/gamemode.rs new file mode 100644 index 0000000..ed39eca --- /dev/null +++ b/src/model/util/gamemode.rs @@ -0,0 +1,37 @@ +//! A model for the game modes. + +use crate::client::param::record::{self, Gamemode as RecordGm}; +use serde::Deserialize; +use std::fmt; + +/// A game mode. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub struct Gamemode(String); + +impl Gamemode { + /// Converts into a [`crate::client::param::record::Gamemode`]. + /// If failed, returns the game mode as is as `Err`. + pub fn into_record_gamemode(&self) -> Result { + match self.0.as_str() { + "40l" => Ok(RecordGm::FortyLines), + "blitz" => Ok(RecordGm::Blitz), + "zenith" => Ok(RecordGm::Zenith), + "zenithex" => Ok(RecordGm::ZenithEx), + "league" => Ok(RecordGm::League), + _ => Err(self.0.clone()), + } + } +} + +impl AsRef for Gamemode { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for Gamemode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index 2794f4e..d74d3a9 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -1,6 +1,7 @@ //! Utility models for the models. pub mod badge_id; +pub mod gamemode; pub mod news_stream; pub mod timestamp; pub mod user_id; From e47eac960e9f3b7d73461dafdaf306503479f0bc Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 13:52:29 +0900 Subject: [PATCH 185/255] =?UTF-8?q?=E2=9C=A8=20Add:=20utility=20model=20`R?= =?UTF-8?q?eplayId`=20and=20use=20it=20for=20`LeaderboardNews::replay=5Fid?= =?UTF-8?q?`=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 6 +++--- src/model/util/mod.rs | 1 + src/model/util/replay_id.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/model/util/replay_id.rs diff --git a/src/model/news.rs b/src/model/news.rs index 4cac9d0..28db804 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -9,7 +9,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ cache::CacheData, error_response::ErrorResponse, league_rank::Rank, user::UserResponse, - util::{gamemode::Gamemode, news_stream::NewsStream, timestamp::Timestamp}, + util::{gamemode::Gamemode, news_stream::NewsStream, replay_id::ReplayId, timestamp::Timestamp}, } }; use serde::Deserialize; @@ -170,7 +170,7 @@ pub struct LeaderboardNews { pub result: f64, /// The replay's shortID. #[serde(rename = "replayid")] - pub replay_id: String, + pub replay_id: ReplayId, } impl LeaderboardNews { @@ -199,7 +199,7 @@ impl LeaderboardNews { /// Returns the replay URL. pub fn replay_url(&self) -> String { - format!("https://tetr.io/#R:{}", self.replay_id) + self.replay_id.replay_url() } } diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index d74d3a9..6da5a4b 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -3,5 +3,6 @@ pub mod badge_id; pub mod gamemode; pub mod news_stream; +pub mod replay_id; pub mod timestamp; pub mod user_id; diff --git a/src/model/util/replay_id.rs b/src/model/util/replay_id.rs new file mode 100644 index 0000000..95adda4 --- /dev/null +++ b/src/model/util/replay_id.rs @@ -0,0 +1,28 @@ +//! A model for replay IDs. + +use serde::Deserialize; +use std::fmt; + +/// A replay's shortID. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub struct ReplayId(String); + +impl ReplayId { + /// Returns the replay URL. + pub fn replay_url(&self) -> String { + format!("https://tetr.io/#R:{}", self) + } +} + +impl AsRef for ReplayId { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for ReplayId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} From 66de27034394b0b6c7680c91417dac9f03c35e08 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 13:54:13 +0900 Subject: [PATCH 186/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`Gamemode`=20for=20`PersonalBestNews::game?= =?UTF-8?q?type`=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/news.rs b/src/model/news.rs index 28db804..8f64388 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -216,7 +216,7 @@ pub struct PersonalBestNews { /// The username of the player. pub username: String, /// The game mode played. - pub gametype: String, + pub gametype: Gamemode, /// The result (score or time) achieved. pub result: f64, /// The replay's shortID. From 1a52646b29dc38c508465bb4b9baf529f29b3bd0 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 13:54:51 +0900 Subject: [PATCH 187/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`ReplayId`=20for=20`PersonalBestNews::repl?= =?UTF-8?q?ay=5Fid`=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index 8f64388..ccc2a69 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -221,7 +221,7 @@ pub struct PersonalBestNews { pub result: f64, /// The replay's shortID. #[serde(rename = "replayid")] - pub replay_id: String, + pub replay_id: ReplayId, } impl PersonalBestNews { @@ -250,7 +250,7 @@ impl PersonalBestNews { /// Returns the replay URL. pub fn replay_url(&self) -> String { - format!("https://tetr.io/#R:{}", self.replay_id) + self.replay_id.replay_url() } } From 64b69afe0f17886e3da5fa0417a4526f00edb9cf Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 13:59:13 +0900 Subject: [PATCH 188/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`BadgeId`=20for=20`BadgeNews::id`=20field?= =?UTF-8?q?=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index ccc2a69..d58e800 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -9,7 +9,7 @@ use crate::{ client::{error::RspErr, Client}, model::{ cache::CacheData, error_response::ErrorResponse, league_rank::Rank, user::UserResponse, - util::{gamemode::Gamemode, news_stream::NewsStream, replay_id::ReplayId, timestamp::Timestamp}, + util::{badge_id::BadgeId, gamemode::Gamemode, news_stream::NewsStream, replay_id::ReplayId, timestamp::Timestamp}, } }; use serde::Deserialize; @@ -269,7 +269,7 @@ pub struct BadgeNews { /// The badge's internal ID, and the filename of the badge icon /// (all PNGs within `/res/badges/`) #[serde(rename = "type")] - pub id: String, + pub id: BadgeId, /// The badge's label. pub label: String, } @@ -300,7 +300,7 @@ impl BadgeNews { /// Returns the badge icon URL. pub fn badge_icon_url(&self) -> String { - format!("https://tetr.io/res/badges/{}.png", self.id) + self.id.icon_url() } } From 2b5297efd8391322e0478810e6013208e5a72da9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 14:02:38 +0900 Subject: [PATCH 189/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`Timestamp`=20for=20`LeaderboardUser::acco?= =?UTF-8?q?unt=5Fcreated=5Fat`=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index ebf4c48..b550b5d 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -13,9 +13,9 @@ use crate::{ league_rank::Rank, role::Role, user::{AchievementRatingCounts, UserResponse}, - util::user_id::UserId, + util::{timestamp::Timestamp, user_id::UserId}, }, - util::{max_f64, to_unix_ts}, + util::max_f64, }; use serde::Deserialize; @@ -69,7 +69,7 @@ pub struct LeaderboardUser { /// When the user account was created. /// If not set, this account was created before join dates were recorded. #[serde(rename = "ts")] - pub account_created_at: Option, + pub account_created_at: Option, /// The user's XP in points. pub xp: f64, /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. @@ -193,7 +193,7 @@ impl LeaderboardUser { /// /// Panics if failed to parse the timestamp. pub fn account_created_at(&self) -> Option { - self.account_created_at.as_ref().map(|ts| to_unix_ts(ts)) + self.account_created_at.as_ref().map(|ts| ts.unix_ts()) } /// Returns the national flag URL of the user's country. From 034b00284c3e270a22d234f8b4b12119f954d783 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 14:12:36 +0900 Subject: [PATCH 190/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`ReplayId`=20for=20`Record::replay=5Fid`?= =?UTF-8?q?=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 56bb1be..118b05a 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -4,7 +4,7 @@ use crate::{ client::{error::RspErr, param::pagination::Prisecter}, - model::{league_rank::Rank, user::UserResponse, util::user_id::UserId}, + model::{league_rank::Rank, user::UserResponse, util::{replay_id::ReplayId, user_id::UserId}}, util::to_unix_ts, }; use serde::Deserialize; @@ -23,7 +23,7 @@ pub struct Record { pub id: String, /// The Record's ReplayID. #[serde(rename = "replayid")] - pub replay_id: String, + pub replay_id: ReplayId, /// Whether the Replay has been pruned. #[serde(rename = "stub")] pub is_stub: bool, @@ -72,7 +72,7 @@ pub struct Record { impl Record { /// Returns the replay URL. pub fn replay_url(&self) -> String { - format!("https://tetr.io/#R:{}", self.replay_id) + self.replay_id.replay_url() } /// Returns a UNIX timestamp when the record was submitted. From ccaf3e3e31794b90ad5fd512f699a53b9bfef845 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 14:14:05 +0900 Subject: [PATCH 191/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/news.rs | 12 +++++++++--- src/model/summary/record.rs | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/model/news.rs b/src/model/news.rs index d58e800..e89f928 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -8,9 +8,15 @@ use crate::{ client::{error::RspErr, Client}, model::{ - cache::CacheData, error_response::ErrorResponse, league_rank::Rank, user::UserResponse, - util::{badge_id::BadgeId, gamemode::Gamemode, news_stream::NewsStream, replay_id::ReplayId, timestamp::Timestamp}, - } + cache::CacheData, + error_response::ErrorResponse, + league_rank::Rank, + user::UserResponse, + util::{ + badge_id::BadgeId, gamemode::Gamemode, news_stream::NewsStream, replay_id::ReplayId, + timestamp::Timestamp, + }, + }, }; use serde::Deserialize; diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 118b05a..13b5271 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -4,7 +4,11 @@ use crate::{ client::{error::RspErr, param::pagination::Prisecter}, - model::{league_rank::Rank, user::UserResponse, util::{replay_id::ReplayId, user_id::UserId}}, + model::{ + league_rank::Rank, + user::UserResponse, + util::{replay_id::ReplayId, user_id::UserId}, + }, util::to_unix_ts, }; use serde::Deserialize; From f1b62f478bb5364ec10c0bfc0c9aa36604ccf511 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 14:14:56 +0900 Subject: [PATCH 192/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`Gamemode`=20for=20`Record:game=5Fmode`=20?= =?UTF-8?q?field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 13b5271..ed8a7cb 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -7,7 +7,7 @@ use crate::{ model::{ league_rank::Rank, user::UserResponse, - util::{replay_id::ReplayId, user_id::UserId}, + util::{gamemode::Gamemode, replay_id::ReplayId, user_id::UserId}, }, util::to_unix_ts, }; @@ -33,7 +33,7 @@ pub struct Record { pub is_stub: bool, /// The played game mode. #[serde(rename = "gamemode")] - pub game_mode: String, + pub game_mode: Gamemode, /// Whether this is the user's current personal best in the game mode. #[serde(rename = "pb")] pub is_personal_best: bool, From ef2953547ddf1486989c117cfa40bad342286b35 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 14:16:26 +0900 Subject: [PATCH 193/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20use?= =?UTF-8?q?=20utility=20model=20`Timestamp`=20for=20`Record::submitted=5Fa?= =?UTF-8?q?t`=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index ed8a7cb..dfb107b 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -7,9 +7,8 @@ use crate::{ model::{ league_rank::Rank, user::UserResponse, - util::{gamemode::Gamemode, replay_id::ReplayId, user_id::UserId}, + util::{gamemode::Gamemode, replay_id::ReplayId, timestamp::Timestamp, user_id::UserId}, }, - util::to_unix_ts, }; use serde::Deserialize; use std::collections::HashMap; @@ -42,7 +41,7 @@ pub struct Record { pub has_been_personal_best: bool, /// The time the Record was submitted. #[serde(rename = "ts")] - pub submitted_at: String, + pub submitted_at: Timestamp, /// If revolved away, the revolution it belongs to. pub revolution: Option, /// The user owning the Record. @@ -85,7 +84,7 @@ impl Record { /// /// Panics if failed to parse the timestamp. pub fn submitted_at(&self) -> i64 { - to_unix_ts(&self.submitted_at) + self.submitted_at.unix_ts() } } From d8c53afb1c6ba72ba895708346d01f9e74ed47d0 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 14:55:41 +0900 Subject: [PATCH 194/255] =?UTF-8?q?=E2=9C=A8=20Add:=20utility=20model=20`R?= =?UTF-8?q?ecordLeaderboard`=20and=20use=20it=20for=20`Record::leaderboard?= =?UTF-8?q?s`=20field=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 7 +++-- src/model/util/mod.rs | 1 + src/model/util/record_leaderboard.rs | 45 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/model/util/record_leaderboard.rs diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index dfb107b..20e0891 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -7,7 +7,10 @@ use crate::{ model::{ league_rank::Rank, user::UserResponse, - util::{gamemode::Gamemode, replay_id::ReplayId, timestamp::Timestamp, user_id::UserId}, + util::{ + gamemode::Gamemode, record_leaderboard::RecordLeaderboard, replay_id::ReplayId, + timestamp::Timestamp, user_id::UserId, + }, }, }; use serde::Deserialize; @@ -55,7 +58,7 @@ pub struct Record { /// The leaderboards this Record is mentioned in. /// /// e.g. `["40l_global", "40l_country_JP"]` - pub leaderboards: Vec, + pub leaderboards: Vec, /// Whether this Record is disputed. #[serde(rename = "disputed")] pub is_disputed: bool, diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index 6da5a4b..216f37c 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -3,6 +3,7 @@ pub mod badge_id; pub mod gamemode; pub mod news_stream; +pub mod record_leaderboard; pub mod replay_id; pub mod timestamp; pub mod user_id; diff --git a/src/model/util/record_leaderboard.rs b/src/model/util/record_leaderboard.rs new file mode 100644 index 0000000..5326270 --- /dev/null +++ b/src/model/util/record_leaderboard.rs @@ -0,0 +1,45 @@ +//! A model for the record leaderboards. + +use crate::client::param::record_leaderboard::{RecordsLeaderboardId, Scope}; +use serde::Deserialize; +use std::fmt; + +/// A record leaderboard. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] +#[non_exhaustive] +pub struct RecordLeaderboard(pub String); + +impl RecordLeaderboard { + /// Converts into a [`crate::client::param::record_leaderboard::RecordsLeaderboardId`]. + pub fn into_id(&self) -> RecordsLeaderboardId { + self._into_id(None) + } + + /// Converts into a [`crate::client::param::record_leaderboard::RecordsLeaderboardId`] with a Revolution ID. + pub fn into_id_with_revolution_id(&self, revolution_id: &str) -> RecordsLeaderboardId { + self._into_id(Some(revolution_id)) + } + + /// Converts into a [`crate::client::param::record_leaderboard::RecordsLeaderboardId`] with an optional Revolution ID. + fn _into_id(&self, revolution_id: Option<&str>) -> RecordsLeaderboardId { + let split_id: Vec<&str> = self.0.split('_').collect(); + let gamemode = split_id[0]; + let scope = match split_id[1] { + "global" => Scope::Global, + _ => Scope::Country(split_id[2].to_string()), + }; + RecordsLeaderboardId::new(gamemode, scope, revolution_id) + } +} + +impl AsRef for RecordLeaderboard { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for RecordLeaderboard { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} From 05aea5b20443e632de984360d28ee6f7f88fedc2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 15:13:04 +0900 Subject: [PATCH 195/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`role`=20module?= =?UTF-8?q?=20to=20`model::util`=20module=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 7 +++++-- src/model/leaderboard.rs | 3 +-- src/model/mod.rs | 1 - src/model/user.rs | 3 +-- src/model/util/mod.rs | 1 + src/model/{ => util}/role.rs | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) rename src/model/{ => util}/role.rs (97%) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index ef37612..6bd1b98 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -6,8 +6,11 @@ use crate::{ client::error::RspErr, model::{ - achievement::Achievement, cache::CacheData, error_response::ErrorResponse, role::Role, - user::UserResponse, util::user_id::UserId, + achievement::Achievement, + cache::CacheData, + error_response::ErrorResponse, + user::UserResponse, + util::{role::Role, user_id::UserId}, }, }; use serde::Deserialize; diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index b550b5d..1e4663d 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -11,9 +11,8 @@ use crate::{ cache::CacheData, error_response::ErrorResponse, league_rank::Rank, - role::Role, user::{AchievementRatingCounts, UserResponse}, - util::{timestamp::Timestamp, user_id::UserId}, + util::{role::Role, timestamp::Timestamp, user_id::UserId}, }, util::max_f64, }; diff --git a/src/model/mod.rs b/src/model/mod.rs index af1e159..1fa0cd9 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -9,7 +9,6 @@ pub mod leaderboard; pub mod league_rank; pub mod news; pub mod records_leaderboard; -pub mod role; pub mod searched_record; pub mod searched_user; pub mod server_activity; diff --git a/src/model/user.rs b/src/model/user.rs index 5e2256b..2b6e574 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -7,8 +7,7 @@ use crate::{ model::{ cache::CacheData, error_response::ErrorResponse, - role::Role, - util::{badge_id::BadgeId, timestamp::Timestamp, user_id::UserId}, + util::{badge_id::BadgeId, role::Role, timestamp::Timestamp, user_id::UserId}, }, util::{deserialize_from_non_str_to_none, max_f64}, }; diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index 216f37c..4a6ed98 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -5,5 +5,6 @@ pub mod gamemode; pub mod news_stream; pub mod record_leaderboard; pub mod replay_id; +pub mod role; pub mod timestamp; pub mod user_id; diff --git a/src/model/role.rs b/src/model/util/role.rs similarity index 97% rename from src/model/role.rs rename to src/model/util/role.rs index ea903d4..cf15d5a 100644 --- a/src/model/role.rs +++ b/src/model/util/role.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use std::fmt; /// A user role. -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] pub enum Role { /// A normal user. #[serde(rename = "user")] From 5d078ceedfddeb42d22f2982067c800a979aa590 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 15:17:14 +0900 Subject: [PATCH 196/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20update=20module?= =?UTF-8?q?=20doc=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/user.rs b/src/model/user.rs index 2b6e574..d9ca014 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,4 +1,4 @@ -//! Models for the endpoint "User Info", and its related types. +//! Models for the endpoint "User Info". //! //! About the endpoint "User Info", //! see the [API document](https://tetr.io/about/api/#usersuser). From 7e24ebf4942d313d38381ca638b0e8acf63a6bb7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 15:19:01 +0900 Subject: [PATCH 197/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`league=5Frank`?= =?UTF-8?q?=20module=20to=20`model::util`=20module=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 3 +-- src/model/mod.rs | 1 - src/model/news.rs | 5 ++--- src/model/summary/league.rs | 2 +- src/model/summary/record.rs | 5 ++--- src/model/{ => util}/league_rank.rs | 0 src/model/util/mod.rs | 1 + 7 files changed, 7 insertions(+), 10 deletions(-) rename src/model/{ => util}/league_rank.rs (100%) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 1e4663d..4b21423 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -10,9 +10,8 @@ use crate::{ model::{ cache::CacheData, error_response::ErrorResponse, - league_rank::Rank, user::{AchievementRatingCounts, UserResponse}, - util::{role::Role, timestamp::Timestamp, user_id::UserId}, + util::{league_rank::Rank, role::Role, timestamp::Timestamp, user_id::UserId}, }, util::max_f64, }; diff --git a/src/model/mod.rs b/src/model/mod.rs index 1fa0cd9..aaed9dc 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -6,7 +6,6 @@ pub mod cache; pub mod error_response; pub mod labs; pub mod leaderboard; -pub mod league_rank; pub mod news; pub mod records_leaderboard; pub mod searched_record; diff --git a/src/model/news.rs b/src/model/news.rs index e89f928..15f5985 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -10,11 +10,10 @@ use crate::{ model::{ cache::CacheData, error_response::ErrorResponse, - league_rank::Rank, user::UserResponse, util::{ - badge_id::BadgeId, gamemode::Gamemode, news_stream::NewsStream, replay_id::ReplayId, - timestamp::Timestamp, + badge_id::BadgeId, gamemode::Gamemode, league_rank::Rank, news_stream::NewsStream, + replay_id::ReplayId, timestamp::Timestamp, }, }, }; diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 77d21b0..212b0eb 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Summary: TETRA LEAGUE", //! see the [API document](https://tetr.io/about/api/#usersusersummariesleague). -use crate::model::{cache::CacheData, error_response::ErrorResponse, league_rank::Rank}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, util::league_rank::Rank}; use serde::Deserialize; use std::collections::HashMap; diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 20e0891..28c0b1d 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -5,11 +5,10 @@ use crate::{ client::{error::RspErr, param::pagination::Prisecter}, model::{ - league_rank::Rank, user::UserResponse, util::{ - gamemode::Gamemode, record_leaderboard::RecordLeaderboard, replay_id::ReplayId, - timestamp::Timestamp, user_id::UserId, + gamemode::Gamemode, league_rank::Rank, record_leaderboard::RecordLeaderboard, + replay_id::ReplayId, timestamp::Timestamp, user_id::UserId, }, }, }; diff --git a/src/model/league_rank.rs b/src/model/util/league_rank.rs similarity index 100% rename from src/model/league_rank.rs rename to src/model/util/league_rank.rs diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index 4a6ed98..a1f7eb3 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -2,6 +2,7 @@ pub mod badge_id; pub mod gamemode; +pub mod league_rank; pub mod news_stream; pub mod record_leaderboard; pub mod replay_id; From 00b121a20579d1788050ae924e1e59cfe123643d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 15:21:20 +0900 Subject: [PATCH 198/255] =?UTF-8?q?=F0=9F=9A=9A=20Move:=20`achievement`=20?= =?UTF-8?q?module=20to=20`model::util`=20module=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 3 +-- src/model/mod.rs | 1 - src/model/summary/achievements.rs | 4 +++- src/model/summary/mod.rs | 4 +++- src/model/{ => util}/achievement.rs | 0 src/model/util/mod.rs | 1 + 6 files changed, 8 insertions(+), 5 deletions(-) rename src/model/{ => util}/achievement.rs (100%) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 6bd1b98..918a461 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -6,11 +6,10 @@ use crate::{ client::error::RspErr, model::{ - achievement::Achievement, cache::CacheData, error_response::ErrorResponse, user::UserResponse, - util::{role::Role, user_id::UserId}, + util::{achievement::Achievement, role::Role, user_id::UserId}, }, }; use serde::Deserialize; diff --git a/src/model/mod.rs b/src/model/mod.rs index aaed9dc..1782e53 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,6 +1,5 @@ //! Easy-to-use models of the various objects received from the API. -pub mod achievement; pub mod achievement_info; pub mod cache; pub mod error_response; diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs index bfbcf7f..f190f81 100644 --- a/src/model/summary/achievements.rs +++ b/src/model/summary/achievements.rs @@ -3,7 +3,9 @@ //! About the endpoint "User Summary: Achievements", //! see the [API document](https://tetr.io/about/api/#usersusersummariesachievements). -use crate::model::{achievement::Achievement, cache::CacheData, error_response::ErrorResponse}; +use crate::model::{ + cache::CacheData, error_response::ErrorResponse, util::achievement::Achievement, +}; use serde::Deserialize; /// A struct for the response for the endpoint "User Summary: Achievements". diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index a199570..b237944 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,6 +1,8 @@ //! Easy-to-use models of the various objects received from the User Summaries API endpoints. -use crate::model::{achievement::Achievement, cache::CacheData, error_response::ErrorResponse}; +use crate::model::{ + cache::CacheData, error_response::ErrorResponse, util::achievement::Achievement, +}; use serde::Deserialize; pub mod achievements; diff --git a/src/model/achievement.rs b/src/model/util/achievement.rs similarity index 100% rename from src/model/achievement.rs rename to src/model/util/achievement.rs diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index a1f7eb3..ef89036 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -1,5 +1,6 @@ //! Utility models for the models. +pub mod achievement; pub mod badge_id; pub mod gamemode; pub mod league_rank; From 2117ddcb932237abb04d3473092ba868c1b0271b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 15:26:31 +0900 Subject: [PATCH 199/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`CacheData::status`?= =?UTF-8?q?=20field=20now=20has=20its=20own=20type=20`Status`=20[#93]`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/cache.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/model/cache.rs b/src/model/cache.rs index 15dc83b..3797829 100644 --- a/src/model/cache.rs +++ b/src/model/cache.rs @@ -3,6 +3,7 @@ //! For more details, see the [API document](https://tetr.io/about/api/#cachedata). use serde::Deserialize; +use std::fmt; /// Data about how a request was cached. #[derive(Clone, Debug, Deserialize)] @@ -11,7 +12,7 @@ pub struct CacheData { /// Whether the cache was hit. /// Either `"hit"`, `"miss"`, or `"awaited"`. /// `"awaited"` means resource was already being requested by another client. - pub status: String, + pub status: Status, /// When this resource was cached. pub cached_at: u64, /// When this resource's cache expires. @@ -50,6 +51,32 @@ impl AsRef for CacheData { } } +/// A status of the cache. +#[derive(Clone, Debug, Deserialize, PartialEq)] +#[non_exhaustive] +pub enum Status { + Hit, + Miss, + /// Resource was already being requested by another client. + Awaited, +} + +impl AsRef for Status { + fn as_ref(&self) -> &Self { + self + } +} + +impl fmt::Display for Status { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Status::Hit => write!(f, "hit"), + Status::Miss => write!(f, "miss"), + Status::Awaited => write!(f, "awaited"), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -57,7 +84,7 @@ mod tests { #[test] fn get_cached_at_and_cached_until() { let cache_data = CacheData { - status: "hit".to_string(), + status: Status::Hit, cached_at: 1661710769000, cached_until: 1661710844000, }; @@ -68,11 +95,11 @@ mod tests { #[test] fn cache_data_as_ref() { let cache_data = CacheData { - status: "hit".to_string(), + status: Status::Hit, cached_at: 1661710769000, cached_until: 1661710844000, }; - assert_eq!(cache_data.as_ref().status, "hit"); + assert_eq!(cache_data.as_ref().status, Status::Hit); assert_eq!(cache_data.as_ref().cached_at(), 1661710769); assert_eq!(cache_data.as_ref().cached_until(), 1661710844); } From d71e00cff13747f377b58881c86771f9cc699541 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 16:33:55 +0900 Subject: [PATCH 200/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20update=20module?= =?UTF-8?q?=20docs=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/util/achievement.rs | 2 +- src/model/util/badge_id.rs | 2 +- src/model/util/user_id.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/util/achievement.rs b/src/model/util/achievement.rs index 3410a8c..d88a834 100644 --- a/src/model/util/achievement.rs +++ b/src/model/util/achievement.rs @@ -1,4 +1,4 @@ -//! A model for the achievement data. +//! A model for achievements. //! //! For more details, see the [API document](https://tetr.io/about/api/#achievementdata). diff --git a/src/model/util/badge_id.rs b/src/model/util/badge_id.rs index a77953d..f4c1d88 100644 --- a/src/model/util/badge_id.rs +++ b/src/model/util/badge_id.rs @@ -1,4 +1,4 @@ -//! A model for the badge's internal IDs. +//! A model for badge's internal IDs. use serde::Deserialize; use std::fmt; diff --git a/src/model/util/user_id.rs b/src/model/util/user_id.rs index a59230e..ac151b2 100644 --- a/src/model/util/user_id.rs +++ b/src/model/util/user_id.rs @@ -1,4 +1,4 @@ -//! A model for the user IDs, +//! A model for user IDs, use crate::{ client::{error::RspErr, Client}, From da816fa09e7c40743aa5509737f531d524c3afd9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 28 Nov 2024 16:43:13 +0900 Subject: [PATCH 201/255] =?UTF-8?q?=E2=9C=85=20Update:=20Doc-tests=20[#93]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/util/league_rank.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/model/util/league_rank.rs b/src/model/util/league_rank.rs index af039dc..8fa56fd 100644 --- a/src/model/util/league_rank.rs +++ b/src/model/util/league_rank.rs @@ -71,7 +71,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::league_rank::Rank; + /// # use tetr_ch::model::util::league_rank::Rank; /// assert_eq!(Rank::D.name(), "D"); /// assert_eq!(Rank::DPlus.name(), "D+"); /// assert_eq!(Rank::CMinus.name(), "C-"); @@ -121,7 +121,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::league_rank::Rank; + /// # use tetr_ch::model::util::league_rank::Rank; /// assert!(!Rank::D.is_unranked()); /// assert!(!Rank::A.is_unranked()); /// assert!(!Rank::X.is_unranked()); @@ -136,7 +136,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::league_rank::Rank; + /// # use tetr_ch::model::util::league_rank::Rank; /// assert_eq!(Rank::D.icon_url(), "https://tetr.io/res/league-ranks/d.png"); /// assert_eq!(Rank::DPlus.icon_url(), "https://tetr.io/res/league-ranks/d+.png"); /// assert_eq!(Rank::CMinus.icon_url(), "https://tetr.io/res/league-ranks/c-.png"); @@ -166,7 +166,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::league_rank::Rank; + /// # use tetr_ch::model::util::league_rank::Rank; /// assert_eq!(Rank::D.color(), 0x907591); /// assert_eq!(Rank::DPlus.color(), 0x8e6091); /// assert_eq!(Rank::CMinus.color(), 0x79558c); From 37871b21e106986ade4e54e02177f6b20cd5cad1 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 29 Nov 2024 15:00:02 +0900 Subject: [PATCH 202/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`Badge::?= =?UTF-8?q?badge=5Ficon=5Furl`=20method=20to=20`icon=5Furl`=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/user.rs b/src/model/user.rs index d9ca014..34efa26 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -278,7 +278,7 @@ pub struct Badge { impl Badge { /// Returns the badge icon URL. - pub fn badge_icon_url(&self) -> String { + pub fn icon_url(&self) -> String { self.id.icon_url() } From c042f42bfec5a007619f6ac48662367ce5cfe64b Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 29 Nov 2024 15:02:28 +0900 Subject: [PATCH 203/255] =?UTF-8?q?=F0=9F=A9=B9=20Fix:=20type=20of=20`*::o?= =?UTF-8?q?ldest=5Frecord=5Fts`=20fields=20from=20`f64`=20to=20`i64`=20[#1?= =?UTF-8?q?5]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/labs/leagueflow.rs | 2 +- src/model/labs/scoreflow.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/labs/leagueflow.rs b/src/model/labs/leagueflow.rs index 4f8fde1..7a0ad0d 100644 --- a/src/model/labs/leagueflow.rs +++ b/src/model/labs/leagueflow.rs @@ -33,7 +33,7 @@ impl AsRef for LabsLeagueflowResponse { pub struct LabsLeagueflow { /// The timestamp of the oldest record found. #[serde(rename = "startTime")] - pub oldest_record_ts: f64, + pub oldest_record_ts: i64, /// The points in the chart. /// /// - 0: The timestamp offset. diff --git a/src/model/labs/scoreflow.rs b/src/model/labs/scoreflow.rs index adf9cce..adb151f 100644 --- a/src/model/labs/scoreflow.rs +++ b/src/model/labs/scoreflow.rs @@ -33,7 +33,7 @@ impl AsRef for LabsScoreflowResponse { pub struct LabsScoreflow { /// The timestamp of the oldest record found. #[serde(rename = "startTime")] - pub oldest_record_ts: f64, + pub oldest_record_ts: i64, /// The points in the chart. /// /// - 0: The timestamp offset. From 9ea57a59caac189f6af39d4ba18b4f3fa2254c23 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 18:14:56 +0900 Subject: [PATCH 204/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`model::macros`=20mo?= =?UTF-8?q?dule=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/mod.rs | 15 +++++++++++++++ src/model/mod.rs | 3 +++ 2 files changed, 18 insertions(+) create mode 100644 src/model/macros/mod.rs diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs new file mode 100644 index 0000000..e857065 --- /dev/null +++ b/src/model/macros/mod.rs @@ -0,0 +1,15 @@ + +//! Macros to implement methods for the models. +//! +//! # Examples +//! +//! ```ignore +//! pub struct User { +//! pub username: String, +//! // ... +//! } +//! +//! impl User { +//! impl_for_username!(); +//! } +//! ``` diff --git a/src/model/mod.rs b/src/model/mod.rs index 1782e53..f4dabc5 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,5 +1,8 @@ //! Easy-to-use models of the various objects received from the API. +#[macro_use] +mod macros; + pub mod achievement_info; pub mod cache; pub mod error_response; From b263fbd4e091e558977e5a995cce4cf901c88301 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 18:19:31 +0900 Subject: [PATCH 205/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fxp!`?= =?UTF-8?q?=20macro=20and=20use=20it=20for=202=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 8 +------- src/model/macros/mod.rs | 24 ++++++++++++++++++++++++ src/model/user.rs | 8 +------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 4b21423..c327bc2 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -124,13 +124,7 @@ impl LeaderboardUser { self.id.get_user().await } - /// Returns the level of the user. - pub fn level(&self) -> u32 { - let xp = self.xp; - // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 - ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() - as u32 - } + impl_for_xp!(); /// Returns the user's TETRA CHANNEL profile URL. pub fn profile_url(&self) -> String { diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index e857065..05528a4 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -13,3 +13,27 @@ //! impl_for_username!(); //! } //! ``` + +/// A macro to implement the methods for `xp` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn level(&self) -> u32 +/// ``` +/// +/// # Dependencies +/// +/// - `xp: f64` field +/// - [`crate::util::max_f64`] function +macro_rules! impl_for_xp { + () => { + /// Returns the level of the user. + pub fn level(&self) -> u32 { + let xp = self.xp; + // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 + ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.) + .floor() as u32 + } + }; +} diff --git a/src/model/user.rs b/src/model/user.rs index 34efa26..ea6c3b8 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -112,13 +112,7 @@ pub struct User { } impl User { - /// Returns the level of the user. - pub fn level(&self) -> u32 { - let xp = self.xp; - // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 - ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() - as u32 - } + impl_for_xp!(); /// Returns the user's TETRA CHANNEL profile URL. pub fn profile_url(&self) -> String { From 8e3bfd82dc4e4a627644b6ddba3872a3d162e160 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 18:28:47 +0900 Subject: [PATCH 206/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fuserna?= =?UTF-8?q?me!`=20macro=20and=20use=20it=20for=2013=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 5 +---- src/model/leaderboard.rs | 6 +----- src/model/macros/mod.rs | 22 ++++++++++++++++++++++ src/model/news.rs | 30 ++++++------------------------ src/model/searched_user.rs | 5 +---- src/model/summary/record.rs | 15 +++------------ src/model/user.rs | 6 +----- 7 files changed, 35 insertions(+), 54 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 918a461..3fb3d74 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -116,10 +116,7 @@ impl PartialUser { self.id.get_user().await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); /// Whether the user is a normal user. pub fn is_normal_user(&self) -> bool { diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index c327bc2..b11ac7b 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -125,11 +125,7 @@ impl LeaderboardUser { } impl_for_xp!(); - - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); /// Whether the user is a normal user. pub fn is_normal_user(&self) -> bool { diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 05528a4..69ef45c 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -14,6 +14,28 @@ //! } //! ``` +/// A macro to implement the methods for `username` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn profile_url(&self) -> String +/// ``` +/// +/// # Dependencies +/// +/// - `username: String` field +/// +/// Go to [String] +macro_rules! impl_for_username { + () => { + /// Returns the user's TETRA CHANNEL profile URL. + pub fn profile_url(&self) -> String { + format!("https://ch.tetr.io/u/{}", self.username) + } + }; +} + /// A macro to implement the methods for `xp` field. /// /// # Methods diff --git a/src/model/news.rs b/src/model/news.rs index 15f5985..f9950f9 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -197,10 +197,7 @@ impl LeaderboardNews { Client::new().get_user(&self.username).await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); /// Returns the replay URL. pub fn replay_url(&self) -> String { @@ -248,10 +245,7 @@ impl PersonalBestNews { Client::new().get_user(&self.username).await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); /// Returns the replay URL. pub fn replay_url(&self) -> String { @@ -298,10 +292,7 @@ impl BadgeNews { Client::new().get_user(&self.username).await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); /// Returns the badge icon URL. pub fn badge_icon_url(&self) -> String { @@ -344,10 +335,7 @@ impl RankUpNews { Client::new().get_user(&self.username).await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); } impl AsRef for RankUpNews { @@ -383,10 +371,7 @@ impl SupporterNews { Client::new().get_user(&self.username).await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); } impl AsRef for SupporterNews { @@ -422,10 +407,7 @@ impl SupporterGiftNews { Client::new().get_user(&self.username).await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); } /// A struct for the response for the endpoint "Latest News". diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index f2f60ba..d029a7c 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -80,10 +80,7 @@ impl UserInfo { Client::new().get_user(&self.id.to_string()).await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); } impl AsRef for UserInfo { diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 28c0b1d..95df410 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -136,10 +136,7 @@ impl PartialUser { self.id.get_user().await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); /// Returns the user's avatar URL. /// @@ -312,10 +309,7 @@ impl PlayerStats { self.id.get_user().await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); } impl AsRef for PlayerStats { @@ -364,10 +358,7 @@ impl PlayerStatsRound { self.id.get_user().await } - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); } impl AsRef for PlayerStatsRound { diff --git a/src/model/user.rs b/src/model/user.rs index ea6c3b8..eb5037b 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -113,11 +113,7 @@ pub struct User { impl User { impl_for_xp!(); - - /// Returns the user's TETRA CHANNEL profile URL. - pub fn profile_url(&self) -> String { - format!("https://ch.tetr.io/u/{}", self.username) - } + impl_for_username!(); /// Whether the user is a normal user. pub fn is_normal_user(&self) -> bool { From f3603dcebd86d68e00bc398aa0ae355d4c7c4628 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 18:37:22 +0900 Subject: [PATCH 207/255] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 69ef45c..344d6e2 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -1,4 +1,3 @@ - //! Macros to implement methods for the models. //! //! # Examples From 1f64ebf7d7667bf44cd18fa10b18872c42d6b29d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 18:48:02 +0900 Subject: [PATCH 208/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Frole!`?= =?UTF-8?q?=20macro=20and=20use=20it=20for=203=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 46 +---------------------- src/model/leaderboard.rs | 46 +---------------------- src/model/macros/mod.rs | 4 ++ src/model/macros/role.rs | 69 +++++++++++++++++++++++++++++++++++ src/model/user.rs | 46 +---------------------- 5 files changed, 76 insertions(+), 135 deletions(-) create mode 100644 src/model/macros/role.rs diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 3fb3d74..cf99053 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -117,51 +117,7 @@ impl PartialUser { } impl_for_username!(); - - /// Whether the user is a normal user. - pub fn is_normal_user(&self) -> bool { - self.role.is_normal_user() - } - - /// Whether the user is an anonymous. - pub fn is_anon(&self) -> bool { - self.role.is_anon() - } - - /// Whether the user is a bot. - pub fn is_bot(&self) -> bool { - self.role.is_bot() - } - - /// Whether the user is a SYSOP. - pub fn is_sysop(&self) -> bool { - self.role.is_sysop() - } - - /// Whether the user is an administrator. - pub fn is_admin(&self) -> bool { - self.role.is_admin() - } - - /// Whether the user is a moderator. - pub fn is_mod(&self) -> bool { - self.role.is_mod() - } - - /// Whether the user is a community moderator. - pub fn is_halfmod(&self) -> bool { - self.role.is_halfmod() - } - - /// Whether the user is banned. - pub fn is_banned(&self) -> bool { - self.role.is_banned() - } - - /// Whether the user is hidden. - pub fn is_hidden(&self) -> bool { - self.role.is_hidden() - } + impl_for_role!(); /// Returns the national flag URL of the user's country. /// diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index b11ac7b..776a8d1 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -126,51 +126,7 @@ impl LeaderboardUser { impl_for_xp!(); impl_for_username!(); - - /// Whether the user is a normal user. - pub fn is_normal_user(&self) -> bool { - self.role.is_normal_user() - } - - /// Whether the user is an anonymous. - pub fn is_anon(&self) -> bool { - self.role.is_anon() - } - - /// Whether the user is a bot. - pub fn is_bot(&self) -> bool { - self.role.is_bot() - } - - /// Whether the user is a SYSOP. - pub fn is_sysop(&self) -> bool { - self.role.is_sysop() - } - - /// Whether the user is an administrator. - pub fn is_admin(&self) -> bool { - self.role.is_admin() - } - - /// Whether the user is a moderator. - pub fn is_mod(&self) -> bool { - self.role.is_mod() - } - - /// Whether the user is a community moderator. - pub fn is_halfmod(&self) -> bool { - self.role.is_halfmod() - } - - /// Whether the user is banned. - pub fn is_banned(&self) -> bool { - self.role.is_banned() - } - - /// Whether the user is hidden. - pub fn is_hidden(&self) -> bool { - self.role.is_hidden() - } + impl_for_role!(); /// Returns a UNIX timestamp when the user account was created. /// diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 344d6e2..7eb0e29 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -13,6 +13,10 @@ //! } //! ``` +/// Includes a macro to implement the methods for `role` field. +#[macro_use] +mod role; + /// A macro to implement the methods for `username` field. /// /// # Methods diff --git a/src/model/macros/role.rs b/src/model/macros/role.rs new file mode 100644 index 0000000..ea2a1b8 --- /dev/null +++ b/src/model/macros/role.rs @@ -0,0 +1,69 @@ +/// A macro to implement the methods for `role` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn is_normal_user(&self) -> bool +/// pub fn is_anon(&self) -> bool +/// pub fn is_bot(&self) -> bool +/// pub fn is_sysop(&self) -> bool +/// pub fn is_admin(&self) -> bool +/// pub fn is_mod(&self) -> bool +/// pub fn is_halfmod(&self) -> bool +/// pub fn is_banned(&self) -> bool +/// pub fn is_hidden(&self) -> bool +/// ``` +/// +/// # Dependencies +/// +/// - `role: Role` field +/// +/// Go to [Role](crate::model::util::role::Role) +macro_rules! impl_for_role { + () => { + /// Whether the user is a normal user. + pub fn is_normal_user(&self) -> bool { + self.role.is_normal_user() + } + + /// Whether the user is an anonymous. + pub fn is_anon(&self) -> bool { + self.role.is_anon() + } + + /// Whether the user is a bot. + pub fn is_bot(&self) -> bool { + self.role.is_bot() + } + + /// Whether the user is a SYSOP. + pub fn is_sysop(&self) -> bool { + self.role.is_sysop() + } + + /// Whether the user is an administrator. + pub fn is_admin(&self) -> bool { + self.role.is_admin() + } + + /// Whether the user is a moderator. + pub fn is_mod(&self) -> bool { + self.role.is_mod() + } + + /// Whether the user is a community moderator. + pub fn is_halfmod(&self) -> bool { + self.role.is_halfmod() + } + + /// Whether the user is banned. + pub fn is_banned(&self) -> bool { + self.role.is_banned() + } + + /// Whether the user is hidden. + pub fn is_hidden(&self) -> bool { + self.role.is_hidden() + } + }; +} diff --git a/src/model/user.rs b/src/model/user.rs index eb5037b..cea9152 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -114,51 +114,7 @@ pub struct User { impl User { impl_for_xp!(); impl_for_username!(); - - /// Whether the user is a normal user. - pub fn is_normal_user(&self) -> bool { - self.role.is_normal_user() - } - - /// Whether the user is an anonymous. - pub fn is_anon(&self) -> bool { - self.role.is_anon() - } - - /// Whether the user is a bot. - pub fn is_bot(&self) -> bool { - self.role.is_bot() - } - - /// Whether the user is a SYSOP. - pub fn is_sysop(&self) -> bool { - self.role.is_sysop() - } - - /// Whether the user is an administrator. - pub fn is_admin(&self) -> bool { - self.role.is_admin() - } - - /// Whether the user is a moderator. - pub fn is_mod(&self) -> bool { - self.role.is_mod() - } - - /// Whether the user is a community moderator. - pub fn is_halfmod(&self) -> bool { - self.role.is_halfmod() - } - - /// Whether the user is banned. - pub fn is_banned(&self) -> bool { - self.role.is_banned() - } - - /// Whether the user is hidden. - pub fn is_hidden(&self) -> bool { - self.role.is_hidden() - } + impl_for_role!(); /// Returns a UNIX timestamp when the user's account created. /// From 6fc3867daca92f44cf3ec1fe0eded37efbfb8547 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 19:30:06 +0900 Subject: [PATCH 209/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fcreate?= =?UTF-8?q?d=5Fat!`=20macro=20and=20use=20it=20for=202=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 15 ++------------- src/model/macros/mod.rs | 3 +++ src/model/macros/some_at.rs | 27 +++++++++++++++++++++++++++ src/model/user.rs | 12 +----------- 4 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 src/model/macros/some_at.rs diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 776a8d1..579b6ec 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -67,7 +67,7 @@ pub struct LeaderboardUser { /// When the user account was created. /// If not set, this account was created before join dates were recorded. #[serde(rename = "ts")] - pub account_created_at: Option, + pub created_at: Option, /// The user's XP in points. pub xp: f64, /// The user's ISO 3166-1 country code, or `None` if hidden/unknown. @@ -127,18 +127,7 @@ impl LeaderboardUser { impl_for_xp!(); impl_for_username!(); impl_for_role!(); - - /// Returns a UNIX timestamp when the user account was created. - /// - /// If this account was created before join dates were recorded, - /// `None` is returned. - /// - /// # Panics - /// - /// Panics if failed to parse the timestamp. - pub fn account_created_at(&self) -> Option { - self.account_created_at.as_ref().map(|ts| ts.unix_ts()) - } + impl_for_created_at!(); /// Returns the national flag URL of the user's country. /// diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 7eb0e29..b009785 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -16,6 +16,9 @@ /// Includes a macro to implement the methods for `role` field. #[macro_use] mod role; +/// Includes macros to implement the methods for `*_at` fields. +#[macro_use] +mod some_at; /// A macro to implement the methods for `username` field. /// diff --git a/src/model/macros/some_at.rs b/src/model/macros/some_at.rs new file mode 100644 index 0000000..868b301 --- /dev/null +++ b/src/model/macros/some_at.rs @@ -0,0 +1,27 @@ +/// A macro to implement the methods for `created_at` field. (user account) +/// +/// # Methods +/// +/// ```ignore +/// pub fn created_at(&self) -> Option +/// ``` +/// +/// # Dependencies +/// +/// - `created_at: Option` field +/// +/// Go to [Option] | [Timestamp](crate::model::util::timestamp::Timestamp) +macro_rules! impl_for_created_at { + () => { + /// Returns a UNIX timestamp when the user's account created. + /// + /// If the account was created before join dates were recorded, `None` is returned. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. + pub fn created_at(&self) -> Option { + self.created_at.as_ref().map(|ts| ts.unix_ts()) + } + }; +} diff --git a/src/model/user.rs b/src/model/user.rs index cea9152..547f41b 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -115,17 +115,7 @@ impl User { impl_for_xp!(); impl_for_username!(); impl_for_role!(); - - /// Returns a UNIX timestamp when the user's account created. - /// - /// If the account was created before join dates were recorded, `None` is returned. - /// - /// # Panics - /// - /// Panics if failed to parse the timestamp. - pub fn created_at(&self) -> Option { - self.created_at.as_ref().map(|ts| ts.unix_ts()) - } + impl_for_created_at!(); /// Whether the user has any badges. pub fn has_badge(&self) -> bool { From 9197374d73ddb5e9a9da38a6145b4b364e2d2174 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 19:41:27 +0900 Subject: [PATCH 210/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Favatar?= =?UTF-8?q?=5Frevision!`=20macro=20and=20use=20it=20for=202=20models=20[#9?= =?UTF-8?q?5]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/mod.rs | 35 +++++++++++++++++++++++++++++++++++ src/model/summary/record.rs | 19 +------------------ src/model/user.rs | 18 +----------------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index b009785..32fd6b5 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -65,3 +65,38 @@ macro_rules! impl_for_xp { } }; } + +/// A macro to implement the methods for `avatar_revision` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn avatar_url(&self) -> String +/// ``` +/// +/// # Dependencies +/// +/// - `avatar_revision: Option` field +/// +/// Go to [Option] +macro_rules! impl_for_avatar_revision { + () => { + /// Returns the user's avatar URL. + /// + /// If the user does not have an avatar, the anonymous's avatar URL is returned. + pub fn avatar_url(&self) -> String { + let default = "https://tetr.io/res/avatar.png".to_string(); + if let Some(ar) = self.avatar_revision { + if ar == 0 { + return default; + } + format!( + "https://tetr.io/user-content/avatars/{}.jpg?rv={}", + self.id, ar + ) + } else { + default + } + } + }; +} diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 95df410..e34183e 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -137,24 +137,7 @@ impl PartialUser { } impl_for_username!(); - - /// Returns the user's avatar URL. - /// - /// If the user does not have an avatar, the anonymous's avatar URL is returned. - pub fn avatar_url(&self) -> String { - let default = "https://tetr.io/res/avatar.png".to_string(); - if let Some(ar) = self.avatar_revision { - if ar == 0 { - return default; - } - format!( - "https://tetr.io/user-content/avatars/{}.jpg?rv={}", - self.id, ar - ) - } else { - default - } - } + impl_for_avatar_revision!(); /// Returns the user's banner URL. /// diff --git a/src/model/user.rs b/src/model/user.rs index 547f41b..bd1d676 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -127,23 +127,7 @@ impl User { self.badges.len() } - /// Returns the user's avatar URL. - /// - /// If the user does not have an avatar, the anonymous's avatar URL is returned. - pub fn avatar_url(&self) -> String { - let default = "https://tetr.io/res/avatar.png".to_string(); - if let Some(ar) = self.avatar_revision { - if ar == 0 { - return default; - } - format!( - "https://tetr.io/user-content/avatars/{}.jpg?rv={}", - self.id, ar - ) - } else { - default - } - } + impl_for_avatar_revision!(); /// Returns the user's banner URL. /// From 62a03b2a4160cb8d9e74fb3e66a34f10c859e6a2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 19:45:28 +0900 Subject: [PATCH 211/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fbanner?= =?UTF-8?q?=5Frevision!`=20macros=20and=20use=20it=20for=202=20models=20[#?= =?UTF-8?q?95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/mod.rs | 36 ++++++++++++++++++++++++++++++++++++ src/model/summary/record.rs | 22 +--------------------- src/model/user.rs | 22 +--------------------- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 32fd6b5..486c640 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -100,3 +100,39 @@ macro_rules! impl_for_avatar_revision { } }; } + +/// A macro to implement the methods for `banner_revision` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn banner_url(&self) -> Option +/// ``` +/// +/// # Dependencies +/// +/// - `banner_revision: Option` field +macro_rules! impl_for_banner_revision { + () => { + /// Returns the user's banner URL. + /// + /// If the user does not have a banner, `None` is returned. + /// + /// ***Ignore the returned value if the user is not a supporter. + /// Because even if the user is not currently a supporter, + /// `Some` may be returned if the banner was once set.** + pub fn banner_url(&self) -> Option { + if let Some(br) = self.banner_revision { + if br == 0 { + return None; + } + Some(format!( + "https://tetr.io/user-content/banners/{}.jpg?rv={}", + self.id, br + )) + } else { + None + } + } + }; +} diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index e34183e..87d24e9 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -138,27 +138,7 @@ impl PartialUser { impl_for_username!(); impl_for_avatar_revision!(); - - /// Returns the user's banner URL. - /// - /// If the user does not have a banner, `None` is returned. - /// - /// ***Ignore the returned value if the user is not a supporter. - /// Because even if the user is not currently a supporter, - /// `Some` may be returned if the banner was once set.** - pub fn banner_url(&self) -> Option { - if let Some(br) = self.banner_revision { - if br == 0 { - return None; - } - Some(format!( - "https://tetr.io/user-content/banners/{}.jpg?rv={}", - self.id, br - )) - } else { - None - } - } + impl_for_banner_revision!(); /// Returns the national flag URL of the user's country. /// diff --git a/src/model/user.rs b/src/model/user.rs index bd1d676..31e8fa3 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -128,27 +128,7 @@ impl User { } impl_for_avatar_revision!(); - - /// Returns the user's banner URL. - /// - /// If the user does not have a banner, `None` is returned. - /// - /// ***Ignore the returned value if the user is not a supporter. - /// Because even if the user is not currently a supporter, - /// `Some` may be returned if the banner was once set.** - pub fn banner_url(&self) -> Option { - if let Some(br) = self.banner_revision { - if br == 0 { - return None; - } - Some(format!( - "https://tetr.io/user-content/banners/{}.jpg?rv={}", - self.id, br - )) - } else { - None - } - } + impl_for_banner_revision!(); /// Returns the national flag URL of the user's country. /// From b1dcea17cf498e647e3ff0769f7d9d2bbf36ce91 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 19:59:57 +0900 Subject: [PATCH 212/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fcountr?= =?UTF-8?q?y!`=20macro=20and=20use=20it=20for=206=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 10 +--------- src/model/leaderboard.rs | 20 ++------------------ src/model/macros/mod.rs | 26 ++++++++++++++++++++++++++ src/model/summary/league.rs | 7 +------ src/model/summary/record.rs | 10 +--------- src/model/user.rs | 10 +--------- 6 files changed, 32 insertions(+), 51 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index cf99053..e19e3d1 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -118,15 +118,7 @@ impl PartialUser { impl_for_username!(); impl_for_role!(); - - /// Returns the national flag URL of the user's country. - /// - /// If the user's country is not public, `None` is returned. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } + impl_for_country!(); } impl AsRef for PartialUser { diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 579b6ec..94e8125 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -128,15 +128,7 @@ impl LeaderboardUser { impl_for_username!(); impl_for_role!(); impl_for_created_at!(); - - /// Returns the national flag URL of the user's country. - /// - /// If the user's country is hidden or unknown, `None` is returned. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } + impl_for_country!(); } impl AsRef for LeaderboardUser { @@ -291,15 +283,7 @@ impl PastUserWithPrisecter { pub async fn get_user(&self) -> RspErr { self.id.get_user().await } - - /// Returns the national flag URL of the user's country. - /// - /// If the user's country is hidden or unknown, `None` is returned. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } + impl_for_country!(); } impl AsRef for PastUserWithPrisecter { diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 486c640..5b0a595 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -42,6 +42,32 @@ macro_rules! impl_for_username { }; } +/// A macro to implement the methods for `country` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn national_flag_url(&self) -> Option +/// ``` +/// +/// # Dependencies +/// +/// - `country: Option` field +/// +/// Go to [Option] | [String] +macro_rules! impl_for_country { + () => { + /// Returns the national flag URL of the user's country. + /// + /// If the user's country is hidden or unknown, `None` is returned. + pub fn national_flag_url(&self) -> Option { + self.country + .as_ref() + .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) + } + }; +} + /// A macro to implement the methods for `xp` field. /// /// # Methods diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 212b0eb..6cd7cba 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -162,12 +162,7 @@ pub struct PastUser { } impl PastUser { - /// Returns the national flag URL of the user's country. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } + impl_for_country!(); } impl AsRef for PastUser { diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 87d24e9..e104038 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -139,15 +139,7 @@ impl PartialUser { impl_for_username!(); impl_for_avatar_revision!(); impl_for_banner_revision!(); - - /// Returns the national flag URL of the user's country. - /// - /// If the user's country is hidden or unknown, `None` is returned. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } + impl_for_country!(); } impl AsRef for PartialUser { diff --git a/src/model/user.rs b/src/model/user.rs index 31e8fa3..f571fab 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -129,15 +129,7 @@ impl User { impl_for_avatar_revision!(); impl_for_banner_revision!(); - - /// Returns the national flag URL of the user's country. - /// - /// If the user's country is hidden or unknown, `None` is returned. - pub fn national_flag_url(&self) -> Option { - self.country - .as_ref() - .map(|cc| format!("https://tetr.io/res/flags/{}.png", cc.to_lowercase())) - } + impl_for_country!(); } impl AsRef for User { From 20936ae251e3d12086defb81367674c42e31d12d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 20:12:21 +0900 Subject: [PATCH 213/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fid=5Fb?= =?UTF-8?q?adge=5Fid!`=20macro=20for=202=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/mod.rs | 22 ++++++++++++++++++++++ src/model/news.rs | 6 +----- src/model/user.rs | 5 +---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 5b0a595..229b333 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -162,3 +162,25 @@ macro_rules! impl_for_banner_revision { } }; } + +/// A macro to implement the methods for `id: BadgeId` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn icon_url(&self) -> String +/// ``` +/// +/// # Dependencies +/// +/// - `id: BadgeId` field +/// +/// Go to [BadgeId](crate::model::util::badge_id::BadgeId) +macro_rules! impl_for_id_badge_id { + () => { + /// Returns the badge icon URL. + pub fn icon_url(&self) -> String { + self.id.icon_url() + } + }; +} diff --git a/src/model/news.rs b/src/model/news.rs index f9950f9..e189dfc 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -293,11 +293,7 @@ impl BadgeNews { } impl_for_username!(); - - /// Returns the badge icon URL. - pub fn badge_icon_url(&self) -> String { - self.id.icon_url() - } + impl_for_id_badge_id!(); } impl AsRef for BadgeNews { diff --git a/src/model/user.rs b/src/model/user.rs index f571fab..161bfe9 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -169,10 +169,7 @@ pub struct Badge { } impl Badge { - /// Returns the badge icon URL. - pub fn icon_url(&self) -> String { - self.id.icon_url() - } + impl_for_id_badge_id!(); /// Returns a UNIX timestamp when the badge was achieved. /// From e1c2ee0e2659e42da76e3ed1bc4bd1eb7d0787ac Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 20:14:45 +0900 Subject: [PATCH 214/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 229b333..3f0d332 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -20,7 +20,7 @@ mod role; #[macro_use] mod some_at; -/// A macro to implement the methods for `username` field. +/// A macro to implement the method for `username` field. /// /// # Methods /// @@ -42,7 +42,7 @@ macro_rules! impl_for_username { }; } -/// A macro to implement the methods for `country` field. +/// A macro to implement the method for `country` field. /// /// # Methods /// @@ -68,7 +68,7 @@ macro_rules! impl_for_country { }; } -/// A macro to implement the methods for `xp` field. +/// A macro to implement the method for `xp` field. /// /// # Methods /// @@ -92,7 +92,7 @@ macro_rules! impl_for_xp { }; } -/// A macro to implement the methods for `avatar_revision` field. +/// A macro to implement the method for `avatar_revision` field. /// /// # Methods /// @@ -127,7 +127,7 @@ macro_rules! impl_for_avatar_revision { }; } -/// A macro to implement the methods for `banner_revision` field. +/// A macro to implement the method for `banner_revision` field. /// /// # Methods /// @@ -163,7 +163,7 @@ macro_rules! impl_for_banner_revision { }; } -/// A macro to implement the methods for `id: BadgeId` field. +/// A macro to implement the method for `id: BadgeId` field. /// /// # Methods /// From 414752669ac55f38d5a18666c6e660d7fa4ea3c8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 20:20:38 +0900 Subject: [PATCH 215/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Freceiv?= =?UTF-8?q?ed=5Fat`=20macro=20and=20use=20it=20for=201=20model=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/some_at.rs | 26 ++++++++++++++++++++++++++ src/model/user.rs | 10 +--------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/model/macros/some_at.rs b/src/model/macros/some_at.rs index 868b301..0460616 100644 --- a/src/model/macros/some_at.rs +++ b/src/model/macros/some_at.rs @@ -25,3 +25,29 @@ macro_rules! impl_for_created_at { } }; } + +/// A macro to implement the methods for `received_at` field. (badge) +/// +/// # Methods +/// +/// ```ignore +/// pub fn received_at(&self) -> Option +/// ``` +/// +/// # Dependencies +/// +/// - `received_at: Option` field +macro_rules! impl_for_received_at { + () => { + /// Returns a UNIX timestamp when the badge was achieved. + /// + /// If the badge was shown, `None` is returned. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. + pub fn received_at(&self) -> Option { + self.received_at.as_ref().map(|ts| ts.unix_ts()) + } + }; +} diff --git a/src/model/user.rs b/src/model/user.rs index 161bfe9..64e5a8c 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -170,15 +170,7 @@ pub struct Badge { impl Badge { impl_for_id_badge_id!(); - - /// Returns a UNIX timestamp when the badge was achieved. - /// - /// # Panics - /// - /// Panics if failed to parse the timestamp. - pub fn received_at(&self) -> Option { - self.received_at.as_ref().map(|ts| ts.unix_ts()) - } + impl_for_received_at!(); } impl AsRef for Badge { From ab0d599ef5aa577e5dd69a80b63cbf315b3d6a31 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 23:26:10 +0900 Subject: [PATCH 216/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Fget=5Fuser!`?= =?UTF-8?q?=20macro=20and=20use=20it=20for=2014=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 31 ++------ src/model/leaderboard.rs | 41 ++--------- src/model/macros/get_user.rs | 97 +++++++++++++++++++++++++ src/model/macros/mod.rs | 3 + src/model/news.rs | 130 ++++------------------------------ src/model/searched_user.rs | 26 +------ src/model/summary/record.rs | 68 ++---------------- src/model/util/user_id.rs | 22 +----- 8 files changed, 131 insertions(+), 287 deletions(-) create mode 100644 src/model/macros/get_user.rs diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index e19e3d1..d1d76e1 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -3,14 +3,10 @@ //! About the endpoint "Achievement Info", //! see the [API document](https://tetr.io/about/api/#achievementsk). -use crate::{ - client::error::RspErr, - model::{ - cache::CacheData, - error_response::ErrorResponse, - user::UserResponse, - util::{achievement::Achievement, role::Role, user_id::UserId}, - }, +use crate::model::{ + cache::CacheData, + error_response::ErrorResponse, + util::{achievement::Achievement, role::Role, user_id::UserId}, }; use serde::Deserialize; @@ -98,24 +94,7 @@ pub struct PartialUser { } impl PartialUser { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.id.get_user().await - } - + impl_get_user!(id); impl_for_username!(); impl_for_role!(); impl_for_country!(); diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 94e8125..1492f48 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -6,11 +6,11 @@ //! see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). use crate::{ - client::{error::RspErr, param::pagination::Prisecter}, + client::param::pagination::Prisecter, model::{ cache::CacheData, error_response::ErrorResponse, - user::{AchievementRatingCounts, UserResponse}, + user::AchievementRatingCounts, util::{league_rank::Rank, role::Role, timestamp::Timestamp, user_id::UserId}, }, util::max_f64, @@ -106,24 +106,7 @@ pub struct LeaderboardUser { } impl LeaderboardUser { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.id.get_user().await - } - + impl_get_user!(id); impl_for_xp!(); impl_for_username!(); impl_for_role!(); @@ -266,23 +249,7 @@ pub struct PastUserWithPrisecter { } impl PastUserWithPrisecter { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.id.get_user().await - } + impl_get_user!(id); impl_for_country!(); } diff --git a/src/model/macros/get_user.rs b/src/model/macros/get_user.rs new file mode 100644 index 0000000..b81d8f2 --- /dev/null +++ b/src/model/macros/get_user.rs @@ -0,0 +1,97 @@ +/// A macro to implement the method `get_user`. +/// +/// # Methods +/// +/// ```ignore +/// pub async fn get_user(&self) -> RspErr +/// ``` +/// +/// # Dependencies +/// +/// - `fn to_string(&self) -> String` method or `{specified field}: impl ToString` field +/// +/// # Examples +/// +/// By implementing the [`ToString`] trait: +/// +/// ```ignore +/// use std::fmt; +/// +/// pub struct UserId(String); +/// +/// impl UserId { +/// impl_get_user!(); +/// } +/// +/// impl fmt::Display for UserId { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "{}", self.0) +/// } +/// } +/// ``` +/// +/// By specifying a field: +/// +/// ```ignore +/// pub struct PartialUser { +/// id: UserId, +/// username: String, +/// // ... +/// } +/// +/// impl PartialUser { +/// impl_get_user!(id); +/// // or: +/// // impl_get_user!(username); +/// } +/// ``` +/// +/// Go to [String] | [ToString] +macro_rules! impl_get_user { + () => { + /// Gets the detailed information about the user. + /// + /// # Errors + /// + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. + pub async fn get_user( + &self, + ) -> crate::client::error::RspErr { + crate::client::Client::new() + .get_user(&self.to_string()) + .await + } + }; + ($field:ident) => { + /// Gets the detailed information about the user. + /// + /// # Errors + /// + /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, + /// if the request failed. + /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. + pub async fn get_user( + &self, + ) -> crate::client::error::RspErr { + crate::client::Client::new() + .get_user(&self.$field.to_string()) + .await + } + }; +} diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 3f0d332..67f639b 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -13,6 +13,9 @@ //! } //! ``` +/// Includes a macro to implement the method `get_user`. +#[macro_use] +mod get_user; /// Includes a macro to implement the methods for `role` field. #[macro_use] mod role; diff --git a/src/model/news.rs b/src/model/news.rs index e189dfc..099518a 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -5,16 +5,12 @@ //! - About the endpoint "Latest News", //! see the [API document](https://tetr.io/about/api/#newsstream). -use crate::{ - client::{error::RspErr, Client}, - model::{ - cache::CacheData, - error_response::ErrorResponse, - user::UserResponse, - util::{ - badge_id::BadgeId, gamemode::Gamemode, league_rank::Rank, news_stream::NewsStream, - replay_id::ReplayId, timestamp::Timestamp, - }, +use crate::model::{ + cache::CacheData, + error_response::ErrorResponse, + util::{ + badge_id::BadgeId, gamemode::Gamemode, league_rank::Rank, news_stream::NewsStream, + replay_id::ReplayId, timestamp::Timestamp, }, }; use serde::Deserialize; @@ -179,24 +175,7 @@ pub struct LeaderboardNews { } impl LeaderboardNews { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.username).await - } - + impl_get_user!(username); impl_for_username!(); /// Returns the replay URL. @@ -227,24 +206,7 @@ pub struct PersonalBestNews { } impl PersonalBestNews { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.username).await - } - + impl_get_user!(username); impl_for_username!(); /// Returns the replay URL. @@ -274,24 +236,7 @@ pub struct BadgeNews { } impl BadgeNews { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.username).await - } - + impl_get_user!(username); impl_for_username!(); impl_for_id_badge_id!(); } @@ -313,24 +258,7 @@ pub struct RankUpNews { } impl RankUpNews { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.username).await - } - + impl_get_user!(username); impl_for_username!(); } @@ -349,24 +277,7 @@ pub struct SupporterNews { } impl SupporterNews { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.username).await - } - + impl_get_user!(username); impl_for_username!(); } @@ -385,24 +296,7 @@ pub struct SupporterGiftNews { } impl SupporterGiftNews { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.username).await - } - + impl_get_user!(username); impl_for_username!(); } diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index d029a7c..9d9fa43 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -3,12 +3,7 @@ //! About the endpoint "User Search", //! see the [API document](https://tetr.io/about/api/#userssearchquery). -use crate::{ - client::{error::RspErr, Client}, - model::{ - cache::CacheData, error_response::ErrorResponse, user::UserResponse, util::user_id::UserId, - }, -}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, util::user_id::UserId}; use serde::Deserialize; /// A struct for the response for the endpoint "User Search". @@ -62,24 +57,7 @@ pub struct UserInfo { } impl UserInfo { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.id.to_string()).await - } - + impl_get_user!(id); impl_for_username!(); } diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index e104038..d64f7ca 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -3,13 +3,10 @@ //! For more details, see the [API document](https://tetr.io/about/api/#recorddata). use crate::{ - client::{error::RspErr, param::pagination::Prisecter}, - model::{ - user::UserResponse, - util::{ - gamemode::Gamemode, league_rank::Rank, record_leaderboard::RecordLeaderboard, - replay_id::ReplayId, timestamp::Timestamp, user_id::UserId, - }, + client::param::pagination::Prisecter, + model::util::{ + gamemode::Gamemode, league_rank::Rank, record_leaderboard::RecordLeaderboard, + replay_id::ReplayId, timestamp::Timestamp, user_id::UserId, }, }; use serde::Deserialize; @@ -118,24 +115,7 @@ pub struct PartialUser { } impl PartialUser { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.id.get_user().await - } - + impl_get_user!(id); impl_for_username!(); impl_for_avatar_revision!(); impl_for_banner_revision!(); @@ -246,24 +226,7 @@ pub struct PlayerStats { } impl PlayerStats { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.id.get_user().await - } - + impl_get_user!(id); impl_for_username!(); } @@ -295,24 +258,7 @@ pub struct PlayerStatsRound { } impl PlayerStatsRound { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - self.id.get_user().await - } - + impl_get_user!(id); impl_for_username!(); } diff --git a/src/model/util/user_id.rs b/src/model/util/user_id.rs index ac151b2..56bc90c 100644 --- a/src/model/util/user_id.rs +++ b/src/model/util/user_id.rs @@ -1,9 +1,5 @@ //! A model for user IDs, -use crate::{ - client::{error::RspErr, Client}, - model::user::UserResponse, -}; use serde::Deserialize; use std::fmt; @@ -12,23 +8,7 @@ use std::fmt; pub struct UserId(String); impl UserId { - /// Gets the detailed information about the user. - /// - /// # Errors - /// - /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. - /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. - /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. - pub async fn get_user(&self) -> RspErr { - Client::new().get_user(&self.to_string()).await - } + impl_get_user!(); /// Returns the user's internal ID. #[deprecated(since = "0.6.0", note = "please use the `.to_string()` method instead")] From a91870d764f0e4edb7e4d7cecc1c11053ca8b037 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 23:31:57 +0900 Subject: [PATCH 217/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fnews?= =?UTF-8?q?=5Fcreated=5Fat!`=20macro=20and=20use=20it=20for=201=20model=20?= =?UTF-8?q?[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/some_at.rs | 24 ++++++++++++++++++++++++ src/model/news.rs | 9 +-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/model/macros/some_at.rs b/src/model/macros/some_at.rs index 0460616..26bc534 100644 --- a/src/model/macros/some_at.rs +++ b/src/model/macros/some_at.rs @@ -51,3 +51,27 @@ macro_rules! impl_for_received_at { } }; } + +/// A macro to implement the methods for `created_at` field. (news item) +/// +/// # Methods +/// +/// ```ignore +/// pub fn created_at(&self) -> i64 +/// ``` +/// +/// # Dependencies +/// +/// - `created_at: Timestamp` field +macro_rules! impl_for_news_created_at { + () => { + /// Returns a UNIX timestamp when the news item was created. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. + pub fn created_at(&self) -> i64 { + self.created_at.unix_ts() + } + }; +} diff --git a/src/model/news.rs b/src/model/news.rs index 099518a..4e2eb0a 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -69,14 +69,7 @@ pub struct News { } impl News { - /// Returns a UNIX timestamp when the news item was created. - /// - /// # Panics - /// - /// Panics if failed to parse the timestamp. - pub fn created_at(&self) -> i64 { - self.created_at.unix_ts() - } + impl_for_news_created_at!(); } impl AsRef for News { From e49e29b7ddaa111309c227c0e95d2ed188fbf58c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Mon, 2 Dec 2024 23:33:26 +0900 Subject: [PATCH 218/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`impl=5F?= =?UTF-8?q?for=5Fcreated=5Fat!`=20macro=20to=20`impl=5Ffor=5Fnews=5Fcreate?= =?UTF-8?q?d=5Fat!`=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 2 +- src/model/macros/some_at.rs | 2 +- src/model/user.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 1492f48..9959f2c 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -110,7 +110,7 @@ impl LeaderboardUser { impl_for_xp!(); impl_for_username!(); impl_for_role!(); - impl_for_created_at!(); + impl_for_account_created_at!(); impl_for_country!(); } diff --git a/src/model/macros/some_at.rs b/src/model/macros/some_at.rs index 26bc534..713d94e 100644 --- a/src/model/macros/some_at.rs +++ b/src/model/macros/some_at.rs @@ -11,7 +11,7 @@ /// - `created_at: Option` field /// /// Go to [Option] | [Timestamp](crate::model::util::timestamp::Timestamp) -macro_rules! impl_for_created_at { +macro_rules! impl_for_account_created_at { () => { /// Returns a UNIX timestamp when the user's account created. /// diff --git a/src/model/user.rs b/src/model/user.rs index 64e5a8c..67843c1 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -115,7 +115,7 @@ impl User { impl_for_xp!(); impl_for_username!(); impl_for_role!(); - impl_for_created_at!(); + impl_for_account_created_at!(); /// Whether the user has any badges. pub fn has_badge(&self) -> bool { From a6eb38e6d38863493f62f6ac7cb5ecae6ee874f8 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 3 Dec 2024 00:45:50 +0900 Subject: [PATCH 219/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Freplay?= =?UTF-8?q?=5Fid!`=20macro=20and=20use=20it=20for=203=20models=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/mod.rs | 22 ++++++++++++++++++++++ src/model/news.rs | 12 ++---------- src/model/summary/record.rs | 5 +---- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index 67f639b..ec7e9a4 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -187,3 +187,25 @@ macro_rules! impl_for_id_badge_id { } }; } + +/// A macro to implement the method for `replay_id` field. +/// +/// # Methods +/// +/// ```ignore +/// pub fn replay_url(&self) -> String +/// ``` +/// +/// # Dependencies +/// +/// - `replay_id: ReplayId` field +/// +/// Go to [ReplayId](crate::model::util::replay_id::ReplayId) +macro_rules! impl_for_replay_id { + () => { + /// Returns the replay URL. + pub fn replay_url(&self) -> String { + self.replay_id.replay_url() + } + }; +} diff --git a/src/model/news.rs b/src/model/news.rs index 4e2eb0a..2c91abd 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -170,11 +170,7 @@ pub struct LeaderboardNews { impl LeaderboardNews { impl_get_user!(username); impl_for_username!(); - - /// Returns the replay URL. - pub fn replay_url(&self) -> String { - self.replay_id.replay_url() - } + impl_for_replay_id!(); } impl AsRef for LeaderboardNews { @@ -201,11 +197,7 @@ pub struct PersonalBestNews { impl PersonalBestNews { impl_get_user!(username); impl_for_username!(); - - /// Returns the replay URL. - pub fn replay_url(&self) -> String { - self.replay_id.replay_url() - } + impl_for_replay_id!(); } impl AsRef for PersonalBestNews { diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index d64f7ca..ca72856 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -72,10 +72,7 @@ pub struct Record { } impl Record { - /// Returns the replay URL. - pub fn replay_url(&self) -> String { - self.replay_id.replay_url() - } + impl_for_replay_id!(); /// Returns a UNIX timestamp when the record was submitted. /// From 1bf329cfc9c2dbec0316b75a2bd13ef51bc3cc85 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Tue, 3 Dec 2024 01:22:39 +0900 Subject: [PATCH 220/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`impl=5Ffor=5Fsubmit?= =?UTF-8?q?ted=5Fat`=20macro=20and=20use=20it=20for=201=20model=20[#95]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/macros/some_at.rs | 24 ++++++++++++++++++++++++ src/model/summary/record.rs | 10 +--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/model/macros/some_at.rs b/src/model/macros/some_at.rs index 713d94e..d9e7fba 100644 --- a/src/model/macros/some_at.rs +++ b/src/model/macros/some_at.rs @@ -75,3 +75,27 @@ macro_rules! impl_for_news_created_at { } }; } + +/// A macro to implement the methods for `submitted_at` field. (record) +/// +/// # Methods +/// +/// ```ignore +/// pub fn submitted_at(&self) -> i64 +/// ``` +/// +/// # Dependencies +/// +/// - `submitted_at: Timestamp` field +macro_rules! impl_for_submitted_at { + () => { + /// Returns a UNIX timestamp when the record was submitted. + /// + /// # Panics + /// + /// Panics if failed to parse the timestamp. + pub fn submitted_at(&self) -> i64 { + self.submitted_at.unix_ts() + } + }; +} diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index ca72856..469a8a4 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -73,15 +73,7 @@ pub struct Record { impl Record { impl_for_replay_id!(); - - /// Returns a UNIX timestamp when the record was submitted. - /// - /// # Panics - /// - /// Panics if failed to parse the timestamp. - pub fn submitted_at(&self) -> i64 { - self.submitted_at.unix_ts() - } + impl_for_submitted_at!(); } impl AsRef for Record { From 7be05c64ecd965d4217569bd8d8ffdb4f7ef6474 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 00:14:29 +0900 Subject: [PATCH 221/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20`impl?= =?UTF-8?q?=5Ffor=5Fxp`=20macro=20now=20uses=20new=20public=20utility=20fu?= =?UTF-8?q?nction=20`xp=5Fto=5Flevel`=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 2 +- src/model/leaderboard.rs | 1 - src/model/macros/mod.rs | 6 +----- src/model/user.rs | 2 +- src/util.rs | 25 ++++++++++++++++--------- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d7e7fa6..0e179b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,4 +64,4 @@ pub mod client; pub mod constants; pub mod model; -pub(crate) mod util; +pub mod util; diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 9959f2c..6f49cfd 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -13,7 +13,6 @@ use crate::{ user::AchievementRatingCounts, util::{league_rank::Rank, role::Role, timestamp::Timestamp, user_id::UserId}, }, - util::max_f64, }; use serde::Deserialize; diff --git a/src/model/macros/mod.rs b/src/model/macros/mod.rs index ec7e9a4..77969fe 100644 --- a/src/model/macros/mod.rs +++ b/src/model/macros/mod.rs @@ -82,15 +82,11 @@ macro_rules! impl_for_country { /// # Dependencies /// /// - `xp: f64` field -/// - [`crate::util::max_f64`] function macro_rules! impl_for_xp { () => { /// Returns the level of the user. pub fn level(&self) -> u32 { - let xp = self.xp; - // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 - ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.) - .floor() as u32 + crate::util::xp_to_level(self.xp) } }; } diff --git a/src/model/user.rs b/src/model/user.rs index 67843c1..892d73e 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -9,7 +9,7 @@ use crate::{ error_response::ErrorResponse, util::{badge_id::BadgeId, role::Role, timestamp::Timestamp, user_id::UserId}, }, - util::{deserialize_from_non_str_to_none, max_f64}, + util::deserialize_from_non_str_to_none, }; use serde::Deserialize; diff --git a/src/util.rs b/src/util.rs index 865cb76..ac503d2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,6 +5,22 @@ use chrono::DateTime; use serde::Deserialize; use serde_json::Value; +/// Converts the given XP to the level. +pub fn xp_to_level(xp: f64) -> u32 { + // (xp/500)^0.6 + (xp / (5000 + max(0, xp-4000000) / 5000)) + 1 + ((xp / 500.).powf(0.6) + (xp / (5000. + max_f64(0., xp - 4000000.) / 5000.)) + 1.).floor() + as u32 +} + +/// Compares and returns the maximum of two 64bit floats`. +fn max_f64(v1: f64, v2: f64) -> f64 { + if v1 < v2 { + v2 + } else { + v1 + } +} + /// Parses an RFC 3339 and ISO 8601 date and time string into a UNIX timestamp. /// /// # Panics @@ -16,15 +32,6 @@ pub(crate) fn to_unix_ts(ts: &str) -> i64 { .timestamp() } -/// Compares and returns the maximum of two 64bit floats`. -pub(crate) fn max_f64(v1: f64, v2: f64) -> f64 { - if v1 < v2 { - v2 - } else { - v1 - } -} - /// Deserializes from the given value to `Option`. /// /// If the given value is string, returns `Some(Timestamp)`. From fc0ad2c92fa0f4039cbb2bd767efe351867bcbf1 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 00:19:49 +0900 Subject: [PATCH 222/255] =?UTF-8?q?=F0=9F=A9=B9=20Fix:=20`Status`=20enum's?= =?UTF-8?q?=20enumerators=20are=20now=20deserialized=20from=20lowercase=20?= =?UTF-8?q?[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/cache.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/model/cache.rs b/src/model/cache.rs index 3797829..f6dd860 100644 --- a/src/model/cache.rs +++ b/src/model/cache.rs @@ -53,6 +53,7 @@ impl AsRef for CacheData { /// A status of the cache. #[derive(Clone, Debug, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] #[non_exhaustive] pub enum Status { Hit, From 04aed5a5482ed401a675f9433b67ae66a91bdb69 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 02:13:19 +0900 Subject: [PATCH 223/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20mention=20about?= =?UTF-8?q?=20API=20exceptions=20in=20docs=20for=20some=20fields=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/summary/record.rs | 4 +++- src/model/user.rs | 20 +++++++++++++++----- src/model/util/achievement.rs | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 469a8a4..ec12a02 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -44,7 +44,9 @@ pub struct Record { /// If revolved away, the revolution it belongs to. pub revolution: Option, /// The user owning the Record. - pub user: Option, // EXCEPTION + /// + /// ***The API document does not say this field is optional.** + pub user: Option, /// Other users mentioned in the Record. /// /// If not empty, this is a multiplayer game diff --git a/src/model/user.rs b/src/model/user.rs index 892d73e..97a327d 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -97,7 +97,9 @@ pub struct User { /// This user's third party connections. pub connections: Connections, /// The amount of players who have added this user to their friends list. - pub friend_count: Option, // EXCEPTION + /// + /// ***The API document does not say this field is optional.** + pub friend_count: Option, // This user's distinguishment banner. pub distinguishment: Option, /// This user's featured achievements. @@ -155,7 +157,9 @@ pub struct Badge { /// The badge's label, shown when hovered. pub label: String, /// Extra flavor text for the badge, shown when hovered. - pub desc: Option, // EXCEPTION + /// + /// ***The API document does not say this field is optional.** + pub desc: Option, /// The badge's timestamp, if shown. /// /// Why it uses `deserialize_with` attribute? @@ -254,11 +258,17 @@ pub struct Distinguishment { #[serde(rename = "type")] pub _type: String, /// The detail of distinguishment banner. - pub detail: Option, // EXCEPTION + /// + /// ***The API document does not say about this field.** + pub detail: Option, /// The header of distinguishment banner. - pub header: Option, // EXCEPTION + /// + /// ***The API document does not say about this field.** + pub header: Option, /// The footer of distinguishment banner. - pub footer: Option, // EXCEPTION + /// + /// ***The API document does not say about this field.** + pub footer: Option, } impl AsRef for Distinguishment { diff --git a/src/model/util/achievement.rs b/src/model/util/achievement.rs index d88a834..87a9e6c 100644 --- a/src/model/util/achievement.rs +++ b/src/model/util/achievement.rs @@ -20,8 +20,10 @@ pub struct Achievement { /// The flavor text of the achievement. pub desc: String, /// The order of this achievement in its category. + /// + /// ***The API document does not say this field is optional.** #[serde(rename = "o")] - pub order: Option, // EXCEPTION + pub order: Option, /// The rank type of this achievement. /// /// - 1 = PERCENTILE β€” ranked by percentile cutoffs (5% Diamond, 10% Platinum, 30% Gold, 50% Silver, 70% Bronze) From a92d22159187dd6bad65e64c086bdd2ddd7e629f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 21:59:47 +0900 Subject: [PATCH 224/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`x=5Fsession=5Fid`?= =?UTF-8?q?=20field=20to=20`Client`=20struct=20for=20session=20management?= =?UTF-8?q?=20[#97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client.rs b/src/client.rs index 1d3b612..2a17cc7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -66,6 +66,7 @@ const API_URL: &str = "https://ch.tetr.io/api/"; #[derive(Default)] pub struct Client { client: reqwest::Client, + x_session_id: Option, } impl Client { @@ -97,6 +98,7 @@ impl Client { pub fn new() -> Self { Self { client: reqwest::Client::new(), + x_session_id: None, } } From 7f959820140de9eb2613d9da73f443ec2c64a0fe Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 22:01:07 +0900 Subject: [PATCH 225/255] =?UTF-8?q?=F0=9F=93=A6=20Add:=20`uuid`=20dependen?= =?UTF-8?q?cy=20[#97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index c53a08a..2f564a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,5 +26,9 @@ version = "0.4.19" default-features = false features = ["clock"] +[dependencies.uuid] +version = "1.11.0" +features = ["v4"] + [dev-dependencies] tokio-test = "0.4.2" From 12dfb8b3015e2c547412dbc62fb8cf1fde294283 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 22:04:31 +0900 Subject: [PATCH 226/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`ClientCreationError?= =?UTF-8?q?`=20enum=20for=20handling=20client=20creation=20errors=20[#97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/error.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/client/error.rs b/src/client/error.rs index d2dd5a0..3ff7a9a 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -40,5 +40,26 @@ impl From for std::io::Error { pub(crate) type RspErr = Result; +/// An enum for the client creation errors. +#[derive(Debug)] +pub enum ClientCreationError { + /// A TLS backend cannot be initialized, or the resolver cannot load the system configuration. + BuildErr(reqwest::Error), + /// The client contains invalid header value characters. + /// Only visible ASCII characters (32-127) are permitted. + InvalidHeaderValue(String), +} + +impl std::error::Error for ClientCreationError {} + +impl fmt::Display for ClientCreationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ClientCreationError::BuildErr(err) => write!(f, "{}", err), + ClientCreationError::InvalidHeaderValue(v) => write!(f, "failed to parse header value `{}`", v), + } + } +} + #[cfg(test)] mod tests {} From 5416f275e42acd5f5fcd3c203ae412c38c3e991c Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 22:06:05 +0900 Subject: [PATCH 227/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`with=5Fsession=5Fid?= =?UTF-8?q?`=20method=20to=20`Client`=20[#97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 53 +++++++++++++++++++++++++++++++++++++++++++-- src/client/error.rs | 4 +++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/client.rs b/src/client.rs index 2a17cc7..4814a8b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,7 +1,7 @@ //! A module for the [`Client`] struct and supporting types. use self::{ - error::RspErr, + error::{ClientCreationError, RspErr}, param::{ news_stream::ToNewsStreamParam, record::{self, Gamemode}, @@ -39,7 +39,8 @@ use crate::{ }, util::validate_limit, }; -use reqwest::{self}; +use reqwest::header; +use uuid::Uuid; const API_URL: &str = "https://ch.tetr.io/api/"; @@ -102,6 +103,54 @@ impl Client { } } + /// Creates a new [`Client`] with the specified `X-Session-ID`. + /// + /// # Arguments + /// + /// - `session_id` - The session ID to set in the `X-Session-ID` header. + /// If `None`, a new session ID is automatically generated. + /// + /// # Examples + /// + /// ``` + /// use tetr_ch::client::Client; + /// + /// # fn main() -> Result<(), tetr_ch::client::error::ClientCreationError> { + /// // Create a new client with a session ID. + /// let client = Client::with_session_id(None)?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// - A [`ClientCreationError::InvalidHeaderValue`] is returned, + /// if the session ID contains invalid characters. + /// Only visible ASCII characters (32-127) are permitted. + /// - A [`ClientCreationError::BuildErr`] is returned, + /// if failed to build the client. + pub fn with_session_id(session_id: Option<&str>) -> Result { + let session_id = if let Some(id) = session_id { + id.to_string() + } else { + Uuid::new_v4().to_string() + }; + match header::HeaderValue::from_str(&session_id) { + Ok(hv) => { + let mut headers = header::HeaderMap::new(); + headers.insert("X-Session-ID", hv); + match reqwest::Client::builder().default_headers(headers).build() { + Ok(client) => Ok(Self { + client, + x_session_id: Some(session_id), + }), + Err(e) => Err(ClientCreationError::BuildErr(e)), + } + } + Err(_) => Err(ClientCreationError::InvalidHeaderValue(session_id)), + } + } + /// Gets the detailed information about the specified user. /// /// About the endpoint "User Info", diff --git a/src/client/error.rs b/src/client/error.rs index 3ff7a9a..63764a3 100644 --- a/src/client/error.rs +++ b/src/client/error.rs @@ -56,7 +56,9 @@ impl fmt::Display for ClientCreationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ClientCreationError::BuildErr(err) => write!(f, "{}", err), - ClientCreationError::InvalidHeaderValue(v) => write!(f, "failed to parse header value `{}`", v), + ClientCreationError::InvalidHeaderValue(v) => { + write!(f, "failed to parse header value `{}`", v) + } } } } From 41f4ed677c84e43fa693395240b64106748389db Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 22:06:45 +0900 Subject: [PATCH 228/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`session=5Fid`=20met?= =?UTF-8?q?hod=20to=20`Client`=20[#97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client.rs b/src/client.rs index 4814a8b..bdad598 100644 --- a/src/client.rs +++ b/src/client.rs @@ -151,6 +151,11 @@ impl Client { } } + /// Returns the session ID. + pub fn session_id(&self) -> Option<&str> { + self.x_session_id.as_deref() + } + /// Gets the detailed information about the specified user. /// /// About the endpoint "User Info", From 3f705c8ed429bf14838ae2508a307285dfebf2dc Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 22:08:21 +0900 Subject: [PATCH 229/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20add=20session=20?= =?UTF-8?q?ID=20reminder=20to=20some=20pagination-able=20methods=20[#97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/client.rs b/src/client.rs index bdad598..511b1cc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -451,6 +451,9 @@ impl Client { /// Gets the user leaderboard fulfilling the search criteria. /// + /// Want to paginate over this data using the [`SearchCriteria::bound`](user_leaderboard::SearchCriteria)? + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// /// About the endpoint "User Leaderboard", /// see the [API document](https://tetr.io/about/api/#usersbyleaderboard). /// @@ -535,6 +538,9 @@ impl Client { /// Gets the array of the historical user blobs fulfilling the search criteria. /// + /// Want to paginate over this data using the [`SearchCriteria::bound`](user_leaderboard::SearchCriteria)? + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// /// About the endpoint "Historical User Leaderboard", /// see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). /// @@ -627,6 +633,9 @@ impl Client { /// Gets the personal record leaderboard of the specified user, /// fulfilling the search criteria. /// + /// Want to paginate over this data using the [`SearchCriteria::bound`](record::SearchCriteria)? + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// /// About the endpoint "User Personal Records", /// see the [API document](https://tetr.io/about/api/#usersuserrecordsgamemodeleaderboard). /// @@ -726,6 +735,9 @@ impl Client { /// Gets the record leaderboard fulfilling the search criteria. /// + /// Want to paginate over this data using the [`SearchCriteria::bound`](record_leaderboard::SearchCriteria)? + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// /// About the endpoint "Records Leaderboard", /// see the [API document](https://tetr.io/about/api/#recordsleaderboard). /// From ed10aef00d430c3fda8fb08ccb46d289f6701a55 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Wed, 4 Dec 2024 22:15:21 +0900 Subject: [PATCH 230/255] =?UTF-8?q?=E2=9C=A8=20Improve:=20some=20`Client`'?= =?UTF-8?q?s=20methods=20are=20now=20reusable=20[#97]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/client.rs b/src/client.rs index 511b1cc..f8226a7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -177,7 +177,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user(self, user: &str) -> RspErr { + pub async fn get_user(&self, user: &str) -> RspErr { let url = format!("{}users/{}", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -213,7 +213,7 @@ impl Client { /// # tokio_test::block_on(run()); /// ``` pub async fn search_user( - self, + &self, social_connection: SocialConnection, ) -> RspErr { let url = format!("{}users/search/{}", API_URL, social_connection.to_param()); @@ -246,7 +246,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_all_summaries(self, user: &str) -> RspErr { + pub async fn get_user_all_summaries(&self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -273,7 +273,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_40l(self, user: &str) -> RspErr { + pub async fn get_user_40l(&self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/40l", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -300,7 +300,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_blitz(self, user: &str) -> RspErr { + pub async fn get_user_blitz(&self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/blitz", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -327,7 +327,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_zenith(self, user: &str) -> RspErr { + pub async fn get_user_zenith(&self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/zenith", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -354,7 +354,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_zenith_ex(self, user: &str) -> RspErr { + pub async fn get_user_zenith_ex(&self, user: &str) -> RspErr { let url = format!( "{}users/{}/summaries/zenithex", API_URL, @@ -385,7 +385,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_league(self, user: &str) -> RspErr { + pub async fn get_user_league(&self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/league", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -412,7 +412,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_zen(self, user: &str) -> RspErr { + pub async fn get_user_zen(&self, user: &str) -> RspErr { let url = format!("{}users/{}/summaries/zen", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -439,7 +439,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_achievements(self, user: &str) -> RspErr { + pub async fn get_user_achievements(&self, user: &str) -> RspErr { let url = format!( "{}users/{}/summaries/achievements", API_URL, @@ -522,7 +522,7 @@ impl Client { /// # } /// ``` pub async fn get_leaderboard( - self, + &self, leaderboard: LeaderboardType, search_criteria: Option, ) -> RspErr { @@ -611,7 +611,7 @@ impl Client { /// # } /// ``` pub async fn get_historical_league_leaderboard( - self, + &self, season: &str, search_criteria: Option, ) -> RspErr { @@ -711,7 +711,7 @@ impl Client { /// # } /// ``` pub async fn get_user_records( - self, + &self, user: &str, gamemode: Gamemode, leaderboard: record::LeaderboardType, @@ -819,7 +819,7 @@ impl Client { /// # } /// ``` pub async fn get_records_leaderboard( - self, + &self, leaderboard: RecordsLeaderboardId, search_criteria: Option, ) -> RspErr { @@ -874,7 +874,7 @@ impl Client { /// # } /// ``` pub async fn search_record( - self, + &self, user_id: &str, gamemode: Gamemode, timestamp: i64, @@ -926,7 +926,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_news_all(self, limit: u8) -> RspErr { + pub async fn get_news_all(&self, limit: u8) -> RspErr { validate_limit(limit); let url = format!("{}news/", API_URL); let res = self @@ -987,7 +987,7 @@ impl Client { /// # } /// ``` pub async fn get_news_latest( - self, + &self, stream: S, limit: u8, ) -> RspErr { @@ -1014,7 +1014,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_server_stats(self) -> RspErr { + pub async fn get_server_stats(&self) -> RspErr { let url = format!("{}general/stats", API_URL); let res = self.client.get(url).send().await; response(res).await @@ -1037,7 +1037,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_server_activity(self) -> RspErr { + pub async fn get_server_activity(&self) -> RspErr { let url = format!("{}general/activity", API_URL); let res = self.client.get(url).send().await; response(res).await @@ -1074,7 +1074,7 @@ impl Client { /// # } /// ``` pub async fn get_labs_scoreflow( - self, + &self, user: &str, gamemode: Gamemode, ) -> RspErr { @@ -1110,7 +1110,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_labs_leagueflow(self, user: &str) -> RspErr { + pub async fn get_labs_leagueflow(&self, user: &str) -> RspErr { let url = format!("{}labs/leagueflow/{}", API_URL, user.to_lowercase()); let res = self.client.get(url).send().await; response(res).await @@ -1134,7 +1134,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_labs_league_ranks(self) -> RspErr { + pub async fn get_labs_league_ranks(&self) -> RspErr { let url = format!("{}labs/league_ranks", API_URL); let res = self.client.get(url).send().await; response(res).await @@ -1163,7 +1163,7 @@ impl Client { /// # } /// ``` pub async fn get_achievement_info( - self, + &self, achievement_id: &str, ) -> RspErr { let url = format!("{}achievements/{}", API_URL, achievement_id); From 0ead1ddb845bbec674e1176d06292f2b8f4f3d24 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 5 Dec 2024 02:09:12 +0900 Subject: [PATCH 231/255] =?UTF-8?q?=E2=9C=A8=20Add:=20re-exports=20for=20`?= =?UTF-8?q?model::util`=20module=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/util/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/model/util/mod.rs b/src/model/util/mod.rs index ef89036..a60654b 100644 --- a/src/model/util/mod.rs +++ b/src/model/util/mod.rs @@ -10,3 +10,9 @@ pub mod replay_id; pub mod role; pub mod timestamp; pub mod user_id; + +pub use self::{ + achievement::Achievement, badge_id::BadgeId, gamemode::Gamemode, league_rank::Rank, + news_stream::NewsStream, record_leaderboard::RecordLeaderboard, replay_id::ReplayId, + role::Role, timestamp::Timestamp, user_id::UserId, +}; From 8da2a1c4a4f2a85534fa3daca8d0abed48ead15d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 5 Dec 2024 02:34:21 +0900 Subject: [PATCH 232/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20now=20u?= =?UTF-8?q?se=20re-exports=20of=20`model::util`=20module=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 2 +- src/model/leaderboard.rs | 2 +- src/model/news.rs | 5 +---- src/model/searched_user.rs | 2 +- src/model/summary/achievements.rs | 4 +--- src/model/summary/league.rs | 2 +- src/model/summary/mod.rs | 4 +--- src/model/summary/record.rs | 5 +---- src/model/user.rs | 2 +- src/model/util/league_rank.rs | 8 ++++---- src/util.rs | 2 +- 11 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index d1d76e1..1cd9d33 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -6,7 +6,7 @@ use crate::model::{ cache::CacheData, error_response::ErrorResponse, - util::{achievement::Achievement, role::Role, user_id::UserId}, + util::{Achievement, Role, UserId}, }; use serde::Deserialize; diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 6f49cfd..d6ef596 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -11,7 +11,7 @@ use crate::{ cache::CacheData, error_response::ErrorResponse, user::AchievementRatingCounts, - util::{league_rank::Rank, role::Role, timestamp::Timestamp, user_id::UserId}, + util::{Rank, Role, Timestamp, UserId}, }, }; use serde::Deserialize; diff --git a/src/model/news.rs b/src/model/news.rs index 2c91abd..d68c3ea 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -8,10 +8,7 @@ use crate::model::{ cache::CacheData, error_response::ErrorResponse, - util::{ - badge_id::BadgeId, gamemode::Gamemode, league_rank::Rank, news_stream::NewsStream, - replay_id::ReplayId, timestamp::Timestamp, - }, + util::{BadgeId, Gamemode, NewsStream, Rank, ReplayId, Timestamp}, }; use serde::Deserialize; diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index 9d9fa43..8a0368f 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Search", //! see the [API document](https://tetr.io/about/api/#userssearchquery). -use crate::model::{cache::CacheData, error_response::ErrorResponse, util::user_id::UserId}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, util::UserId}; use serde::Deserialize; /// A struct for the response for the endpoint "User Search". diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs index f190f81..128e2fd 100644 --- a/src/model/summary/achievements.rs +++ b/src/model/summary/achievements.rs @@ -3,9 +3,7 @@ //! About the endpoint "User Summary: Achievements", //! see the [API document](https://tetr.io/about/api/#usersusersummariesachievements). -use crate::model::{ - cache::CacheData, error_response::ErrorResponse, util::achievement::Achievement, -}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, util::Achievement}; use serde::Deserialize; /// A struct for the response for the endpoint "User Summary: Achievements". diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 6cd7cba..25b21ba 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -3,7 +3,7 @@ //! About the endpoint "User Summary: TETRA LEAGUE", //! see the [API document](https://tetr.io/about/api/#usersusersummariesleague). -use crate::model::{cache::CacheData, error_response::ErrorResponse, util::league_rank::Rank}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, util::Rank}; use serde::Deserialize; use std::collections::HashMap; diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index b237944..df320b6 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,8 +1,6 @@ //! Easy-to-use models of the various objects received from the User Summaries API endpoints. -use crate::model::{ - cache::CacheData, error_response::ErrorResponse, util::achievement::Achievement, -}; +use crate::model::{cache::CacheData, error_response::ErrorResponse, util::Achievement}; use serde::Deserialize; pub mod achievements; diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index ec12a02..2615172 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -4,10 +4,7 @@ use crate::{ client::param::pagination::Prisecter, - model::util::{ - gamemode::Gamemode, league_rank::Rank, record_leaderboard::RecordLeaderboard, - replay_id::ReplayId, timestamp::Timestamp, user_id::UserId, - }, + model::util::{Gamemode, Rank, RecordLeaderboard, ReplayId, Timestamp, UserId}, }; use serde::Deserialize; use std::collections::HashMap; diff --git a/src/model/user.rs b/src/model/user.rs index 97a327d..fcb8daf 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -7,7 +7,7 @@ use crate::{ model::{ cache::CacheData, error_response::ErrorResponse, - util::{badge_id::BadgeId, role::Role, timestamp::Timestamp, user_id::UserId}, + util::{BadgeId, Role, Timestamp, UserId}, }, util::deserialize_from_non_str_to_none, }; diff --git a/src/model/util/league_rank.rs b/src/model/util/league_rank.rs index 8fa56fd..8504b92 100644 --- a/src/model/util/league_rank.rs +++ b/src/model/util/league_rank.rs @@ -71,7 +71,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::util::league_rank::Rank; + /// # use tetr_ch::model::util::Rank; /// assert_eq!(Rank::D.name(), "D"); /// assert_eq!(Rank::DPlus.name(), "D+"); /// assert_eq!(Rank::CMinus.name(), "C-"); @@ -121,7 +121,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::util::league_rank::Rank; + /// # use tetr_ch::model::util::Rank; /// assert!(!Rank::D.is_unranked()); /// assert!(!Rank::A.is_unranked()); /// assert!(!Rank::X.is_unranked()); @@ -136,7 +136,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::util::league_rank::Rank; + /// # use tetr_ch::model::util::Rank; /// assert_eq!(Rank::D.icon_url(), "https://tetr.io/res/league-ranks/d.png"); /// assert_eq!(Rank::DPlus.icon_url(), "https://tetr.io/res/league-ranks/d+.png"); /// assert_eq!(Rank::CMinus.icon_url(), "https://tetr.io/res/league-ranks/c-.png"); @@ -166,7 +166,7 @@ impl Rank { /// # Examples /// /// ``` - /// # use tetr_ch::model::util::league_rank::Rank; + /// # use tetr_ch::model::util::Rank; /// assert_eq!(Rank::D.color(), 0x907591); /// assert_eq!(Rank::DPlus.color(), 0x8e6091); /// assert_eq!(Rank::CMinus.color(), 0x79558c); diff --git a/src/util.rs b/src/util.rs index ac503d2..21ac75a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ //! Utilities for tetr-ch-rs. -use crate::model::util::timestamp::Timestamp; +use crate::model::util::Timestamp; use chrono::DateTime; use serde::Deserialize; use serde_json::Value; From 96fc77b3af94749824d013c8c85b1ad53d97822d Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 5 Dec 2024 03:20:21 +0900 Subject: [PATCH 233/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`model::prelude`=20m?= =?UTF-8?q?odule=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/mod.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/model/mod.rs b/src/model/mod.rs index f4dabc5..792853e 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,4 +1,10 @@ //! Easy-to-use models of the various objects received from the API. +//! +//! You can import the models that are enums and tuple structs from the [`model::prelude`](prelude) module. +//! +//! ``` +//! use tetr_ch::model::prelude::*; +//! ``` #[macro_use] mod macros; @@ -18,3 +24,28 @@ pub mod summary; pub mod user; pub mod user_records; pub mod util; + +/// A prelude for the models. +/// +/// # Example +/// +/// ``` +/// use tetr_ch::model::prelude::*; +/// ``` +pub mod prelude { + pub use super::{ + cache::Status as CacheStatus, + news::NewsData, + util::{ + Achievement, BadgeId, Gamemode, NewsStream as NewsStreamModel, Rank, + RecordLeaderboard as RecordLeaderboardModel, ReplayId, Role, Timestamp, UserId, + }, + }; + + pub(crate) use super::{ + cache::CacheData, error_response::ErrorResponse, summary::record::Record, + }; + pub(crate) use crate::client::param::pagination::Prisecter; + pub(crate) use serde::Deserialize; + pub(crate) use std::fmt; +} From eba32368eaa353c7e9c92fd3af1036aa6f1eee27 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 5 Dec 2024 03:20:41 +0900 Subject: [PATCH 234/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20files?= =?UTF-8?q?=20for=20models=20now=20use=20re-exports=20of=20`model::prelude?= =?UTF-8?q?`=20module=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/achievement_info.rs | 7 +------ src/model/cache.rs | 3 +-- src/model/error_response.rs | 2 +- src/model/labs/league_ranks.rs | 3 +-- src/model/labs/leagueflow.rs | 3 +-- src/model/labs/scoreflow.rs | 3 +-- src/model/leaderboard.rs | 11 +---------- src/model/news.rs | 9 ++------- src/model/records_leaderboard.rs | 3 +-- src/model/searched_record.rs | 3 +-- src/model/searched_user.rs | 3 +-- src/model/server_activity.rs | 3 +-- src/model/server_stats.rs | 3 +-- src/model/summary/achievements.rs | 3 +-- src/model/summary/blitz.rs | 3 +-- src/model/summary/forty_lines.rs | 3 +-- src/model/summary/league.rs | 3 +-- src/model/summary/mod.rs | 3 +-- src/model/summary/record.rs | 8 ++------ src/model/summary/zen.rs | 3 +-- src/model/summary/zenith.rs | 3 +-- src/model/user.rs | 10 +--------- src/model/user_records.rs | 3 +-- src/model/util/achievement.rs | 2 +- src/model/util/badge_id.rs | 3 +-- src/model/util/gamemode.rs | 7 ++++--- src/model/util/league_rank.rs | 7 +++---- src/model/util/news_stream.rs | 4 +--- src/model/util/record_leaderboard.rs | 3 +-- src/model/util/replay_id.rs | 3 +-- src/model/util/role.rs | 3 +-- src/model/util/timestamp.rs | 4 +--- src/model/util/user_id.rs | 3 +-- 33 files changed, 40 insertions(+), 97 deletions(-) diff --git a/src/model/achievement_info.rs b/src/model/achievement_info.rs index 1cd9d33..d0a1c12 100644 --- a/src/model/achievement_info.rs +++ b/src/model/achievement_info.rs @@ -3,12 +3,7 @@ //! About the endpoint "Achievement Info", //! see the [API document](https://tetr.io/about/api/#achievementsk). -use crate::model::{ - cache::CacheData, - error_response::ErrorResponse, - util::{Achievement, Role, UserId}, -}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Achievement Info". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/cache.rs b/src/model/cache.rs index f6dd860..a32b2dc 100644 --- a/src/model/cache.rs +++ b/src/model/cache.rs @@ -2,8 +2,7 @@ //! //! For more details, see the [API document](https://tetr.io/about/api/#cachedata). -use serde::Deserialize; -use std::fmt; +use crate::model::prelude::*; /// Data about how a request was cached. #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/error_response.rs b/src/model/error_response.rs index 1adebe3..93d1603 100644 --- a/src/model/error_response.rs +++ b/src/model/error_response.rs @@ -1,6 +1,6 @@ //! A model for the error response. -use serde::Deserialize; +use crate::model::prelude::*; /// An error response. #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/labs/league_ranks.rs b/src/model/labs/league_ranks.rs index 62b066f..6a8f573 100644 --- a/src/model/labs/league_ranks.rs +++ b/src/model/labs/league_ranks.rs @@ -3,8 +3,7 @@ //! About the endpoint "Labs League Ranks", //! see the [API document](https://tetr.io/about/api/#labsleagueranks). -use crate::model::{cache::CacheData, error_response::ErrorResponse}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Labs League Ranks". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/labs/leagueflow.rs b/src/model/labs/leagueflow.rs index 7a0ad0d..90753f7 100644 --- a/src/model/labs/leagueflow.rs +++ b/src/model/labs/leagueflow.rs @@ -3,8 +3,7 @@ //! About the endpoint "Labs Leagueflow", //! see the [API document](https://tetr.io/about/api/#labsleagueflowuser). -use crate::model::{cache::CacheData, error_response::ErrorResponse}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Labs Leagueflow". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/labs/scoreflow.rs b/src/model/labs/scoreflow.rs index adb151f..a11e7d1 100644 --- a/src/model/labs/scoreflow.rs +++ b/src/model/labs/scoreflow.rs @@ -3,8 +3,7 @@ //! About the endpoint "Labs Scoreflow", //! see the [API document](https://tetr.io/about/api/#labsscoreflowusergamemode). -use crate::model::{cache::CacheData, error_response::ErrorResponse}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Labs Scoreflow". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index d6ef596..53c0a81 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -5,16 +5,7 @@ //! - About the endpoint "Historical User Leaderboard", //! see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). -use crate::{ - client::param::pagination::Prisecter, - model::{ - cache::CacheData, - error_response::ErrorResponse, - user::AchievementRatingCounts, - util::{Rank, Role, Timestamp, UserId}, - }, -}; -use serde::Deserialize; +use crate::model::{prelude::*, user::AchievementRatingCounts}; /// A struct for the response for the endpoint "User Leaderboard". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/news.rs b/src/model/news.rs index d68c3ea..65ff471 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -5,12 +5,7 @@ //! - About the endpoint "Latest News", //! see the [API document](https://tetr.io/about/api/#newsstream). -use crate::model::{ - cache::CacheData, - error_response::ErrorResponse, - util::{BadgeId, Gamemode, NewsStream, Rank, ReplayId, Timestamp}, -}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "All Latest News". #[derive(Clone, Debug, Deserialize)] @@ -55,7 +50,7 @@ pub struct News { #[serde(rename = "_id")] pub id: String, /// The item's stream. - pub stream: NewsStream, + pub stream: NewsStreamModel, /// The item's type. pub r#type: String, /// The item's records. diff --git a/src/model/records_leaderboard.rs b/src/model/records_leaderboard.rs index 134dc3a..244f688 100644 --- a/src/model/records_leaderboard.rs +++ b/src/model/records_leaderboard.rs @@ -3,8 +3,7 @@ //! About the endpoint "Records Leaderboard", //! see the [API document](https://tetr.io/about/api/#recordsleaderboard). -use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Records Leaderboard". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/searched_record.rs b/src/model/searched_record.rs index 5d07fc9..d2305e7 100644 --- a/src/model/searched_record.rs +++ b/src/model/searched_record.rs @@ -3,8 +3,7 @@ //! About the endpoint "Record Search", //! see the [API document](https://tetr.io/about/api/#recordsreverse). -use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Record Search". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/searched_user.rs b/src/model/searched_user.rs index 8a0368f..afd6097 100644 --- a/src/model/searched_user.rs +++ b/src/model/searched_user.rs @@ -3,8 +3,7 @@ //! About the endpoint "User Search", //! see the [API document](https://tetr.io/about/api/#userssearchquery). -use crate::model::{cache::CacheData, error_response::ErrorResponse, util::UserId}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "User Search". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/server_activity.rs b/src/model/server_activity.rs index 68c57b4..bc995cd 100644 --- a/src/model/server_activity.rs +++ b/src/model/server_activity.rs @@ -3,8 +3,7 @@ //! About the endpoint "Server Activity", //! see the [API document](https://tetr.io/about/api/#generalactivity). -use crate::model::{cache::CacheData, error_response::ErrorResponse}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Server Activity". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/server_stats.rs b/src/model/server_stats.rs index da0ce95..c9a1ba9 100644 --- a/src/model/server_stats.rs +++ b/src/model/server_stats.rs @@ -3,8 +3,7 @@ //! About the endpoint "Server Statistics", //! see the [API document](https://tetr.io/about/api/#generalstats). -use crate::model::{cache::CacheData, error_response::ErrorResponse}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "Server Statistics". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/summary/achievements.rs b/src/model/summary/achievements.rs index 128e2fd..83dde24 100644 --- a/src/model/summary/achievements.rs +++ b/src/model/summary/achievements.rs @@ -3,8 +3,7 @@ //! About the endpoint "User Summary: Achievements", //! see the [API document](https://tetr.io/about/api/#usersusersummariesachievements). -use crate::model::{cache::CacheData, error_response::ErrorResponse, util::Achievement}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "User Summary: Achievements". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/summary/blitz.rs b/src/model/summary/blitz.rs index 848b762..459ed8e 100644 --- a/src/model/summary/blitz.rs +++ b/src/model/summary/blitz.rs @@ -3,8 +3,7 @@ //! About the endpoint "User Summary: BLITZ", //! see the [API document](https://tetr.io/about/api/#usersusersummariesblitz). -use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "User Summary: BLITZ". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/summary/forty_lines.rs b/src/model/summary/forty_lines.rs index a2d54d8..c2b676c 100644 --- a/src/model/summary/forty_lines.rs +++ b/src/model/summary/forty_lines.rs @@ -3,8 +3,7 @@ //! About the endpoint "User Summary: 40 LINES", //! see the [API document](https://tetr.io/about/api/#usersusersummaries40l). -use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "User Summary: 40 LINES". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/summary/league.rs b/src/model/summary/league.rs index 25b21ba..108a497 100644 --- a/src/model/summary/league.rs +++ b/src/model/summary/league.rs @@ -3,8 +3,7 @@ //! About the endpoint "User Summary: TETRA LEAGUE", //! see the [API document](https://tetr.io/about/api/#usersusersummariesleague). -use crate::model::{cache::CacheData, error_response::ErrorResponse, util::Rank}; -use serde::Deserialize; +use crate::model::prelude::*; use std::collections::HashMap; /// A struct for the response for the endpoint "User Summary: TETRA LEAGUE". diff --git a/src/model/summary/mod.rs b/src/model/summary/mod.rs index df320b6..c3c01ed 100644 --- a/src/model/summary/mod.rs +++ b/src/model/summary/mod.rs @@ -1,7 +1,6 @@ //! Easy-to-use models of the various objects received from the User Summaries API endpoints. -use crate::model::{cache::CacheData, error_response::ErrorResponse, util::Achievement}; -use serde::Deserialize; +use crate::model::prelude::*; pub mod achievements; pub mod blitz; diff --git a/src/model/summary/record.rs b/src/model/summary/record.rs index 2615172..0abba24 100644 --- a/src/model/summary/record.rs +++ b/src/model/summary/record.rs @@ -2,11 +2,7 @@ //! //! For more details, see the [API document](https://tetr.io/about/api/#recorddata). -use crate::{ - client::param::pagination::Prisecter, - model::util::{Gamemode, Rank, RecordLeaderboard, ReplayId, Timestamp, UserId}, -}; -use serde::Deserialize; +use crate::model::prelude::*; use std::collections::HashMap; /// A record data. @@ -53,7 +49,7 @@ pub struct Record { /// The leaderboards this Record is mentioned in. /// /// e.g. `["40l_global", "40l_country_JP"]` - pub leaderboards: Vec, + pub leaderboards: Vec, /// Whether this Record is disputed. #[serde(rename = "disputed")] pub is_disputed: bool, diff --git a/src/model/summary/zen.rs b/src/model/summary/zen.rs index 0d1aa34..21e3265 100644 --- a/src/model/summary/zen.rs +++ b/src/model/summary/zen.rs @@ -3,8 +3,7 @@ //! About the endpoint "User Summary: ZEN", //! see the [API document](https://tetr.io/about/api/#usersusersummarieszen). -use crate::model::{cache::CacheData, error_response::ErrorResponse}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "User Summary: ZEN". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/summary/zenith.rs b/src/model/summary/zenith.rs index 3a6bba1..4bd0ccf 100644 --- a/src/model/summary/zenith.rs +++ b/src/model/summary/zenith.rs @@ -5,8 +5,7 @@ //! - About the endpoint "User Summary: EXPERT QUICK PLAY", //! see the [API document](https://tetr.io/about/api/#usersusersummarieszenithex). -use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "User Summary: QUICK PLAY". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/user.rs b/src/model/user.rs index fcb8daf..69f7ce1 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -3,15 +3,7 @@ //! About the endpoint "User Info", //! see the [API document](https://tetr.io/about/api/#usersuser). -use crate::{ - model::{ - cache::CacheData, - error_response::ErrorResponse, - util::{BadgeId, Role, Timestamp, UserId}, - }, - util::deserialize_from_non_str_to_none, -}; -use serde::Deserialize; +use crate::{model::prelude::*, util::deserialize_from_non_str_to_none}; /// A struct for the response for the endpoint "User Info". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/user_records.rs b/src/model/user_records.rs index c7c819c..e2bd11a 100644 --- a/src/model/user_records.rs +++ b/src/model/user_records.rs @@ -3,8 +3,7 @@ //! About the endpoint "User Personal Records", //! see the [API document](https://tetr.io/about/api/#usersuserrecordsgamemodeleaderboard). -use crate::model::{cache::CacheData, error_response::ErrorResponse, summary::record::Record}; -use serde::Deserialize; +use crate::model::prelude::*; /// A struct for the response for the endpoint "User Personal Records". #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/util/achievement.rs b/src/model/util/achievement.rs index 87a9e6c..8c055d1 100644 --- a/src/model/util/achievement.rs +++ b/src/model/util/achievement.rs @@ -2,7 +2,7 @@ //! //! For more details, see the [API document](https://tetr.io/about/api/#achievementdata). -use serde::Deserialize; +use crate::model::prelude::*; /// An achievement. #[derive(Clone, Debug, Deserialize)] diff --git a/src/model/util/badge_id.rs b/src/model/util/badge_id.rs index f4c1d88..660a438 100644 --- a/src/model/util/badge_id.rs +++ b/src/model/util/badge_id.rs @@ -1,7 +1,6 @@ //! A model for badge's internal IDs. -use serde::Deserialize; -use std::fmt; +use crate::model::prelude::*; /// A badge's internal ID. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/model/util/gamemode.rs b/src/model/util/gamemode.rs index ed39eca..adf98d4 100644 --- a/src/model/util/gamemode.rs +++ b/src/model/util/gamemode.rs @@ -1,8 +1,9 @@ //! A model for the game modes. -use crate::client::param::record::{self, Gamemode as RecordGm}; -use serde::Deserialize; -use std::fmt; +use crate::{ + client::param::record::{self, Gamemode as RecordGm}, + model::prelude::*, +}; /// A game mode. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/model/util/league_rank.rs b/src/model/util/league_rank.rs index 8504b92..5917b3f 100644 --- a/src/model/util/league_rank.rs +++ b/src/model/util/league_rank.rs @@ -1,7 +1,6 @@ //! A model for the ranks in TETRA LEAGUE. -use serde::Deserialize; -use std::fmt::{self, Display, Formatter}; +use crate::model::prelude::*; /// An enum for the ranks in TETRA LEAGUE. #[derive(Clone, Debug, Deserialize)] @@ -299,8 +298,8 @@ impl AsRef for Rank { } } -impl Display for Rank { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl fmt::Display for Rank { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Rank::D => write!(f, "d"), Rank::DPlus => write!(f, "d+"), diff --git a/src/model/util/news_stream.rs b/src/model/util/news_stream.rs index 21b5620..3bf16c3 100644 --- a/src/model/util/news_stream.rs +++ b/src/model/util/news_stream.rs @@ -2,10 +2,8 @@ use crate::{ client::{error::RspErr, param::news_stream::ToNewsStreamParam, Client}, - model::news::NewsLatestResponse, + model::{news::NewsLatestResponse, prelude::*}, }; -use serde::Deserialize; -use std::fmt; /// A news stream. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/model/util/record_leaderboard.rs b/src/model/util/record_leaderboard.rs index 5326270..a6f69a3 100644 --- a/src/model/util/record_leaderboard.rs +++ b/src/model/util/record_leaderboard.rs @@ -1,8 +1,7 @@ //! A model for the record leaderboards. use crate::client::param::record_leaderboard::{RecordsLeaderboardId, Scope}; -use serde::Deserialize; -use std::fmt; +use crate::model::prelude::*; /// A record leaderboard. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/model/util/replay_id.rs b/src/model/util/replay_id.rs index 95adda4..d8ab875 100644 --- a/src/model/util/replay_id.rs +++ b/src/model/util/replay_id.rs @@ -1,7 +1,6 @@ //! A model for replay IDs. -use serde::Deserialize; -use std::fmt; +use crate::model::prelude::*; /// A replay's shortID. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/model/util/role.rs b/src/model/util/role.rs index cf15d5a..dbdb68b 100644 --- a/src/model/util/role.rs +++ b/src/model/util/role.rs @@ -1,7 +1,6 @@ //! A model for the user roles. -use serde::Deserialize; -use std::fmt; +use crate::model::prelude::*; /// A user role. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/model/util/timestamp.rs b/src/model/util/timestamp.rs index 7787a4b..08f14c1 100644 --- a/src/model/util/timestamp.rs +++ b/src/model/util/timestamp.rs @@ -1,8 +1,6 @@ //! A model for timestamp. -use crate::util::to_unix_ts; -use serde::Deserialize; -use std::fmt; +use crate::{model::prelude::*, util::to_unix_ts}; /// A timestamp string. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] diff --git a/src/model/util/user_id.rs b/src/model/util/user_id.rs index 56bc90c..e5d5ea5 100644 --- a/src/model/util/user_id.rs +++ b/src/model/util/user_id.rs @@ -1,7 +1,6 @@ //! A model for user IDs, -use serde::Deserialize; -use std::fmt; +use crate::model::prelude::*; /// A user's internal ID. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)] From 28739dbee7da3d57b4b7379383df25fbc63ceff9 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 5 Dec 2024 05:54:42 +0900 Subject: [PATCH 235/255] =?UTF-8?q?=E2=9C=A8=20Add:=20`crate::prelude`=20m?= =?UTF-8?q?odule=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0e179b6..b526a00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,5 +63,25 @@ pub mod client; pub mod constants; pub mod model; - pub mod util; + +/// A prelude for the tetr-ch-rs. +/// +/// # Example +/// +/// ``` +/// use tetr_ch::prelude::*; +/// ``` +pub mod prelude { + pub use crate::client::{ + param::{ + news_stream::NewsStream as NewsStreamParam, + record::Gamemode as RecordGamemode, + record_leaderboard::{RecordsLeaderboardId, Scope}, + search_user::SocialConnection, + user_leaderboard::LeaderboardType as UserLeaderboardType, + *, + }, + Client, + }; +} From 14dc0d6039b276a227da8aa0980a4b32ae35fdad Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 5 Dec 2024 05:54:57 +0900 Subject: [PATCH 236/255] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve:=20now=20u?= =?UTF-8?q?se=20re-exports=20of=20crate::prelude=20module=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 108 +++++++++++++++++++----------------------------- tests/client.rs | 13 +++--- 2 files changed, 49 insertions(+), 72 deletions(-) diff --git a/src/client.rs b/src/client.rs index f8226a7..b086312 100644 --- a/src/client.rs +++ b/src/client.rs @@ -51,7 +51,7 @@ const API_URL: &str = "https://ch.tetr.io/api/"; /// Creating a new [`Client`] instance and getting information about the user "RINRIN-RS". /// /// ```no_run -/// use tetr_ch::client::Client; +/// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// // Create a new client. @@ -91,7 +91,7 @@ impl Client { /// # Examples /// /// ``` - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// // Create a new client. /// let client = Client::new(); @@ -113,7 +113,7 @@ impl Client { /// # Examples /// /// ``` - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # fn main() -> Result<(), tetr_ch::client::error::ClientCreationError> { /// // Create a new client with a session ID. @@ -168,7 +168,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -197,7 +197,7 @@ impl Client { /// Searches for an account by Discord ID `724976600873041940`. /// /// ```no_run - /// use tetr_ch::client::{Client, param::search_user::SocialConnection}; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -237,7 +237,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -264,7 +264,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -291,7 +291,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -318,7 +318,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -345,7 +345,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -376,7 +376,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -403,7 +403,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -430,7 +430,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -471,10 +471,7 @@ impl Client { /// - Filter by Japan /// /// ```no_run - /// use tetr_ch::client::{ - /// Client, - /// param::user_leaderboard::{self, LeaderboardType} - /// }; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -489,7 +486,7 @@ impl Client { /// /// // Get the user leaderboard. /// let user = client.get_leaderboard( - /// LeaderboardType::League, + /// UserLeaderboardType::League, /// Some(criteria) /// ).await?; /// # Ok(()) @@ -501,10 +498,7 @@ impl Client { /// Panics if the search criteria `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// # use tetr_ch::client::{ - /// # Client, - /// # param::user_leaderboard::{self, LeaderboardType} - /// # }; + /// # use tetr_ch::prelude::*; /// # async fn run() -> std::io::Result<()> { /// # let client = Client::new(); /// let criteria = user_leaderboard::SearchCriteria { @@ -515,7 +509,7 @@ impl Client { /// /// // Panics! /// let user = client.get_leaderboard( - /// LeaderboardType::League, + /// UserLeaderboardType::League, /// Some(criteria) /// ).await?; /// # Ok(()) @@ -559,10 +553,7 @@ impl Client { /// - Filter by Japan /// /// ```no_run - /// use tetr_ch::client::{ - /// Client, - /// param::user_leaderboard::{self, LeaderboardType} - /// }; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -590,10 +581,7 @@ impl Client { /// Panics if the search criteria `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// # use tetr_ch::client::{ - /// # Client, - /// # param::user_leaderboard::{self, LeaderboardType} - /// # }; + /// # use tetr_ch::prelude::*; /// # async fn run() -> std::io::Result<()> { /// # let client = Client::new(); /// let criteria = user_leaderboard::SearchCriteria { @@ -654,10 +642,7 @@ impl Client { /// - Three entries /// /// ```no_run - /// use tetr_ch::client::{ - /// Client, - /// param::record::{self, Gamemode, LeaderboardType} - /// }; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -674,9 +659,9 @@ impl Client { /// // Record of the user "RINRIN-RS" /// "rinrin-rs", /// // 40 LINES - /// Gamemode::FortyLines, + /// record::Gamemode::FortyLines, /// // Top score leaderboard - /// LeaderboardType::Top, + /// record::LeaderboardType::Top, /// Some(criteria) /// ).await?; /// # Ok(()) @@ -688,10 +673,7 @@ impl Client { /// Panics if the search criteria `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// # use tetr_ch::client::{ - /// # Client, - /// # param::record::{self, Gamemode, LeaderboardType} - /// # }; + /// # use tetr_ch::prelude::*; /// # async fn run() -> std::io::Result<()> { /// # let client = Client::new(); /// let criteria = record::SearchCriteria { @@ -703,8 +685,8 @@ impl Client { /// // Panics! /// let user = client.get_user_records( /// "rinrin-rs", - /// Gamemode::FortyLines, - /// LeaderboardType::Top, + /// record::Gamemode::FortyLines, + /// record::LeaderboardType::Top, /// Some(criteria) /// ).await?; /// # Ok(()) @@ -757,10 +739,7 @@ impl Client { /// - Revolution ID: `@2024w31` /// /// ```no_run - /// use tetr_ch::client::{ - /// Client, - /// param::record_leaderboard::{self, RecordsLeaderboardId, Scope} - /// }; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -794,10 +773,7 @@ impl Client { /// Panics if the search criteria `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// # use tetr_ch::client::{ - /// # Client, - /// # param::record_leaderboard::{self, RecordsLeaderboardId, Scope} - /// # }; + /// # use tetr_ch::prelude::*; /// # async fn run() -> std::io::Result<()> { /// # let client = Client::new(); /// let criteria = record_leaderboard::SearchCriteria { @@ -856,7 +832,7 @@ impl Client { /// - Timestamp: `1680053762145` (`2023-03-29T01:36:02.145Z`) /// /// ```no_run - /// use tetr_ch::client::{param::record::Gamemode, Client}; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -866,7 +842,7 @@ impl Client { /// // User ID: `621db46d1d638ea850be2aa0` /// "621db46d1d638ea850be2aa0", /// // Gamemode: `blitz` (BLITZ) - /// Gamemode::Blitz, + /// RecordGamemode::Blitz, /// // Timestamp: `1680053762145` (`2023-03-29T01:36:02.145Z`) /// 1680053762145 /// ).await?; @@ -901,7 +877,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -917,7 +893,7 @@ impl Client { /// Panics if the argument `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// # use tetr_ch::client::Client; + /// # use tetr_ch::prelude::*; /// # async fn run() -> std::io::Result<()> { /// # let client = Client::new(); /// // Panics! @@ -953,7 +929,7 @@ impl Client { /// Gets three latest news of the user `621db46d1d638ea850be2aa0`. /// /// ```no_run - /// use tetr_ch::client::{Client, param::news_stream::NewsStream}; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -961,7 +937,7 @@ impl Client { /// // Get the latest news. /// let user = client.get_news_latest( /// // News of the user `621db46d1d638ea850be2aa0` - /// NewsStream::User("621db46d1d638ea850be2aa0".to_string()), + /// NewsStreamParam::User("621db46d1d638ea850be2aa0".to_string()), /// // Three news /// 3, /// ).await?; @@ -974,12 +950,12 @@ impl Client { /// Panics if the argument `limit` is not between 1 and 100. /// /// ```should_panic,no_run - /// # use tetr_ch::client::{Client, param::news_stream::NewsStream}; + /// # use tetr_ch::prelude::*; /// # async fn run() -> std::io::Result<()> { /// # let client = Client::new(); /// // Panics! /// let user = client.get_news_latest( - /// NewsStream::Global, + /// NewsStreamParam::Global, /// // 101 news (out of bounds) /// 101, /// ).await?; @@ -1005,7 +981,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -1028,7 +1004,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -1058,7 +1034,7 @@ impl Client { /// Gets the graph of the 40 LINES records of the user `RINRIN-RS`. /// /// ```no_run - /// use tetr_ch::client::{param::record::Gamemode, Client}; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -1068,7 +1044,7 @@ impl Client { /// // Records of the user "RINRIN-RS" /// "rinrin-rs", /// // 40 LINES records - /// Gamemode::FortyLines + /// RecordGamemode::FortyLines /// ).await?; /// # Ok(()) /// # } @@ -1100,7 +1076,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -1124,7 +1100,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); @@ -1152,7 +1128,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use tetr_ch::client::Client; + /// use tetr_ch::prelude::*; /// /// # async fn run() -> std::io::Result<()> { /// let client = Client::new(); diff --git a/tests/client.rs b/tests/client.rs index 855e744..2ba9599 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -1,4 +1,4 @@ -use tetr_ch::client::{param::news_stream::*, *}; +use tetr_ch::prelude::*; #[test] fn get_usr_data() { @@ -18,18 +18,19 @@ fn get_server_activity_data() { #[test] fn get_latest_global_news_data() { - let _ = tokio_test::block_on(Client::new().get_news_latest(NewsStream::Global, 3)); + let _ = tokio_test::block_on(Client::new().get_news_latest(NewsStreamParam::Global, 3)); } #[test] fn get_latest_user_scale_news_data() { - let _ = tokio_test::block_on( - Client::new().get_news_latest(NewsStream::User("621db46d1d638ea850be2aa0".to_string()), 3), - ); + let _ = tokio_test::block_on(Client::new().get_news_latest( + NewsStreamParam::User("621db46d1d638ea850be2aa0".to_string()), + 3, + )); } #[test] #[should_panic] fn panic_if_invalid_limit_range_in_getting_latest_news() { - let _ = tokio_test::block_on(Client::new().get_news_latest(NewsStream::Global, 0)); + let _ = tokio_test::block_on(Client::new().get_news_latest(NewsStreamParam::Global, 0)); } From 6fd6a6c73fef6b698871127db823768f6b54f1ee Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Thu, 5 Dec 2024 06:45:33 +0900 Subject: [PATCH 237/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20indent=20next=20?= =?UTF-8?q?lines=20in=20lists=20in=20docs=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation --- src/client.rs | 22 ++++++++++---------- src/client/param/record.rs | 10 ++++----- src/client/param/record_leaderboard.rs | 10 ++++----- src/client/param/user_leaderboard.rs | 10 ++++----- src/model/labs/leagueflow.rs | 4 ++-- src/model/labs/scoreflow.rs | 4 ++-- src/model/leaderboard.rs | 4 ++-- src/model/macros/get_user.rs | 28 +++++++++++++------------- src/model/news.rs | 4 ++-- src/model/summary/zenith.rs | 4 ++-- src/model/util/news_stream.rs | 14 ++++++------- 11 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/client.rs b/src/client.rs index b086312..2681c46 100644 --- a/src/client.rs +++ b/src/client.rs @@ -76,15 +76,15 @@ impl Client { //! The `get_*` methods and `search_*` methods return a `Result`. //! //! - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - //! if the request failed. + //! if the request failed. //! - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - //! if the response did not match the expected format but the HTTP request succeeded. - //! There may be defectives in this wrapper or the TETRA CHANNEL API document. + //! if the response did not match the expected format but the HTTP request succeeded. + //! There may be defectives in this wrapper or the TETRA CHANNEL API document. //! - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - //! if the HTTP request failed and the response did not match the expected format. - //! Even if the HTTP request failed, - //! it may be possible to deserialize the response containing an error message, - //! so the deserialization will be tried before returning this error. + //! if the HTTP request failed and the response did not match the expected format. + //! Even if the HTTP request failed, + //! it may be possible to deserialize the response containing an error message, + //! so the deserialization will be tried before returning this error. /// Creates a new [`Client`]. /// @@ -108,7 +108,7 @@ impl Client { /// # Arguments /// /// - `session_id` - The session ID to set in the `X-Session-ID` header. - /// If `None`, a new session ID is automatically generated. + /// If `None`, a new session ID is automatically generated. /// /// # Examples /// @@ -125,10 +125,10 @@ impl Client { /// # Errors /// /// - A [`ClientCreationError::InvalidHeaderValue`] is returned, - /// if the session ID contains invalid characters. - /// Only visible ASCII characters (32-127) are permitted. + /// if the session ID contains invalid characters. + /// Only visible ASCII characters (32-127) are permitted. /// - A [`ClientCreationError::BuildErr`] is returned, - /// if failed to build the client. + /// if failed to build the client. pub fn with_session_id(session_id: Option<&str>) -> Result { let session_id = if let Some(id) = session_id { id.to_string() diff --git a/src/client/param/record.rs b/src/client/param/record.rs index 956fef1..239fb6e 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -146,7 +146,7 @@ impl SearchCriteria { /// # Arguments /// /// - `bound` - The upper bound to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. /// /// A **prisecter** is consisting of three floats. /// The `prisecter` field in a response data allows you to continue paginating. @@ -172,9 +172,9 @@ impl SearchCriteria { /// # Arguments /// /// - `bound` - The lower bound to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If use this, the search order is reversed - /// (returning the lowest items that match the query) + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) /// /// A **prisecter** is consisting of three floats. /// The `prisecter` field in a response data allows you to continue paginating. @@ -200,7 +200,7 @@ impl SearchCriteria { /// # Arguments /// /// - `limit` - The amount of entries to return. - /// Between 1 and 100. 25 by default. + /// Between 1 and 100. 25 by default. /// /// # Examples /// diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index 8daca2c..67f3cf2 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -134,7 +134,7 @@ impl SearchCriteria { /// # Arguments /// /// - `bound` - The upper bound to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. /// /// A **prisecter** is consisting of three floats. /// The `prisecter` field in a response data allows you to continue paginating. @@ -160,9 +160,9 @@ impl SearchCriteria { /// # Arguments /// /// - `bound` - The lower bound to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If use this, the search order is reversed - /// (returning the lowest items that match the query) + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) /// /// A **prisecter** is consisting of three floats. /// The `prisecter` field in a response data allows you to continue paginating. @@ -188,7 +188,7 @@ impl SearchCriteria { /// # Arguments /// /// - `limit` - The amount of entries to return. - /// Between 1 and 100. 25 by default. + /// Between 1 and 100. 25 by default. /// /// # Examples /// diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 82e54dd..54427f5 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -102,7 +102,7 @@ impl SearchCriteria { /// # Arguments /// /// - `bound` -: The upper bound to paginate downwards: - /// take the lowest seen prisecter and pass that back through this field to continue scrolling. + /// take the lowest seen prisecter and pass that back through this field to continue scrolling. /// /// A **prisecter** is consisting of three floats. /// The `prisecter` field in a response data allows you to continue paginating. @@ -128,9 +128,9 @@ impl SearchCriteria { /// # Arguments /// /// - `bound` - The lower bound to paginate upwards: - /// take the highest seen prisecter and pass that back through this field to continue scrolling. - /// If use this, the search order is reversed - /// (returning the lowest items that match the query) + /// take the highest seen prisecter and pass that back through this field to continue scrolling. + /// If use this, the search order is reversed + /// (returning the lowest items that match the query) /// /// A **prisecter** is consisting of three floats. /// The `prisecter` field in a response data allows you to continue paginating. @@ -156,7 +156,7 @@ impl SearchCriteria { /// # Arguments /// /// - `limit` - The amount of entries to return. - /// Between 1 and 100. 25 by default. + /// Between 1 and 100. 25 by default. /// /// # Examples /// diff --git a/src/model/labs/leagueflow.rs b/src/model/labs/leagueflow.rs index 90753f7..dbce1ad 100644 --- a/src/model/labs/leagueflow.rs +++ b/src/model/labs/leagueflow.rs @@ -36,7 +36,7 @@ pub struct LabsLeagueflow { /// The points in the chart. /// /// - 0: The timestamp offset. - /// Add the [`LabsLeagueflow::oldest_record_ts`] to get the true timestamp. + /// Add the [`LabsLeagueflow::oldest_record_ts`] to get the true timestamp. /// - 1: The result of the match, where: /// - 1: victory /// - 2: defeat @@ -47,7 +47,7 @@ pub struct LabsLeagueflow { /// - 7: match nullified /// - 2: The user's TR after the match. /// - 3: The opponent's TR before the match. - /// (If the opponent was unranked, same as 2.) + /// (If the opponent was unranked, same as 2.) pub points: Vec<[i64; 4]>, } diff --git a/src/model/labs/scoreflow.rs b/src/model/labs/scoreflow.rs index a11e7d1..fd46dc3 100644 --- a/src/model/labs/scoreflow.rs +++ b/src/model/labs/scoreflow.rs @@ -36,9 +36,9 @@ pub struct LabsScoreflow { /// The points in the chart. /// /// - 0: The timestamp offset. - /// Add the [`LabsScoreflow::oldest_record_ts`] to get the true timestamp. + /// Add the [`LabsScoreflow::oldest_record_ts`] to get the true timestamp. /// - 1: Whether the score set was a Personal Best. - /// 0 = not a Personal Best, 1 = Personal Best. + /// 0 = not a Personal Best, 1 = Personal Best. /// - 2: The score achieved. (For 40 LINES, this is negative.) pub points: Vec<[i64; 3]>, } diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 53c0a81..96c722c 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -1,9 +1,9 @@ //! Models for the endpoints "User Leaderboard", "Historical User Leaderboard". //! //! - About the endpoint "User Leaderboard", -//! see the [API document](https://tetr.io/about/api/#usersbyleaderboard). +//! see the [API document](https://tetr.io/about/api/#usersbyleaderboard). //! - About the endpoint "Historical User Leaderboard", -//! see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). +//! see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). use crate::model::{prelude::*, user::AchievementRatingCounts}; diff --git a/src/model/macros/get_user.rs b/src/model/macros/get_user.rs index b81d8f2..3a408f1 100644 --- a/src/model/macros/get_user.rs +++ b/src/model/macros/get_user.rs @@ -54,15 +54,15 @@ macro_rules! impl_get_user { /// # Errors /// /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. + /// if the request failed. /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user( &self, ) -> crate::client::error::RspErr { @@ -77,15 +77,15 @@ macro_rules! impl_get_user { /// # Errors /// /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. + /// if the request failed. /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_user( &self, ) -> crate::client::error::RspErr { diff --git a/src/model/news.rs b/src/model/news.rs index 65ff471..9357c26 100644 --- a/src/model/news.rs +++ b/src/model/news.rs @@ -1,9 +1,9 @@ //! Models for the TETRA NEWS endpoints. //! //! - About the endpoint "All Latest News", -//! see the [API document](https://tetr.io/about/api/#newsall). +//! see the [API document](https://tetr.io/about/api/#newsall). //! - About the endpoint "Latest News", -//! see the [API document](https://tetr.io/about/api/#newsstream). +//! see the [API document](https://tetr.io/about/api/#newsstream). use crate::model::prelude::*; diff --git a/src/model/summary/zenith.rs b/src/model/summary/zenith.rs index 4bd0ccf..f739eb9 100644 --- a/src/model/summary/zenith.rs +++ b/src/model/summary/zenith.rs @@ -1,9 +1,9 @@ //! Models for the endpoints "User Summary: QUICK PLAY", "User Summary: EXPERT QUICK PLAY". //! //! - About the endpoint "User Summary: QUICK PLAY", -//! see the [API document](https://tetr.io/about/api/#usersusersummarieszenith). +//! see the [API document](https://tetr.io/about/api/#usersusersummarieszenith). //! - About the endpoint "User Summary: EXPERT QUICK PLAY", -//! see the [API document](https://tetr.io/about/api/#usersusersummarieszenithex). +//! see the [API document](https://tetr.io/about/api/#usersusersummarieszenithex). use crate::model::prelude::*; diff --git a/src/model/util/news_stream.rs b/src/model/util/news_stream.rs index 3bf16c3..871b723 100644 --- a/src/model/util/news_stream.rs +++ b/src/model/util/news_stream.rs @@ -25,15 +25,15 @@ impl NewsStream { /// # Errors /// /// - A [`ResponseError::RequestErr`](crate::client::error::ResponseError::RequestErr) is returned, - /// if the request failed. + /// if the request failed. /// - A [`ResponseError::DeserializeErr`](crate::client::error::ResponseError::DeserializeErr) is returned, - /// if the response did not match the expected format but the HTTP request succeeded. - /// There may be defectives in this wrapper or the TETRA CHANNEL API document. + /// if the response did not match the expected format but the HTTP request succeeded. + /// There may be defectives in this wrapper or the TETRA CHANNEL API document. /// - A [`ResponseError::HttpErr`](crate::client::error::ResponseError::HttpErr) is returned, - /// if the HTTP request failed and the response did not match the expected format. - /// Even if the HTTP request failed, - /// it may be possible to deserialize the response containing an error message, - /// so the deserialization will be tried before returning this error. + /// if the HTTP request failed and the response did not match the expected format. + /// Even if the HTTP request failed, + /// it may be possible to deserialize the response containing an error message, + /// so the deserialization will be tried before returning this error. pub async fn get_news_items(self, limit: u8) -> RspErr { Client::new().get_news_latest(self, limit).await } From 4db1420ee25635278132c1d5e4397ad41649dba2 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:06:23 +0900 Subject: [PATCH 238/255] =?UTF-8?q?=F0=9F=94=A5=20Delete:=20old=20examples?= =?UTF-8?q?=20[#81]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/ex01_get_user_details/Cargo.toml | 13 --- examples/ex01_get_user_details/src/main.rs | 40 -------- .../ex02_get_server_statistics/Cargo.toml | 13 --- .../ex02_get_server_statistics/src/main.rs | 24 ----- examples/ex03_get_server_activity/Cargo.toml | 13 --- examples/ex03_get_server_activity/src/main.rs | 22 ----- examples/ex04_get_user_records/Cargo.toml | 13 --- examples/ex04_get_user_records/src/main.rs | 66 ------------- .../ex05_get_league_leaderboard/Cargo.toml | 13 --- .../ex05_get_league_leaderboard/src/main.rs | 43 --------- examples/ex06_get_xp_leaderboard/Cargo.toml | 13 --- examples/ex06_get_xp_leaderboard/src/main.rs | 41 -------- examples/ex07_get_streams/Cargo.toml | 13 --- examples/ex07_get_streams/src/main.rs | 93 ------------------- examples/ex08_get_latest_news/Cargo.toml | 13 --- examples/ex08_get_latest_news/src/main.rs | 48 ---------- examples/ex09_constants/Cargo.toml | 13 --- examples/ex09_constants/src/main.rs | 58 ------------ examples/ex10_search_accounts/Cargo.toml | 13 --- examples/ex10_search_accounts/src/main.rs | 39 -------- 20 files changed, 604 deletions(-) delete mode 100644 examples/ex01_get_user_details/Cargo.toml delete mode 100644 examples/ex01_get_user_details/src/main.rs delete mode 100644 examples/ex02_get_server_statistics/Cargo.toml delete mode 100644 examples/ex02_get_server_statistics/src/main.rs delete mode 100644 examples/ex03_get_server_activity/Cargo.toml delete mode 100644 examples/ex03_get_server_activity/src/main.rs delete mode 100644 examples/ex04_get_user_records/Cargo.toml delete mode 100644 examples/ex04_get_user_records/src/main.rs delete mode 100644 examples/ex05_get_league_leaderboard/Cargo.toml delete mode 100644 examples/ex05_get_league_leaderboard/src/main.rs delete mode 100644 examples/ex06_get_xp_leaderboard/Cargo.toml delete mode 100644 examples/ex06_get_xp_leaderboard/src/main.rs delete mode 100644 examples/ex07_get_streams/Cargo.toml delete mode 100644 examples/ex07_get_streams/src/main.rs delete mode 100644 examples/ex08_get_latest_news/Cargo.toml delete mode 100644 examples/ex08_get_latest_news/src/main.rs delete mode 100644 examples/ex09_constants/Cargo.toml delete mode 100644 examples/ex09_constants/src/main.rs delete mode 100644 examples/ex10_search_accounts/Cargo.toml delete mode 100644 examples/ex10_search_accounts/src/main.rs diff --git a/examples/ex01_get_user_details/Cargo.toml b/examples/ex01_get_user_details/Cargo.toml deleted file mode 100644 index 9ed7f0c..0000000 --- a/examples/ex01_get_user_details/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex01_get_user_details" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex01_get_user_details/src/main.rs b/examples/ex01_get_user_details/src/main.rs deleted file mode 100644 index 7391dff..0000000 --- a/examples/ex01_get_user_details/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -use tetr_ch::client::Client; - -#[tokio::main] -async fn main() { - // Set the user (name or id). - let user = "rinrin-rs"; - - // Create a new client. - let client = Client::new(); - - // Get the user details. - let usr = match client.get_user(user).await { - Ok(u) => u, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Print the user details (some examples). - println!("Level: {}", usr.level()); - println!("XP: {}", usr.data.as_ref().unwrap().user.xp); - println!("Role: {}", usr.data.as_ref().unwrap().user.role.to_string()); - println!("Rank: {}", usr.data.as_ref().unwrap().user.league.rank.as_str()); - println!("Rank icon: {}", usr.data.as_ref().unwrap().user.league.rank.icon_url()); - println!("Rank color: {}", usr.data.as_ref().unwrap().user.league.rank.color()); - //println!("Reached {:.2}%", usr.rank_progress().unwrap()); The user "RINRIN-RS" may be unranked. - println!("β„–{}", usr.data.as_ref().unwrap().user.league.standing); - println!("β„–{} (local)", usr.data.as_ref().unwrap().user.league.standing_local); - println!("Badges count: {}", usr.badges_count()); - println!("Icon URL: {}", usr.face()); - println!("Country: {:?}", usr.data.as_ref().unwrap().user.country); - println!( - "Discord: {} ({})", - usr.data.as_ref().unwrap().user.connections.discord.as_ref().unwrap().name, - usr.data.as_ref().unwrap().user.connections.discord.as_ref().unwrap().id - ); - - // Learn more about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/user/struct.UserResponse.html -} diff --git a/examples/ex02_get_server_statistics/Cargo.toml b/examples/ex02_get_server_statistics/Cargo.toml deleted file mode 100644 index e635701..0000000 --- a/examples/ex02_get_server_statistics/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex02_get_server_statistics" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex02_get_server_statistics/src/main.rs b/examples/ex02_get_server_statistics/src/main.rs deleted file mode 100644 index e046269..0000000 --- a/examples/ex02_get_server_statistics/src/main.rs +++ /dev/null @@ -1,24 +0,0 @@ -use tetr_ch::client::Client; - -#[tokio::main] -async fn main() { - // Create a new client. - let client = Client::new(); - - // Get the server statics. - let stats = match client.get_server_stats().await { - Ok(s) => s, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Print the server statics (some examples). - println!("Total users: {}", stats.data.as_ref().unwrap().user_count); - println!("Registered users: {}", stats.registered_players()); - println!("Anonymous users: {}", stats.data.as_ref().unwrap().anon_count); - println!("Ranked users: {}", stats.data.as_ref().unwrap().ranked_count); - - // Learn more about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/server_stats/struct.ServerStats.html -} diff --git a/examples/ex03_get_server_activity/Cargo.toml b/examples/ex03_get_server_activity/Cargo.toml deleted file mode 100644 index d4c3a80..0000000 --- a/examples/ex03_get_server_activity/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex03_get_server_activity" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] \ No newline at end of file diff --git a/examples/ex03_get_server_activity/src/main.rs b/examples/ex03_get_server_activity/src/main.rs deleted file mode 100644 index 24cc388..0000000 --- a/examples/ex03_get_server_activity/src/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -use tetr_ch::client::Client; - -#[tokio::main] -async fn main() { - // Create a new client. - let client = Client::new(); - - // Get the server activity. - let activity = match client.get_server_activity().await { - Ok(a) => a, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Print the all server activity and activity length. - println!("{:?}", activity.data.as_ref().unwrap().activity); - println!("\nArray length: {}", activity.data.as_ref().unwrap().activity.len()); - - // Learn more about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/server_activity/struct.ServerActivity.html -} diff --git a/examples/ex04_get_user_records/Cargo.toml b/examples/ex04_get_user_records/Cargo.toml deleted file mode 100644 index cf1fa49..0000000 --- a/examples/ex04_get_user_records/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex04_get_user_records" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex04_get_user_records/src/main.rs b/examples/ex04_get_user_records/src/main.rs deleted file mode 100644 index 922be09..0000000 --- a/examples/ex04_get_user_records/src/main.rs +++ /dev/null @@ -1,66 +0,0 @@ -use tetr_ch::client::Client; - -#[tokio::main] -async fn main() { - // Set the user (name or id). - let user = "rinrin-rs"; - - // Create a new client. - let client = Client::new(); - - // Get the user's (best) records. - let records = match client.get_user_records(user).await { - Ok(r) => r, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Print the user's (best) records (some examples). - println!( - "40 LINES result: {:.3}s", - records - .data - .as_ref() - .unwrap() - .records - .forty_lines - .record - .as_ref() - .unwrap() - .endcontext - .clone() - .single_play() - .unwrap() - .final_time - .unwrap() - / 1000. - ); - println!( - "40 LINES PPS(Pieces Per Second): {}", - records.forty_lines_pps() - ); - println!( - "BLITZ result: {}", - records - .data - .as_ref() - .unwrap() - .records - .blitz - .record - .as_ref() - .unwrap() - .endcontext - .clone() - .single_play() - .unwrap() - .score - .unwrap() - ); - println!("BLITZ SPP(Score Per Piece): {}", records.blitz_spp()); - println!("ZEN level: {}", records.data.as_ref().unwrap().zen.level); - - // Learn more about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/user/struct.UserRecordsResponse.html -} diff --git a/examples/ex05_get_league_leaderboard/Cargo.toml b/examples/ex05_get_league_leaderboard/Cargo.toml deleted file mode 100644 index 7a88771..0000000 --- a/examples/ex05_get_league_leaderboard/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex05_get_league_leaderboard" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex05_get_league_leaderboard/src/main.rs b/examples/ex05_get_league_leaderboard/src/main.rs deleted file mode 100644 index de354ae..0000000 --- a/examples/ex05_get_league_leaderboard/src/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -use tetr_ch::client::{query::LeagueLeaderboardQuery, Client}; - -#[tokio::main] -async fn main() { - // Create a new client. - let client = Client::new(); - - // Set the query parameters. - // This is default(25000TR or less, fifty entries) query. - let query1 = LeagueLeaderboardQuery::new(); - - // Get the TETRA LEAGUE leaderboard. - let _tl_leaderboard1 = match client.get_league_leaderboard(query1).await { - Ok(l) => l, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - - // Other example queries: - - // 15200TR or less, three entries, filter by Japan. - let _query2 = LeagueLeaderboardQuery::new() - .after(15200.) - .limit(3) - .country("jp"); - - // 15200TR or higher. - // Also sort by TR ascending. - let _query3 = LeagueLeaderboardQuery::new().before(15200.); - - // Full leaderboard. - let _query4 = LeagueLeaderboardQuery::new().limit(0); - - // You can restore the query parameters to default as follows: - let mut _query5 = LeagueLeaderboardQuery::new().country("us"); - _query5.init(); - - - // Learn about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/league_leaderboard/struct.LeagueLeaderboardResponse.html -} diff --git a/examples/ex06_get_xp_leaderboard/Cargo.toml b/examples/ex06_get_xp_leaderboard/Cargo.toml deleted file mode 100644 index ef91815..0000000 --- a/examples/ex06_get_xp_leaderboard/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex06_get_xp_leaderboard" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex06_get_xp_leaderboard/src/main.rs b/examples/ex06_get_xp_leaderboard/src/main.rs deleted file mode 100644 index 2cad1b3..0000000 --- a/examples/ex06_get_xp_leaderboard/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -use tetr_ch::client::{query::XPLeaderboardQuery, Client}; - -#[tokio::main] -async fn main() { - // Create a new client. - let client = Client::new(); - - // Set the query parameters. - // This is default(descending, fifty entries) query. - let query1 = XPLeaderboardQuery::new(); - - // Get the XP leaderboard. - let _xp_leaderboard1 = match client.get_xp_leaderboard(query1).await { - Ok(l) => l, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - - // Other example queries: - - // 50,000,000,000,000xp or less, thirty entries, filter by Japan. - let _query2 = XPLeaderboardQuery::new() - .after(50_000_000_000_000.) - .limit(3) - .country("jp"); - - // 50,000,000,000,000xp or higher. - // Also sort by XP ascending. - let _query3 = XPLeaderboardQuery::new() - .before(50_000_000_000_000.); - - // You can restore the query parameters to default as follows: - let mut _query4 = XPLeaderboardQuery::new().country("us"); - _query4.init(); - - - // Learn about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/xp_leaderboard/struct.XPLeaderboardResponse.html -} diff --git a/examples/ex07_get_streams/Cargo.toml b/examples/ex07_get_streams/Cargo.toml deleted file mode 100644 index 85aa5e2..0000000 --- a/examples/ex07_get_streams/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex07_get_streams" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex07_get_streams/src/main.rs b/examples/ex07_get_streams/src/main.rs deleted file mode 100644 index 9bd639e..0000000 --- a/examples/ex07_get_streams/src/main.rs +++ /dev/null @@ -1,93 +0,0 @@ -use tetr_ch::client::{ - Client, - stream::{StreamType, StreamContext} -}; - -#[tokio::main] -async fn main() { - // Create a new client. - let client = Client::new(); - - // Get a stream. - let _stream1 = match client - .get_stream( - // 40 LINES - StreamType::FortyLines, - // User's best - StreamContext::UserBest, - // User ID - Some("621db46d1d638ea850be2aa0"), - ) - .await - { - Ok(s) => s, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Some examples for other arguments setups: - - // The global 40 LINES stream. - let _stream2 = match Client::new() - .get_stream( - StreamType::FortyLines, - StreamContext::Global, - None - ) - .await - { - Ok(s) => s, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // User `5e32fc85ab319c2ab1beb07c`'s best BLITZ records stream. - let _stream3 = match Client::new() - .get_stream( - StreamType::Blitz, - StreamContext::UserBest, - Some("5e32fc85ab319c2ab1beb07c") - ) - .await - { - Ok(s) => s, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // User `5e32fc85ab319c2ab1beb07c`'s recent mixed records stream. - let _stream4 = match Client::new() - .get_stream( - StreamType::Any, - StreamContext::UserRecent, - Some("5e32fc85ab319c2ab1beb07c") - ) - .await - { - Ok(s) => s, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // User `5e32fc85ab319c2ab1beb07c`'s recent TETRA LEAGUE records stream. - let _stream5 = match Client::new() - .get_stream( - StreamType::League, - StreamContext::UserRecent, - Some("5e32fc85ab319c2ab1beb07c") - ) - .await - { - Ok(s) => s, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Learn about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/stream/struct.StreamResponse.html -} diff --git a/examples/ex08_get_latest_news/Cargo.toml b/examples/ex08_get_latest_news/Cargo.toml deleted file mode 100644 index 5319864..0000000 --- a/examples/ex08_get_latest_news/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex08_get_latest_news" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex08_get_latest_news/src/main.rs b/examples/ex08_get_latest_news/src/main.rs deleted file mode 100644 index 59029c9..0000000 --- a/examples/ex08_get_latest_news/src/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -use tetr_ch::client::{Client, stream::NewsSubject}; - -#[tokio::main] -async fn main() { - // Create a new client. - let client = Client::new(); - - // Get the latest news. - let _latest_news1 = match client.get_latest_news( - // News of user `621db46d1d638ea850be2aa0`. - NewsSubject::User("621db46d1d638ea850be2aa0".to_string()), - // Three news. - 3, - ).await { - Ok(n) => n, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Some examples for other arguments setups: - - // Latest news of all users. - let _latest_news2 = match Client::new().get_latest_news( - NewsSubject::Any, - 3, - ).await { - Ok(n) => n, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Global latest news. - let _latest_news3 = match Client::new().get_latest_news( - NewsSubject::Global, - 3, - ).await { - Ok(n) => n, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - - // Learn about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/user/struct.UserResponse.html -} diff --git a/examples/ex09_constants/Cargo.toml b/examples/ex09_constants/Cargo.toml deleted file mode 100644 index 29a85b7..0000000 --- a/examples/ex09_constants/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex09_constants" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex09_constants/src/main.rs b/examples/ex09_constants/src/main.rs deleted file mode 100644 index fea1ff6..0000000 --- a/examples/ex09_constants/src/main.rs +++ /dev/null @@ -1,58 +0,0 @@ -use tetr_ch::model::league::Rank; - -fn main() { - // This library is providing the color code for each rank as a constant. - dbg!(Rank::D_COL); - dbg!(Rank::D_PLUS_COL); - dbg!(Rank::C_MINUS_COL); - dbg!(Rank::C_COL); - dbg!(Rank::C_PLUS_COL); - dbg!(Rank::B_MINUS_COL); - dbg!(Rank::B_COL); - dbg!(Rank::B_PLUS_COL); - dbg!(Rank::A_MINUS_COL); - dbg!(Rank::A_COL); - dbg!(Rank::A_PLUS_COL); - dbg!(Rank::S_MINUS_COL); - dbg!(Rank::S_COL); - dbg!(Rank::S_PLUS_COL); - dbg!(Rank::SS_COL); - dbg!(Rank::U_COL); - dbg!(Rank::X_COL); - dbg!(Rank::XX_COL); - dbg!(Rank::Z_COL); - - // You can also get the rank color from each rank model. - // Example: - assert_eq!(Rank::D_COL, Rank::D.color()); - - // You can see each rank color visually in the doc below: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/league/enum.Rank.html#associatedconstant.D_COL - - - - // ! The following constants are deprecated: - // https://docs.rs/tetr_ch/latest/tetr_ch/constants/rank_col/ - /* - use tetr_ch::constants::rank_col; - dbg!(rank_col::D); - dbg!(rank_col::D_PLUS); - dbg!(rank_col::C_MINUS); - dbg!(rank_col::C); - dbg!(rank_col::C_PLUS); - dbg!(rank_col::B_MINUS); - dbg!(rank_col::B); - dbg!(rank_col::B_PLUS); - dbg!(rank_col::A_MINUS); - dbg!(rank_col::A); - dbg!(rank_col::A_PLUS); - dbg!(rank_col::S_MINUS); - dbg!(rank_col::S); - dbg!(rank_col::S_PLUS); - dbg!(rank_col::SS); - dbg!(rank_col::U); - dbg!(rank_col::X); - dbg!(rank_col::XX); - dbg!(rank_col::Z); - */ -} diff --git a/examples/ex10_search_accounts/Cargo.toml b/examples/ex10_search_accounts/Cargo.toml deleted file mode 100644 index 2e05e7c..0000000 --- a/examples/ex10_search_accounts/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ex10_search_accounts" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dependencies.tetr_ch] -path = "../../" - -[dependencies.tokio] -version = "1.20" -features = ["full"] diff --git a/examples/ex10_search_accounts/src/main.rs b/examples/ex10_search_accounts/src/main.rs deleted file mode 100644 index 8ab9e4f..0000000 --- a/examples/ex10_search_accounts/src/main.rs +++ /dev/null @@ -1,39 +0,0 @@ -use tetr_ch::client::Client; - -#[tokio::main] -async fn main() { - // Create a new client. - let client = Client::new(); - - // Search a TETRA.IO account by Discord ID. - let usr = match client.search_user("724976600873041940").await { - Ok(u) => u, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - - // Print the requested data. - println!("{:?}\n", usr.data.as_ref().unwrap().user); - - // Get the user details. - let user_details = match usr.data.as_ref().unwrap().get_user().await { - Ok(u) => u, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - println!("Details: {:?}\n", user_details); - - // And get the user's records. - let user_records = match usr.data.as_ref().unwrap().get_records().await { - Ok(u) => u, - Err(err) => { - panic!("Error: {}\n", err.to_string()); - } - }; - println!("Records: {:?}\n", user_records); - - // Learn more about what we can get from following docs: - // https://docs.rs/tetr_ch/latest/tetr_ch/model/searched_user/struct.SearchedUserResponse.html -} From 1a2504f347644182011ff4e3aca79a264c79ac76 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:07:09 +0900 Subject: [PATCH 239/255] =?UTF-8?q?=F0=9F=93=A6=20Add:=20`tokio`=20as=20a?= =?UTF-8?q?=20dev-dependency=20with=20full=20features=20[#81]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 2f564a1..10da3fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,4 +31,5 @@ version = "1.11.0" features = ["v4"] [dev-dependencies] +tokio = { version = "1", features = ["full"] } tokio-test = "0.4.2" From 7fe118190f2a7fb616764abd225506b392c2db8a Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:08:34 +0900 Subject: [PATCH 240/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Update:=20`/exa?= =?UTF-8?q?mples/`=20[#81]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/01_get-user-info.rs | 43 ++++++++++++ examples/02_search-for-account.rs | 35 ++++++++++ examples/03_get-user-summaries.rs | 54 +++++++++++++++ examples/04_get-user-leaderboard.rs | 66 +++++++++++++++++++ .../05_get-historical-user-leaderboard.rs | 52 +++++++++++++++ examples/06_get-user-records.rs | 56 ++++++++++++++++ examples/07_get-record-leaderboard.rs | 57 ++++++++++++++++ examples/08_search-for-record.rs | 42 ++++++++++++ examples/09_get-news.rs | 39 +++++++++++ examples/10_get-server-stats.rs | 30 +++++++++ examples/11_get-server-activity.rs | 32 +++++++++ examples/12_get-scoreflow-leagueflow.rs | 33 ++++++++++ examples/13_get-all-rank-metadata.rs | 30 +++++++++ examples/14_get-achievement-info.rs | 35 ++++++++++ examples/15_pagination-for-leaderboard.rs | 66 +++++++++++++++++++ examples/README.md | 47 +++++++++---- 16 files changed, 703 insertions(+), 14 deletions(-) create mode 100644 examples/01_get-user-info.rs create mode 100644 examples/02_search-for-account.rs create mode 100644 examples/03_get-user-summaries.rs create mode 100644 examples/04_get-user-leaderboard.rs create mode 100644 examples/05_get-historical-user-leaderboard.rs create mode 100644 examples/06_get-user-records.rs create mode 100644 examples/07_get-record-leaderboard.rs create mode 100644 examples/08_search-for-record.rs create mode 100644 examples/09_get-news.rs create mode 100644 examples/10_get-server-stats.rs create mode 100644 examples/11_get-server-activity.rs create mode 100644 examples/12_get-scoreflow-leagueflow.rs create mode 100644 examples/13_get-all-rank-metadata.rs create mode 100644 examples/14_get-achievement-info.rs create mode 100644 examples/15_pagination-for-leaderboard.rs diff --git a/examples/01_get-user-info.rs b/examples/01_get-user-info.rs new file mode 100644 index 0000000..46d00e6 --- /dev/null +++ b/examples/01_get-user-info.rs @@ -0,0 +1,43 @@ +//! Gets the detailed information about the specified user. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 01_get-user-info +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + // Create a new client. + let client = Client::new(); + + // Set the username or user ID to get the information. + let user = "rinrin-rs"; + + // Get the information. + let response = match client.get_user(user).await { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + // Check if there is an error. + // An error "No such user!" will be returned here if the user does not exist. + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + println!("Name: {}", data.username); + println!("ID: {}", data.id); + println!("XP: {}", data.xp); + println!("Level: {}", data.level()); + println!("Role: {}", data.role); + println!("Country: {:?}", data.country); + println!("Avatar URL: {}", data.avatar_url()); + println!("Discord: {:?}", data.connections.discord); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/user/struct.User.html +} diff --git a/examples/02_search-for-account.rs b/examples/02_search-for-account.rs new file mode 100644 index 0000000..79107e6 --- /dev/null +++ b/examples/02_search-for-account.rs @@ -0,0 +1,35 @@ +//! Searches for a TETR.IO user account by the social connection. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 02_search-for-account +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the social connection to search for. + // The API document says searching for the other social links will be added in the near future. + let social_connection = SocialConnection::Discord("724976600873041940".to_string()); + + // Search for the account. + let response = match client.search_user(social_connection).await { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap().user.unwrap(); + println!("Name: {}", data.username); + println!("ID: {}", data.id); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/searched_user/struct.UserData.html +} diff --git a/examples/03_get-user-summaries.rs b/examples/03_get-user-summaries.rs new file mode 100644 index 0000000..7a5d224 --- /dev/null +++ b/examples/03_get-user-summaries.rs @@ -0,0 +1,54 @@ +//! Gets some summaries of the specified user. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 03_get-user-summaries +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the username or user ID to get the summaries. + let user = "rinrin-rs"; + + // Get the summary of the user's 40 LINES games. + let _ = client.get_user_40l(user).await; + + // Get the summary of the user's BLITZ games. + let _ = client.get_user_blitz(user).await; + + // Get the summary of the user's QUICK PLAY games. + let _ = client.get_user_zenith(user).await; + + // Get the summary of the user's EXPERT QUICK PLAY games. + let _ = client.get_user_zenith_ex(user).await; + + // Get the summary of the user's TETRA LEAGUE standing. + let _ = client.get_user_league(user).await; + + // Get the summary of the user's ZEN progress. + let _ = client.get_user_zen(user).await; + + // Get all the achievements of the user. + let _ = client.get_user_achievements(user).await; + + // Get all the summaries of the user. + // + // WARNING: Consider whether you really need to use this method. + // If you only collect data for one or two game modes, + // use the methods for the individual summaries instead. + let _ = client.get_user_all_summaries(user).await; + + // For more information about the data structures, see: + // - 40 LINES: https://docs.rs/tetr_ch/latest/tetr_ch/model/summary/forty_lines/struct.FortyLines.html + // - BLITZ: https://docs.rs/tetr_ch/latest/tetr_ch/model/summary/blitz/struct.Blitz.html + // - QUICK PLAY, EXPERT QUICK PLAY: https://docs.rs/tetr_ch/latest/tetr_ch/model/summary/zenith/struct.Zenith.html + // - TETRA LEAGUE: https://docs.rs/tetr_ch/latest/tetr_ch/model/summary/league/struct.LeagueData.html + // - ZEN: https://docs.rs/tetr_ch/latest/tetr_ch/model/summary/zen/struct.Zen.html + // - Achievements: https://docs.rs/tetr_ch/latest/tetr_ch/model/util/achievement/struct.Achievement.html + // - All summaries: https://docs.rs/tetr_ch/latest/tetr_ch/model/summary/struct.AllSummaries.html +} diff --git a/examples/04_get-user-leaderboard.rs b/examples/04_get-user-leaderboard.rs new file mode 100644 index 0000000..3c68ba2 --- /dev/null +++ b/examples/04_get-user-leaderboard.rs @@ -0,0 +1,66 @@ +//! Gets the user leaderboard fulfilling the search criteria. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 04_get-user-leaderboard +//! ``` +//! +//! Want to paginate over this data? +//! Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. +//! For more details, see the example in `/examples/15_pagination-for-leaderboard.rs`. + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the search criteria to filter users by. + let criteria = user_leaderboard::SearchCriteria::new() + // Upper bound is `[15200, 0, 0]` + .after([15200., 0., 0.]) + // Three entries + .limit(3) + // Filter by Japan + .country("jp"); + + // Get the leaderboard. + let response = match client + .get_leaderboard( + // A TETRA LEAGUE leaderboard. + UserLeaderboardType::League, + Some(criteria), + ) + .await + { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + data.entries + .iter() + .for_each(|entry| println!("{}", entry.username.to_uppercase())); + + // Some examples of search criteria: + + // Default search criteria. + // No bounds, no limit, no country filter. + let _ = user_leaderboard::SearchCriteria::new(); + + // Lower bound is `[15200, 0, 0]`. + // The leaderboard order is reversed. + let _ = user_leaderboard::SearchCriteria::new().before([15200., 0., 0.]); + + // Use the `init` method to initialize the search criteria to default. + let mut criteria = user_leaderboard::SearchCriteria::new().country("us"); + criteria.init(); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/leaderboard/struct.Leaderboard.html +} diff --git a/examples/05_get-historical-user-leaderboard.rs b/examples/05_get-historical-user-leaderboard.rs new file mode 100644 index 0000000..522b4f4 --- /dev/null +++ b/examples/05_get-historical-user-leaderboard.rs @@ -0,0 +1,52 @@ +//! Gets the array of the historical user blobs fulfilling the search criteria. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 05_get-historical-user-leaderboard +//! ``` +//! +//! Want to paginate over this data? +//! Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. +//! For more details, see the example in `/examples/15_pagination-for-leaderboard.rs`. + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the season to get the array. + // "1" is the Season 1. + let season = "1"; + + // Set the search criteria to filter users by. + let criteria = user_leaderboard::SearchCriteria::new() + // Upper bound is `[24997, 0, 0]` + .after([24997., 0., 0.]) + // Five entries + .limit(5) + // Filter by Japan + .country("jp"); + + // Get the array. + let response = match client + .get_historical_league_leaderboard(season, Some(criteria)) + .await + { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + data.entries + .iter() + .for_each(|entry| println!("{}", entry.username.to_uppercase())); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/leaderboard/struct.HistoricalLeaderboard.html +} diff --git a/examples/06_get-user-records.rs b/examples/06_get-user-records.rs new file mode 100644 index 0000000..5b08a5d --- /dev/null +++ b/examples/06_get-user-records.rs @@ -0,0 +1,56 @@ +//! Gets the personal record leaderboard of the specified user, fulfilling the search criteria. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 06_get-user-records +//! ``` +//! +//! Want to paginate over this data? +//! Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. +//! For more details, see the example in `/examples/15_pagination-for-leaderboard.rs`. + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the username or user ID to get the personal records. + let user = "rinrin-rs"; + + // Set the search criteria to filter records by. + let criteria = record::SearchCriteria::new() + // Upper bound is `[500000, 0, 0]` + .after([500000., 0., 0.]) + // Three entries + .limit(3); + + // Get the records. + let response = match client + .get_user_records( + user, + // BLITZ + record::Gamemode::Blitz, + // Top score leaderboard + record::LeaderboardType::Top, + Some(criteria), + ) + .await + { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + data.entries + .iter() + .for_each(|record| println!("{}", record.replay_url())); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/user_records/struct.UserRecords.html +} diff --git a/examples/07_get-record-leaderboard.rs b/examples/07_get-record-leaderboard.rs new file mode 100644 index 0000000..b56004f --- /dev/null +++ b/examples/07_get-record-leaderboard.rs @@ -0,0 +1,57 @@ +//! Gets the record leaderboard fulfilling the search criteria. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 07_get-record-leaderboard +//! ``` +//! +//! Want to paginate over this data? +//! Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. +//! For more details, see the example in `/examples/15_pagination-for-leaderboard.rs`. + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the record leaderboard ID to look up. + // Record leaderboard ID: `zenith_country_JP@2024w31` + let leaderboard_id = RecordsLeaderboardId::new( + // Game mode: `zenith` (QUICK PLAY) + "zenith", + // Scope: `JP` (Japan) + Scope::Country("JP".to_string()), + // Revolution ID: `@2024w31` + Some("@2024w31"), + ); + + // Set the search criteria to filter records by. + let criteria = record_leaderboard::SearchCriteria::new() + // Upper bound is `[500000, 0, 0]` + .after([500000., 0., 0.]) + // Three entries + .limit(3); + + // Get the information. + let response = match client + .get_records_leaderboard(leaderboard_id, Some(criteria)) + .await + { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + data.entries + .iter() + .for_each(|entry| println!("{}", entry.replay_url())); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/records_leaderboard/struct.RecordsLeaderboard.html +} diff --git a/examples/08_search-for-record.rs b/examples/08_search-for-record.rs new file mode 100644 index 0000000..39ce350 --- /dev/null +++ b/examples/08_search-for-record.rs @@ -0,0 +1,42 @@ +//! Searches for a record of the specified user with the specified timestamp. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 08_search-for-record +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the user ID to search for. + let user_id = "621db46d1d638ea850be2aa0"; + + // Search for the record. + let response = match client + .search_record( + user_id, + // Gamemode: `blitz` (BLITZ) + RecordGamemode::Blitz, + // Timestamp: `1680053762145` (`2023-03-29T01:36:02.145Z`) + 1680053762145, + ) + .await + { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + println!("Replay URL: {}", data.replay_url()); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/summary/record/struct.Record.html +} diff --git a/examples/09_get-news.rs b/examples/09_get-news.rs new file mode 100644 index 0000000..116e5b9 --- /dev/null +++ b/examples/09_get-news.rs @@ -0,0 +1,39 @@ +//! Gets the news items. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 09_get-news +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Get three latest news items in any stream. + let _ = client.get_news_all(3).await; + + // Gets the latest news items in the global news stream. + let _ = client + .get_news_latest( + // The global news stream. + NewsStreamParam::Global, + // Three news + 3, + ) + .await; + + // Gets the latest news items in the specified user's news stream. + let _ = client + .get_news_latest( + // The news stream of the user `621db46d1d638ea850be2aa0` + NewsStreamParam::User("621db46d1d638ea850be2aa0".to_string()), + 3, + ) + .await; + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/news/struct.NewsItems.html +} diff --git a/examples/10_get-server-stats.rs b/examples/10_get-server-stats.rs new file mode 100644 index 0000000..9891ac3 --- /dev/null +++ b/examples/10_get-server-stats.rs @@ -0,0 +1,30 @@ +//! Gets some statistics about the TETR.IO. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 10_get-server-stats +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Get the statistics. + let response = match client.get_server_stats().await { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + println!("{:#?}", data); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/server_stats/struct.ServerStats.html +} diff --git a/examples/11_get-server-activity.rs b/examples/11_get-server-activity.rs new file mode 100644 index 0000000..62a269e --- /dev/null +++ b/examples/11_get-server-activity.rs @@ -0,0 +1,32 @@ +//! Gets the array of the user activity over the last 2 days. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 11_get-server-activity +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Get the array. + let response = match client.get_server_activity().await { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + println!("Peak: {:?}", data.peak()); + println!("Trough: {:?}", data.trough()); + println!("Average: {:?}", data.average()); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/server_activity/struct.ServerActivity.html +} diff --git a/examples/12_get-scoreflow-leagueflow.rs b/examples/12_get-scoreflow-leagueflow.rs new file mode 100644 index 0000000..fe60a17 --- /dev/null +++ b/examples/12_get-scoreflow-leagueflow.rs @@ -0,0 +1,33 @@ +//! Gets the condensed graph of all of the specified user’s records or TETRA LEAGUE matches. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 12_get-scoreflow-leagueflow +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the username or user ID to get the graph. + let user = "rinrin-rs"; + + // Get the condensed graph of the user's 40 LINES records. + let _ = client + .get_labs_scoreflow( + user, + // 40 LINES + RecordGamemode::FortyLines, + ) + .await; + + // Get the condensed graph of the user's matches in TETRA LEAGUE. + let _ = client.get_labs_leagueflow(user).await; + + // For more information about the data structures, see: + // - Scoreflow: https://docs.rs/tetr_ch/latest/tetr_ch/model/labs/scoreflow/struct.LabsScoreflow.html + // - Leagueflow: https://docs.rs/tetr_ch/latest/tetr_ch/model/labs/leagueflow/struct.LabsLeagueflow.html +} diff --git a/examples/13_get-all-rank-metadata.rs b/examples/13_get-all-rank-metadata.rs new file mode 100644 index 0000000..8c1a802 --- /dev/null +++ b/examples/13_get-all-rank-metadata.rs @@ -0,0 +1,30 @@ +//! Gets the view over all TETRA LEAGUE ranks and their metadata. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 13_get-all-rank-metadata +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Get the metadata. + let response = match client.get_labs_league_ranks().await { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + println!("{:?}", data); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/labs/league_ranks/struct.LabsLeagueRanks.html +} diff --git a/examples/14_get-achievement-info.rs b/examples/14_get-achievement-info.rs new file mode 100644 index 0000000..9959a60 --- /dev/null +++ b/examples/14_get-achievement-info.rs @@ -0,0 +1,35 @@ +//! Gets the data about the specified achievement itself, its cutoffs, and its leaderboard. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 14_get-achievement-info +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + let client = Client::new(); + + // Set the achievement ID to get data. (e.g. "15") + let achievement_id = "15"; + + // Get the data. + let response = match client.get_achievement_info(achievement_id).await { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); + } + + let data = response.data.unwrap(); + println!("{:#?}", data.achievement); + println!("\n{} entries\n", data.leaderboard.len()); + println!("Cutoffs: {:#?}", data.cutoffs); + + // For more information about the data structure, see: + // https://docs.rs/tetr_ch/latest/tetr_ch/model/achievement_info/struct.AchievementInfo.html +} diff --git a/examples/15_pagination-for-leaderboard.rs b/examples/15_pagination-for-leaderboard.rs new file mode 100644 index 0000000..35724c9 --- /dev/null +++ b/examples/15_pagination-for-leaderboard.rs @@ -0,0 +1,66 @@ +//! Pagination for the leaderboards. +//! +//! Run the following Cargo command to run this example: +//! +//! ```bash +//! cargo run --example 15_pagination-for-leaderboard +//! ``` + +use tetr_ch::prelude::*; + +#[tokio::main] +async fn main() { + // `Client` struct that created by `Client::new()` does not have `X-Session-ID` header. + // So use `Client::with_session_id()` to create a new `Client`, + // if you are often re-requesting the same datasets. + // This not only assures the data you receive is consistent, + // it also helps reduce database calls on their(TETRA CHANNEL API) side. + + // Create a new client with an automatically generated session ID. + // Or, if you have a session ID that wanna use, pass it as an argument. + let client = Client::with_session_id(None).unwrap(); + + // You can get the session ID by calling the `session_id` method. + println!("Generated session ID: {}", client.session_id().unwrap()); + + // Get the top 1 ~ 50 on the TETRA LEAGUE leaderboard. + let users = client + .get_leaderboard( + UserLeaderboardType::League, + // 50 entries. + Some(user_leaderboard::SearchCriteria::new().limit(50)), + ) + .await + .unwrap() + .data + .unwrap() + .entries; + + println!("β„–1 {}", users[0].username.to_uppercase()); + // ... + println!("β„–50 {}", users[49].username.to_uppercase()); + + // A prisecter is consisting of three floats. It allows you to continue paginating. + let prisecter = &users[49].prisecter; + + // Get the top 51 ~ 100 on the TETRA LEAGUE leaderboard using the prisecter. + let users = client + .get_leaderboard( + UserLeaderboardType::League, + Some( + user_leaderboard::SearchCriteria::new() + .limit(100) + // Set the upper bound with the prisecter. + .after(prisecter.to_array()), + ), + ) + .await + .unwrap() + .data + .unwrap() + .entries; + + println!("β„–51 {}", users[0].username.to_uppercase()); + // ... + println!("β„–100 {}", users[49].username.to_uppercase()); +} diff --git a/examples/README.md b/examples/README.md index 88f65bc..1699359 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,14 +1,33 @@ -# Some examples - -These are examples for using this library. - -1. [Get user details](./ex01_get_user_details/) -1. [Get server statistics](./ex02_get_server_statistics/) -1. [Get server activity](./ex03_get_server_activity/) -1. [Get user's (best) records](./ex04_get_user_records/) -1. [Get the TETRA LEAGUE leaderboard](./ex05_get_league_leaderboard/) -1. [Get the XP leaderboard](./ex06_get_xp_leaderboard/) -1. [Get streams](./ex07_get_streams/) -1. [Get latest news](./ex08_get_latest_news/) -1. [Constants](./ex09_constants/) -1. [Search TETR.IO accounts](./ex10_search_accounts/) +# Examples + +Run the following Cargo command to run an example: + +```bash +cargo run --example +``` + +--- + +1. [Get user info](./01_get-user-info.rs) `01_get-user-info` +1. [Search for account](./02_search-for-account.rs) `02_search-for-account` +1. [Get user summaries](./03_get-user-summaries.rs) `03_get-user-summaries` + - 40 LINES + - BLITZ + - QUICK PLAY + - EXPERT QUICK PLAY + - TETRA LEAGUE + - ZEN + - Achievements + - All +1. [Get user leaderboard](./04_get-user-leaderboard.rs) `04_get-user-leaderboard` +1. [Get historical user leaderboard](./05_get-historical-user-leaderboard.rs) `05_get-historical-user-leaderboard` +1. [Get user records](./06_get-user-records.rs) `06_get-user-records` +1. [Get record leaderboard](./07_get-record-leaderboard.rs) `07_get-record-leaderboard` +1. [Search for record](./08_search-for-record.rs) `08_search-for-record` +1. [Get news](./09_get-news.rs) `09_get-news` +1. [Get server stats](./10_get-server-stats.rs) `10_get-server-stats` +1. [Get server activity](./11_get-server-activity.rs) `11_get-server-activity` +1. [Get scoreflow / leagueflow](./12_get-scoreflow-leagueflow.rs) `12_get-scoreflow-leagueflow` +1. [Get all rank metadata](./13_get-all-rank-metadata.rs) `13_get-all-rank-metadata` +1. [Get achievement info](./14_get-achievement-info.rs) `14_get-achievement-info` +1. [Pagination for leaderboard](./15_pagination-for-leaderboard.rs) `15_pagination-for-leaderboard` From 21deaabfa0dbf3a09300b3bd35de0a6c8d141595 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:29:05 +0900 Subject: [PATCH 241/255] =?UTF-8?q?=F0=9F=A9=B9=20Fix:=20`PartialLeagueDat?= =?UTF-8?q?a`'s=20some=20fields=20are=20now=20optional=20(API=20doc=20exce?= =?UTF-8?q?ption)=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/leaderboard.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/model/leaderboard.rs b/src/model/leaderboard.rs index 96c722c..c5ee098 100644 --- a/src/model/leaderboard.rs +++ b/src/model/leaderboard.rs @@ -128,18 +128,28 @@ pub struct PartialLeagueData { /// This user's rank. pub rank: Rank, /// This user's highest achieved rank this season. + /// + /// ***The API document does not say this field is optional.** #[serde(rename = "bestrank")] - pub best_rank: Rank, + pub best_rank: Option, /// This user's Glicko-2 rating. pub glicko: f64, /// This user's Glicko-2 Rating Deviation. - pub rd: f64, + /// + /// ***The API document does not say this field is optional.** + pub rd: Option, /// This user's average APM (attack per minute) over the last 10 games. - pub apm: f64, + /// + /// ***The API document does not say this field is optional.** + pub apm: Option, /// This user's average PPS (pieces per second) over the last 10 games. - pub pps: f64, + /// + /// ***The API document does not say this field is optional.** + pub pps: Option, /// This user's average VS (versus score) over the last 10 games. - pub vs: f64, + /// + /// ***The API document does not say this field is optional.** + pub vs: Option, /// Whether this user's RD is rising (has not played in the last week). #[serde(rename = "decaying")] pub is_decaying: bool, From 64f9ac77e8b8e5cba037cad61923dace2c34f5e7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:37:57 +0900 Subject: [PATCH 242/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Add:=20derive?= =?UTF-8?q?=20`Clone`,=20`Debug`=20for=20some=20types=20for=20parameters?= =?UTF-8?q?=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/news_stream.rs | 1 + src/client/param/record.rs | 2 ++ src/client/param/record_leaderboard.rs | 2 ++ src/client/param/search_user.rs | 1 + src/client/param/user_leaderboard.rs | 1 + 5 files changed, 7 insertions(+) diff --git a/src/client/param/news_stream.rs b/src/client/param/news_stream.rs index ea138ae..12821e7 100644 --- a/src/client/param/news_stream.rs +++ b/src/client/param/news_stream.rs @@ -1,6 +1,7 @@ //! Features for news streams. /// A news stream. +#[derive(Clone, Debug)] pub enum NewsStream { /// A global news stream. Global, diff --git a/src/client/param/record.rs b/src/client/param/record.rs index 239fb6e..5cc6d4c 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -4,6 +4,7 @@ use super::pagination::Bound; use crate::util::validate_limit; /// A game mode of a record. +#[derive(Clone, Debug)] pub enum Gamemode { /// 40 LINES. FortyLines, @@ -48,6 +49,7 @@ impl Gamemode { } /// A record leaderboard type. +#[derive(Clone, Debug)] pub enum LeaderboardType { /// Top scores. Top, diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index 67f3cf2..bc3a5e1 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -4,6 +4,7 @@ use super::pagination::Bound; use crate::util::validate_limit; /// A record leaderboard ID. +#[derive(Clone, Debug)] pub struct RecordsLeaderboardId { /// The game mode. e.g. `40l`. pub gamemode: String, @@ -60,6 +61,7 @@ impl RecordsLeaderboardId { } /// A scope of record leaderboards. +#[derive(Clone, Debug)] pub enum Scope { /// Global scope. Global, diff --git a/src/client/param/search_user.rs b/src/client/param/search_user.rs index 8b2c86c..b15ef02 100644 --- a/src/client/param/search_user.rs +++ b/src/client/param/search_user.rs @@ -3,6 +3,7 @@ /// A social connection. /// /// [API document](https://tetr.io/about/api/#userssearchquery) says searching for the other social links will be added in the near future. +#[derive(Clone, Debug)] pub enum SocialConnection { /// A Discord ID. Discord(String), diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 54427f5..d32566d 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -4,6 +4,7 @@ use super::pagination::Bound; use crate::util::validate_limit; /// A user leaderboard type. +#[derive(Clone, Debug)] pub enum LeaderboardType { /// A TETRA LEAGUE leaderboard. League, From 47626a80bb4e3b1b6640598c874c4835ffbb0757 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:41:16 +0900 Subject: [PATCH 243/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20docs=20for=20`Se?= =?UTF-8?q?archCriteria`=20structs=20now=20use=20`tetr=5Fch::prelude`=20mo?= =?UTF-8?q?dule=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/record.rs | 10 +++++----- src/client/param/record_leaderboard.rs | 10 +++++----- src/client/param/user_leaderboard.rs | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/client/param/record.rs b/src/client/param/record.rs index 5cc6d4c..2da3a8d 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -88,23 +88,23 @@ impl LeaderboardType { /// # Examples /// /// ``` -/// use tetr_ch::client::param::record::SearchCriteria; +/// use tetr_ch::prelude::*; /// /// // Default search criteria. -/// let c1 = SearchCriteria::new(); +/// let c1 = record::SearchCriteria::new(); /// /// // Upper bound is `[500000, 0, 0]`, three entries. -/// let c2 = SearchCriteria::new() +/// let c2 = record::SearchCriteria::new() /// .after([500000., 0., 0.]) /// .limit(3); /// /// // Lower bound is `[500000, 0, 0]`. /// // Also the search order is reversed. -/// let c3 = SearchCriteria::new() +/// let c3 = record::SearchCriteria::new() /// .before([500000., 0., 0.]); /// /// // You can initialize the search criteria to default as follows: -/// let mut c4 = SearchCriteria::new().limit(10); +/// let mut c4 = record::SearchCriteria::new().limit(10); /// c4.init(); /// ``` #[derive(Clone, Debug, Default)] diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index bc3a5e1..c2473e2 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -76,23 +76,23 @@ pub enum Scope { /// # Examples /// /// ``` -/// use tetr_ch::client::param::record_leaderboard::SearchCriteria; +/// use tetr_ch::prelude::*; /// /// // Default search criteria. -/// let c1 = SearchCriteria::new(); +/// let c1 = record_leaderboard::SearchCriteria::new(); /// /// // Upper bound is `[500000, 0, 0]`, three entries. -/// let c2 = SearchCriteria::new() +/// let c2 = record_leaderboard::SearchCriteria::new() /// .after([500000., 0., 0.]) /// .limit(3); /// /// // Lower bound is `[500000, 0, 0]`. /// // Also the search order is reversed. -/// let c3 = SearchCriteria::new() +/// let c3 = record_leaderboard::SearchCriteria::new() /// .before([500000., 0., 0.]); /// /// // You can initialize the search criteria to default as follows: -/// let mut c4 = SearchCriteria::new().limit(10); +/// let mut c4 = record_leaderboard::SearchCriteria::new().limit(10); /// c4.init(); /// ``` #[derive(Clone, Debug, Default)] diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index d32566d..8b5bba4 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -39,24 +39,24 @@ impl LeaderboardType { /// # Examples /// /// ``` -/// use tetr_ch::client::param::user_leaderboard::SearchCriteria; +/// use tetr_ch::prelude::*; /// /// // Default search criteria. -/// let c1 = SearchCriteria::new(); +/// let c1 = user_leaderboard::SearchCriteria::new(); /// /// // Upper bound is `[15200, 0, 0]`, three entries, filter by Japan. -/// let c2 = SearchCriteria::new() +/// let c2 = user_leaderboard::SearchCriteria::new() /// .after([15200., 0., 0.]) /// .limit(3) /// .country("jp"); /// /// // Lower bound is `[15200, 0, 0]`. /// // Also the search order is reversed. -/// let c3 = SearchCriteria::new() +/// let c3 = user_leaderboard::SearchCriteria::new() /// .before([15200., 0., 0.]); /// /// // You can initialize the search criteria to default as follows: -/// let mut c4 = SearchCriteria::new().country("us"); +/// let mut c4 = user_leaderboard::SearchCriteria::new().country("us"); /// c4.init(); /// ``` #[derive(Clone, Debug, Default)] From f90cbc1528187c661d84823bf713af30a8ad32e0 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:46:49 +0900 Subject: [PATCH 244/255] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20`SearchCriteria::?= =?UTF-8?q?init`=20methods=20were=20not=20working=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/record.rs | 5 +++-- src/client/param/record_leaderboard.rs | 5 +++-- src/client/param/user_leaderboard.rs | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/client/param/record.rs b/src/client/param/record.rs index 2da3a8d..eef6bdd 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -139,8 +139,9 @@ impl SearchCriteria { /// let mut criteria = SearchCriteria::new(); /// criteria.init(); /// ``` - pub fn init(self) -> Self { - Self::default() + pub fn init(&mut self) { + self.bound = None; + self.limit = None; } /// Sets the upper bound. diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index c2473e2..25a136b 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -127,8 +127,9 @@ impl SearchCriteria { /// let mut criteria = SearchCriteria::new(); /// criteria.init(); /// ``` - pub fn init(self) -> Self { - Self::default() + pub fn init(&mut self) { + self.bound = None; + self.limit = Some(25); } /// Sets the upper bound. diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 8b5bba4..0d56800 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -94,8 +94,10 @@ impl SearchCriteria { /// let mut criteria = SearchCriteria::new().country("us"); /// criteria.init(); /// ``` - pub fn init(self) -> Self { - Self::default() + pub fn init(&mut self) { + self.bound = None; + self.limit = None; + self.country = None; } /// Sets the upper bound. From 31d882c29a221b28acc4c97e9618c482f8e9f3c4 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:49:59 +0900 Subject: [PATCH 245/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20improve=20commen?= =?UTF-8?q?ts=20in=20`SearchCriteria`=20structs'=20docs=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/param/record.rs | 2 +- src/client/param/record_leaderboard.rs | 2 +- src/client/param/user_leaderboard.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/param/record.rs b/src/client/param/record.rs index eef6bdd..aca28a5 100644 --- a/src/client/param/record.rs +++ b/src/client/param/record.rs @@ -99,7 +99,7 @@ impl LeaderboardType { /// .limit(3); /// /// // Lower bound is `[500000, 0, 0]`. -/// // Also the search order is reversed. +/// // The leaderboard order is reversed. /// let c3 = record::SearchCriteria::new() /// .before([500000., 0., 0.]); /// diff --git a/src/client/param/record_leaderboard.rs b/src/client/param/record_leaderboard.rs index 25a136b..980cf21 100644 --- a/src/client/param/record_leaderboard.rs +++ b/src/client/param/record_leaderboard.rs @@ -87,7 +87,7 @@ pub enum Scope { /// .limit(3); /// /// // Lower bound is `[500000, 0, 0]`. -/// // Also the search order is reversed. +/// // The leaderboard order is reversed. /// let c3 = record_leaderboard::SearchCriteria::new() /// .before([500000., 0., 0.]); /// diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 0d56800..5172524 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -51,7 +51,7 @@ impl LeaderboardType { /// .country("jp"); /// /// // Lower bound is `[15200, 0, 0]`. -/// // Also the search order is reversed. +/// // The leaderboard order is reversed. /// let c3 = user_leaderboard::SearchCriteria::new() /// .before([15200., 0., 0.]); /// From 106e349f2a43f0eac20c8bc3c005cd17dd2196e4 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 03:59:34 +0900 Subject: [PATCH 246/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20add=20pagination?= =?UTF-8?q?=20example=20reference=20and=20session=20ID=20reminder=20in=20s?= =?UTF-8?q?ome=20docs=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.rs | 16 ++++++++++++---- src/client/param/pagination.rs | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/client.rs b/src/client.rs index 2681c46..ad33245 100644 --- a/src/client.rs +++ b/src/client.rs @@ -452,7 +452,9 @@ impl Client { /// Gets the user leaderboard fulfilling the search criteria. /// /// Want to paginate over this data using the [`SearchCriteria::bound`](user_leaderboard::SearchCriteria)? - /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// For more details, see the example in + /// [`15_pagination-for-leaderboard.rs`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/15_pagination-for-leaderboard.rs). /// /// About the endpoint "User Leaderboard", /// see the [API document](https://tetr.io/about/api/#usersbyleaderboard). @@ -533,7 +535,9 @@ impl Client { /// Gets the array of the historical user blobs fulfilling the search criteria. /// /// Want to paginate over this data using the [`SearchCriteria::bound`](user_leaderboard::SearchCriteria)? - /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// For more details, see the example in + /// [`15_pagination-for-leaderboard.rs`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/15_pagination-for-leaderboard.rs). /// /// About the endpoint "Historical User Leaderboard", /// see the [API document](https://tetr.io/about/api/#usershistoryleaderboardseason). @@ -622,7 +626,9 @@ impl Client { /// fulfilling the search criteria. /// /// Want to paginate over this data using the [`SearchCriteria::bound`](record::SearchCriteria)? - /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// For more details, see the example in + /// [`15_pagination-for-leaderboard.rs`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/15_pagination-for-leaderboard.rs). /// /// About the endpoint "User Personal Records", /// see the [API document](https://tetr.io/about/api/#usersuserrecordsgamemodeleaderboard). @@ -718,7 +724,9 @@ impl Client { /// Gets the record leaderboard fulfilling the search criteria. /// /// Want to paginate over this data using the [`SearchCriteria::bound`](record_leaderboard::SearchCriteria)? - /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`] to ensure data consistency. + /// For more details, see the example in + /// [`15_pagination-for-leaderboard.rs`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/15_pagination-for-leaderboard.rs). /// /// About the endpoint "Records Leaderboard", /// see the [API document](https://tetr.io/about/api/#recordsleaderboard). diff --git a/src/client/param/pagination.rs b/src/client/param/pagination.rs index a6a2ee6..ebe8bd0 100644 --- a/src/client/param/pagination.rs +++ b/src/client/param/pagination.rs @@ -1,4 +1,9 @@ //! Features for pagination. +//! +//! Want to paginate over some data? +//! Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`](crate::client::Client::with_session_id) to ensure data consistency. +//! For more details, see the example in +//! [`15_pagination-for-leaderboard.rs`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/15_pagination-for-leaderboard.rs). use serde::Deserialize; @@ -6,6 +11,11 @@ use serde::Deserialize; /// /// A **prisecter** is consisting of three floats. /// It allows you to continue paginating. +/// +/// Want to paginate over some data? +/// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`](crate::client::Client::with_session_id) to ensure data consistency. +/// For more details, see the example in +/// [`15_pagination-for-leaderboard.rs`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/15_pagination-for-leaderboard.rs). #[derive(Clone, Debug, Deserialize)] pub struct Prisecter { /// The primary sort key. @@ -32,6 +42,11 @@ impl AsRef for Prisecter { } /// A bound to paginate. +/// +/// Want to paginate over some data? +/// Remember to pass an `X-Session-ID` header using the [`Client::with_session_id`](crate::client::Client::with_session_id) to ensure data consistency. +/// For more details, see the example in +/// [`15_pagination-for-leaderboard.rs`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/15_pagination-for-leaderboard.rs). #[derive(Clone, Debug)] pub enum Bound { /// An upper bound. From cb91fc1b5a3e5d01688cccfb24eff2bb6528d426 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 05:38:34 +0900 Subject: [PATCH 247/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20adjus?= =?UTF-8?q?t=20LICENSE=20file=20content=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index 4885ea8..7c88091 100755 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,21 @@ MIT License -Copyright 2023 Rinrin.rs +Copyright (c) 2023-2024 Rinrin.rs -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From b76b8e485cf19b42393d5980b14d356d815ab230 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 05:54:10 +0900 Subject: [PATCH 248/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20update=20`README?= =?UTF-8?q?.md`=20and=20`lib.rs`=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 121 ++++++++++++++++++++++++++++++++++++----------------- src/lib.rs | 109 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 159 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 00d08be..d9be1b9 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,63 @@ # tetr-ch-rs ![Latest release version](https://img.shields.io/github/v/release/Rinrin0413/tetr-ch-rs?color=007722&label=Latest%20release&style=flat-square) [![Codecov](https://img.shields.io/codecov/c/github/Rinrin0413/tetr-ch-rs?color=%23ff0077&logo=Codecov&style=flat-square)](https://app.codecov.io/gh/Rinrin0413/tetr-ch-rs) -tetr-ch-rs is a Rust library for the [TETRA CHANNEL API](https://tetr.io/about/api/). - -You can get the following from the TETRA CHANNEL API with this library: - -- Public details for each user. -- Some single player records. -- Some statistics about the [TETR.IO](https://tetr.io). -- Graph of user activity. -- Some streams. -- TETRA LEAGUE Leaderboard. -- XP Leaderboard. -- The latest news. - -Also you can search for [TETR.IO](https://tetr.io) accounts by Discord account. - -But the TETRA CHANNEL API is in beta -so this library may not work properly in the future :( - -**\* This library is NOT official.** - -# Installation +A Rust wrapper for the [TETRA CHANNEL API](https://tetr.io/about/api). + +You can get the following data by using this library: + +- Detailed user information +- User's summaries + - 40 LINES + - BLITZ + - QUICK PLAY + - EXPERT QUICK PLAY + - TETRA LEAGUE + - ZEN + - Achievements +- User leaderboards +- User records +- Record leaderboards +- Rank metadata +- and more... + +Also you can: + +- Search for TETR.IO account by social connections. +- Search for record by user ID and timestamp. + +> [!WARNING] +> +> This library is not an officially provided wrapper. +> +> TETR.IO is an ongoing project in continuous development. +> The TETRA CHANNEL API may change with or without notice between updates. +> So this wrapper may be outdated in the future. +> +>
+> And read the TETRA CHANNEL API rules before using this library: +>
+> +>
+> +> > Usage of the TETRA CHANNEL API does not require an account or bot account. +> > Please do note that requests are logged. Some simple rules: +> > +> > - **Do not flood the API with requests.** This should be obvious, but just to be sure. +> > Please keep the amount of requests at a moderate rate - once a second should be fine for most cases, short bursts are OK. +> > Please consider other users! +> > - **Honor caching data.** If a response indicates its cache will expire after 10 minutes, +> > please do not rerequest the data during that time, as the data should not change in that time, +> > assuming you are sending an `X-Session-ID` header. +> > - **Send an `X-Session-ID` header** if you are often rerequesting the same datasets. +> > This not only assures the data you receive is consistent, it also helps reduce database calls on our side. +> > - **Don't use a `X-Session-ID` header for requests that are not related.** That way, load balancing can function as expected. +> > - **Do not use the API in ways that break the TETR.IO [Terms of Service](https://tetr.io/about/terms/).** Should be obvious. +> > +> > ― https://tetr.io/about/api +> +>
+>
+ +## Installation Run the following Cargo command in your project directory: @@ -28,36 +65,44 @@ Run the following Cargo command in your project directory: cargo add tetr_ch ``` -# Examples +## Examples The following example is a template for getting user details. ```rust -use tetr_ch::client::Client; +use tetr_ch::prelude::*; #[tokio::main] async fn main() { - // Set the user (name or id). - let user = "rinrin-rs"; - // Create a new client. let client = Client::new(); - // Get the user details. - // And send the requested data or error message. - match client.get_user(user).await { - Ok(u) => { - println!("{:?}\n", u); - } - Err(err) => { - eprintln!("Error: {}\n", err.to_string()); - } + // Set the username or user ID to get the information. + let user = "rinrin-rs"; + + // Get the data. + let response = match client.get_user(user).await { + Ok(res) => res, + Err(err) => panic!("Response error: {}\n", err), + }; + + // Check if there is an error. + // An error "No such user!" will be returned here if the user does not exist. + if let Some(err) = response.error { + panic!("Error: {}\n", err.msg.expect("no error message")); } + + let data = response.data.unwrap(); + println!("Name: {}", data.username); + println!("ID: {}", data.id); + println!("XP: {}", data.xp); + println!("Level: {}", data.level()); + println!("Avatar URL: {}", data.avatar_url()); } ``` -See [full examples](./examples/). +All the examples can be found in the [`examples`](/examples) directory. -And see the [docs](https://docs.rs/tetr_ch). +For more information about this library, see the [documentation](https://docs.rs/tetr_ch). -[![MIT](https://img.shields.io/github/license/Rinrin0413/tetr-ch-rs?color=%23A11D32&style=for-the-badge)](./LICENSE) +[![MIT](https://img.shields.io/github/license/Rinrin0413/tetr-ch-rs?color=%23A11D32&style=for-the-badge)](/LICENSE) diff --git a/src/lib.rs b/src/lib.rs index b526a00..3a6bf30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,24 +1,59 @@ //! ![Latest release version](https://img.shields.io/github/v/release/Rinrin0413/tetr-ch-rs?color=007722&label=Latest%20release&style=flat-square) [![Codecov](https://img.shields.io/codecov/c/github/Rinrin0413/tetr-ch-rs?color=%23ff0077&logo=Codecov&style=flat-square)](https://app.codecov.io/gh/Rinrin0413/tetr-ch-rs) //! -//! tetr-ch-rs is a library for the [TETRA CHANNEL API](https://tetr.io/about/api/). -//! -//! You can get the following from the TETRA CHANNEL API with this library: -//! -//! - Public details for each user. -//! - Some single player records. -//! - Some statistics about the [TETR.IO](https://tetr.io). -//! - Graph of user activity. -//! - Some streams. -//! - TETRA LEAGUE Leaderboard. -//! - XP Leaderboard. -//! - The latest news. -//! -//! Also you can search for [TETR.IO](https://tetr.io) accounts by Discord account. -//! -//! But the TETRA CHANNEL API is in beta -//! so this library may not work properly in the future :( -//! -//! **\* This library is NOT official.** +//! A Rust wrapper for the [TETRA CHANNEL API](https://tetr.io/about/api). +//! +//! You can get the following data by using this library: +//! +//! - Detailed user information +//! - User's summaries +//! - 40 LINES +//! - BLITZ +//! - QUICK PLAY +//! - EXPERT QUICK PLAY +//! - TETRA LEAGUE +//! - ZEN +//! - Achievements +//! - User leaderboards +//! - User records +//! - Record leaderboards +//! - Rank metadata +//! - and more... +//! +//! Also you can: +//! +//! - Search for TETR.IO account by social connections. +//! - Search for record by user ID and timestamp. +//! +//! # Warning +//! +//! This library is not an officially provided wrapper. +//! +//! TETR.IO is an ongoing project in continuous development. +//! The TETRA CHANNEL API may change with or without notice between updates. +//! So this wrapper may be outdated in the future. +//! +//!
+//! And read the TETRA CHANNEL API rules before using this library: +//!
+//! +//! > Usage of the TETRA CHANNEL API does not require an account or bot account. +//! > Please do note that requests are logged. Some simple rules: +//! > +//! > - **Do not flood the API with requests.** This should be obvious, but just to be sure. +//! > Please keep the amount of requests at a moderate rate - once a second should be fine for most cases, short bursts are OK. +//! > Please consider other users! +//! > - **Honor caching data.** If a response indicates its cache will expire after 10 minutes, +//! > please do not rerequest the data during that time, as the data should not change in that time, +//! > assuming you are sending an `X-Session-ID` header. +//! > - **Send an `X-Session-ID` header** if you are often rerequesting the same datasets. +//! > This not only assures the data you receive is consistent, it also helps reduce database calls on our side. +//! > - **Don't use a `X-Session-ID` header for requests that are not related.** That way, load balancing can function as expected. +//! > - **Do not use the API in ways that break the TETR.IO [Terms of Service](https://tetr.io/about/terms/).** Should be obvious. +//! > +//! > ― [https://tetr.io/about/api](https://tetr.io/about/api) +//! +//!
+//!
//! //! # Installation //! @@ -33,30 +68,38 @@ //! The following example is a template for getting user details. //! //! ```ignore -//! use tetr_ch::client::Client; +//! use tetr_ch::prelude::*; //! //! #[tokio::main] //! async fn main() { -//! // Set the user (name or id). -//! let user = "rinrin-rs"; -//! //! // Create a new client. //! let client = Client::new(); //! -//! // Get the user details. -//! // And send the requested data or error message. -//! match client.get_user(user).await { -//! Ok(u) => { -//! println!("{:?}\n", u); -//! } -//! Err(err) => { -//! eprintln!("Error: {}\n", err.to_string()); -//! } +//! // Set the username or user ID to get the information. +//! let user = "rinrin-rs"; +//! +//! // Get the data. +//! let response = match client.get_user(user).await { +//! Ok(res) => res, +//! Err(err) => panic!("Response error: {}\n", err), +//! }; +//! +//! // Check if there is an error. +//! // An error "No such user!" will be returned here if the user does not exist. +//! if let Some(err) = response.error { +//! panic!("Error: {}\n", err.msg.expect("no error message")); //! } +//! +//! let data = response.data.unwrap(); +//! println!("Name: {}", data.username); +//! println!("ID: {}", data.id); +//! println!("XP: {}", data.xp); +//! println!("Level: {}", data.level()); +//! println!("Avatar URL: {}", data.avatar_url()); //! } //! ``` //! -//! See [full examples](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples/). +//! All the examples can be found in the [`examples`](https://github.com/Rinrin0413/tetr-ch-rs/tree/master/examples) directory. //! //! [![MIT](https://img.shields.io/github/license/Rinrin0413/tetr-ch-rs?color=%23A11D32&style=for-the-badge)](https://docs.rs/crate/tetr_ch/latest/source/LICENSE) From 21443e422fc83e8c421c6a363a37abeb24ba736f Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Fri, 6 Dec 2024 07:15:41 +0900 Subject: [PATCH 249/255] =?UTF-8?q?=F0=9F=A9=B9=20Add:=20URL=20encodings?= =?UTF-8?q?=20[#15]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/client.rs | 63 +++++++++++++++++++--------- src/client/param/user_leaderboard.rs | 4 +- src/util.rs | 6 +++ 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 10da3fc..f0defa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ edition = "2021" [dependencies] http = "0.2.8" +percent-encoding = "2.3.1" serde_json = "1.0.108" [dependencies.reqwest] diff --git a/src/client.rs b/src/client.rs index ad33245..4a07c97 100644 --- a/src/client.rs +++ b/src/client.rs @@ -37,7 +37,7 @@ use crate::{ user::UserResponse, user_records::UserRecordsResponse, }, - util::validate_limit, + util::{encode, validate_limit}, }; use reqwest::header; use uuid::Uuid; @@ -178,7 +178,8 @@ impl Client { /// # } /// ``` pub async fn get_user(&self, user: &str) -> RspErr { - let url = format!("{}users/{}", API_URL, user.to_lowercase()); + dbg!(encode(user.to_lowercase())); + let url = format!("{}users/{}", API_URL, encode(user.to_lowercase())); let res = self.client.get(url).send().await; response(res).await } @@ -216,7 +217,11 @@ impl Client { &self, social_connection: SocialConnection, ) -> RspErr { - let url = format!("{}users/search/{}", API_URL, social_connection.to_param()); + let url = format!( + "{}users/search/{}", + API_URL, + encode(social_connection.to_param()) + ); let res = self.client.get(url).send().await; response(res).await } @@ -247,7 +252,7 @@ impl Client { /// # } /// ``` pub async fn get_user_all_summaries(&self, user: &str) -> RspErr { - let url = format!("{}users/{}/summaries", API_URL, user.to_lowercase()); + let url = format!("{}users/{}/summaries", API_URL, encode(user.to_lowercase())); let res = self.client.get(url).send().await; response(res).await } @@ -274,7 +279,11 @@ impl Client { /// # } /// ``` pub async fn get_user_40l(&self, user: &str) -> RspErr { - let url = format!("{}users/{}/summaries/40l", API_URL, user.to_lowercase()); + let url = format!( + "{}users/{}/summaries/40l", + API_URL, + encode(user.to_lowercase()) + ); let res = self.client.get(url).send().await; response(res).await } @@ -301,7 +310,11 @@ impl Client { /// # } /// ``` pub async fn get_user_blitz(&self, user: &str) -> RspErr { - let url = format!("{}users/{}/summaries/blitz", API_URL, user.to_lowercase()); + let url = format!( + "{}users/{}/summaries/blitz", + API_URL, + encode(user.to_lowercase()) + ); let res = self.client.get(url).send().await; response(res).await } @@ -328,7 +341,11 @@ impl Client { /// # } /// ``` pub async fn get_user_zenith(&self, user: &str) -> RspErr { - let url = format!("{}users/{}/summaries/zenith", API_URL, user.to_lowercase()); + let url = format!( + "{}users/{}/summaries/zenith", + API_URL, + encode(user.to_lowercase()) + ); let res = self.client.get(url).send().await; response(res).await } @@ -358,7 +375,7 @@ impl Client { let url = format!( "{}users/{}/summaries/zenithex", API_URL, - user.to_lowercase() + encode(user.to_lowercase()) ); let res = self.client.get(url).send().await; response(res).await @@ -386,7 +403,11 @@ impl Client { /// # } /// ``` pub async fn get_user_league(&self, user: &str) -> RspErr { - let url = format!("{}users/{}/summaries/league", API_URL, user.to_lowercase()); + let url = format!( + "{}users/{}/summaries/league", + API_URL, + encode(user.to_lowercase()) + ); let res = self.client.get(url).send().await; response(res).await } @@ -413,7 +434,11 @@ impl Client { /// # } /// ``` pub async fn get_user_zen(&self, user: &str) -> RspErr { - let url = format!("{}users/{}/summaries/zen", API_URL, user.to_lowercase()); + let url = format!( + "{}users/{}/summaries/zen", + API_URL, + encode(user.to_lowercase()) + ); let res = self.client.get(url).send().await; response(res).await } @@ -443,7 +468,7 @@ impl Client { let url = format!( "{}users/{}/summaries/achievements", API_URL, - user.to_lowercase() + encode(user.to_lowercase()) ); let res = self.client.get(url).send().await; response(res).await @@ -527,7 +552,7 @@ impl Client { criteria.validate_limit(); query_params = criteria.build(); } - let url = format!("{}users/by/{}", API_URL, leaderboard.to_param()); + let url = format!("{}users/by/{}", API_URL, encode(leaderboard.to_param())); let res = self.client.get(url).query(&query_params).send().await; response(res).await } @@ -616,7 +641,7 @@ impl Client { "{}users/history/{}/{}", API_URL, LeaderboardType::League.to_param(), - season + encode(season) ); let res = self.client.get(url).query(&query_params).send().await; response(res).await @@ -713,7 +738,7 @@ impl Client { let url = format!( "{}users/{}/records/{}/{}", API_URL, - user.to_lowercase(), + encode(user.to_lowercase()), gamemode.to_param(), leaderboard.to_param() ); @@ -812,7 +837,7 @@ impl Client { criteria.validate_limit(); query_params = criteria.build(); } - let url = format!("{}records/{}", API_URL, leaderboard.to_param()); + let url = format!("{}records/{}", API_URL, encode(leaderboard.to_param())); let res = self.client.get(url).query(&query_params).send().await; response(res).await } @@ -976,7 +1001,7 @@ impl Client { limit: u8, ) -> RspErr { validate_limit(limit); - let url = format!("{}news/{}", API_URL, stream.to_param()); + let url = format!("{}news/{}", API_URL, encode(stream.to_param())); let res = self.client.get(url).query(&[("limit", limit)]).send().await; response(res).await } @@ -1065,7 +1090,7 @@ impl Client { let url = format!( "{}labs/scoreflow/{}/{}", API_URL, - user.to_lowercase(), + encode(user.to_lowercase()), gamemode.to_param() ); let res = self.client.get(url).send().await; @@ -1095,7 +1120,7 @@ impl Client { /// # } /// ``` pub async fn get_labs_leagueflow(&self, user: &str) -> RspErr { - let url = format!("{}labs/leagueflow/{}", API_URL, user.to_lowercase()); + let url = format!("{}labs/leagueflow/{}", API_URL, encode(user.to_lowercase())); let res = self.client.get(url).send().await; response(res).await } @@ -1150,7 +1175,7 @@ impl Client { &self, achievement_id: &str, ) -> RspErr { - let url = format!("{}achievements/{}", API_URL, achievement_id); + let url = format!("{}achievements/{}", API_URL, encode(achievement_id)); let res = self.client.get(url).send().await; response(res).await } diff --git a/src/client/param/user_leaderboard.rs b/src/client/param/user_leaderboard.rs index 5172524..3da055a 100644 --- a/src/client/param/user_leaderboard.rs +++ b/src/client/param/user_leaderboard.rs @@ -1,7 +1,7 @@ //! Features for user leaderboards. use super::pagination::Bound; -use crate::util::validate_limit; +use crate::util::{encode, validate_limit}; /// A user leaderboard type. #[derive(Clone, Debug)] @@ -241,7 +241,7 @@ impl SearchCriteria { result.push(("limit".to_string(), l.to_string())); } if let Some(c) = self.country { - result.push(("country".to_string(), c.to_uppercase())); + result.push(("country".to_string(), encode(c.to_uppercase()))); } result } diff --git a/src/util.rs b/src/util.rs index 21ac75a..4d39c86 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,6 +2,7 @@ use crate::model::util::Timestamp; use chrono::DateTime; +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use serde::Deserialize; use serde_json::Value; @@ -62,6 +63,11 @@ pub(crate) fn validate_limit(value: u8) { ); } +/// Encode the given string for URLs. +pub(crate) fn encode(input: impl ToString) -> String { + utf8_percent_encode(&input.to_string().replace('.', " "), NON_ALPHANUMERIC).to_string() +} + #[cfg(test)] mod tests { use super::*; From 7045ba8e5ce32c7d48e33c4abe73dbe4d7212e93 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 7 Dec 2024 17:19:10 +0900 Subject: [PATCH 250/255] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Change:=20`ToNe?= =?UTF-8?q?wsStreamParam::to=5Fparam`=20method=20is=20now=20take=20referen?= =?UTF-8?q?ce=20instead=20of=20ownership?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because functions that take ownerships and return non- `impl Copy` ownerships should be named `into_*`. This is not good for naming consistency. --- src/client/param/news_stream.rs | 4 ++-- src/model/util/news_stream.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/param/news_stream.rs b/src/client/param/news_stream.rs index 12821e7..45e4d92 100644 --- a/src/client/param/news_stream.rs +++ b/src/client/param/news_stream.rs @@ -22,7 +22,7 @@ impl ToNewsStreamParam for NewsStream { /// assert_eq!(global.to_param(), "global"); /// assert_eq!(user.to_param(), "user_621db46d1d638ea850be2aa0"); /// ``` - fn to_param(self) -> String { + fn to_param(&self) -> String { match self { NewsStream::Global => "global".to_string(), NewsStream::User(id) => format!("user_{}", id), @@ -32,5 +32,5 @@ impl ToNewsStreamParam for NewsStream { pub trait ToNewsStreamParam { /// Converts into a parameter string. - fn to_param(self) -> String; + fn to_param(&self) -> String; } diff --git a/src/model/util/news_stream.rs b/src/model/util/news_stream.rs index 871b723..d1ccb70 100644 --- a/src/model/util/news_stream.rs +++ b/src/model/util/news_stream.rs @@ -62,7 +62,7 @@ impl fmt::Display for NewsStream { } impl ToNewsStreamParam for NewsStream { - fn to_param(self) -> String { - self.0 + fn to_param(&self) -> String { + self.0.clone() } } From 1a40b4fc3e97dd26269eb4e7b0ea1f7551b597f7 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 7 Dec 2024 17:25:11 +0900 Subject: [PATCH 251/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`Gamemod?= =?UTF-8?q?e::into=5Frecord=5Fgamemode`=20to=20`to=5Frecord=5Fgamemode`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Functions that take references and return non- `impl Copy` ownerships should be named `to_*`. --- src/model/util/gamemode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/util/gamemode.rs b/src/model/util/gamemode.rs index adf98d4..4cdde65 100644 --- a/src/model/util/gamemode.rs +++ b/src/model/util/gamemode.rs @@ -13,7 +13,7 @@ pub struct Gamemode(String); impl Gamemode { /// Converts into a [`crate::client::param::record::Gamemode`]. /// If failed, returns the game mode as is as `Err`. - pub fn into_record_gamemode(&self) -> Result { + pub fn to_record_gamemode(&self) -> Result { match self.0.as_str() { "40l" => Ok(RecordGm::FortyLines), "blitz" => Ok(RecordGm::Blitz), From f045834d6a4435811a777aa0840b923f4b0338bd Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 7 Dec 2024 17:29:25 +0900 Subject: [PATCH 252/255] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Rename:=20`RecordL?= =?UTF-8?q?eaderboard::into=5F*`=20methods=20to=20`to=5F*`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Functions that take references and return non- `impl Copy` ownerships should be named `to_*`. --- src/model/util/record_leaderboard.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/model/util/record_leaderboard.rs b/src/model/util/record_leaderboard.rs index a6f69a3..4fd2537 100644 --- a/src/model/util/record_leaderboard.rs +++ b/src/model/util/record_leaderboard.rs @@ -10,17 +10,17 @@ pub struct RecordLeaderboard(pub String); impl RecordLeaderboard { /// Converts into a [`crate::client::param::record_leaderboard::RecordsLeaderboardId`]. - pub fn into_id(&self) -> RecordsLeaderboardId { - self._into_id(None) + pub fn to_id(&self) -> RecordsLeaderboardId { + self._to_id(None) } /// Converts into a [`crate::client::param::record_leaderboard::RecordsLeaderboardId`] with a Revolution ID. - pub fn into_id_with_revolution_id(&self, revolution_id: &str) -> RecordsLeaderboardId { - self._into_id(Some(revolution_id)) + pub fn to_id_with_revolution_id(&self, revolution_id: &str) -> RecordsLeaderboardId { + self._to_id(Some(revolution_id)) } /// Converts into a [`crate::client::param::record_leaderboard::RecordsLeaderboardId`] with an optional Revolution ID. - fn _into_id(&self, revolution_id: Option<&str>) -> RecordsLeaderboardId { + fn _to_id(&self, revolution_id: Option<&str>) -> RecordsLeaderboardId { let split_id: Vec<&str> = self.0.split('_').collect(); let gamemode = split_id[0]; let scope = match split_id[1] { From 81d54039e003d3b664c6e610bb856dbfdb5ab2ce Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 7 Dec 2024 17:38:18 +0900 Subject: [PATCH 253/255] =?UTF-8?q?=F0=9F=99=88=20Update:=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - πŸ™ˆ Unignore: `tetr_ch_bin/` - πŸ™ˆ Ignore: `examples/bin.rs` --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3ff400d..9693a46 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ Cargo.lock TODO.md .vscode/ check_examples.sh -tetr_ch_bin/ +examples/bin.rs From 5becc812ce766acdb5157735d378577c4bf03efb Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 7 Dec 2024 17:41:55 +0900 Subject: [PATCH 254/255] =?UTF-8?q?=F0=9F=93=9A=20Docs:=20add=20separators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a215dd..8261638 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Decoding error when the property `ts` of the object "badges" was not present +--- + # v0.5.0 2023-11-30 ## Fixes @@ -31,6 +33,8 @@ - Update `.gitignore` +--- + # v0.4.0 2023-06-29 ## Changes @@ -47,6 +51,8 @@ This has significantly changed the structure around records. - Make `ResponseError` a standard error type by [@jlkn](https://github.com/jlkn) in [[#2](https://github.com/Rinrin0413/tetr-ch-rs/pull/2)] +--- + # v0.3.5 2023-05-23 ## Improvements @@ -57,10 +63,16 @@ This has significantly changed the structure around records. - Fixed redundant and raggedly named functions. +--- + # v0.3.4 2022-12-18 +--- + # v0.3.3 2022-12-12 +--- + # v0.3.2 2022-10-28 ## Additions From ad7f06c3e837b825b7e1672bcd8be34657b9a450 Mon Sep 17 00:00:00 2001 From: "Rinrin.rs" Date: Sat, 7 Dec 2024 18:59:58 +0900 Subject: [PATCH 255/255] =?UTF-8?q?=F0=9F=94=96=20Upgrade:=20v0.6.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ Cargo.toml | 6 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8261638..1937318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# v0.6.0 2024-12-07 + +## Breaking Changes + +- πŸ’₯ [TETR.IO BETA 1.2.0](https://tetr.io/about/patchnotes/#chlog_BETA_1_2_0)+ are now supported. [#15] + - ✨ Added new methods of the [`Client`](https://docs.rs/tetr_ch/0.6.0/tetr_ch/client/struct.Client.html) struct for the new API endpoints. + - πŸ”₯ Removed the methods and elements for the discontinued endpoints. [#64] + - ✨ Added support for the new rank X+. [#61] + +## Features + +- ✨ The [`Client`](https://docs.rs/tetr_ch/0.6.0/tetr_ch/client/struct.Client.html) struct now supports `X-Session-ID` header. [#97] + - Use [`Client::with_session_id`](https://docs.rs/tetr_ch/0.6.0/tetr_ch/client/struct.Client.html#method.with_session_id). +- ✨ Added two prelude modules [`tetr_ch::prelude`](https://docs.rs/tetr_ch/0.6.0/tetr_ch/prelude/index.html) and [`tetr_ch::model::prelude`](https://docs.rs/tetr_ch/0.6.0/tetr_ch/model/util/index.html). +- ✨ Added [`xp_to_level`](https://docs.rs/tetr_ch/0.6.0/tetr_ch/util/fn.xp_to_level.html) function. + +## Improvements + +- πŸ› οΈ Parameters that are used in the URL are now encoded. +- πŸ“š Improved the documentation. + +## Other Changes + +- πŸ› οΈ The enumerators of the [`ResponseError`](https://docs.rs/tetr_ch/0.6.0/tetr_ch/client/error/enum.ResponseError.html) enum now have each error type. [#89] + +--- + # v0.5.1 (hotfix) 2023-12-01 ## Fixes diff --git a/Cargo.toml b/Cargo.toml index f0defa4..1aace20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "tetr_ch" -description = "A library for the TETRA CHANNEL API." -version = "0.5.1" +description = "A Rust wrapper for the TETRA CHANNEL API. " +version = "0.6.0" authors = ["Rinrin.rs "] license-file = "LICENSE" repository = "https://github.com/Rinrin0413/tetr-ch-rs.git" readme = "README.md" -keywords = ["tetrio", "tetr-io", "tetr_io", "tetra-channel-api", "tetra_channel_api"] +keywords = ["tetrio", "tetr-io", "tetr_io", "tetra-channel-api", "wrapper"] edition = "2021" [dependencies]