bdo_rs/
lib.rs

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        // Don't translate here - let the BDO server handle allyabase:// protocol
258        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}