1#![doc(html_root_url = "https://docs.rs/dbl-rs/0.4.0")]
40#![deny(rust_2018_idioms)]
41
42use reqwest::header::AUTHORIZATION;
43use reqwest::{Client as ReqwestClient, Response};
44use reqwest::{Method, StatusCode};
45use url::Url;
46
47macro_rules! api {
48 ($e:expr) => {
49 concat!("https://top.gg/api", $e)
50 };
51 ($e:expr, $($rest:tt)*) => {
52 format!(api!($e), $($rest)*)
53 };
54}
55
56mod error;
57pub mod types;
58pub mod widget;
59
60pub use error::Error;
61
62use types::*;
63
64#[derive(Clone)]
66pub struct Client {
67 client: ReqwestClient,
68 token: String,
69}
70
71impl Client {
72 pub fn new(token: String) -> Result<Self, Error> {
74 let client = ReqwestClient::builder().build().map_err(error::from)?;
75 Ok(Client { client, token })
76 }
77
78 pub fn new_with_client(client: ReqwestClient, token: String) -> Self {
80 Client { client, token }
81 }
82
83 pub async fn get<T>(&self, bot: T) -> Result<Bot, Error>
85 where
86 T: Into<BotId>,
87 {
88 let url = api!("/bots/{}", bot.into());
89 get(self, url).await
90 }
91
92 pub async fn search(&self, filter: &Filter) -> Result<Listing, Error> {
102 let url = Url::parse_with_params(api!("/bots"), &filter.0).map_err(Error::Url)?;
103 get(self, url.to_string()).await
104 }
105
106 pub async fn stats<T>(&self, bot: T) -> Result<Stats, Error>
108 where
109 T: Into<BotId>,
110 {
111 let url = api!("/bots/{}/stats", bot.into());
112 get(self, url).await
113 }
114
115 pub async fn update_stats<T>(&self, bot: T, stats: ShardStats) -> Result<(), Error>
128 where
129 T: Into<BotId>,
130 {
131 let url = api!("/bots/{}/stats", bot.into());
132 post(self, url, Some(stats)).await
133 }
134
135 pub async fn votes<T>(&self, bot: T) -> Result<Vec<User>, Error>
137 where
138 T: Into<BotId>,
139 {
140 let url = api!("/bots/{}/votes", bot.into());
141 get(self, url).await
142 }
143
144 pub async fn has_voted<T, U>(&self, bot: T, user: U) -> Result<bool, Error>
146 where
147 T: Into<BotId>,
148 U: Into<UserId>,
149 {
150 let bot = bot.into();
151 let user = user.into();
152 let url = api!("/bots/{}/check?userId={}", bot, user);
153 let v: UserVoted = get(self, url).await?;
154 Ok(v.voted > 0)
155 }
156
157 pub async fn user<T>(&self, user: T) -> Result<DetailedUser, Error>
159 where
160 T: Into<UserId>,
161 {
162 let url = api!("/users/{}", user.into());
163 get(self, url).await
164 }
165}
166
167async fn request<T>(
168 client: &Client,
169 method: Method,
170 url: String,
171 data: Option<T>,
172) -> Result<Response, Error>
173where
174 T: serde::Serialize + Sized,
175{
176 let mut req = client
177 .client
178 .request(method, &url)
179 .header(AUTHORIZATION, &*client.token);
180
181 if let Some(data) = data {
182 req = req.json(&data);
183 }
184
185 let resp = match req.send().await {
186 Ok(resp) => resp,
187 Err(e) => return Err(error::from(e)),
188 };
189 match resp.status() {
190 StatusCode::TOO_MANY_REQUESTS => {
191 let rl = match resp.json::<Ratelimit>().await {
192 Ok(rl) => rl,
193 Err(e) => return Err(error::from(e)),
194 };
195 Err(error::ratelimit(rl.retry_after))
196 }
197 _ => resp.error_for_status().map_err(error::from),
198 }
199}
200
201async fn get<T>(client: &Client, url: String) -> Result<T, Error>
202where
203 T: serde::de::DeserializeOwned + Sized,
204{
205 let resp = request(client, Method::GET, url, None::<()>).await?;
206 match resp.json().await {
207 Ok(data) => Ok(data),
208 Err(e) => Err(error::from(e)),
209 }
210}
211
212async fn post<T>(client: &Client, url: String, data: Option<T>) -> Result<(), Error>
213where
214 T: serde::Serialize + Sized,
215{
216 request(client, Method::POST, url, data).await?;
217 Ok(())
218}