Browse Source

Document *everything*

tags/v0.5.1
NGnius 3 years ago
parent
commit
c6a5ce2272
17 changed files with 280 additions and 5 deletions
  1. +1
    -1
      Cargo.toml
  2. +9
    -2
      src/cardlife/live.rs
  3. +29
    -1
      src/cardlife/live_json.rs
  4. +3
    -0
      src/cardlife/mod.rs
  5. +4
    -0
      src/cardlife/server.rs
  6. +22
    -0
      src/cardlife/server_json.rs
  7. +2
    -0
      src/cardlife_simple/mod.rs
  8. +5
    -0
      src/lib.rs
  9. +3
    -0
      src/robocraft/auth.rs
  10. +35
    -0
      src/robocraft/cubes.rs
  11. +13
    -0
      src/robocraft/factory.rs
  12. +54
    -1
      src/robocraft/factory_json.rs
  13. +65
    -0
      src/robocraft/factory_request_builder.rs
  14. +4
    -0
      src/robocraft/mod.rs
  15. +8
    -0
      src/robocraft_simple/factory.rs
  16. +21
    -0
      src/robocraft_simple/factory_request_builder.rs
  17. +2
    -0
      src/robocraft_simple/mod.rs

+ 1
- 1
Cargo.toml View File

@@ -1,6 +1,6 @@
[package]
name = "libfj"
version = "0.1.0"
version = "0.4.1"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
edition = "2018"
description = "An unofficial collection of APIs used in FreeJam games and mods"


+ 9
- 2
src/cardlife/live.rs View File

@@ -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<AuthenticationInfo>,
}

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<LiveAPI, Error> {
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<AuthenticationInfo, Error> {
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<LobbyInfo, Error> {
let url = Url::parse(LOBBY_DOMAIN)
.unwrap()
@@ -70,4 +77,4 @@ impl LiveAPI {
}
Err(result.err().unwrap())
}
}
}

+ 29
- 1
src/cardlife/live_json.rs View File

@@ -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<String>, // ???
/// Account flags (dev, admin, etc.???)
#[serde(rename = "Flags")]
flags: Vec<String>, // ???
/// 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<String>, // ???
/// 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<LiveGameInfo>,
}

/// 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)
}
}
}

+ 3
- 0
src/cardlife/mod.rs View File

@@ -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;


+ 4
- 0
src/cardlife/server.rs View File

@@ -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<U: IntoUrl>(url: U) -> Result<CLreServer, ()> {
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<GameInfo, Error> {
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<StatusInfo, Error> {
let response = self.client.get(self.addr.join("/status.json").unwrap())
.send().await;


+ 22
- 0
src/cardlife/server_json.rs View File

@@ -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<PlayerStatusInfo>
}

/// 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,
}


+ 2
- 0
src/cardlife_simple/mod.rs View File

@@ -1 +1,3 @@
//! Simple, blocking Cardlife API (WIP)
//! Nothing is here yet, sorry!
// TODO

+ 5
- 0
src/lib.rs View File

@@ -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")]


+ 3
- 0
src/robocraft/auth.rs View File

@@ -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<String, ()>;
}

/// Token provider which uses DEFAULT_TOKEN
pub struct DefaultTokenProvider {
}



+ 35
- 0
src/robocraft/cubes.rs View File

@@ -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<Cube>,
}

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<FactoryRobotGetInfo>(data)` instead of this lower-level function.
pub fn parse(cube_data: &mut Vec<u8>, colour_data: &mut Vec<u8>) -> Result<Self, ()> {
// 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<u8>, Vec<u8>) {
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]
}


+ 13
- 0
src/robocraft/factory.rs View File

@@ -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<dyn ITokenProvider>,
}

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<dyn ITokenProvider>) -> 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<FactoryInfo<RoboShopItemsInfo>, 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<FactoryInfo<FactoryRobotGetInfo>, Error> {
let url = Url::parse(FACTORY_DOMAIN)
.unwrap()


+ 54
- 1
src/robocraft/factory_json.rs View File

@@ -82,60 +82,90 @@ impl ListPayload {
}
}


/// Standard factory response format.
#[derive(Deserialize, Serialize, Clone)]
pub struct FactoryInfo<T> {
#[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<FactoryRobotListInfo>,
}

/// 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<String>,
/// Author ban date, in standard ISO format (probable None)
#[serde(rename = "banDate")]
pub ban_date: Option<String>,
/// Is this robot featured?
#[serde(rename = "featured")]
pub featured: bool,
/// CRF Banner message
#[serde(rename = "bannerMessage")]
pub banner_message: Option<String>,
/// 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/<item_id> 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<String>,
/// ISO date banned
#[serde(rename = "banDate")]
pub ban_date: Option<String>,
/// Robot is featured?
#[serde(rename = "featured")]
pub featured: bool,
/// CRF banner message
#[serde(rename = "bannerMessage")]
pub banner_message: Option<String>,
/// 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
}


+ 65
- 0
src/robocraft/factory_request_builder.rs View File

@@ -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<FactoryInfo<RoboShopItemsInfo>, Error> {
self.reqwest_builder = self.reqwest_builder.json(&self.payload);
if let Some(token) = self.token.clone() {


+ 4
- 0
src/robocraft/mod.rs View File

@@ -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";

+ 8
- 0
src/robocraft_simple/factory.rs View File

@@ -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<dyn ITokenProvider>,
}

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<FactoryInfo<RoboShopItemsInfo>, 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<FactoryInfo<FactoryRobotGetInfo>, Error> {
let url = Url::parse(FACTORY_DOMAIN)
.unwrap()


+ 21
- 0
src/robocraft_simple/factory_request_builder.rs View File

@@ -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<FactoryInfo<RoboShopItemsInfo>, Error> {
self.reqwest_builder = self.reqwest_builder;
if let Some(token) = self.token.clone() {


+ 2
- 0
src/robocraft_simple/mod.rs View File

@@ -1,3 +1,5 @@
//! Simple, blocking Robocraft API

mod factory;
mod factory_request_builder;
pub use factory::{FactoryAPI};


Loading…
Cancel
Save