Implement most of the important foundry endpoints
This commit is contained in:
parent
8bd6e2995f
commit
7d60b6fa15
6 changed files with 517 additions and 56 deletions
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "libfj"
|
||||
version = "0.6.1"
|
||||
version = "0.7.0"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "An unofficial collection of APIs used in FreeJam games and mods"
|
||||
|
@ -21,8 +21,6 @@ serde_json = "^1"
|
|||
reqwest = { version = "^0.11", features = ["json"], optional = true}
|
||||
url = "^2.2"
|
||||
ureq = { version = "^2", features = ["json"], optional = true}
|
||||
cookie_store = { version = "0.16", optional = true}
|
||||
cookie = { version = "0.16", optional = true}
|
||||
async-trait = { version = "0.1", optional = true }
|
||||
base64 = "^0.13"
|
||||
num_enum = "^0.5"
|
||||
|
@ -46,4 +44,4 @@ robocraft = ["reqwest", "ureq"]
|
|||
cardlife = ["reqwest"]
|
||||
techblox = ["chrono", "highhash", "half", "libfj_parsable_macro_derive"]
|
||||
convert = ["obj", "genmesh", "cgmath"]
|
||||
robocraft2 = ["reqwest", "reqwest/cookies", "async-trait"]
|
||||
robocraft2 = ["reqwest", "async-trait", "chrono"]
|
||||
|
|
|
@ -1,13 +1,49 @@
|
|||
use std::sync::Mutex;
|
||||
|
||||
use reqwest::{Client, Error};
|
||||
use reqwest::{Client, Error as ReqwestError, Response};
|
||||
use url::{Url};
|
||||
|
||||
use crate::robocraft2::{SearchPayload, SearchResponse, ITokenProvider};
|
||||
use crate::robocraft2::{ITokenProvider, ErrorPayload};
|
||||
use crate::robocraft2::{SearchPayload, SearchResponse, CreateRobotPayload, CreateRobotResponse, FactoryInfoResponse, PublishRobotPayload, PublishRobotResponse, MyRobotsResponse, GetRobotResponse};
|
||||
|
||||
/// Community Factory Robot 2 root URL
|
||||
pub const FACTORY_DOMAIN: &str = "https://factory.production.robocraft2.com";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FactoryError {
|
||||
Protocol(ReqwestError),
|
||||
Response(ErrorPayload),
|
||||
ResponseCode(ReqwestError, u16)
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FactoryError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
Self::Protocol(p) => if let Some(status) = p.status() {
|
||||
write!(f, "HTTP Error {}: {}", status, p)
|
||||
} else {
|
||||
write!(f, "HTTP Error: {}", p)
|
||||
}
|
||||
Self::Response(r) => write!(f, "Factory Error #{}: {}", r.error, r.error_message),
|
||||
Self::ResponseCode(p, s) => write!(f, "HTTP Error {}: {}", s, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FactoryError {}
|
||||
|
||||
async fn handle_json_response<D: for<'a> serde::Deserialize<'a>>(response: Response) -> Result<D, FactoryError> {
|
||||
let status_code: u16 = response.status().into();
|
||||
if status_code > 199 && status_code < 300 {
|
||||
Ok(response.json::<D>().await.map_err(FactoryError::Protocol)?)
|
||||
} else {
|
||||
match response.json::<ErrorPayload>().await {
|
||||
Ok(err) => Err(FactoryError::Response(err)),
|
||||
Err(e) => Err(FactoryError::ResponseCode(e, status_code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CRF API implementation
|
||||
pub struct FactoryAPI {
|
||||
client: Client,
|
||||
|
@ -32,13 +68,12 @@ impl FactoryAPI {
|
|||
}
|
||||
|
||||
/// Retrieve CRF robots on the main page.
|
||||
///
|
||||
/// For searching, use `list_builder()` instead.
|
||||
pub async fn list(&self) -> Result<SearchResponse, Error> {
|
||||
pub async fn list(&self) -> Result<SearchResponse, FactoryError> {
|
||||
self.search(SearchPayload::default()).await
|
||||
}
|
||||
|
||||
pub async fn search(&self, params: SearchPayload) -> Result<SearchResponse, Error> {
|
||||
/// Search for robots on the CRF which meet the provided parameters
|
||||
pub async fn search(&self, params: SearchPayload) -> Result<SearchResponse, FactoryError> {
|
||||
let mut url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join("/v1/foundry/search")
|
||||
|
@ -87,11 +122,136 @@ impl FactoryAPI {
|
|||
}
|
||||
url.query_pairs_mut().append_pair("sortBy", ¶ms.sort_by);
|
||||
url.query_pairs_mut().append_pair("orderBy", ¶ms.order_by);
|
||||
let mut request_builder = self.client.get(url);
|
||||
if let Ok(token) = self.token.lock().unwrap().token().await {
|
||||
request_builder = request_builder.header("Authorization", "Bearer ".to_owned() + &token);
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.get(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
handle_json_response::<SearchResponse>(result).await
|
||||
}
|
||||
|
||||
pub async fn create_robot(&self, robot: CreateRobotPayload) -> Result<CreateRobotResponse, FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join("/v1/foundry/garage")
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.post(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&robot)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
handle_json_response::<CreateRobotResponse>(result).await
|
||||
}
|
||||
|
||||
pub async fn publish_robot(&self, robot: PublishRobotPayload, id: String) -> Result<PublishRobotResponse, FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join(&format!("/v1/foundry/vehicles/{}/publish", id))
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.post(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&robot)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
handle_json_response(result).await
|
||||
}
|
||||
|
||||
pub async fn unpublish_bot(&self, id: String) -> Result<(), FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join(&format!("/v1/foundry/vehicles/{}/unpublish", id))
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.post(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
let status_code = result.status().as_u16();
|
||||
if status_code > 199 && status_code < 300 {
|
||||
Ok(())
|
||||
} else {
|
||||
match result.json::<ErrorPayload>().await {
|
||||
Ok(err) => Err(FactoryError::Response(err)),
|
||||
Err(e) => Err(FactoryError::ResponseCode(e, status_code))
|
||||
}
|
||||
}
|
||||
let result = request_builder.send().await?;
|
||||
result.json::<SearchResponse>().await
|
||||
}
|
||||
|
||||
pub async fn delete_robot(&self, id: String) -> Result<(), FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join(&format!("/v1/foundry/vehicles/{}", id))
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.delete(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.header("Content-Type", "application/json")
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
let status_code = result.status().as_u16();
|
||||
if status_code > 199 && status_code < 300 {
|
||||
Ok(())
|
||||
} else {
|
||||
match result.json::<ErrorPayload>().await {
|
||||
Ok(err) => Err(FactoryError::Response(err)),
|
||||
Err(e) => Err(FactoryError::ResponseCode(e, status_code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn factory_info(&self) -> Result<FactoryInfoResponse, FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join("/v1/foundry/info")
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.get(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
handle_json_response::<FactoryInfoResponse>(result).await
|
||||
}
|
||||
|
||||
pub async fn my_robots(&self) -> Result<MyRobotsResponse, FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join("/v1/foundry/garage")
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.get(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
handle_json_response::<MyRobotsResponse>(result).await
|
||||
}
|
||||
|
||||
pub async fn my_published_robots(&self) -> Result<MyRobotsResponse, FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join("/v1/foundry/published")
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.get(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
handle_json_response::<MyRobotsResponse>(result).await
|
||||
}
|
||||
|
||||
pub async fn get(&self, id: String) -> Result<GetRobotResponse, FactoryError> {
|
||||
let url = Url::parse(FACTORY_DOMAIN)
|
||||
.unwrap()
|
||||
.join(&format!("/v1/foundry/vehicles/{}", id))
|
||||
.unwrap();
|
||||
let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
|
||||
let result = self.client.get(url)
|
||||
.header("Authorization", "Bearer ".to_owned() + &token)
|
||||
.send().await
|
||||
.map_err(FactoryError::Protocol)?;
|
||||
handle_json_response::<GetRobotResponse>(result).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ErrorPayload {
|
||||
#[serde(rename = "error")]
|
||||
pub error: isize,
|
||||
#[serde(rename = "errorMessage")]
|
||||
pub error_message: String,
|
||||
}
|
||||
|
||||
// search endpoint
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
|
@ -119,7 +127,7 @@ pub struct RobotInfo {
|
|||
|
||||
impl std::string::ToString for RobotInfo {
|
||||
fn to_string(&self) -> String {
|
||||
format!("{} by {} ({})", &self.name, &self.creator_name, &self.id)
|
||||
format!("{} ({}) by {} ({})", &self.name, &self.id, &self.creator_name, &self.creator_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,3 +138,109 @@ pub struct RobotPrice {
|
|||
#[serde(rename = "amount")]
|
||||
pub amount: isize,
|
||||
}
|
||||
|
||||
// create robot endpoint
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct CreateRobotPayload {
|
||||
#[serde(rename = "name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "data")]
|
||||
pub data: String, // base64
|
||||
#[serde(rename = "image")]
|
||||
pub image: String, // base64??
|
||||
#[serde(rename = "baseCpu")]
|
||||
pub base_cpu: isize,
|
||||
#[serde(rename = "weaponCpu")]
|
||||
pub weapon_cpu: isize,
|
||||
#[serde(rename = "cosmeticCpu")]
|
||||
pub cosmetic_cpu: isize,
|
||||
#[serde(rename = "clusterCount")]
|
||||
pub cluster_count: isize,
|
||||
#[serde(rename = "blockCounts")]
|
||||
pub block_counts: std::collections::HashMap<usize, usize>,
|
||||
#[serde(rename = "materialsUsed")]
|
||||
pub materials_used: std::collections::HashSet<isize>,
|
||||
#[serde(rename = "minimumOffsetX")]
|
||||
pub minimum_offset_x: f64,
|
||||
#[serde(rename = "minimumOffsetY")]
|
||||
pub minimum_offset_y: f64,
|
||||
#[serde(rename = "minimumOffsetZ")]
|
||||
pub minimum_offset_z: f64,
|
||||
#[serde(rename = "maximumOffsetX")]
|
||||
pub maximum_offset_x: f64,
|
||||
#[serde(rename = "maximumOffsetY")]
|
||||
pub maximum_offset_y: f64,
|
||||
#[serde(rename = "maximumOffsetZ")]
|
||||
pub maximum_offset_z: f64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct CreateRobotResponse {
|
||||
#[serde(rename = "header")]
|
||||
pub header: RobotInfo,
|
||||
}
|
||||
|
||||
// factory info endpoint
|
||||
|
||||
// (no payload -- this is a GET request)
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct FactoryInfoResponse {
|
||||
#[serde(rename = "robotCount")]
|
||||
pub robot_count: isize,
|
||||
#[serde(rename = "robotLimit")]
|
||||
pub robot_limit: isize,
|
||||
#[serde(rename = "publishedRobotCount")]
|
||||
pub published_robot_count: isize,
|
||||
#[serde(rename = "publishedRobotLimit")]
|
||||
pub published_robot_limit: isize,
|
||||
}
|
||||
|
||||
// publish robot endpoint
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct PublishRobotPayload {
|
||||
#[serde(rename = "name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "description")]
|
||||
pub description: String,
|
||||
#[serde(rename = "techPoints")]
|
||||
pub techpoints: isize,
|
||||
#[serde(rename = "bloxCoin")]
|
||||
pub bloxcoin: isize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct PublishRobotResponse {
|
||||
#[serde(rename = "header")]
|
||||
pub header: RobotInfo,
|
||||
#[serde(rename = "data")]
|
||||
pub data: String, // base64
|
||||
}
|
||||
|
||||
// get my robots endpoint
|
||||
|
||||
// (no payload -- this is a GET request)
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct MyRobotsResponse {
|
||||
#[serde(rename = "vehicles")]
|
||||
pub vehicles: Vec<RobotInfo>,
|
||||
}
|
||||
|
||||
// get robot endpoint
|
||||
|
||||
// (no payload -- this is a GET request)
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct GetRobotResponse {
|
||||
#[serde(rename = "header")]
|
||||
pub header: RobotInfo,
|
||||
#[serde(rename = "data")]
|
||||
pub data: String, // base64
|
||||
#[serde(rename = "description")]
|
||||
pub description: String,
|
||||
#[serde(rename = "created")]
|
||||
pub created: String, // date
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
//! Subject to change and breakages as RC2 is still in an early development stage.
|
||||
|
||||
mod factory;
|
||||
pub use factory::FactoryAPI;
|
||||
pub use factory::{FactoryAPI, FactoryError};
|
||||
|
||||
mod factory_json;
|
||||
pub use factory_json::{SearchPayload, SearchResponse, SearchResponseItem, RobotInfo, RobotPrice};
|
||||
pub use factory_json::{ErrorPayload, SearchPayload, SearchResponse, SearchResponseItem, RobotInfo, RobotPrice, CreateRobotPayload, CreateRobotResponse, FactoryInfoResponse, PublishRobotPayload, PublishRobotResponse, MyRobotsResponse, GetRobotResponse};
|
||||
|
||||
mod portal;
|
||||
pub use self::portal::{PortalTokenProvider, AccountInfo, PortalCheckResponse, ITokenProvider};
|
||||
|
|
|
@ -4,12 +4,13 @@ use reqwest::{Client, Error};
|
|||
//use cookie_store::CookieStore;
|
||||
//use url::{Url};
|
||||
use serde_json::from_slice;
|
||||
use chrono::{DateTime, naive::NaiveDateTime, Utc};
|
||||
|
||||
/// Token generator for authenticated API endpoints
|
||||
#[async_trait::async_trait]
|
||||
pub trait ITokenProvider {
|
||||
/// Retrieve the token to use
|
||||
async fn token(&mut self) -> Result<String, ()>;
|
||||
async fn token(&mut self) -> Result<String, Error>;
|
||||
}
|
||||
|
||||
/// Token provider for an existing Freejam account, authenticated through the web browser portal.
|
||||
|
@ -22,6 +23,8 @@ pub struct PortalTokenProvider {
|
|||
jwt: PortalCheckResponse,
|
||||
/// Ureq HTTP client
|
||||
client: Client,
|
||||
/// target game
|
||||
target: String,
|
||||
}
|
||||
|
||||
impl PortalTokenProvider {
|
||||
|
@ -34,7 +37,7 @@ impl PortalTokenProvider {
|
|||
pub async fn target(value: String) -> Result<Self, Error> {
|
||||
let client = Client::new();
|
||||
let payload = PortalStartPayload {
|
||||
target: value,
|
||||
target: value.clone(),
|
||||
};
|
||||
let start_response = client.post("https://account.freejamgames.com/api/authenticate/portal/start")
|
||||
.header("Content-Type", "application/json")
|
||||
|
@ -62,7 +65,7 @@ impl PortalTokenProvider {
|
|||
let check_res = check_response.json::<PortalCheckResponse>().await?;
|
||||
|
||||
// login with token we just got
|
||||
Self::login_internal(check_res, client).await
|
||||
Self::login_internal(check_res, client, value).await
|
||||
}
|
||||
|
||||
pub async fn with_email(email: &str, password: &str) -> Result<Self, Error> {
|
||||
|
@ -96,7 +99,7 @@ impl PortalTokenProvider {
|
|||
/// Automatically validate portal
|
||||
async fn auto_portal(client: Client, value: String, token: String) -> Result<Self, Error> {
|
||||
let payload = PortalStartPayload {
|
||||
target: value,
|
||||
target: value.clone(),
|
||||
};
|
||||
let start_response = client.post("https://account.freejamgames.com/api/authenticate/portal/start")
|
||||
.header("Content-Type", "application/json")
|
||||
|
@ -121,10 +124,20 @@ impl PortalTokenProvider {
|
|||
let check_res = check_response.json::<PortalCheckResponse>().await?;
|
||||
|
||||
// login with token we just got
|
||||
Self::login_internal(check_res, client).await
|
||||
Self::login_internal(check_res, client, value).await
|
||||
}
|
||||
|
||||
async fn login_internal(token_data: PortalCheckResponse, client: Client) -> Result<Self, Error> {
|
||||
async fn login_internal(token_data: PortalCheckResponse, client: Client, target: String) -> Result<Self, Error> {
|
||||
let progress_res = Self::login_step(&token_data, &client).await?;
|
||||
Ok(Self {
|
||||
token: progress_res,
|
||||
jwt: token_data,
|
||||
client: client,
|
||||
target: target,
|
||||
})
|
||||
}
|
||||
|
||||
async fn login_step(token_data: &PortalCheckResponse, client: &Client) -> Result<ProgressionLoginResponse, Error> {
|
||||
let payload = ProgressionLoginPayload {
|
||||
token: token_data.token.clone(),
|
||||
};
|
||||
|
@ -132,17 +145,12 @@ impl PortalTokenProvider {
|
|||
.header("Content-Type", "application/json")
|
||||
.json(&payload)
|
||||
.send().await?;
|
||||
let progress_res = progress_response.json::<ProgressionLoginResponse>().await?;
|
||||
Ok(Self {
|
||||
token: progress_res,
|
||||
jwt: token_data,
|
||||
client: client,
|
||||
})
|
||||
progress_response.json::<ProgressionLoginResponse>().await
|
||||
}
|
||||
|
||||
/// Login using the portal token data from a previous portal authentication
|
||||
pub async fn login(token_data: PortalCheckResponse) -> Result<Self, Error> {
|
||||
Self::login_internal(token_data, Client::new()).await
|
||||
pub async fn login(token_data: PortalCheckResponse, target: String) -> Result<Self, Error> {
|
||||
Self::login_internal(token_data, Client::new(), target).await
|
||||
}
|
||||
|
||||
pub fn get_account_info(&self) -> Result<AccountInfo, Error> {
|
||||
|
@ -156,13 +164,27 @@ impl PortalTokenProvider {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl ITokenProvider for PortalTokenProvider {
|
||||
async fn token(&mut self) -> Result<String, ()> {
|
||||
// TODO re-authenticate when expired
|
||||
if let Some(token) = self.token.token.clone() {
|
||||
Ok(token)
|
||||
} else {
|
||||
Err(())
|
||||
async fn token(&mut self) -> Result<String, Error> {
|
||||
let decoded_jwt = self.jwt.decode_jwt_data();
|
||||
let expiry = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(decoded_jwt.exp as i64, 0), Utc);
|
||||
let now = Utc::now();
|
||||
if now >= expiry || self.token.token.is_none() {
|
||||
// refresh token when expired
|
||||
// TODO make sure refresh token isn't also expired
|
||||
// (it would be a bit concerning if you decide to run libfj for 1+ month, though)
|
||||
let payload = RefreshTokenPayload {
|
||||
target: self.target.clone(),
|
||||
refresh_token: self.jwt.refresh_token.clone(),
|
||||
public_id: decoded_jwt.public_id,
|
||||
};
|
||||
let refresh_response = self.client.post("https://account.freejamgames.com/api/authenticate/token/refresh")
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&payload)
|
||||
.send().await?;
|
||||
self.jwt = refresh_response.json::<PortalCheckResponse>().await?;
|
||||
self.token = Self::login_step(&self.jwt, &self.client).await?;
|
||||
}
|
||||
Ok(self.token.token.clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,6 +271,16 @@ pub(crate) struct ProgressionLoginResponse {
|
|||
pub server_token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub(crate) struct RefreshTokenPayload {
|
||||
#[serde(rename = "Target")]
|
||||
pub target: String, // "Techblox"
|
||||
#[serde(rename = "RefreshToken")]
|
||||
pub refresh_token: String,
|
||||
#[serde(rename = "PublicId")]
|
||||
pub public_id: String,
|
||||
}
|
||||
|
||||
/// Robocraft2 account information.
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct AccountInfo {
|
||||
|
|
|
@ -31,24 +31,6 @@ async fn robocraft_factory_default_query() -> Result<(), ()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
#[tokio::test]
|
||||
async fn robocraft2_factory_default_query() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.list().await;
|
||||
assert!(result.is_ok());
|
||||
let robo_info = result.unwrap();
|
||||
assert_ne!(robo_info.results.len(), 0);
|
||||
for robot in &robo_info.results {
|
||||
assert_ne!(robot.robot.name, "");
|
||||
assert_ne!(robot.robot.creator_id, "");
|
||||
assert_ne!(robot.robot.creator_id, "");
|
||||
assert_ne!(robot.robot.image, "");
|
||||
//println!("FactoryRobotListInfo.to_string() -> `{}`", robot.to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft")]
|
||||
fn builder() -> robocraft::FactorySearchBuilder {
|
||||
robocraft::FactoryAPI::new().list_builder()
|
||||
|
@ -139,3 +121,178 @@ async fn robocraft_factory_robot_cubes() -> Result<(), ()> {
|
|||
assert_eq!(colour_str, bot_info.response.colour_data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
//#[tokio::test]
|
||||
async fn robocraft2_factory_default_query() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.list().await;
|
||||
assert!(result.is_ok());
|
||||
let robo_info = unwrap_factory2(result);
|
||||
assert_ne!(robo_info.results.len(), 0);
|
||||
for robot in &robo_info.results {
|
||||
assert_ne!(robot.robot.name, "");
|
||||
assert_ne!(robot.robot.creator_id, "");
|
||||
assert_ne!(robot.robot.creator_id, "");
|
||||
assert_ne!(robot.robot.image, "");
|
||||
//println!("RobotInfo.to_string() -> `{}`", robot.robot.to_string());
|
||||
println!("SearchResponseItem {}", serde_json::to_string_pretty(&robot).unwrap());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
#[tokio::test]
|
||||
async fn robocraft2_factory_info() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.factory_info().await;
|
||||
assert!(result.is_ok());
|
||||
let crf_info = unwrap_factory2(result);
|
||||
println!("FactoryInfo {:?}", crf_info);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
//#[tokio::test]
|
||||
async fn robocraft2_factory_upload() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
|
||||
// copy default bot
|
||||
let result = api.get("08dab2c9-7a72-4ec4-843c-154fe8768e91".to_owned()).await;
|
||||
assert!(result.is_ok());
|
||||
let robot = unwrap_factory2(result);
|
||||
|
||||
let result = api.create_robot(
|
||||
robocraft2::CreateRobotPayload {
|
||||
name: "API is easy".to_owned(),
|
||||
data: robot.data, // base64
|
||||
image: "".to_owned(), // base64
|
||||
base_cpu: 42,
|
||||
weapon_cpu: 1,
|
||||
cosmetic_cpu: 6,
|
||||
cluster_count: 1,
|
||||
block_counts: vec![
|
||||
(42, 3),
|
||||
(3, 6)
|
||||
].drain(..).collect(),
|
||||
materials_used: vec![
|
||||
8,
|
||||
4
|
||||
].drain(..).collect(),
|
||||
minimum_offset_x: 0.0,
|
||||
minimum_offset_y: 0.0,
|
||||
minimum_offset_z: 0.0,
|
||||
maximum_offset_x: 0.0,
|
||||
maximum_offset_y: 0.0,
|
||||
maximum_offset_z: 0.0,
|
||||
}
|
||||
).await;
|
||||
//assert!(result.is_ok());
|
||||
let robot_info = unwrap_factory2(result);
|
||||
println!("CreateRobotInfo {:?}", robot_info);
|
||||
let result = api.publish_robot(
|
||||
robocraft2::PublishRobotPayload {
|
||||
name: "CRF API oh my".to_owned(),
|
||||
description: "There once was a person named NGnius, who simply wasn't that bright.\nBut he had a misleading name, and wasn't not quite unhated\nSo he thought it alright to put up a fight\nand get banned for reverse engineering".to_owned(),
|
||||
techpoints: -42,
|
||||
bloxcoin: 123
|
||||
}, robot_info.header.id.clone()).await;
|
||||
//assert!(result.is_ok());
|
||||
let _publish_info = unwrap_factory2(result);
|
||||
|
||||
// clean up
|
||||
let result = api.unpublish_bot(robot_info.header.id.clone()).await;
|
||||
//assert!(result.is_ok());
|
||||
let _robot = unwrap_factory2(result);
|
||||
|
||||
let result = api.delete_robot(robot_info.header.id).await;
|
||||
//assert!(result.is_ok());
|
||||
let _robot = unwrap_factory2(result);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
#[tokio::test]
|
||||
async fn robocraft2_factory_my_bots() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.my_robots().await;
|
||||
assert!(result.is_ok());
|
||||
let robo_info = unwrap_factory2(result);
|
||||
assert_ne!(robo_info.vehicles.len(), 0);
|
||||
for robot in &robo_info.vehicles {
|
||||
assert_ne!(robot.name, "");
|
||||
assert_ne!(robot.creator_id, "");
|
||||
assert_ne!(robot.creator_id, "");
|
||||
assert_ne!(robot.image, "");
|
||||
println!("My bot `{}`", robot.to_string());
|
||||
//println!("my vehicle {}", serde_json::to_string_pretty(&robot).unwrap());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
#[tokio::test]
|
||||
async fn robocraft2_factory_my_published_bots() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.my_published_robots().await;
|
||||
assert!(result.is_ok());
|
||||
let robo_info = unwrap_factory2(result);
|
||||
//assert_ne!(robo_info.vehicles.len(), 0);
|
||||
for robot in &robo_info.vehicles {
|
||||
assert_ne!(robot.name, "");
|
||||
assert_ne!(robot.creator_id, "");
|
||||
assert_ne!(robot.creator_id, "");
|
||||
assert_ne!(robot.image, "");
|
||||
println!("My pub bot `{}`", robot.to_string());
|
||||
//println!("pub vehicle {}", serde_json::to_string_pretty(&robot).unwrap());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
//#[tokio::test]
|
||||
async fn robocraft2_factory_bot() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.get("08dab2c9-7a72-4ec4-843c-154fe8768e91".to_owned()).await;
|
||||
//assert!(result.is_ok());
|
||||
let robot = unwrap_factory2(result);
|
||||
assert_ne!(robot.header.name, "");
|
||||
assert_ne!(robot.header.creator_id, "");
|
||||
assert_ne!(robot.header.creator_id, "");
|
||||
//assert_ne!(robot.header.image, "");
|
||||
//assert_ne!(robot.description, "");
|
||||
assert_ne!(robot.data, "");
|
||||
println!("robot {}", serde_json::to_string_pretty(&robot).unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
//#[tokio::test]
|
||||
async fn robocraft2_factory_delete_bot() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.delete_robot("08dab2d2-dcae-4f52-8a77-bbce7cf10124".to_owned()).await;
|
||||
//assert!(result.is_ok());
|
||||
let _robot = unwrap_factory2(result);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "robocraft2")]
|
||||
//#[tokio::test]
|
||||
async fn robocraft2_factory_unpublish_bot() -> Result<(), ()> {
|
||||
let api = robocraft2::FactoryAPI::with_auth(Box::new(robocraft2::PortalTokenProvider::with_username("FJAPIC00L", "P4$$w0rd").await.unwrap()));
|
||||
let result = api.unpublish_bot("08dab2d3-2a68-48c6-8fd6-a59a663336ca".to_owned()).await;
|
||||
//assert!(result.is_ok());
|
||||
let _robot = unwrap_factory2(result);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unwrap_factory2<T>(result: Result<T, robocraft2::FactoryError>) -> T {
|
||||
match result {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
//println!("FactoryError: {}", e);
|
||||
panic!("CRF2 Error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue