skyblock_rs/
client.rs

1use futures::{TryFutureExt, StreamExt};
2use hyper::{Client, Uri, Body};
3use hyper_tls::HttpsConnector;
4use serde::Deserialize;
5use std::error::Error;
6use std::time::{SystemTime, Duration};
7use std::{thread, fmt};
8use crate::Result;
9
10const BASE_URL: &'static str = "https://api.hypixel.net/skyblock/";
11
12#[derive(Serialize, Deserialize, Debug)]
13#[serde(transparent)]
14pub struct ApiError(String);
15
16impl fmt::Display for ApiError {
17	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18		write!(f, "api call failed: {}", self.0)
19	}
20}
21
22impl Error for ApiError {}
23
24#[derive(Serialize, Deserialize, Debug)]
25pub struct Key<'a> {
26	key: &'a str,
27	#[serde(default = "SystemTime::now")]
28	window: SystemTime,
29	#[serde(default)]
30	uses: usize,
31	#[serde(default = "hypixel_api_window_size")]
32	window_size: u64,
33	#[serde(default = "hypixel_api_window_limit")]
34	window_limit: usize,
35}
36
37fn hypixel_api_window_size() -> u64 { 60 }
38
39fn hypixel_api_window_limit() -> usize { 120 }
40
41impl<'a> Key<'a> {
42	pub fn new(key: &'a str, window_limit: usize, window_size: u64) -> Key {
43		Key {
44			key,
45			window: SystemTime::now(),
46			uses: 0,
47			window_size,
48			window_limit,
49		}
50	}
51
52	pub fn timeout(&self) -> bool {
53		self.window.elapsed().unwrap_or(Duration::from_secs(0)).as_secs() >= self.window_size
54	}
55
56	pub fn can_use(&mut self) -> bool {
57		self.uses < self.window_limit || self.timeout()
58	}
59
60	pub fn consume<'b>(&'b mut self) -> Option<&'a str> {
61		if self.timeout() {
62			self.window = SystemTime::now();
63			self.uses = 0;
64		}
65
66		if self.can_use() {
67			self.uses += 1;
68			Some(self.key)
69		} else {
70			None
71		}
72	}
73}
74
75pub struct SkyblockApi<'a> {
76	keys: Vec<Key<'a>>,
77}
78
79impl<'a> SkyblockApi<'a> {
80	pub fn pooled(keys: Vec<&str>) -> SkyblockApi {
81		SkyblockApi {
82			keys: keys.into_iter().map(|k| Key::new(k, 120, 60)).collect(),
83		}
84	}
85
86	pub fn singleton(key: &str) -> SkyblockApi {
87		Self::pooled(vec![key])
88	}
89
90	fn get_key_sync(&mut self) -> &str {
91		loop {
92			for key in &mut self.keys {
93				if let Some(key) = key.consume() {
94					return key;
95				}
96			}
97
98			thread::sleep(Duration::from_millis(50));
99		}
100	}
101
102	pub async fn get<T>(&mut self, path: &str, params: Vec<(&str, String)>) -> Result<T> where
103		T: for<'de> Deserialize<'de> {
104		let uri: Uri = format!("{}{}?key={}{}", BASE_URL, path, self.get_key_sync(), params.iter()
105			.map(|(k, v)| {
106				format!("&{}={}", k, v)
107			})
108			.collect::<Vec<_>>()
109			.join("")
110		).parse().unwrap();
111
112		let https = HttpsConnector::new();
113		let cli = Client::builder().build::<_, Body>(https);
114
115		cli.get(uri)
116			.map_ok(|x| x.into_body())
117			.map_ok(|x| x
118				.fold(Ok(vec![]), |buf: Result<_>, chunk| async {
119					let mut buf = buf?;
120					buf.extend(&chunk?[..]);
121					Ok(buf)
122				})
123				.map_ok(|slice|
124					Ok(serde_json::from_slice(&slice[..])?)
125				)
126			).await?.await?
127	}
128}
129
130#[derive(Serialize, Deserialize, Debug)]
131#[serde(untagged)]
132pub(crate) enum ApiBody<T> {
133	Error {
134		cause: ApiError
135	},
136	Ok(T),
137}
138
139impl<T> Into<Result<T>> for ApiBody<T> {
140	fn into(self) -> Result<T> {
141		match self {
142			Self::Ok(i) => {
143				Ok(i)
144			}
145			Self::Error { cause } => {
146				Err(Box::new(cause))
147			}
148		}
149	}
150}