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}