1pub mod structs;
2
3#[cfg(test)]
4mod tests;
5
6use reqwest::{Client, Response};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9use serde_json::Value;
10use sessionless::hex::IntoHex;
11use sessionless::{Sessionless, Signature};
12use std::time::{SystemTime, UNIX_EPOCH};
13use std::collections::HashMap;
14use std::option::Option;
15use crate::structs::{BDOUser, SuccessResult, EmojicodeResponse};
16
17#[derive(Debug, Serialize, Deserialize)]
18#[serde(rename_all="camelCase")]
19pub struct Spellbook {
20 pub spellbookName: String,
21 #[serde(flatten)]
22 spells: serde_json::Value
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26#[serde(rename_all="camelCase")]
27pub struct Bases {
28 pub bases: serde_json::Value
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32#[serde(rename_all="camelCase")]
33pub struct Spellbooks {
34 pub spellbooks: Vec<Spellbook>
35}
36
37pub struct BDO {
38 base_url: String,
39 client: Client,
40 pub sessionless: Sessionless,
41}
42
43impl BDO {
44 pub fn new(base_url: Option<String>, sessionless: Option<Sessionless>) -> Self {
45 println!("🏗️ BDO::new() called with base_url: {:?}", base_url);
46 let final_base_url = base_url.unwrap_or("https://dev.bdo.allyabase.com/".to_string());
47 println!("🏗️ BDO using final base_url: {}", final_base_url);
48 BDO {
49 base_url: final_base_url,
50 client: Client::new(),
51 sessionless: sessionless.unwrap_or(Sessionless::new()),
52 }
53 }
54
55 async fn get(&self, url: &str) -> Result<Response, reqwest::Error> {
56 self.client.get(url).send().await
57 }
58
59 async fn post(&self, url: &str, payload: serde_json::Value) -> Result<Response, reqwest::Error> {
60 self.client
61 .post(url)
62 .json(&payload)
63 .send()
64 .await
65 }
66
67 async fn put(&self, url: &str, payload: serde_json::Value) -> Result<Response, reqwest::Error> {
68 self.client
69 .put(url)
70 .json(&payload)
71 .send()
72 .await
73 }
74
75 async fn delete(&self, url: &str, payload: serde_json::Value) -> Result<Response, reqwest::Error> {
76 self.client
77 .delete(url)
78 .json(&payload)
79 .send()
80 .await
81 }
82
83 fn get_timestamp() -> String {
84 SystemTime::now()
85 .duration_since(UNIX_EPOCH)
86 .expect("Time went backwards")
87 .as_millis()
88 .to_string()
89 }
90
91 pub async fn create_user(&self, hash: &str, bdo: &Value, is_public: &bool) -> Result<BDOUser, Box<dyn std::error::Error>> {
92 let timestamp = Self::get_timestamp();
93 let pub_key = self.sessionless.public_key().to_hex();
94 let signature = self.sessionless.sign(&format!("{}{}{}", timestamp, pub_key, hash)).to_hex();
95
96 let payload = json!({
97 "timestamp": timestamp,
98 "pubKey": pub_key,
99 "hash": hash,
100 "bdo": bdo,
101 "public": is_public,
102 "signature": signature
103 }).as_object().unwrap().clone();
104
105dbg!("{}", payload.clone());
106
107 println!("🔧 BDO client base_url: {}", self.base_url);
108 let url = format!("{}user/create", self.base_url);
109 println!("🔗 BDO final URL: {}", &url);
110dbg!("{}", &url);
111 let res = self.put(&url, serde_json::Value::Object(payload)).await?;
112dbg!("{}", &res);
113 let user: BDOUser = res.json().await?;
114
115 Ok(user)
116 }
117
118 pub async fn update_bdo(&self, uuid: &str, hash: &str, bdo: &Value, is_public: &bool) -> Result<BDOUser, Box<dyn std::error::Error>> {
119 let timestamp = Self::get_timestamp();
120 let message = format!("{}{}{}", timestamp, uuid, hash);
121 let signature = self.sessionless.sign(message).to_hex();
122
123 let payload = json!({
124 "timestamp": timestamp,
125 "uuid": uuid,
126 "hash": hash,
127 "pub": is_public,
128 "pubKey": self.sessionless.public_key().to_hex(),
129 "bdo": bdo,
130 "signature": signature
131 }).as_object().unwrap().clone();
132
133 let url = format!("{}user/{}/bdo", self.base_url, uuid);
134 let res = self.put(&url, serde_json::Value::Object(payload)).await?;
135 let user: BDOUser = res.json().await?;
136
137 Ok(user)
138 }
139
140 pub async fn get_bdo(&self, uuid: &str, hash: &str) -> Result<BDOUser, Box<dyn std::error::Error>> {
141 let timestamp = Self::get_timestamp();
142 let message = format!("{}{}{}", timestamp, uuid, hash);
143 let signature = self.sessionless.sign(message).to_hex();
144
145 let url = format!("{}user/{}/bdo?timestamp={}&hash={}&signature={}", self.base_url, uuid, timestamp, hash, signature);
146 let res = self.get(&url).await?;
147 let user: BDOUser = res.json().await?;
148
149 Ok(user)
150 }
151
152 pub async fn get_public_bdo(&self, uuid: &str, hash: &str, pub_key: &str) -> Result<BDOUser, Box<dyn std::error::Error>> {
153 let timestamp = Self::get_timestamp();
154 let message = format!("{}{}{}", timestamp, uuid, hash);
155 let signature = self.sessionless.sign(message).to_hex();
156
157 let url = format!("{}user/{}/bdo?timestamp={}&hash={}&signature={}&pubKey={}", self.base_url, uuid, timestamp, hash, signature, pub_key);
158dbg!("{}", &url);
159dbg!("{}", &self.sessionless.public_key().to_hex());
160 let res = self.get(&url).await?;
161 let user: BDOUser = res.json().await?;
162
163 Ok(user)
164 }
165
166 pub async fn get_bases(&self, uuid: &str, hash: &str) -> Result<Value, Box<dyn std::error::Error>> {
167 let timestamp = Self::get_timestamp();
168 let message = format!("{}{}{}", timestamp, uuid, hash);
169 let signature = self.sessionless.sign(message).to_hex();
170
171 let url = format!("{}user/{}/bases?timestamp={}&hash={}&signature={}", self.base_url, uuid, timestamp, hash, signature);
172 let res = self.get(&url).await?;
173 let bases: Bases = res.json().await?;
174
175 Ok(bases.bases)
176 }
177
178 pub async fn save_bases(&self, uuid: &str, hash: &str, bases: &Bases) -> Result<Value, Box<dyn std::error::Error>> {
179 let timestamp = Self::get_timestamp();
180 let message = format!("{}{}{}", timestamp, uuid, hash);
181 let signature = self.sessionless.sign(message).to_hex();
182
183 let payload = json!({
184 "timestamp": timestamp,
185 "uuid": uuid,
186 "hash": hash,
187 "bases": bases,
188 "signature": signature
189 }).as_object().unwrap().clone();
190
191 let url = format!("{}user/{}/bases", self.base_url, uuid);
192 let res = self.put(&url, serde_json::Value::Object(payload)).await?;
193 let bases: Bases = res.json().await?;
194
195 Ok(bases.bases)
196 }
197
198
199
200 pub async fn get_spellbooks(&self, uuid: &str, hash: &str) -> Result<Vec<Spellbook>, Box<dyn std::error::Error>> {
201 let timestamp = Self::get_timestamp();
202 let message = format!("{}{}{}", timestamp, uuid, hash);
203 let signature = self.sessionless.sign(message).to_hex();
204
205 let url = format!("{}user/{}/spellbooks?timestamp={}&hash={}&signature={}", self.base_url, uuid, timestamp, hash, signature);
206 let res = self.get(&url).await?;
207 let spellbooks: Spellbooks = res.json().await?;
208
209 Ok(spellbooks.spellbooks)
210 }
211
212 pub async fn put_spellbook(&self, uuid: &str, hash: &str, spellbook: &Spellbook) -> Result<Vec<Spellbook>, Box<dyn std::error::Error>> {
213 let timestamp = Self::get_timestamp();
214 let message = format!("{}{}{}", timestamp, uuid, hash);
215 let signature = self.sessionless.sign(message).to_hex();
216
217 let payload = json!({
218 "timestamp": timestamp,
219 "uuid": uuid,
220 "hash": hash,
221 "spellbook": spellbook,
222 "signature": signature
223 }).as_object().unwrap().clone();
224
225 let url = format!("{}user/{}/spellbooks", self.base_url, uuid);
226 let res = self.put(&url, serde_json::Value::Object(payload)).await?;
227 let spellbooks: Vec<Spellbook> = res.json().await?;
228
229 Ok(spellbooks)
230 }
231
232 pub async fn delete_user(&self, uuid: &str, hash: &str) -> Result<SuccessResult, Box<dyn std::error::Error>> {
233 let timestamp = Self::get_timestamp();
234 let message = format!("{}{}", timestamp, uuid);
235 let signature = self.sessionless.sign(&message).to_hex();
236
237 let payload = json!({
238 "timestamp": timestamp,
239 "uuid": uuid,
240 "hash": hash,
241 "signature": signature
242 }).as_object().unwrap().clone();
243
244 let url = format!("{}user/{}/delete", self.base_url, uuid);
245 let res = self.delete(&url, serde_json::Value::Object(payload)).await?;
246 let success: SuccessResult = res.json().await?;
247
248 Ok(success)
249 }
250
251
252 pub async fn teleport(&self, uuid: &str, hash: &str, url: &str) -> Result<Value, Box<dyn std::error::Error>> {
253 let timestamp = Self::get_timestamp();
254 let message = format!("{}{}{}", timestamp, uuid, hash);
255 let signature = self.sessionless.sign(&message).to_hex();
256
257 let teleport_url = format!(
259 "{}user/{}/teleport?timestamp={}&hash={}&signature={}&url={}",
260 self.base_url,
261 uuid,
262 timestamp,
263 hash,
264 signature,
265 urlencoding::encode(url)
266 );
267
268 dbg!(&teleport_url);
269 let res = self.get(&teleport_url).await?;
270 let teleported_content: Value = res.json().await?;
271
272 Ok(teleported_content)
273 }
274
275 pub async fn get_bdo_by_emojicode(&self, emojicode: &str) -> Result<EmojicodeResponse, Box<dyn std::error::Error>> {
276 let encoded_emojicode = urlencoding::encode(emojicode);
277 let url = format!("{}emoji/{}", self.base_url, encoded_emojicode);
278
279 let res = self.get(&url).await?;
280 let emojicode_response: EmojicodeResponse = res.json().await?;
281
282 Ok(emojicode_response)
283 }
284}