diff --git a/Cargo.toml b/Cargo.toml index 8e92910..8340332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfj" -version = "0.1.0" +version = "0.4.1" authors = ["NGnius (Graham) "] edition = "2018" description = "An unofficial collection of APIs used in FreeJam games and mods" diff --git a/src/cardlife/live.rs b/src/cardlife/live.rs index 19a293b..f2485fd 100644 --- a/src/cardlife/live.rs +++ b/src/cardlife/live.rs @@ -6,12 +6,14 @@ use crate::cardlife::{AuthenticationInfo, AuthenticationPayload, LobbyInfo, Lobb const AUTHENTICATION_DOMAIN: &str = "https://live-auth.cardlifegame.com/"; const LOBBY_DOMAIN: &str = "https://live-lobby.cardlifegame.com/"; +/// Cardlife live information API pub struct LiveAPI { client: Client, auth: Option, } impl LiveAPI { + /// Create a new instance pub fn new() -> LiveAPI { LiveAPI { client: Client::new(), @@ -19,6 +21,7 @@ impl LiveAPI { } } + /// Create a new instance and login using email pub async fn login_email(email: &str, password: &str) -> Result { let mut instance = LiveAPI::new(); let result = instance.authenticate_email(email, password).await; @@ -29,7 +32,8 @@ impl LiveAPI { return Err(result.err().unwrap()); } } - + + /// Login using email and password pub async fn authenticate_email(&mut self, email: &str, password: &str) -> Result { let url = Url::parse(AUTHENTICATION_DOMAIN) .unwrap() @@ -51,7 +55,10 @@ impl LiveAPI { } Err(result.err().unwrap()) } + + // TODO username authentication + /// Retrieve lobby information for all active Cardlife servers pub async fn lobbies(&self) -> Result { let url = Url::parse(LOBBY_DOMAIN) .unwrap() @@ -70,4 +77,4 @@ impl LiveAPI { } Err(result.err().unwrap()) } -} \ No newline at end of file +} diff --git a/src/cardlife/live_json.rs b/src/cardlife/live_json.rs index 91cef35..98547f6 100644 --- a/src/cardlife/live_json.rs +++ b/src/cardlife/live_json.rs @@ -8,24 +8,36 @@ pub(crate) struct AuthenticationPayload { pub password: String, } +/// Authentication information #[derive(Deserialize, Serialize, Clone)] pub struct AuthenticationInfo { + /// User's public ID #[serde(rename = "PublicId")] pub public_id: String, + /// User's email address #[serde(rename = "EmailAddress")] pub email_address: String, + /// Account display name #[serde(rename = "DisplayName")] pub display_name: String, + /// Account purchases (???) #[serde(rename = "Purchases")] purchases: Vec, // ??? + /// Account flags (dev, admin, etc.???) #[serde(rename = "Flags")] flags: Vec, // ??? + /// Is confirmed account? #[serde(rename = "Confirmed")] pub confirmed: bool, + /// Temporary account token #[serde(rename = "Token")] pub token: String, + /// Steam ID + /// + /// Since Steam users cannot be authenticated using this lib, this will always be blank or None #[serde(rename = "SteamId")] steam_id: Option, // ??? + /// User ID #[serde(rename = "ID")] pub id: usize, } @@ -42,38 +54,54 @@ pub(crate) struct LobbyPayload { pub public_id: String, } +/// Lobby information for available Cardlife servers #[derive(Deserialize, Serialize, Clone)] pub struct LobbyInfo { #[serde(rename = "Games")] + /// Available servers' information pub games: Vec, } +/// Server information for a single Cardlife server #[derive(Deserialize, Serialize, Clone)] pub struct LiveGameInfo { + /// Server game ID #[serde(rename = "Id")] pub id: usize, + /// World name #[serde(rename = "WorldName")] pub world_name: String, + /// Max players #[serde(rename = "MaxPlayers")] pub max_players: usize, + /// Current player count #[serde(rename = "CurrentPlayers")] pub current_players: usize, + /// Server version #[serde(rename = "GameVersion")] pub game_version: String, + /// Ping latency #[serde(rename = "Ping")] pub ping: usize, + /// Account has already joined this server? #[serde(rename = "HasPlayed")] pub has_played: bool, + /// Server is password protected? #[serde(rename = "HasPassword")] pub has_password: bool, + /// PvP is enabled on this server? #[serde(rename = "IsPvp")] pub is_pvp: bool, + /// EasyAntiCheat is enabled on this server? #[serde(rename = "IsAntiCheatEnabled")] pub is_anticheat_enabled: bool, + /// Official server? #[serde(rename = "IsOfficial")] pub is_official: bool, + /// Mods installed on this server #[serde(rename = "ModInfo")] pub mod_info: String, + /// Server region #[serde(rename = "Region")] pub region: String, } @@ -82,4 +110,4 @@ impl std::string::ToString for LiveGameInfo { fn to_string(&self) -> String { format!("{} ({}):{}/{}", self.world_name, self.id, self.current_players, self.max_players) } -} \ No newline at end of file +} diff --git a/src/cardlife/mod.rs b/src/cardlife/mod.rs index 05545a9..897e6dd 100644 --- a/src/cardlife/mod.rs +++ b/src/cardlife/mod.rs @@ -1,3 +1,6 @@ +//! Cardlife vanilla and modded (CLre) APIs (WIP) +//! LiveAPI and CLreServer are mostly complete, but some other APIs are missing. + mod client; mod server; diff --git a/src/cardlife/server.rs b/src/cardlife/server.rs index 81b8cbb..af50a8f 100644 --- a/src/cardlife/server.rs +++ b/src/cardlife/server.rs @@ -2,12 +2,14 @@ use reqwest::{Client, IntoUrl, Error}; use url::{Origin, Url}; use crate::cardlife::{GameInfo, StatusInfo}; +/// CLre_server web server API implemenation pub struct CLreServer { client: Client, addr: Url, } impl CLreServer { + /// Create a new instance pub fn new(url: U) -> Result { let url_result = url.into_url(); if let Ok(uri) = url_result { @@ -25,6 +27,7 @@ impl CLreServer { Err(()) } + /// Retrieve the current game info pub async fn game_info(&self) -> Result { let response = self.client.get(self.addr.join("/c/game.json").unwrap()) .send().await; @@ -34,6 +37,7 @@ impl CLreServer { Err(response.err().unwrap()) } + /// Retrieve CLre_server information pub async fn status_info(&self) -> Result { let response = self.client.get(self.addr.join("/status.json").unwrap()) .send().await; diff --git a/src/cardlife/server_json.rs b/src/cardlife/server_json.rs index bc3f046..1bfaf15 100644 --- a/src/cardlife/server_json.rs +++ b/src/cardlife/server_json.rs @@ -1,23 +1,33 @@ use serde::{Deserialize, Serialize}; +/// CLre game info #[derive(Deserialize, Serialize, Clone)] pub struct GameInfo { + /// Max allowed player count #[serde(rename = "MaxPlayers")] pub max_players: usize, + /// Server world ID #[serde(rename = "GameId")] pub game_id: usize, + /// Server world GUID #[serde(rename = "GameGuid")] pub game_guid: String, + /// World name #[serde(rename = "WorldName")] pub world_name: String, + /// Game host type #[serde(rename = "GameHostType")] pub game_host_type: usize, + /// Is PvP enabled? #[serde(rename = "PvP")] pub pvp: bool, + /// Photon server region override #[serde(rename = "PhotonRegionOverride")] pub photon_region_override: String, + /// Server password #[serde(rename = "ServerPassword")] pub server_password: String, + /// Admin priviledge password #[serde(rename = "AdminPassword")] pub admin_password: String, } @@ -28,30 +38,42 @@ impl std::string::ToString for GameInfo { } } +/// CLre_server status information #[derive(Deserialize, Serialize, Clone)] pub struct StatusInfo { + /// Maximum player count #[serde(rename = "PlayersMax")] pub max_players: usize, + /// Current player count #[serde(rename = "PlayerCount")] pub player_count: usize, + /// Server status (enum as string) #[serde(rename = "Status")] pub status: String, + /// Information on all online players in this server #[serde(rename = "OnlinePlayers")] pub online_players: Vec } +/// A single online player's information #[derive(Deserialize, Serialize, Clone)] pub struct PlayerStatusInfo { + /// Player public ID #[serde(rename = "id")] pub id: String, + /// Player name #[serde(rename = "name")] pub name: String, + /// Is the player a developer? #[serde(rename = "isDev")] pub is_dev: bool, + /// Player's location on x-axis #[serde(rename = "x")] pub x: f32, + /// Player's location on y-axis #[serde(rename = "y")] pub y: f32, + /// Player's location on z-axis #[serde(rename = "z")] pub z: f32, } diff --git a/src/cardlife_simple/mod.rs b/src/cardlife_simple/mod.rs index 70b786d..165ab2c 100644 --- a/src/cardlife_simple/mod.rs +++ b/src/cardlife_simple/mod.rs @@ -1 +1,3 @@ +//! Simple, blocking Cardlife API (WIP) +//! Nothing is here yet, sorry! // TODO diff --git a/src/lib.rs b/src/lib.rs index f106cf7..bc7406d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,8 @@ +//! An unofficial collection of APIs used in Robocraft and Cardlife. +//! +//! This crate is WIP, but the available APIs are tested and very usable. +#![warn(missing_docs)] + pub mod cardlife; pub mod robocraft; #[cfg(feature = "simple")] diff --git a/src/robocraft/auth.rs b/src/robocraft/auth.rs index aece03d..cb0463e 100644 --- a/src/robocraft/auth.rs +++ b/src/robocraft/auth.rs @@ -1,10 +1,13 @@ use crate::robocraft::{DEFAULT_TOKEN}; +/// Token generator for authenticated API endpoints pub trait ITokenProvider { + /// Retrieve the token to use fn token(&self) -> Result; } +/// Token provider which uses DEFAULT_TOKEN pub struct DefaultTokenProvider { } diff --git a/src/robocraft/cubes.rs b/src/robocraft/cubes.rs index a6c3717..46b8816 100644 --- a/src/robocraft/cubes.rs +++ b/src/robocraft/cubes.rs @@ -3,13 +3,22 @@ use std::io::Read; // TODO(maybe) parse iteratively instead of one-shot +/// A collection of cube data +/// +/// This holds all data parsed from cube_data and colour_data. +/// Individual Cube structs can be iterated through. #[derive(Clone)] pub struct Cubes { + /// Parsed cube count (the first 32 bits of data parsed to `u32`) pub provided_len: u32, cubes: Vec, } impl Cubes { + /// Process the raw bytes containing block data from a Robocraft CRF bot + /// + /// `cube_data` and `colour_data` correspond to the `cube_data` and `colour_data` fields of FactoryRobotGetInfo. + /// In generally, you should use `Cubes::from(data)` instead of this lower-level function. pub fn parse(cube_data: &mut Vec, colour_data: &mut Vec) -> Result { // read first 4 bytes (cube count) from both arrays and make sure they match let mut cube_buf = [0; 4]; @@ -66,6 +75,13 @@ impl Cubes { }) } + /// Dump the raw bytes containing block data for a Robocraft bot. + /// + /// The first tuple item is cube data, and the second item is colour data. + /// Use this to write a modified robot to file. + /// This is the inverse of `Cubes::parse(...)`. + /// + /// I'm not sure what this would actually be useful for... pub fn dump(&self) -> (Vec, Vec) { let mut cube_buf = Vec::new(); let mut colour_buf = Vec::new(); @@ -78,6 +94,10 @@ impl Cubes { (cube_buf, colour_buf) } + /// Get the actual amount of cubes. + /// + /// This differs from `provided_len` by being the amount of cubes parsed (successfully), instead of something parsed from block data. + /// For any valid robot data, `data.provided_len == data.len()`. pub fn len(&self) -> usize { self.cubes.len() } @@ -93,13 +113,22 @@ impl<'a> std::iter::IntoIterator for &'a Cubes { } } +/// A single block in a Robocraft robot. +/// +/// From the front of a Robocraft garage bay, looking at the back, all positions are measured from the back bottom right corner. #[derive(Copy, Clone)] pub struct Cube { + /// The cube id pub id: u32, + /// The cube's x position (left to right) pub x: u8, // left to right + /// The cube's y position (bottom to top) pub y: u8, // bottom to top + /// The cube's z position (back to front) pub z: u8, // back to front + /// The cube's orientation pub orientation: u8, + /// The cube's colour, one of the 24 possible colours in Robocraft pub colour: u8, } @@ -143,11 +172,17 @@ impl Cube { Ok(4) } + /// Dump the raw cube data as used in the Robocraft CRF. + /// + /// This is useless by itself, use `Cubes.dump()` for a valid robot. pub fn dump_cube_data(&self) -> [u8; 8] { let id_buf = self.id.to_le_bytes(); [id_buf[0], id_buf[1], id_buf[2], id_buf[3], self.x, self.y, self.z, self.orientation] } + /// Dump the raw colour data as used in the Robocraft CRF. + /// + /// This is useless by itself, use `Cubes.dump()` for a valid robot. pub fn dump_colour_data(&self) -> [u8; 4] { [self.colour, self.x, self.y, self.z] } diff --git a/src/robocraft/factory.rs b/src/robocraft/factory.rs index 231d004..8b5539a 100644 --- a/src/robocraft/factory.rs +++ b/src/robocraft/factory.rs @@ -4,14 +4,17 @@ use url::{Url}; use crate::robocraft::{ITokenProvider, DefaultTokenProvider, FactoryInfo, FactorySearchBuilder, RoboShopItemsInfo, FactoryRobotGetInfo}; use crate::robocraft::factory_json::ListPayload; +/// Community Factory Robot root URL pub const FACTORY_DOMAIN: &str = "https://factory.robocraftgame.com/"; +/// CRF API implementation pub struct FactoryAPI { client: Client, token: Box, } impl FactoryAPI { + /// Create a new instance, using `DefaultTokenProvider`. pub fn new() -> FactoryAPI { FactoryAPI { client: Client::new(), @@ -19,6 +22,7 @@ impl FactoryAPI { } } + /// Create a new instance using the provided token provider. pub fn with_auth(token_provider: Box) -> FactoryAPI { FactoryAPI { client: Client::new(), @@ -26,6 +30,9 @@ impl FactoryAPI { } } + /// Retrieve CRF robots on the main page. + /// + /// For searching, use `list_builder()` instead. pub async fn list(&self) -> Result, Error> { let url = Url::parse(FACTORY_DOMAIN) .unwrap() @@ -44,6 +51,9 @@ impl FactoryAPI { Err(result.err().unwrap()) } + /// Build a CRF search query. + /// + /// This follows the builder pattern, so functions can be chained. pub fn list_builder(&self) -> FactorySearchBuilder { let url = Url::parse(FACTORY_DOMAIN) .unwrap() @@ -57,6 +67,9 @@ impl FactoryAPI { FactorySearchBuilder::new(request_builder, token_opt) } + /// Get in-depth info on a CRF robot. + /// + /// `item_id` corresponds to the field with the same name for FactoryRobotGetInfo and FactoryRobotListInfo. pub async fn get(&self, item_id: usize) -> Result, Error> { let url = Url::parse(FACTORY_DOMAIN) .unwrap() diff --git a/src/robocraft/factory_json.rs b/src/robocraft/factory_json.rs index 8d59bb2..ab87b48 100644 --- a/src/robocraft/factory_json.rs +++ b/src/robocraft/factory_json.rs @@ -82,60 +82,90 @@ impl ListPayload { } } + +/// Standard factory response format. #[derive(Deserialize, Serialize, Clone)] pub struct FactoryInfo { #[serde(rename = "response")] + /// The response data pub response: T, #[serde(rename = "statusCode")] + /// HTTP status code for the query pub status_code: usize, } +/// Collection of robots in response to a list query. #[derive(Deserialize, Serialize, Clone)] pub struct RoboShopItemsInfo { #[serde(rename = "roboShopItems")] + /// Robot items pub roboshop_items: Vec, } +/// Information about a single robot in response to a list query. +/// +/// This does not include robot block data since it is not returned by this API endpoint. +/// Use `FactoryAPI.get(data.item_id)` to retrieve all info for a single robot. #[derive(Deserialize, Serialize, Clone)] pub struct FactoryRobotListInfo { + /// Item ID #[serde(rename = "itemId")] pub item_id: usize, + /// Robot name #[serde(rename = "itemName")] pub item_name: String, + /// Robot description #[serde(rename = "itemDescription")] pub item_description: String, + /// Thumbnail URL, as displayed to preview the robot. #[serde(rename = "thumbnail")] pub thumbnail: String, // url + /// Robot author's username or UUID #[serde(rename = "addedBy")] pub added_by: String, + /// Robot author's display name #[serde(rename = "addedByDisplayName")] pub added_by_display_name: String, + /// Date added, in standard ISO format #[serde(rename = "addedDate")] pub added_date: String, // ISO date + /// Expiry date, in standard ISO format #[serde(rename = "expiryDate")] pub expiry_date: String, // ISO date + /// Robot CPU value #[serde(rename = "cpu")] pub cpu: usize, + /// Robot RR #[serde(rename = "totalRobotRanking")] pub total_robot_ranking: usize, + /// Robot's rentals #[serde(rename = "rentCount")] pub rent_count: usize, + /// Robot's purchases #[serde(rename = "buyCount")] pub buy_count: usize, + /// Is this robot buyable? (probably yes, unless you're a mod/admin or missing parts) #[serde(rename = "buyable")] pub buyable: bool, + /// Removed date, in standard ISO format (probably None, unless authenticated as a mod/admin) #[serde(rename = "removedDate")] pub removed_date: Option, + /// Author ban date, in standard ISO format (probable None) #[serde(rename = "banDate")] pub ban_date: Option, + /// Is this robot featured? #[serde(rename = "featured")] pub featured: bool, + /// CRF Banner message #[serde(rename = "bannerMessage")] pub banner_message: Option, + /// Robot's combat rating, out of 5 #[serde(rename = "combatRating")] pub combat_rating: f32, + /// Robot's cosmetic rating, out of 5 #[serde(rename = "cosmeticRating")] pub cosmetic_rating: f32, + /// Robot's count of (some?) blocks it uses #[serde(rename = "cubeAmounts")] pub cube_amounts: String, // JSON as str } @@ -147,51 +177,74 @@ impl std::string::ToString for FactoryRobotListInfo { } // get/ endpoint - +/// Complete information about a single robot in response to a get query. +/// Please refer to FactoryRobotListInfo for more in-depth documentation of fields. #[derive(Deserialize, Serialize, Clone)] pub struct FactoryRobotGetInfo { + /// Item ID #[serde(rename = "id")] pub item_id: usize, + /// Robot name #[serde(rename = "name")] pub item_name: String, + /// Robot description #[serde(rename = "description")] pub item_description: String, + /// Robot thumbnail URL #[serde(rename = "thumbnail")] pub thumbnail: String, // url + /// Robot author's username or UUID #[serde(rename = "addedBy")] pub added_by: String, + /// Robot author's display name #[serde(rename = "addedByDisplayName")] pub added_by_display_name: String, + /// ISO date added #[serde(rename = "addedDate")] pub added_date: String, // ISO date + /// ISO date expiring #[serde(rename = "expiryDate")] pub expiry_date: String, // ISO date + /// CPU #[serde(rename = "cpu")] pub cpu: usize, + /// RR #[serde(rename = "totalRobotRanking")] pub total_robot_ranking: usize, + /// Robot rent count #[serde(rename = "rentCount")] pub rent_count: usize, + /// Robot buy count #[serde(rename = "buyCount")] pub buy_count: usize, + /// Robot is buyable? #[serde(rename = "buyable")] pub buyable: bool, + /// ISO date removed #[serde(rename = "removedDate")] pub removed_date: Option, + /// ISO date banned #[serde(rename = "banDate")] pub ban_date: Option, + /// Robot is featured? #[serde(rename = "featured")] pub featured: bool, + /// CRF banner message #[serde(rename = "bannerMessage")] pub banner_message: Option, + /// Robot's combat rating, out of 5 #[serde(rename = "combatRating")] pub combat_rating: f32, + /// Robot's cosmetic rating, out of 5 #[serde(rename = "cosmeticRating")] pub cosmetic_rating: f32, + /// Robot block cube and position data #[serde(rename = "cubeData")] pub cube_data: String, + /// Robot block colour data #[serde(rename = "colourData")] pub colour_data: String, + /// Cube counts #[serde(rename = "cubeAmounts")] pub cube_amounts: String, // JSON as str } diff --git a/src/robocraft/factory_request_builder.rs b/src/robocraft/factory_request_builder.rs index a8d4fdd..a84a0ba 100644 --- a/src/robocraft/factory_request_builder.rs +++ b/src/robocraft/factory_request_builder.rs @@ -4,57 +4,93 @@ use num_enum::{TryFromPrimitive}; use crate::robocraft::{FactoryInfo, RoboShopItemsInfo}; use crate::robocraft::factory_json::ListPayload; +/// Factory list response ordering #[derive(Eq, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum FactoryOrderType { + /// Suggested (default) Suggested = 0, + /// Combat rating (decreasing?) CombatRating = 1, + /// Cosmetic rating (decreasing?) CosmeticRating = 2, + /// Date added (oldest first?) Added = 3, + /// CPU value (decreasing?) CPU = 4, + /// Purchases (decreasing) MostBought = 5, } +/// Robot movement categories #[derive(Eq, PartialEq, TryFromPrimitive)] #[repr(u32)] pub enum FactoryMovementType { + /// Vrooooom Wheels = 100000, + /// Woooooosh Hovers = 200000, + /// Fwoooosh Aerofoils=300000, + /// Also fwoooosh (but actually a different movement type, trust me) Thrusters=400000, + /// Also also fwoooosh (but also a different movement type) Rudders=500000, + /// Ewwww InsectLegs=600000, + /// Mechs are cool MechLegs=700000, + /// Skis and turning skis Skis=800000, + /// All tank treads TankTreads=900000, + /// Wrrrrrrrrrr Rotors=1000000, + /// Mech legs, but faster Sprinters=1100000, + /// Wrrrrr but for Fwoooosh Propellers=1200000 } +/// Robot weapon categories #[derive(Eq, PartialEq, TryFromPrimitive)] #[repr(u32)] pub enum FactoryWeaponType { + /// All laser weapons (aka Lasor, SMG) Laser=10000000, + /// All plasma launcher weapons PlasmaLauncher=20000000, + /// Mortar GyroMortar=25000000, + /// All rails RailCannon=30000000, + /// All healing weapons NanoDisruptor=40000000, + /// All tesla blade melee weapons TeslaBlade=50000000, + /// All aeroflak weapons AeroflakCannon=60000000, + /// All shotgun weapons IonCannon=65000000, + /// Lol ProtoSeeker=70100000, + /// All chain weapons ChainShredder=75000000, } +/// Text field search modes #[derive(Eq, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum FactoryTextSearchType { + /// Search players and robot names All=0, + /// Search players only Player=1, + /// Search robot names only Name=2, } +/// Factory API list query builder pub struct FactorySearchBuilder { reqwest_builder: RequestBuilder, payload: ListPayload, @@ -70,16 +106,19 @@ impl FactorySearchBuilder { } } + /// Retrieve list page page_number pub fn page(mut self, page_number: isize) -> Self { self.payload.page = page_number; self } + /// Retrieve page_size items per page (this is unreliable) pub fn items_per_page(mut self, page_size: isize) -> Self { self.payload.page_size = page_size; self } + /// Order list by order_type pub fn order(mut self, order_type: FactoryOrderType) -> Self { self.payload.order = order_type as isize; self @@ -93,6 +132,10 @@ impl FactorySearchBuilder { } */ + /// Retrieve items with movement type. + /// + /// Multiple calls to this function will cause logical OR behaviour. + /// e.g. results will contain robots with Wheels OR Aerofoils (or both). 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); @@ -103,12 +146,17 @@ impl FactorySearchBuilder { self } + /// Override allowed movement types pub fn movement_raw(mut self, filter: String) -> Self { self.payload.movement_filter = filter.clone(); self.payload.movement_category_filter = filter.clone(); self } + /// Retrieve items with weapon type. + /// + /// Multiple calls to this function will cause logical OR behaviour. + /// e.g. results will contain robots with ChainShredder OR GyroMortar (or both). 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); @@ -119,64 +167,81 @@ impl FactorySearchBuilder { self } + /// Override allowed weapon types pub fn weapon_raw(mut self, filter: String) -> Self { self.payload.weapon_filter = filter.clone(); self.payload.weapon_category_filter = filter.clone(); self } + /// Retrieve items within the specified CPU min and max values pub fn cpu_range(mut self, min: isize, max: isize) -> Self { self.payload.minimum_cpu = min; self.payload.maximum_cpu = max; self } + /// Retrieve items with CPU no lower than min + /// overrides cpu_range() pub fn min_cpu(mut self, min: isize) -> Self { self.payload.minimum_cpu = min; self } + /// Retrieve items with CPU no greater than max + /// overrides cpu_range() pub fn max_cpu(mut self, max: isize) -> Self { self.payload.maximum_cpu = max; self } + /// Retrieve items with any minimum CPU pub fn no_minimum_cpu(mut self) -> Self { self.payload.minimum_cpu = -1; self } + /// Retrieve items with any maximum CPU pub fn no_maximum_cpu(mut self) -> Self { self.payload.maximum_cpu = -1; self } + /// Retrieve items which match text pub fn text(mut self, t: String) -> Self { self.payload.text_filter = t; self } + /// Text filter searches search_type 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 + /// Retrieve only items which are buyable for current account? (default: false) + /// Buyable means that the account owns all blocks required. + /// This will cause an error when using DEFAULT_TOKEN pub fn buyable(mut self, b: bool) -> Self { self.payload.buyable = b; self } + /// Retrieve items with featured robot at start? (default: false) pub fn prepend_featured(mut self, b: bool) -> Self { self.payload.prepend_featured_robot = b; self } + /// Retrieve default robot list? (default: false) + /// The default page is the CRF landing page (I think?) pub fn default_page(mut self, b: bool) -> Self { self.payload.default_page = b; self } + /// Execute list query pub async fn send(mut self) -> Result, Error> { self.reqwest_builder = self.reqwest_builder.json(&self.payload); if let Some(token) = self.token.clone() { diff --git a/src/robocraft/mod.rs b/src/robocraft/mod.rs index 1e548bb..aaa1ba6 100644 --- a/src/robocraft/mod.rs +++ b/src/robocraft/mod.rs @@ -1,3 +1,6 @@ +//! Robocraft APIs for the CRF and leaderboards (WIP) +//! FactoryAPI is mostly complete, but many other APIs are missing. + mod factory; mod factory_json; mod factory_request_builder; @@ -13,4 +16,5 @@ pub use self::cubes::{Cube, Cubes}; mod auth; pub use self::auth::{ITokenProvider, DefaultTokenProvider}; +/// Token defined in a javascript file from Freejam which never expires pub const DEFAULT_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJQdWJsaWNJZCI6IjEyMyIsIkRpc3BsYXlOYW1lIjoiVGVzdCIsIlJvYm9jcmFmdE5hbWUiOiJGYWtlQ1JGVXNlciIsIkZsYWdzIjpbXSwiaXNzIjoiRnJlZWphbSIsInN1YiI6IldlYiIsImlhdCI6MTU0NTIyMzczMiwiZXhwIjoyNTQ1MjIzNzkyfQ.ralLmxdMK9rVKPZxGng8luRIdbTflJ4YMJcd25dKlqg"; diff --git a/src/robocraft_simple/factory.rs b/src/robocraft_simple/factory.rs index b7450ea..0925d8e 100644 --- a/src/robocraft_simple/factory.rs +++ b/src/robocraft_simple/factory.rs @@ -6,12 +6,17 @@ use crate::robocraft::{ITokenProvider, DefaultTokenProvider, FACTORY_DOMAIN, Fac use crate::robocraft::{ListPayload}; use crate::robocraft_simple::FactorySearchBuilder; +/// Simpler CRF API implementation. +/// Refer to libfj::robocraft::FactoryAPI for in-depth documentation. +/// The only API difference is that this API is blocking (i.e. no async). +/// This version also works with Wine and Proton since it does not rely on tokio. pub struct FactoryAPI { client: Agent, token: Box, } impl FactoryAPI { + /// Create a new instance using `DefaultTokenProvider`. pub fn new() -> FactoryAPI { FactoryAPI { client: Agent::new(), @@ -19,6 +24,7 @@ impl FactoryAPI { } } + /// List CRF robots pub fn list(&self) -> Result, Error> { let url = Url::parse(FACTORY_DOMAIN) .unwrap() @@ -41,6 +47,7 @@ impl FactoryAPI { Err(result.err().unwrap()) } + /// Build a list query pub fn list_builder(&self) -> FactorySearchBuilder { let url = Url::parse(FACTORY_DOMAIN) .unwrap() @@ -54,6 +61,7 @@ impl FactoryAPI { FactorySearchBuilder::new(request_builder, token_opt) } + /// Get complete information on a robot. pub fn get(&self, item_id: usize) -> Result, Error> { let url = Url::parse(FACTORY_DOMAIN) .unwrap() diff --git a/src/robocraft_simple/factory_request_builder.rs b/src/robocraft_simple/factory_request_builder.rs index b79e640..0f1f9c2 100644 --- a/src/robocraft_simple/factory_request_builder.rs +++ b/src/robocraft_simple/factory_request_builder.rs @@ -3,6 +3,9 @@ use ureq::{Request, Response, Error}; use crate::robocraft::{FactoryInfo, RoboShopItemsInfo, FactoryTextSearchType, FactoryWeaponType, FactoryMovementType, FactoryOrderType}; use crate::robocraft::{ListPayload}; +/// Factory API list query builder. +/// This is the simpler, blocking equivalent of libfj::robocraft::FactorySearchBuilder. +/// Please refer to that struct's documentation for details. #[derive(Clone)] pub struct FactorySearchBuilder { reqwest_builder: Request, @@ -19,16 +22,19 @@ impl FactorySearchBuilder { } } + /// Set page number pub fn page(mut self, page_number: isize) -> Self { self.payload.page = page_number; self } + /// Set page size pub fn items_per_page(mut self, page_size: isize) -> Self { self.payload.page_size = page_size; self } + /// Set results ordering pub fn order(mut self, order_type: FactoryOrderType) -> Self { self.payload.order = order_type as isize; self @@ -42,12 +48,14 @@ impl FactorySearchBuilder { } */ + /// Override movement filter pub fn movement_raw(mut self, filter: String) -> Self { self.payload.movement_filter = filter.clone(); self.payload.movement_category_filter = filter.clone(); self } + /// Add allowed movement type 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); @@ -58,12 +66,14 @@ impl FactorySearchBuilder { self } + /// Override weapon filter pub fn weapon_raw(mut self, filter: String) -> Self { self.payload.weapon_filter = filter.clone(); self.payload.weapon_category_filter = filter.clone(); self } + /// Add allowed weapon type 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); @@ -74,58 +84,69 @@ impl FactorySearchBuilder { self } + /// Set CPU value min and max pub fn cpu_range(mut self, min: isize, max: isize) -> Self { self.payload.minimum_cpu = min; self.payload.maximum_cpu = max; self } + /// Set CPU minimum value pub fn min_cpu(mut self, min: isize) -> Self { self.payload.minimum_cpu = min; self } + /// Set CPU maximum value pub fn max_cpu(mut self, max: isize) -> Self { self.payload.maximum_cpu = max; self } + /// Removem minimum CPU limit pub fn no_minimum_cpu(mut self) -> Self { self.payload.minimum_cpu = -1; self } + /// Remove maximum CPU limit pub fn no_maximum_cpu(mut self) -> Self { self.payload.maximum_cpu = -1; self } + /// Set text filter pub fn text(mut self, t: String) -> Self { self.payload.text_filter = t; self } + /// Set fields which text filter searches 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 + /// Only search robots which can be bought by the current account? pub fn buyable(mut self, b: bool) -> Self { self.payload.buyable = b; self } + /// Prepend a featured robot to the response? pub fn prepend_featured(mut self, b: bool) -> Self { self.payload.prepend_featured_robot = b; self } + /// Retrieve default CRF page? pub fn default_page(mut self, b: bool) -> Self { self.payload.default_page = b; self } + /// Execute list query pub fn send(mut self) -> Result, Error> { self.reqwest_builder = self.reqwest_builder; if let Some(token) = self.token.clone() { diff --git a/src/robocraft_simple/mod.rs b/src/robocraft_simple/mod.rs index c8ce723..eaf1a4e 100644 --- a/src/robocraft_simple/mod.rs +++ b/src/robocraft_simple/mod.rs @@ -1,3 +1,5 @@ +//! Simple, blocking Robocraft API + mod factory; mod factory_request_builder; pub use factory::{FactoryAPI};