diff --git a/Cargo.toml b/Cargo.toml index 84e858d..0df4dc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,14 @@ description = "An unofficial collection of APIs used in FreeJam games and mods" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = { version = "1.0", features = ["derive"]} -serde_json = "1.0" -reqwest = { version = "0.11.1", features = ["json"]} -url = "2.2.1" +serde = { version = "^1", features = ["derive"]} +serde_json = "^1" +reqwest = { version = "^0.11", features = ["json"]} +url = "^2.2" +ureq = { version = "^2", features = ["json"], optional = true} [dev-dependencies] -tokio = { version = "1.4.0", features = ["macros"]} \ No newline at end of file +tokio = { version = "1.4.0", features = ["macros"]} + +[features] +simple = ["ureq"] diff --git a/src/cardlife_simple/mod.rs b/src/cardlife_simple/mod.rs new file mode 100644 index 0000000..70b786d --- /dev/null +++ b/src/cardlife_simple/mod.rs @@ -0,0 +1 @@ +// TODO diff --git a/src/lib.rs b/src/lib.rs index c4cf11c..f106cf7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,6 @@ pub mod cardlife; pub mod robocraft; +#[cfg(feature = "simple")] +pub mod robocraft_simple; +#[cfg(feature = "simple")] +pub mod cardlife_simple; diff --git a/src/robocraft/factory.rs b/src/robocraft/factory.rs index 965a229..231d004 100644 --- a/src/robocraft/factory.rs +++ b/src/robocraft/factory.rs @@ -4,7 +4,7 @@ use url::{Url}; use crate::robocraft::{ITokenProvider, DefaultTokenProvider, FactoryInfo, FactorySearchBuilder, RoboShopItemsInfo, FactoryRobotGetInfo}; use crate::robocraft::factory_json::ListPayload; -const FACTORY_DOMAIN: &str = "https://factory.robocraftgame.com/"; +pub const FACTORY_DOMAIN: &str = "https://factory.robocraftgame.com/"; pub struct FactoryAPI { client: Client, diff --git a/src/robocraft/mod.rs b/src/robocraft/mod.rs index 1124780..ba0c20b 100644 --- a/src/robocraft/mod.rs +++ b/src/robocraft/mod.rs @@ -1,9 +1,11 @@ mod factory; mod factory_json; mod factory_request_builder; -pub use self::factory::{FactoryAPI}; +pub use self::factory::{FactoryAPI, FACTORY_DOMAIN}; pub use self::factory_json::{FactoryInfo, FactoryRobotListInfo, RoboShopItemsInfo, FactoryRobotGetInfo}; pub use self::factory_request_builder::{FactorySearchBuilder, FactoryMovementType, FactoryOrderType, FactoryWeaponType, FactoryTextSearchType}; +#[cfg(feature = "simple")] +pub(crate) use self::factory_json::{ListPayload}; mod auth; pub use self::auth::{ITokenProvider, DefaultTokenProvider}; diff --git a/src/robocraft_simple/factory.rs b/src/robocraft_simple/factory.rs new file mode 100644 index 0000000..b7450ea --- /dev/null +++ b/src/robocraft_simple/factory.rs @@ -0,0 +1,76 @@ +use ureq::{Agent, Error, Response}; +use url::Url; +use serde_json::{to_string}; + +use crate::robocraft::{ITokenProvider, DefaultTokenProvider, FACTORY_DOMAIN, FactoryInfo, RoboShopItemsInfo, FactoryRobotGetInfo}; +use crate::robocraft::{ListPayload}; +use crate::robocraft_simple::FactorySearchBuilder; + +pub struct FactoryAPI { + client: Agent, + token: Box, +} + +impl FactoryAPI { + pub fn new() -> FactoryAPI { + FactoryAPI { + client: Agent::new(), + token: Box::new(DefaultTokenProvider{}), + } + } + + pub fn list(&self) -> Result, Error> { + let url = Url::parse(FACTORY_DOMAIN) + .unwrap() + .join("/api/roboShopItems/list") + .unwrap(); + let payload = ListPayload::default(); + let mut request_builder = self.client.post(url.as_str()) + .set("Content-Type", "application/json"); + if let Ok(token) = self.token.token() { + request_builder = request_builder.set("Authorization", &("Web ".to_owned() + &token)); + } + let result = request_builder.send_string(&to_string(&payload).unwrap()); + if let Ok(response) = result { + let json_res = response.into_json::>(); + if let Ok(json) = json_res { + return Ok(json); + } + return Err(Error::Status(500, Response::new(500, "Malformed JSON", "").unwrap())); // server returned malformed data + } + Err(result.err().unwrap()) + } + + pub fn list_builder(&self) -> FactorySearchBuilder { + let url = Url::parse(FACTORY_DOMAIN) + .unwrap() + .join("/api/roboShopItems/list") + .unwrap(); + let mut token_opt = None; + if let Ok(token) = self.token.token() { + token_opt = Some(token); + } + let request_builder = self.client.post(url.as_str()); + FactorySearchBuilder::new(request_builder, token_opt) + } + + pub fn get(&self, item_id: usize) -> Result, Error> { + let url = Url::parse(FACTORY_DOMAIN) + .unwrap() + .join(&format!("/api/roboShopItems/get/{}", item_id)) + .unwrap(); + let mut request_builder = self.client.get(url.as_str()); + if let Ok(token) = self.token.token() { + request_builder = request_builder.set("Authorization", &("Web ".to_owned() + &token)); + } + let result = request_builder.call(); + if let Ok(response) = result { + let json_res = response.into_json::>(); + if let Ok(json) = json_res { + return Ok(json); + } + return Err(Error::Status(500, Response::new(500, "Malformed JSON", "").unwrap())); // server returned malformed data + } + Err(result.err().unwrap()) + } +} diff --git a/src/robocraft_simple/factory_request_builder.rs b/src/robocraft_simple/factory_request_builder.rs new file mode 100644 index 0000000..8a8ee9e --- /dev/null +++ b/src/robocraft_simple/factory_request_builder.rs @@ -0,0 +1,131 @@ +use ureq::{Request, Response, Error}; + +use crate::robocraft::{FactoryInfo, RoboShopItemsInfo, FactoryTextSearchType, FactoryWeaponType, FactoryMovementType, FactoryOrderType}; +use crate::robocraft::{ListPayload}; + +pub struct FactorySearchBuilder { + reqwest_builder: Request, + payload: ListPayload, + token: Option, +} + +impl FactorySearchBuilder { + pub(crate) fn new(request_builder: Request, token: Option) -> FactorySearchBuilder { + FactorySearchBuilder { + reqwest_builder: request_builder.set("Content-Type", "application/json"), + payload: ListPayload::empty(), + token, + } + } + + pub fn page(mut self, page_number: isize) -> Self { + self.payload.page = page_number; + self + } + + pub fn items_per_page(mut self, page_size: isize) -> Self { + self.payload.page_size = page_size; + self + } + + pub fn order(mut self, order_type: FactoryOrderType) -> Self { + self.payload.order = order_type as isize; + self + } + + /* // this appears to not do anything (removed to prevent confusion) + // use text_search_type(FactoryTextSearchType::Player) instead + pub fn players_only(mut self, p: bool) -> Self { + self.payload.player_filter = p; + self + } + */ + + pub fn movement_or(mut self, movement_type: FactoryMovementType) -> Self { + if self.payload.movement_filter == "" { + self.payload.movement_filter = format!("{},{}", &self.payload.movement_filter, movement_type as isize); + } else { + self.payload.movement_filter = (movement_type as isize).to_string(); + } + self.payload.movement_category_filter = self.payload.movement_filter.clone(); + self + } + + pub fn weapon_or(mut self, weapon_type: FactoryWeaponType) -> Self { + if self.payload.weapon_filter == "" { + self.payload.weapon_filter = format!("{},{}", &self.payload.weapon_filter, weapon_type as isize); + } else { + self.payload.weapon_filter = (weapon_type as isize).to_string(); + } + self.payload.weapon_category_filter = self.payload.weapon_filter.clone(); + self + } + + pub fn cpu_range(mut self, min: isize, max: isize) -> Self { + self.payload.minimum_cpu = min; + self.payload.maximum_cpu = max; + self + } + + pub fn min_cpu(mut self, min: isize) -> Self { + self.payload.minimum_cpu = min; + self + } + + pub fn max_cpu(mut self, max: isize) -> Self { + self.payload.maximum_cpu = max; + self + } + + pub fn no_minimum_cpu(mut self) -> Self { + self.payload.minimum_cpu = -1; + self + } + + pub fn no_maximum_cpu(mut self) -> Self { + self.payload.maximum_cpu = -1; + self + } + + pub fn text(mut self, t: String) -> Self { + self.payload.text_filter = t; + self + } + + pub fn text_search_type(mut self, search_type: FactoryTextSearchType) -> Self { + self.payload.text_search_field = search_type as isize; + self + } + + // setting buyable to false while using the default token provider will cause HTTP status 500 error + pub fn buyable(mut self, b: bool) -> Self { + self.payload.buyable = b; + self + } + + pub fn prepend_featured(mut self, b: bool) -> Self { + self.payload.prepend_featured_robot = b; + self + } + + pub fn default_page(mut self, b: bool) -> Self { + self.payload.default_page = b; + self + } + + pub fn send(mut self) -> Result, Error> { + self.reqwest_builder = self.reqwest_builder; + if let Some(token) = self.token.clone() { + self.reqwest_builder = self.reqwest_builder.set("Authorization", &("Web ".to_owned() + &token)); + } + let result = self.reqwest_builder.send_string(&serde_json::to_string(&self.payload).unwrap()); + if let Ok(response) = result { + let json_res = response.into_json::>(); + if let Ok(json) = json_res { + return Ok(json); + } + return Err(Error::Status(500, Response::new(500, "Malformed JSON", "").unwrap())); // server returned malformed data + } + Err(result.err().unwrap()) + } +} diff --git a/src/robocraft_simple/mod.rs b/src/robocraft_simple/mod.rs new file mode 100644 index 0000000..c8ce723 --- /dev/null +++ b/src/robocraft_simple/mod.rs @@ -0,0 +1,4 @@ +mod factory; +mod factory_request_builder; +pub use factory::{FactoryAPI}; +pub use factory_request_builder::{FactorySearchBuilder}; diff --git a/tests/robocraft_factory_simple.rs b/tests/robocraft_factory_simple.rs new file mode 100644 index 0000000..5e60334 --- /dev/null +++ b/tests/robocraft_factory_simple.rs @@ -0,0 +1,81 @@ +#[cfg(feature = "simple")] +use libfj::robocraft_simple; +#[cfg(feature = "simple")] +use libfj::robocraft; + +#[cfg(feature = "simple")] +#[test] +fn robocraft_factory_api_init_simple() -> Result<(), ()> { + robocraft_simple::FactoryAPI::new(); + Ok(()) +} + +#[cfg(feature = "simple")] +fn builder() -> robocraft_simple::FactorySearchBuilder { + robocraft_simple::FactoryAPI::new().list_builder() +} + +#[cfg(feature = "simple")] +fn assert_factory_list(robo_info: robocraft::FactoryInfo) -> Result<(), ()> { + assert_ne!(robo_info.response.roboshop_items.len(), 0); + assert_eq!(robo_info.status_code, 200); + for robot in &robo_info.response.roboshop_items { + assert_ne!(robot.item_name, ""); + assert_ne!(robot.added_by, ""); + assert_ne!(robot.added_by_display_name, ""); + assert_ne!(robot.thumbnail, ""); + println!("FactoryRobotListInfo.to_string() -> `{}`", robot.to_string()); + } + Ok(()) +} + +#[test] +#[cfg(feature = "simple")] +fn robocraft_factory_custom_query_simple() -> Result<(), ()> { + let result = builder() + .movement_or(robocraft::FactoryMovementType::Wheels) + .weapon_or(robocraft::FactoryWeaponType::Laser) + .page(2) + .items_per_page(10) + .send(); + assert!(result.is_ok()); + let robo_info = result.unwrap(); + assert_ne!(robo_info.response.roboshop_items.len(), 0); + //assert_eq!(robo_info.response.roboshop_items.len(), 16); the API behaviour is weird, I swear it's not me! + assert!(robo_info.response.roboshop_items.len() >= 10); + assert_eq!(robo_info.status_code, 200); + for robot in &robo_info.response.roboshop_items { + assert_ne!(robot.item_name, ""); + assert_ne!(robot.added_by, ""); + assert_ne!(robot.added_by_display_name, ""); + assert_ne!(robot.thumbnail, ""); + println!("FactoryRobotListInfo.to_string() -> `{}`", robot.to_string()); + } + Ok(()) +} + +#[test] +#[cfg(feature = "simple")] +fn robocraft_factory_player_query() -> Result<(), ()> { + let result = builder() + .text("Baerentoeter".to_string()) + .text_search_type(robocraft::FactoryTextSearchType::Player) + .items_per_page(10) + .send(); + assert!(result.is_ok()); + assert_factory_list(result.unwrap()) +} + +#[test] +#[cfg(feature = "simple")] +fn robocraft_factory_robot_query() -> Result<(), ()> { + let api = robocraft_simple::FactoryAPI::new(); + let result = api.get(6478345 /* featured robot id*/); + assert!(result.is_ok()); + let bot_info = result.unwrap(); + assert_ne!(bot_info.response.item_name, ""); + assert_eq!(bot_info.response.item_id, 6478345); + assert_ne!(bot_info.response.cube_data, ""); + assert_ne!(bot_info.response.colour_data, ""); + Ok(()) +}