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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ Most endpoints require a bearer token but some don't. E.g. the daily puzzle, etc
|---------|:-------:|-------------|
| `oauth` | yes | OAuth2 authorization code flow with PKCE, for acting on behalf of other users. |

## CLI

```sh
cargo install --path cli
lichess users get thibault
lichess --api-token "$LICHESS_TOKEN" challenges create <username> --rated
lichess --json puzzles daily
```

- `--api-token` / `-a`: for endpoints that require authentication.
- `--json`: pretty-printed JSON instead of Rust debug format.
- `--verbose` / `-v`: enable debug logging.

Run `lichess --help` or `lichess <category> --help` for the full list of categories and commands.

## Contributing

If you have any ideas, bug reports, feature requests, or fixes, please make an issue or submit a pull request.
Expand Down
10 changes: 8 additions & 2 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ color-eyre = "0.6.5"
futures = "0.3.34"
rand = "0.10.2"
reqwest = "0.13.4"
tokio = { version = "1.53.1", features = ["macros", "rt"] }
serde = "1.0.229"
serde_json = "1.0.151"
tokio = { version = "1.53.1", features = ["rt", "time", "net"] }
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }

[dev-dependencies]
assert_cmd = "2.0.19"
predicates = "3.1.4"
93 changes: 93 additions & 0 deletions cli/src/commands/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use clap::Subcommand;
use color_eyre::Result;
use color_eyre::eyre::WrapErr;
use lichess_api::client::LichessApi;
use lichess_api::model::account::*;
use reqwest;

use crate::output;

type Lichess = LichessApi<reqwest::Client>;

#[derive(Debug, Subcommand)]
pub enum AccountCommand {
/// Get your public profile
Profile,
/// Get your email address
Email,
/// Get your preferences
Preferences,
/// Get your kid mode status
KidModeStatus,
/// Set your kid mode status
SetKidMode {
/// Turn kid mode on or off
#[arg(long)]
on: bool,
},
/// Get your timeline
Timeline {
/// Only include entries since this timestamp (ms)
#[arg(long)]
since: Option<u64>,
/// Max number of entries to fetch
#[arg(long)]
nb: Option<u32>,
},
}

impl AccountCommand {
pub async fn run(self, lichess: Lichess, json: bool) -> Result<()> {
match self {
AccountCommand::Profile => {
let profile = lichess
.get_profile()
.await
.wrap_err("failed to fetch profile")?;
output::print(&profile, json);
Ok(())
}
AccountCommand::Email => {
let email = lichess
.get_email_address()
.await
.wrap_err("failed to fetch email address")?;
output::print(&email, json);
Ok(())
}
AccountCommand::Preferences => {
let preferences = lichess
.get_preferences()
.await
.wrap_err("failed to fetch preferences")?;
output::print(&preferences, json);
Ok(())
}
AccountCommand::KidModeStatus => {
let status = lichess
.get_kid_mode_status()
.await
.wrap_err("failed to fetch kid mode status")?;
output::print(&status, json);
Ok(())
}
AccountCommand::SetKidMode { on } => {
let result = lichess
.set_kid_mode_status(on)
.await
.wrap_err("failed to set kid mode status")?;
println!("Kid mode set to {on}: {}", result);
Ok(())
}
AccountCommand::Timeline { since, nb } => {
let query = timeline::GetQuery { since, nb };
let timeline = lichess
.get_timeline(query)
.await
.wrap_err("failed to fetch timeline")?;
output::print(&timeline, json);
Ok(())
}
}
}
}
85 changes: 85 additions & 0 deletions cli/src/commands/analysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use clap::{Subcommand, ValueEnum};
use color_eyre::Result;
use color_eyre::eyre::WrapErr;
use lichess_api::client::LichessApi;
use lichess_api::model::VariantKey;
use lichess_api::model::analysis::*;
use reqwest;

use crate::output;

type Lichess = LichessApi<reqwest::Client>;

#[derive(Debug, Clone, ValueEnum)]
pub enum Variant {
Standard,
Chess960,
Crazyhouse,
Antichess,
Atomic,
Horde,
KingOfTheHill,
RacingKings,
ThreeCheck,
FromPosition,
}

impl From<Variant> for VariantKey {
fn from(variant: Variant) -> Self {
match variant {
Variant::Standard => VariantKey::Standard,
Variant::Chess960 => VariantKey::Chess960,
Variant::Crazyhouse => VariantKey::Crazyhouse,
Variant::Antichess => VariantKey::Antichess,
Variant::Atomic => VariantKey::Atomic,
Variant::Horde => VariantKey::Horde,
Variant::KingOfTheHill => VariantKey::KingOfTheHill,
Variant::RacingKings => VariantKey::RacingKings,
Variant::ThreeCheck => VariantKey::ThreeCheck,
Variant::FromPosition => VariantKey::FromPosition,
}
}
}

#[derive(Debug, Subcommand)]
pub enum AnalysisCommand {
/// Get the cloud evaluation of a position
CloudEval {
/// FEN of the position
fen: String,
/// Number of principal variations
#[arg(long)]
multi_pv: Option<u32>,
/// Chess variant
#[arg(long, value_enum)]
variant: Option<Variant>,
},
}

impl AnalysisCommand {
pub async fn run(self, lichess: Lichess, json: bool) -> Result<()> {
match self {
AnalysisCommand::CloudEval {
fen,
multi_pv,
variant,
} => {
let query = cloud::GetQuery {
fen: fen.clone(),
variation_count: multi_pv,
variant: variant.map(Into::into),
};
let evaluation = lichess
.get_cloud_evaluation(query)
.await
.wrap_err_with(|| {
format!(
"failed to fetch cloud evaluation for fen '{fen}' (position may not be evaluated)"
)
})?;
output::print(&evaluation, json);
Ok(())
}
}
}
}
Loading
Loading