An unofficial collection of APIs used in FreeJam games and mods
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

294 lines
12KB

  1. use std::sync::Mutex;
  2. use reqwest::{Client, Error as ReqwestError, Response};
  3. use url::{Url};
  4. use crate::robocraft2::{ITokenProvider, ErrorPayload};
  5. use crate::robocraft2::{SearchPayload, SearchResponse, CreateRobotPayload, CreateRobotResponse, FactoryInfoResponse, PublishRobotPayload, PublishRobotResponse, MyRobotsResponse, GetRobotResponse, ModerateRobotPayload, ReportRobotPayload};
  6. /// Community Factory Robot 2 root URL
  7. pub const FACTORY_DOMAIN: &str = "https://factory.production.robocraft2.com";
  8. #[derive(Debug)]
  9. pub enum FactoryError {
  10. Protocol(ReqwestError),
  11. Response(ErrorPayload),
  12. ResponseCode(ReqwestError, u16)
  13. }
  14. impl std::fmt::Display for FactoryError {
  15. fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
  16. match self {
  17. Self::Protocol(p) => if let Some(status) = p.status() {
  18. write!(f, "HTTP Error {}: {}", status, p)
  19. } else {
  20. write!(f, "HTTP Error: {}", p)
  21. }
  22. Self::Response(r) => write!(f, "Factory Error #{}: {}", r.error, r.error_message),
  23. Self::ResponseCode(p, s) => write!(f, "HTTP Error {}: {}", s, p)
  24. }
  25. }
  26. }
  27. impl std::error::Error for FactoryError {}
  28. async fn handle_json_response<D: for<'a> serde::Deserialize<'a>>(response: Response) -> Result<D, FactoryError> {
  29. let status_code: u16 = response.status().into();
  30. if status_code > 199 && status_code < 300 {
  31. Ok(response.json::<D>().await.map_err(FactoryError::Protocol)?)
  32. } else {
  33. match response.json::<ErrorPayload>().await {
  34. Ok(err) => Err(FactoryError::Response(err)),
  35. Err(e) => Err(FactoryError::ResponseCode(e, status_code))
  36. }
  37. }
  38. }
  39. /// CRF API implementation
  40. pub struct FactoryAPI {
  41. client: Client,
  42. token: Mutex<Box<dyn ITokenProvider + Send>>,
  43. }
  44. impl FactoryAPI {
  45. /*/// Create a new instance, using `DefaultTokenProvider`.
  46. pub fn new() -> FactoryAPI {
  47. FactoryAPI {
  48. client: Client::new(),
  49. token: Box::new(DefaultTokenProvider{}),
  50. }
  51. }*/
  52. /// Create a new instance using the provided token provider.
  53. pub fn with_auth(token_provider: Box<dyn ITokenProvider + Send>) -> FactoryAPI {
  54. FactoryAPI {
  55. client: Client::new(),
  56. token: Mutex::new(token_provider),
  57. }
  58. }
  59. /// Retrieve CRF robots on the main page.
  60. pub async fn list(&self) -> Result<SearchResponse, FactoryError> {
  61. self.search(SearchPayload::default()).await
  62. }
  63. /// Search for robots on the CRF which meet the provided parameters
  64. pub async fn search(&self, params: SearchPayload) -> Result<SearchResponse, FactoryError> {
  65. let mut url = Url::parse(FACTORY_DOMAIN)
  66. .unwrap()
  67. .join("/v1/foundry/search")
  68. .unwrap();
  69. if let Some(text) = &params.text {
  70. url.query_pairs_mut().append_pair("text", text);
  71. }
  72. if let Some(base_minimum_cpu) = params.base_minimum_cpu {
  73. url.query_pairs_mut().append_pair("baseCpuMinimum", &base_minimum_cpu.to_string());
  74. }
  75. if let Some(base_maximum_cpu) = &params.base_maximum_cpu {
  76. url.query_pairs_mut().append_pair("baseCpuMaximum", &base_maximum_cpu.to_string());
  77. }
  78. if let Some(x) = &params.weapon_minimum_cpu {
  79. url.query_pairs_mut().append_pair("weaponCpuMinimum", &x.to_string());
  80. }
  81. if let Some(x) = &params.weapon_maximum_cpu {
  82. url.query_pairs_mut().append_pair("weaponCpuMaximum", &x.to_string());
  83. }
  84. if let Some(x) = &params.cosmetic_minimum_cpu {
  85. url.query_pairs_mut().append_pair("cosmeticCpuMinimum", &x.to_string());
  86. }
  87. if let Some(x) = &params.cosmetic_maximum_cpu {
  88. url.query_pairs_mut().append_pair("cosmeticCpuMaximum", &x.to_string());
  89. }
  90. if let Some(x) = &params.cluster_minimum {
  91. url.query_pairs_mut().append_pair("clusterMinimum", &x.to_string());
  92. }
  93. if let Some(x) = &params.cluster_maximum {
  94. url.query_pairs_mut().append_pair("clusterMaximum", &x.to_string());
  95. }
  96. if let Some(x) = &params.date_minimum {
  97. url.query_pairs_mut().append_pair("dateMinimum", x);
  98. }
  99. if let Some(x) = &params.date_maximum {
  100. url.query_pairs_mut().append_pair("dateMaximum", x);
  101. }
  102. if let Some(x) = &params.purchased_only {
  103. url.query_pairs_mut().append_pair("purchasedOnly", if *x { "true" } else { "false" });
  104. }
  105. if let Some(x) = &params.creator_id {
  106. url.query_pairs_mut().append_pair("creatorId", x);
  107. }
  108. if let Some(x) = &params.page {
  109. url.query_pairs_mut().append_pair("page", &x.to_string());
  110. }
  111. if let Some(x) = &params.count {
  112. url.query_pairs_mut().append_pair("count", &x.to_string());
  113. }
  114. url.query_pairs_mut().append_pair("sortBy", &params.sort_by);
  115. url.query_pairs_mut().append_pair("orderBy", &params.order_by);
  116. url.query_pairs_mut().append_pair("modFilter", &params.moderation_filter);
  117. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  118. let result = self.client.get(url)
  119. .header("Authorization", "Bearer ".to_owned() + &token)
  120. .send().await
  121. .map_err(FactoryError::Protocol)?;
  122. //println!("result: {}", result.text().await.map_err(FactoryError::Protocol)?);
  123. //todo!()
  124. handle_json_response::<SearchResponse>(result).await
  125. }
  126. pub async fn create_robot(&self, robot: CreateRobotPayload) -> Result<CreateRobotResponse, FactoryError> {
  127. let url = Url::parse(FACTORY_DOMAIN)
  128. .unwrap()
  129. .join("/v1/foundry/garage")
  130. .unwrap();
  131. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  132. let result = self.client.post(url)
  133. .header("Authorization", "Bearer ".to_owned() + &token)
  134. .header("Content-Type", "application/json")
  135. .json(&robot)
  136. .send().await
  137. .map_err(FactoryError::Protocol)?;
  138. handle_json_response::<CreateRobotResponse>(result).await
  139. }
  140. pub async fn publish_robot(&self, robot: PublishRobotPayload, id: String) -> Result<PublishRobotResponse, FactoryError> {
  141. let url = Url::parse(FACTORY_DOMAIN)
  142. .unwrap()
  143. .join(&format!("/v1/foundry/vehicles/{}/publish", id))
  144. .unwrap();
  145. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  146. let result = self.client.post(url)
  147. .header("Authorization", "Bearer ".to_owned() + &token)
  148. .header("Content-Type", "application/json")
  149. .json(&robot)
  150. .send().await
  151. .map_err(FactoryError::Protocol)?;
  152. handle_json_response(result).await
  153. }
  154. pub async fn unpublish_bot(&self, id: String) -> Result<(), FactoryError> {
  155. let url = Url::parse(FACTORY_DOMAIN)
  156. .unwrap()
  157. .join(&format!("/v1/foundry/vehicles/{}/unpublish", id))
  158. .unwrap();
  159. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  160. let result = self.client.post(url)
  161. .header("Authorization", "Bearer ".to_owned() + &token)
  162. .send().await
  163. .map_err(FactoryError::Protocol)?;
  164. let status_code = result.status().as_u16();
  165. if status_code > 199 && status_code < 300 {
  166. Ok(())
  167. } else {
  168. match result.json::<ErrorPayload>().await {
  169. Ok(err) => Err(FactoryError::Response(err)),
  170. Err(e) => Err(FactoryError::ResponseCode(e, status_code))
  171. }
  172. }
  173. }
  174. pub async fn delete_robot(&self, id: String) -> Result<(), FactoryError> {
  175. let url = Url::parse(FACTORY_DOMAIN)
  176. .unwrap()
  177. .join(&format!("/v1/foundry/vehicles/{}", id))
  178. .unwrap();
  179. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  180. let result = self.client.delete(url)
  181. .header("Authorization", "Bearer ".to_owned() + &token)
  182. .header("Content-Type", "application/json")
  183. .send().await
  184. .map_err(FactoryError::Protocol)?;
  185. let status_code = result.status().as_u16();
  186. if status_code > 199 && status_code < 300 {
  187. Ok(())
  188. } else {
  189. match result.json::<ErrorPayload>().await {
  190. Ok(err) => Err(FactoryError::Response(err)),
  191. Err(e) => Err(FactoryError::ResponseCode(e, status_code))
  192. }
  193. }
  194. }
  195. pub async fn factory_info(&self) -> Result<FactoryInfoResponse, FactoryError> {
  196. let url = Url::parse(FACTORY_DOMAIN)
  197. .unwrap()
  198. .join("/v1/foundry/info")
  199. .unwrap();
  200. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  201. let result = self.client.get(url)
  202. .header("Authorization", "Bearer ".to_owned() + &token)
  203. .send().await
  204. .map_err(FactoryError::Protocol)?;
  205. handle_json_response::<FactoryInfoResponse>(result).await
  206. }
  207. pub async fn my_robots(&self) -> Result<MyRobotsResponse, FactoryError> {
  208. let url = Url::parse(FACTORY_DOMAIN)
  209. .unwrap()
  210. .join("/v1/foundry/garage")
  211. .unwrap();
  212. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  213. let result = self.client.get(url)
  214. .header("Authorization", "Bearer ".to_owned() + &token)
  215. .send().await
  216. .map_err(FactoryError::Protocol)?;
  217. handle_json_response::<MyRobotsResponse>(result).await
  218. }
  219. pub async fn my_published_robots(&self) -> Result<MyRobotsResponse, FactoryError> {
  220. let url = Url::parse(FACTORY_DOMAIN)
  221. .unwrap()
  222. .join("/v1/foundry/published")
  223. .unwrap();
  224. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  225. let result = self.client.get(url)
  226. .header("Authorization", "Bearer ".to_owned() + &token)
  227. .send().await
  228. .map_err(FactoryError::Protocol)?;
  229. handle_json_response::<MyRobotsResponse>(result).await
  230. }
  231. pub async fn get(&self, id: String) -> Result<GetRobotResponse, FactoryError> {
  232. let url = Url::parse(FACTORY_DOMAIN)
  233. .unwrap()
  234. .join(&format!("/v1/foundry/vehicles/{}", id))
  235. .unwrap();
  236. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  237. let result = self.client.get(url)
  238. .header("Authorization", "Bearer ".to_owned() + &token)
  239. .send().await
  240. .map_err(FactoryError::Protocol)?;
  241. handle_json_response::<GetRobotResponse>(result).await
  242. }
  243. pub async fn moderate(&self, payload: ModerateRobotPayload, id: String) -> Result<(), FactoryError> {
  244. let url = Url::parse(FACTORY_DOMAIN)
  245. .unwrap()
  246. .join(&format!("/v1/foundry/vehicles/{}/moderate", id))
  247. .unwrap();
  248. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  249. let _result = self.client.post(url)
  250. .header("Authorization", "Bearer ".to_owned() + &token)
  251. .header("Content-Type", "application/json")
  252. .json(&payload)
  253. .send().await
  254. .map_err(FactoryError::Protocol)?;
  255. Ok(())
  256. }
  257. pub async fn report(&self, payload: ReportRobotPayload, id: String) -> Result<(), FactoryError> {
  258. let url = Url::parse(FACTORY_DOMAIN)
  259. .unwrap()
  260. .join(&format!("/v1/foundry/vehicles/{}/report", id))
  261. .unwrap();
  262. let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?;
  263. let _result = self.client.post(url)
  264. .header("Authorization", "Bearer ".to_owned() + &token)
  265. .header("Content-Type", "application/json")
  266. .json(&payload)
  267. .send().await
  268. .map_err(FactoryError::Protocol)?;
  269. Ok(())
  270. }
  271. }