A small async Rust library for working with the DDNet and DDStats public APIs.
- Crates.io: https://crates.io/crates/ddapi-rs
- Docs.rs: https://docs.rs/ddapi-rs
- PyPI: https://pypi.org/project/ddapi
ddnet(default) - DDNet API (ddnet.org)ddstats- DDStats API (ddstats.tw)cache- in-memory cache for responses (usesmoka)full- enablesddnet,ddstats,cache
Default (DDNet only):
cargo add ddapi-rsEnable DDStats too:
cargo add ddapi-rs -F ddstatsEnable caching:
cargo add ddapi-rs -F cacheEverything:
cargo add ddapi-rs -F fullThe crate is async and uses tokio.
DDNet example (enabled by default):
use ddapi_rs::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let api = DDApi::new();
// ddnet: player points
let player = api.player("nameless tee").await?;
println!("{}: {}", player.player, player.points.points.unwrap_or(0));
Ok(())
}DDStats example (requires -F ddstats):
use ddapi_rs::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let api = DDApi::new();
// ddstats: profile info
let profile = api.profile("ByFox").await?;
println!("{} ({})", profile.name, profile.clan.unwrap_or_default());
Ok(())
}If you do not want a combined client, you can use DDnetClient / DDstatsClient.
use ddapi_rs::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let ddnet = DDnetClient::new();
let p = ddnet.player("nameless tee").await?;
println!("{}: {}", p.player, p.points.points.unwrap_or(0));
Ok(())
}use ddapi_rs::prelude::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<()> {
let mut api = DDApi::new();
api.set_cache(1000, Duration::from_secs(60 * 5));
// Will be cached based on URL + TTL
let _ = api.status().await?;
Ok(())
}use ddapi_rs::prelude::*;
use reqwest::Client;
fn main() {
let client = Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.unwrap();
let _api = DDApi::new_with_client(client);
}pip install ddapiThe Python package mirrors the Rust API. All methods are async and return
typed dataclasses (ddapi.ddnet.* / ddapi.ddstats.*) instead of raw dicts.
Fields that may be absent in the API response are None.
import asyncio
import ddapi
async def main():
api = ddapi.DDApi()
# ddnet: player points
player = await api.player("nameless tee")
print(player.player, player.points.points) # fields, not dict keys
# optional data is None - no KeyError
if player.team_rank is not None:
print(player.team_rank.rank)
# ddstats: profile info
stats = ddapi.DDstatsClient()
profile = await stats.profile("ByFox")
print(profile.name, profile.clan)
asyncio.run(main())Exposed API:
DDApi- combined client (DDNet methods take precedence)DDnetClient- DDNet API only:master,custom_master,skins,player,query,query_map,query_mapper,map,releases_map,status,latest_finish,latest_finish_with_latestDDstatsClient- DDStats API only:player,map,maps,profile,teeroddapi.ddnet/ddapi.ddstats- dataclass models (e.g.ddapi.ddnet.Player)DDError- exception raised on API errors__version__- matches the Rust crate version
Notes:
custom_mastertakes a master server index (1-4).- Timestamps arrive as
datetime(UTC); dates asdate. - Optional per-client in-memory cache:
api = ddapi.DDApi()
api.set_cache(10_000, 600) # capacity, ttl in seconds