Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
run: cargo build --verbose

- name: wasm check
run: cargo check --verbose --target wasm32-unknown-unknown
run: cargo check --lib --verbose --target wasm32-unknown-unknown

- name: test
run: cargo test --verbose
41 changes: 3 additions & 38 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,38 +1,3 @@
[package]
name = "lichess-api"
version = "0.6.0"
edition = "2021"
license = "Apache-2.0"
description = "A Rust client for Lichess API v2.0.0"
keywords = ["lichess", "api", "client"]
categories = ["api-bindings", "asynchronous"]
homepage = "https://github.com/ion232/lichess-api"
repository = "https://github.com/ion232/lichess-api"
readme = "README.md"

[dependencies]
reqwest = { version = "0.12.7", features = ["json", "stream"] }

# Other dependencies.
async-std = "1.13.1"
bytes = "1.10.1"
futures = "0.3.31"
futures-core = "0.3.31"
http = "1.3.1"
mime = "0.3.17"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
serde_with = { version = "3.12.0", features = ["chrono"] }
serde_urlencoded = "0.7.1"
thiserror = "2.0.12"
tracing = "0.1.41"
url = "2.5.4"

[dev-dependencies]
clap = { version = "4.5.32", features = ["derive"] }
color-eyre = "0.6.3"
getrandom = { version = "0.3.2" }
rand = "0.9.0"
tokio = { version = "1.44.1", features = ["macros", "rt"] }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }

[workspace]
members = ["cli", "lib"]
resolver = "2"
25 changes: 25 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "lichess-cli"
version = "0.6.0"
edition = "2024"
license = "Apache-2.0"
description = "A cli client for the lichess API."
keywords = ["lichess", "api", "client"]
categories = ["api-bindings"]
homepage = "https://github.com/ion232/lichess-api"
repository = "https://github.com/ion232/lichess-api"

[[bin]]
name = "lichess"
path = "src/main.rs"

[dependencies]
lichess-api = { path = "../lib" }
clap = { version = "4.5.32", features = ["derive"] }
color-eyre = "0.6.3"
futures = "0.3.31"
rand = "0.9.0"
reqwest = "0.12.7"
tokio = { version = "1.44.1", features = ["macros", "rt"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
184 changes: 184 additions & 0 deletions cli/src/commands/challenges.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use clap::Subcommand;
use color_eyre::Result;
use lichess_api::client::LichessApi;
use lichess_api::model::VariantKey;
use lichess_api::model::challenges::*;
use reqwest;

type Lichess = LichessApi<reqwest::Client>;

#[derive(Debug, Subcommand)]
pub enum ChallengesCommand {
/// List your challenges
List,
/// Create a challenge
Create {
/// Username to challenge
username: String,
/// Whether the game is rated
#[arg(long)]
rated: bool,
/// Clock limit in seconds
#[arg(long)]
clock_limit: Option<u32>,
/// Clock increment in seconds
#[arg(long)]
clock_increment: Option<u32>,
/// Days per turn for correspondence games
#[arg(long)]
days: Option<u32>,
/// Chess variant
#[arg(long, default_value = "standard")]
variant: String,
/// Custom starting position (FEN)
#[arg(long)]
fen: Option<String>,
/// Message to the opponent
#[arg(long)]
message: Option<String>,
},
/// Accept a challenge
Accept {
/// Challenge ID
challenge_id: String,
},
/// Decline a challenge
Decline {
/// Challenge ID
challenge_id: String,
/// Reason for declining
#[arg(long, value_enum)]
reason: Option<DeclineReason>,
},
/// Cancel a challenge you sent
Cancel {
/// Challenge ID
challenge_id: String,
/// Opponent token (if applicable)
#[arg(long)]
opponent_token: Option<String>,
},
}

#[derive(Debug, Clone, clap::ValueEnum)]
pub enum DeclineReason {
Generic,
Later,
TooFast,
TooSlow,
TimeControl,
Rated,
Casual,
Standard,
Variant,
NoBot,
OnlyBot,
}

impl From<DeclineReason> for decline::Reason {
fn from(reason: DeclineReason) -> Self {
match reason {
DeclineReason::Generic => decline::Reason::Generic,
DeclineReason::Later => decline::Reason::Later,
DeclineReason::TooFast => decline::Reason::TooFast,
DeclineReason::TooSlow => decline::Reason::TooSlow,
DeclineReason::TimeControl => decline::Reason::TimeControl,
DeclineReason::Rated => decline::Reason::Rated,
DeclineReason::Casual => decline::Reason::Casual,
DeclineReason::Standard => decline::Reason::Standard,
DeclineReason::Variant => decline::Reason::Variant,
DeclineReason::NoBot => decline::Reason::NoBot,
DeclineReason::OnlyBot => decline::Reason::OnlyBot,
}
}
}

impl ChallengesCommand {
pub async fn run(self, lichess: Lichess) -> Result<()> {
match self {
ChallengesCommand::List => {
let challenges = lichess.list_challenges().await?;
println!("Incoming challenges:");
for challenge in &challenges.r#in {
println!(" {} - {}", challenge.base.id, challenge.base.url);
}
println!("Outgoing challenges:");
for challenge in &challenges.out {
println!(" {} - {}", challenge.base.id, challenge.base.url);
}
Ok(())
}
ChallengesCommand::Create {
username,
rated,
clock_limit,
clock_increment,
days,
variant,
fen,
message,
} => {
let variant_key = match variant.as_str() {
"standard" => VariantKey::Standard,
"chess960" => VariantKey::Chess960,
"crazyhouse" => VariantKey::Crazyhouse,
"antichess" => VariantKey::Antichess,
"atomic" => VariantKey::Atomic,
"horde" => VariantKey::Horde,
"kingOfTheHill" => VariantKey::KingOfTheHill,
"racingKings" => VariantKey::RacingKings,
"threeCheck" => VariantKey::ThreeCheck,
_ => {
println!("Invalid variant: {}", variant);
return Ok(());
}
};

let challenge = CreateChallenge {
base: ChallengeBase {
clock_limit: clock_limit,
clock_increment: clock_increment,
days: days.map(|d| d.into()),
variant: variant_key,
fen: fen,
},
rated: rated,
keep_alive_stream: false,
accept_by_token: None,
message: message,
rules: String::new(),
};

let request = create::PostRequest::new(&username, challenge);
let result = lichess.create_challenge(request).await?;
println!("Challenge created: {:#?}", result);
Ok(())
}
ChallengesCommand::Accept { challenge_id } => {
let request = accept::PostRequest::new(&challenge_id);
let result = lichess.accept_challenge(request).await?;
println!("Challenge accepted: {}", result);
Ok(())
}
ChallengesCommand::Decline {
challenge_id,
reason,
} => {
let decline_reason = reason.unwrap_or(DeclineReason::Generic);
let request = decline::PostRequest::new(challenge_id, decline_reason.into());
let result = lichess.decline_challenge(request).await?;
println!("Challenge declined: {}", result);
Ok(())
}
ChallengesCommand::Cancel {
challenge_id,
opponent_token,
} => {
let request = cancel::PostRequest::new(challenge_id, opponent_token);
let result = lichess.cancel_challenge(request).await?;
println!("Challenge cancelled: {}", result);
Ok(())
}
}
}
}
Loading