use std::collections::HashMap;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::client::LichessClient;
use crate::config::Host;
use crate::error::Result;
use crate::http;
use crate::model::{LichessLightUser, LichessUserExtended};
#[derive(Debug, Deserialize)]
struct EmailResponse {
email: String,
}
#[derive(Debug, Deserialize)]
struct KidStatus {
kid: bool,
}
#[derive(Debug)]
pub struct AccountApi<'a> {
client: &'a LichessClient,
}
impl<'a> AccountApi<'a> {
pub(crate) fn new(client: &'a LichessClient) -> Self {
Self { client }
}
pub async fn profile(&self) -> Result<LichessUserExtended> {
let request = self
.client
.request(Method::GET, Host::Default, "/api/account");
http::json(request, "LichessUserExtended").await
}
pub async fn email(&self) -> Result<String> {
let request = self
.client
.request(Method::GET, Host::Default, "/api/account/email");
let response: EmailResponse = http::json(request, "account email").await?;
Ok(response.email)
}
pub async fn kid_mode(&self) -> Result<bool> {
let request = self
.client
.request(Method::GET, Host::Default, "/api/account/kid");
let status: KidStatus = http::json(request, "kid mode status").await?;
Ok(status.kid)
}
pub async fn set_kid_mode(&self, enabled: bool) -> Result<()> {
let request = self
.client
.request(Method::POST, Host::Default, "/api/account/kid")
.query(&[("v", enabled)]);
http::ok(request).await
}
pub async fn preferences(&self) -> Result<LichessPreferences> {
let request = self
.client
.request(Method::GET, Host::Default, "/api/account/preferences");
http::json(request, "LichessPreferences").await
}
pub async fn timeline(&self) -> Result<LichessTimeline> {
let request = self
.client
.request(Method::GET, Host::Default, "/api/timeline");
http::json(request, "LichessTimeline").await
}
}
impl LichessClient {
#[must_use]
pub fn account(&self) -> AccountApi<'_> {
AccountApi::new(self)
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct LichessUserPreferences {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub theme: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub piece_set: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sound_set: Option<String>,
#[serde(flatten)]
pub other: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessPreferences {
#[serde(default)]
pub prefs: LichessUserPreferences,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessTimelineEntry {
#[serde(rename = "type")]
pub entry_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub date: Option<i64>,
#[serde(flatten)]
pub data: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct LichessTimeline {
#[serde(default)]
pub entries: Vec<LichessTimelineEntry>,
#[serde(default)]
pub users: HashMap<String, LichessLightUser>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_preferences_with_typed_and_flattened_fields() {
let json = r#"{"prefs":{"theme":"blue","pieceSet":"cburnett","zen":1,
"confirmResign":1},"language":"en-GB"}"#;
let prefs: LichessPreferences = serde_json::from_str(json).unwrap();
assert_eq!(prefs.prefs.theme.as_deref(), Some("blue"));
assert_eq!(prefs.language.as_deref(), Some("en-GB"));
assert_eq!(prefs.prefs.other.get("zen"), Some(&Value::from(1)));
}
#[test]
fn parses_timeline_entry() {
let json = r#"{"entries":[{"type":"follow","date":1,"u1":"a","u2":"b"}],
"users":{"a":{"id":"a","name":"A"}}}"#;
let timeline: LichessTimeline = serde_json::from_str(json).unwrap();
assert_eq!(timeline.entries[0].entry_type, "follow");
assert_eq!(timeline.entries[0].data.get("u1"), Some(&Value::from("a")));
assert_eq!(timeline.users["a"].name, "A");
}
}