diff --git a/lib/src/api/board.rs b/lib/src/api/board.rs index a4a3e45..cccc128 100644 --- a/lib/src/api/board.rs +++ b/lib/src/api/board.rs @@ -27,6 +27,13 @@ impl LichessApi { self.get_ok(request.into()).await } + pub async fn board_claim_draw( + &self, + request: impl Into, + ) -> Result { + self.get_ok(request.into()).await + } + pub async fn board_claim_victory( &self, request: impl Into, diff --git a/lib/src/api/bot.rs b/lib/src/api/bot.rs index 6e91bf8..6b05275 100644 --- a/lib/src/api/bot.rs +++ b/lib/src/api/bot.rs @@ -21,6 +21,20 @@ impl LichessApi { self.get_ok(request.into()).await } + pub async fn bot_claim_draw( + &self, + request: impl Into, + ) -> Result { + self.get_ok(request.into()).await + } + + pub async fn bot_claim_victory( + &self, + request: impl Into, + ) -> Result { + self.get_ok(request.into()).await + } + pub async fn bot_draw_game(&self, request: impl Into) -> Result { self.get_ok(request.into()).await } @@ -54,6 +68,13 @@ impl LichessApi { self.get_streamed_models(request.into()).await } + pub async fn bot_handle_takeback( + &self, + request: impl Into, + ) -> Result { + self.get_ok(request.into()).await + } + pub async fn bot_upgrade_account( &self, request: impl Into, diff --git a/lib/src/api/challenges.rs b/lib/src/api/challenges.rs index ddbe48b..7465cd3 100644 --- a/lib/src/api/challenges.rs +++ b/lib/src/api/challenges.rs @@ -54,4 +54,18 @@ impl LichessApi { ) -> Result { self.get_ok(request.into()).await } + + pub async fn show_challenge( + &self, + request: impl Into, + ) -> Result { + self.get_single_model(request.into()).await + } + + pub async fn admin_challenge_tokens( + &self, + request: impl Into, + ) -> Result { + self.get_single_model(request.into()).await + } } diff --git a/lib/src/api/fide.rs b/lib/src/api/fide.rs index 246a792..80d246c 100644 --- a/lib/src/api/fide.rs +++ b/lib/src/api/fide.rs @@ -13,4 +13,11 @@ impl LichessApi { pub async fn get_fide_player(&self, request: impl Into) -> Result { self.get_single_model(request.into()).await } + + pub async fn get_fide_player_ratings( + &self, + request: impl Into, + ) -> Result { + self.get_single_model(request.into()).await + } } diff --git a/lib/src/api/games.rs b/lib/src/api/games.rs index 2937677..9c24c14 100644 --- a/lib/src/api/games.rs +++ b/lib/src/api/games.rs @@ -74,4 +74,26 @@ impl LichessApi { ) -> Result { self.get_single_model(request.into()).await } + + pub async fn export_bookmarked_games( + &self, + request: impl Into, + ) -> Result>> { + self.get_streamed_models(request.into()).await + } + + pub async fn export_imported_games(&self) -> Result>> { + self.get_pgn(export::imports::GetRequest::new()).await + } + + pub async fn get_game_chat( + &self, + request: impl Into, + ) -> Result>> { + self.get_streamed_models(request.into()).await + } + + pub async fn bookmark_game(&self, request: impl Into) -> Result<()> { + self.get_empty(request.into()).await + } } diff --git a/lib/src/api/puzzles.rs b/lib/src/api/puzzles.rs index 29b487a..450b706 100644 --- a/lib/src/api/puzzles.rs +++ b/lib/src/api/puzzles.rs @@ -54,4 +54,25 @@ impl LichessApi { ) -> Result { self.get_single_model(request.into()).await } + + pub async fn get_puzzle_batch( + &self, + request: impl Into, + ) -> Result { + self.get_single_model(request.into()).await + } + + pub async fn solve_puzzle_batch( + &self, + request: impl Into, + ) -> Result { + self.get_single_model(request.into()).await + } + + pub async fn get_puzzle_race_results( + &self, + request: impl Into, + ) -> Result { + self.get_single_model(request.into()).await + } } diff --git a/lib/src/model/board/claim_draw.rs b/lib/src/model/board/claim_draw.rs new file mode 100644 index 0000000..4bfd889 --- /dev/null +++ b/lib/src/model/board/claim_draw.rs @@ -0,0 +1,20 @@ +use crate::model::Request; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery; + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(game_id: &str) -> Self { + let path = format!("/api/board/game/{game_id}/claim-draw"); + Self::post(path, None, None, None) + } +} + +impl> From for PostRequest { + fn from(s: S) -> Self { + Self::new(s.as_ref()) + } +} diff --git a/lib/src/model/board/mod.rs b/lib/src/model/board/mod.rs index 5a4b0f2..fd719bc 100644 --- a/lib/src/model/board/mod.rs +++ b/lib/src/model/board/mod.rs @@ -3,6 +3,7 @@ pub mod stream; pub mod abort; pub mod berserk; pub mod chat; +pub mod claim_draw; pub mod claim_victory; pub mod draw; pub mod r#move; diff --git a/lib/src/model/bot/claim_draw.rs b/lib/src/model/bot/claim_draw.rs new file mode 100644 index 0000000..dddac7c --- /dev/null +++ b/lib/src/model/bot/claim_draw.rs @@ -0,0 +1,20 @@ +use crate::model::Request; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery; + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(game_id: &str) -> Self { + let path = format!("/api/bot/game/{game_id}/claim-draw"); + Self::post(path, None, None, None) + } +} + +impl> From for PostRequest { + fn from(s: S) -> Self { + Self::new(s.as_ref()) + } +} diff --git a/lib/src/model/bot/claim_victory.rs b/lib/src/model/bot/claim_victory.rs new file mode 100644 index 0000000..284f9d2 --- /dev/null +++ b/lib/src/model/bot/claim_victory.rs @@ -0,0 +1,20 @@ +use crate::model::Request; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery; + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(game_id: &str) -> Self { + let path = format!("/api/bot/game/{game_id}/claim-victory"); + Self::post(path, None, None, None) + } +} + +impl> From for PostRequest { + fn from(s: S) -> Self { + Self::new(s.as_ref()) + } +} diff --git a/lib/src/model/bot/mod.rs b/lib/src/model/bot/mod.rs index b9e7ef1..090ad1e 100644 --- a/lib/src/model/bot/mod.rs +++ b/lib/src/model/bot/mod.rs @@ -2,8 +2,11 @@ pub mod stream; pub mod abort; pub mod chat; +pub mod claim_draw; +pub mod claim_victory; pub mod draw; pub mod r#move; pub mod online; pub mod resign; +pub mod takeback; pub mod upgrade; diff --git a/lib/src/model/bot/takeback.rs b/lib/src/model/bot/takeback.rs new file mode 100644 index 0000000..ca370bd --- /dev/null +++ b/lib/src/model/bot/takeback.rs @@ -0,0 +1,15 @@ +use crate::model::Request; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery; + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(game_id: &str, accept: bool) -> Self { + let accept = if accept { "yes" } else { "no" }; + let path = format!("/api/bot/game/{game_id}/takeback/{accept}"); + Self::post(path, None, None, None) + } +} diff --git a/lib/src/model/broadcasts/create_tournament.rs b/lib/src/model/broadcasts/create_tournament.rs index 9231715..f05be96 100644 --- a/lib/src/model/broadcasts/create_tournament.rs +++ b/lib/src/model/broadcasts/create_tournament.rs @@ -1,6 +1,7 @@ use crate::model::broadcasts::BroadcastTiebreakExtendedCode; use crate::model::{Body, Request}; use serde::Serialize; +use serde::ser::SerializeMap; use serde_with::skip_serializing_none; #[skip_serializing_none] @@ -37,6 +38,48 @@ pub struct CreateBroadcastTournamentForm { pub teams: Option, pub tier: Option, pub tiebreaks: Option>, + #[serde(flatten)] + pub grouping: Option, +} + +/// Groups this broadcast tournament together with others. +/// +/// `score_groups` serializes as indexed form keys (`grouping.scoreGroups[0]`, +/// `grouping.scoreGroups[1]`, ...) per the Lichess API's non-standard array +/// encoding for this field, which a single struct field cannot otherwise +/// produce with `serde_urlencoded` - hence the manual `Serialize` impl below. +#[derive(Default, Clone, Debug)] +pub struct BroadcastGrouping { + pub info_name: Option, + /// Linebreak separated list of tournament IDs to group together. + pub info_tours: Option, + /// Each entry is a comma separated list of tournament IDs grouped for scoring. + pub score_groups: Option>, +} + +impl Serialize for BroadcastGrouping { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let score_groups_len = self.score_groups.as_ref().map_or(0, Vec::len); + let len = self.info_name.is_some() as usize + + self.info_tours.is_some() as usize + + score_groups_len; + let mut map = serializer.serialize_map(Some(len))?; + if let Some(name) = &self.info_name { + map.serialize_entry("grouping.info.name", name)?; + } + if let Some(tours) = &self.info_tours { + map.serialize_entry("grouping.info.tours", tours)?; + } + if let Some(score_groups) = &self.score_groups { + for (i, group) in score_groups.iter().enumerate() { + map.serialize_entry(&format!("grouping.scoreGroups[{i}]"), group)?; + } + } + map.end() + } } #[derive(Default, Clone, Debug, Serialize)] @@ -49,3 +92,47 @@ impl PostRequest { Self::post("/broadcast/new", None, Body::Form(form), None) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn grouping_serializes_score_groups_as_indexed_keys() { + let form = CreateBroadcastTournamentForm { + name: "Sinquefield Cup".to_string(), + grouping: Some(BroadcastGrouping { + info_name: Some("Chess Olympiad | Open".to_string()), + info_tours: Some("wYigbpXq\nM5YHvpOX".to_string()), + score_groups: Some(vec![ + "wYigbpXq,M5YHvpOX".to_string(), + "q6ezoCXP".to_string(), + ]), + }), + ..Default::default() + }; + + let encoded = serde_urlencoded::to_string(&form).unwrap(); + + assert_eq!( + encoded, + "name=Sinquefield+Cup\ + &grouping.info.name=Chess+Olympiad+%7C+Open\ + &grouping.info.tours=wYigbpXq%0AM5YHvpOX\ + &grouping.scoreGroups%5B0%5D=wYigbpXq%2CM5YHvpOX\ + &grouping.scoreGroups%5B1%5D=q6ezoCXP" + ); + } + + #[test] + fn grouping_omitted_when_none() { + let form = CreateBroadcastTournamentForm { + name: "Sinquefield Cup".to_string(), + ..Default::default() + }; + + let encoded = serde_urlencoded::to_string(&form).unwrap(); + + assert_eq!(encoded, "name=Sinquefield+Cup"); + } +} diff --git a/lib/src/model/challenges/admin_challenge.rs b/lib/src/model/challenges/admin_challenge.rs new file mode 100644 index 0000000..f9d3b4c --- /dev/null +++ b/lib/src/model/challenges/admin_challenge.rs @@ -0,0 +1,28 @@ +use crate::model::{Body, Request}; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery; + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(tokens: AdminChallengeTokens) -> Self { + let path = "/api/token/admin-challenge".to_string(); + Self::post(path, None, Body::Form(tokens), None) + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct AdminChallengeTokens { + /// Usernames separated with commas + pub users: String, + /// User visible description of the token + pub description: String, +} + +impl From for PostRequest { + fn from(tokens: AdminChallengeTokens) -> Self { + Self::new(tokens) + } +} diff --git a/lib/src/model/challenges/mod.rs b/lib/src/model/challenges/mod.rs index b499c3a..1a13755 100644 --- a/lib/src/model/challenges/mod.rs +++ b/lib/src/model/challenges/mod.rs @@ -1,16 +1,21 @@ pub mod accept; pub mod add_time; +pub mod admin_challenge; pub mod ai; pub mod cancel; pub mod create; pub mod decline; pub mod list; pub mod open; +pub mod show; pub mod start_clocks; use crate::model::{Color, Days, GameCompat, Speed, Title, Variant, VariantKey}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::collections::HashMap; + +pub type AdminChallengeTokenResults = HashMap; #[derive(Clone, Debug, Serialize)] pub struct OpenChallenge { diff --git a/lib/src/model/challenges/show.rs b/lib/src/model/challenges/show.rs new file mode 100644 index 0000000..0cd9b14 --- /dev/null +++ b/lib/src/model/challenges/show.rs @@ -0,0 +1,20 @@ +use crate::model::Request; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct GetQuery; + +pub type GetRequest = Request; + +impl GetRequest { + pub fn new(challenge_id: &str) -> Self { + let path = format!("/api/challenge/{challenge_id}/show"); + Self::get(path, None, None) + } +} + +impl> From for GetRequest { + fn from(s: S) -> Self { + Self::new(s.as_ref()) + } +} diff --git a/lib/src/model/fide/mod.rs b/lib/src/model/fide/mod.rs index 28e72a9..631f433 100644 --- a/lib/src/model/fide/mod.rs +++ b/lib/src/model/fide/mod.rs @@ -1,4 +1,5 @@ pub mod player; +pub mod ratings; pub mod search; use crate::model::Title; diff --git a/lib/src/model/fide/ratings.rs b/lib/src/model/fide/ratings.rs new file mode 100644 index 0000000..9c8df54 --- /dev/null +++ b/lib/src/model/fide/ratings.rs @@ -0,0 +1,28 @@ +use crate::model::Request; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct GetQuery; + +pub type GetRequest = Request; + +impl GetRequest { + pub fn new(id: u32) -> Self { + Self::get(format!("/api/fide/player/{id}/ratings"), None, None) + } +} + +impl From for GetRequest { + fn from(id: u32) -> Self { + Self::new(id) + } +} + +/// Each rating point encodes a year, month, and elo rating as a single number, +/// e.g. `2015081568` decodes to August 2015, elo 1568. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PlayerRatings { + pub standard: Vec, + pub rapid: Vec, + pub blitz: Vec, +} diff --git a/lib/src/model/games/bookmark.rs b/lib/src/model/games/bookmark.rs new file mode 100644 index 0000000..c525d77 --- /dev/null +++ b/lib/src/model/games/bookmark.rs @@ -0,0 +1,18 @@ +use crate::model::Request; +use serde::Serialize; +use serde_with::skip_serializing_none; + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery { + pub v: Option, +} + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(game_id: &str, query: PostQuery) -> Self { + let path = format!("/bookmark/{game_id}"); + Self::post(path, query, None, None) + } +} diff --git a/lib/src/model/games/chat.rs b/lib/src/model/games/chat.rs new file mode 100644 index 0000000..1162cfe --- /dev/null +++ b/lib/src/model/games/chat.rs @@ -0,0 +1,25 @@ +use crate::model::Request; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct GetQuery; + +pub type GetRequest = Request; + +impl GetRequest { + pub fn new(game_id: &str) -> Self { + Self::get(format!("/api/game/{game_id}/chat"), None, None) + } +} + +impl> From for GetRequest { + fn from(s: S) -> Self { + Self::new(s.as_ref()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ChatLine { + pub text: String, + pub user: String, +} diff --git a/lib/src/model/games/export/bookmarks.rs b/lib/src/model/games/export/bookmarks.rs new file mode 100644 index 0000000..524cd4a --- /dev/null +++ b/lib/src/model/games/export/bookmarks.rs @@ -0,0 +1,33 @@ +use super::Base; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct GetQuery { + #[serde(flatten)] + pub base: Base, + #[serde(skip_serializing_if = "Option::is_none")] + pub since: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub until: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_fen: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sort: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum Sort { + DateAsc, + DateDesc, +} + +pub type GetRequest = crate::model::Request; + +impl GetRequest { + pub fn new(query: GetQuery) -> Self { + Self::get("/api/games/export/bookmarks", query, None) + } +} diff --git a/lib/src/model/games/export/imports.rs b/lib/src/model/games/export/imports.rs new file mode 100644 index 0000000..2071725 --- /dev/null +++ b/lib/src/model/games/export/imports.rs @@ -0,0 +1,13 @@ +use crate::model::Request; +use serde::Serialize; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct GetQuery; + +pub type GetRequest = Request; + +impl GetRequest { + pub fn new() -> Self { + Self::get("/api/games/export/imports", None, None) + } +} diff --git a/lib/src/model/games/export/mod.rs b/lib/src/model/games/export/mod.rs index 6f12a43..6a0776b 100644 --- a/lib/src/model/games/export/mod.rs +++ b/lib/src/model/games/export/mod.rs @@ -1,5 +1,7 @@ +pub mod bookmarks; pub mod by_ids; pub mod by_user; +pub mod imports; pub mod one; pub mod ongoing; diff --git a/lib/src/model/games/mod.rs b/lib/src/model/games/mod.rs index cf03c4d..553e12a 100644 --- a/lib/src/model/games/mod.rs +++ b/lib/src/model/games/mod.rs @@ -1,3 +1,5 @@ +pub mod bookmark; +pub mod chat; pub mod export; pub mod import; pub mod ongoing; diff --git a/lib/src/model/puzzles/batch.rs b/lib/src/model/puzzles/batch.rs new file mode 100644 index 0000000..d77f5f7 --- /dev/null +++ b/lib/src/model/puzzles/batch.rs @@ -0,0 +1,84 @@ +use crate::model::puzzles::{PuzzleAndGame, next::Difficulty}; +use crate::model::{Body, Request}; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize)] +pub struct GetQuery { + pub difficulty: Option, + pub nb: Option, + pub color: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Color { + White, + Black, +} + +pub type GetRequest = Request; + +impl GetRequest { + pub fn new(angle: &str, query: GetQuery) -> Self { + let path = format!("/api/puzzle/batch/{angle}"); + Self::get(path, query, None) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Select { + pub puzzles: Vec, + pub glicko: Glicko, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Glicko { + pub rating: f64, + pub deviation: f64, + pub provisional: Option, +} + +#[skip_serializing_none] +#[derive(Default, Clone, Debug, Serialize)] +pub struct PostQuery { + pub nb: Option, +} + +#[derive(Clone, Debug, Serialize)] +pub struct SolveRequest { + pub solutions: Vec, +} + +#[derive(Clone, Debug, Serialize)] +pub struct Solution { + pub id: String, + pub win: bool, + pub rated: bool, +} + +pub type PostRequest = Request; + +impl PostRequest { + pub fn new(angle: &str, query: PostQuery, solutions: SolveRequest) -> Self { + let path = format!("/api/puzzle/batch/{angle}"); + Self::post(path, query, Body::Json(solutions), None) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SolveResponse { + pub puzzles: Vec, + pub glicko: Glicko, + pub rounds: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Round { + pub id: String, + pub win: bool, + pub rating_diff: i32, +} diff --git a/lib/src/model/puzzles/mod.rs b/lib/src/model/puzzles/mod.rs index 4497041..e79abc5 100644 --- a/lib/src/model/puzzles/mod.rs +++ b/lib/src/model/puzzles/mod.rs @@ -1,9 +1,11 @@ pub mod activity; +pub mod batch; pub mod daily; pub mod dashboard; pub mod id; pub mod next; pub mod race; +pub mod racer; pub mod replay; pub mod storm_dashboard; @@ -26,6 +28,7 @@ pub struct Game { pub rated: bool, } +#[serde_with::skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Puzzle { pub id: String, @@ -33,6 +36,9 @@ pub struct Puzzle { pub initial_ply: i32, pub plays: i32, pub rating: i32, + pub fen: Option, + #[serde(rename = "lastMove")] + pub last_move: Option, pub solution: Vec, pub themes: Vec, } @@ -52,5 +58,7 @@ pub struct Player { pub rating: i32, pub flair: Option, pub patron: Option, + #[serde(rename = "patronColor")] + pub patron_color: Option, pub title: Option, } diff --git a/lib/src/model/puzzles/racer.rs b/lib/src/model/puzzles/racer.rs new file mode 100644 index 0000000..c3d9305 --- /dev/null +++ b/lib/src/model/puzzles/racer.rs @@ -0,0 +1,53 @@ +use crate::model::Request; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +#[derive(Default, Clone, Debug, Serialize)] +pub struct GetQuery; + +pub type GetRequest = Request<GetQuery>; + +impl GetRequest { + pub fn new(id: &str) -> Self { + let path = format!("/api/racer/{id}"); + Self::get(path, None, None) + } +} + +impl<S: AsRef<str>> From<S> for GetRequest { + fn from(s: S) -> Self { + Self::new(s.as_ref()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RaceResults { + pub id: String, + pub owner: String, + pub players: Vec<RacePlayer>, + pub puzzles: Vec<RacePuzzle>, + pub finishes_at: u64, + pub starts_at: u64, +} + +#[skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RacePlayer { + pub name: String, + pub score: i32, + pub id: Option<String>, + pub flair: Option<String>, + /// Deprecated: use `patron_color` instead. + pub patron: Option<bool>, + pub patron_color: Option<u32>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct RacePuzzle { + pub id: String, + pub fen: String, + pub line: String, + pub rating: i32, +} diff --git a/lib/tests/data/response/fide_player_ratings.json b/lib/tests/data/response/fide_player_ratings.json new file mode 100644 index 0000000..fdda5c1 --- /dev/null +++ b/lib/tests/data/response/fide_player_ratings.json @@ -0,0 +1,24 @@ +{ + "blitz": [], + "rapid": [2015081568, 2022091568, 2022101620, 2024021620, 2024031772, 2025121772], + "standard": [ + 2015021577, + 2015071577, + 2015081679, + 2015111679, + 2015121616, + 2016071616, + 2016081618, + 2016101618, + 2016111685, + 2017031685, + 2017041692, + 2017061692, + 2017071698, + 2017081698, + 2017091720, + 2024021720, + 2024031832, + 2025121832 + ] +} diff --git a/lib/tests/data/response/puzzle_batch_select.json b/lib/tests/data/response/puzzle_batch_select.json new file mode 100644 index 0000000..4e3d549 --- /dev/null +++ b/lib/tests/data/response/puzzle_batch_select.json @@ -0,0 +1,33 @@ +{ + "puzzles": [ + { + "game": { + "id": "P1M5pws5", + "perf": { "key": "rapid", "name": "Rapid" }, + "rated": true, + "players": [ + { + "name": "Ivan", + "title": "GM", + "flair": "people.woman-tipping-hand-light-skin-tone", + "id": "ivan", + "color": "white", + "rating": 2059 + }, + { "name": "Rudra", "id": "rudra", "color": "black", "rating": 1992 } + ], + "pgn": "d4 Nf6 Nf3 g6 Nc3 Bg7 e4 d6 Be3 Nc6 d5 Nb8 Qd2 O-O O-O-O c5 Bh6 a6 a4 b5 axb5 axb5 Bxb5 Ra1+ Nb1 Nxe4 Qe3 Nxf2", + "clock": "10+0" + }, + "puzzle": { + "id": "8KtG9", + "rating": 1512, + "plays": 5171, + "solution": ["h6g7", "g8g7", "e3f2"], + "themes": ["middlegame", "intermezzo", "short", "advantage"], + "initialPly": 27 + } + } + ], + "glicko": { "rating": 1649.0, "deviation": 93.66 } +} diff --git a/lib/tests/data/response/puzzle_batch_solve.json b/lib/tests/data/response/puzzle_batch_solve.json new file mode 100644 index 0000000..6ebf609 --- /dev/null +++ b/lib/tests/data/response/puzzle_batch_solve.json @@ -0,0 +1,34 @@ +{ + "puzzles": [ + { + "game": { + "id": "j1fb41pT", + "perf": { "key": "classical", "name": "Classical" }, + "rated": true, + "players": [ + { + "name": "Boris", + "patron": true, + "patronColor": 1, + "id": "boris", + "color": "white", + "rating": 1651 + }, + { "name": "Benjamin", "id": "benjamin", "color": "black", "rating": 1703 } + ], + "pgn": "e4 e6 Nf3 c5 d4 cxd4 Qxd4 Nc6 Qc4 Bb4+ c3 Ba5 b4 Bb6 b5 Na5 Qb4 Ne7 Ba3 a6 Qxe7+ Qxe7 Bxe7 Kxe7 bxa6 bxa6 Bd3 Rd8 O-O g6 Nbd2 d5 Rab1 Bc7 h3 Rb8 c4 dxc4 Bxc4 Rxb1 Rxb1 Bf4 Rd1 Bxd2 Nxd2 Rxd2", + "clock": "15+15" + }, + "puzzle": { + "id": "BlU5t", + "rating": 1677, + "plays": 19418, + "solution": ["d1d2", "a5c4", "d2c2", "c4d6", "e4e5", "c8d7", "e5d6"], + "themes": ["endgame", "veryLong", "advantage"], + "initialPly": 45 + } + } + ], + "glicko": { "rating": 1663.73, "deviation": 91.04 }, + "rounds": [{ "id": "8KtG9", "win": true, "ratingDiff": 14 }] +} diff --git a/lib/tests/data/response/puzzle_race_results.json b/lib/tests/data/response/puzzle_race_results.json new file mode 100644 index 0000000..acb9647 --- /dev/null +++ b/lib/tests/data/response/puzzle_race_results.json @@ -0,0 +1,19 @@ +{ + "id": "nkEw7", + "owner": "lichess", + "players": [ + { "name": "Hossienshirmohamady", "flair": "activity.yo-yo", "id": "hossienshirmohamady", "score": 62 }, + { "name": "ttrv", "flair": "activity.trophy", "patron": true, "patronColor": 10, "id": "ttrv", "score": 37 }, + { "name": "BubblyBakerHound", "score": 6 } + ], + "puzzles": [ + { + "id": "oiHY2", + "fen": "1r6/7R/1n2p2p/p2kN3/3P3P/PPp5/2P5/2K5 b - - 1 38", + "line": "d5d4 e5c6 d4e4 c6b8", + "rating": 1016 + } + ], + "startsAt": 1758635599928, + "finishesAt": 1758635689928 +} diff --git a/lib/tests/offline.rs b/lib/tests/offline.rs index 8163e5e..2263de7 100644 --- a/lib/tests/offline.rs +++ b/lib/tests/offline.rs @@ -59,6 +59,17 @@ pub fn puzzle_race() { test_response_model::<puzzles::race::PuzzleRacer>("puzzle_racer"); } +#[test] +pub fn puzzle_batch() { + test_response_model::<puzzles::batch::Select>("puzzle_batch_select"); + test_response_model::<puzzles::batch::SolveResponse>("puzzle_batch_solve"); +} + +#[test] +pub fn puzzle_racer_results() { + test_response_model::<puzzles::racer::RaceResults>("puzzle_race_results"); +} + #[test] pub fn puzzle_dashboard() { test_response_model::<puzzles::dashboard::PuzzleDashboard>("puzzle_dashboard"); @@ -171,6 +182,11 @@ pub fn broadcasts() { test_response_model::<broadcasts::BroadcastPgnPush>("broadcast_pgn_push"); } +#[test] +pub fn fide_player_ratings() { + test_response_model::<fide::ratings::PlayerRatings>("fide_player_ratings"); +} + fn test_response_model<Model: Serialize + DeserializeOwned>(file_name: &str) { let path = format!("./tests/data/response/{}.json", file_name); test_model::<Model>(path);