use crate::{Client, Code, RoliError};
use reqwest::header;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
const PLAYER_SEARCH_API: &str = "https://www.rolimons.com/api/playersearch";
const PLAYER_API: &str = "https://www.rolimons.com/api/playerassets/";
#[derive(Clone, Debug, Serialize, Deserialize)]
struct PlayerSearchResponse {
success: bool,
result_count: i64,
players: Vec<Vec<Code>>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
pub struct PlayerSearchResult {
pub user_id: u64,
pub username: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct PlayerProfileResponse {
success: bool,
#[serde(rename = "playerTerminated")]
player_terminated: bool,
#[serde(rename = "playerPrivacyEnabled")]
player_privacy_enabled: bool,
#[serde(rename = "playerVerified")]
player_verified: bool,
#[serde(rename = "playerId")]
player_id: u64,
#[serde(rename = "chartNominalScanTime")]
chart_nominal_scan_time: u64,
#[serde(rename = "playerAssets")]
player_assets: HashMap<String, Vec<u64>>,
#[serde(rename = "isOnline")]
is_online: bool,
#[serde(rename = "presenceType")]
presence_type: u8,
#[serde(rename = "lastOnline")]
last_online: u64,
#[serde(rename = "lastLocation")]
last_location: String,
#[serde(rename = "lastPlaceId")]
last_place_id: Option<u64>,
#[serde(rename = "locationGameIsTracked")]
location_game_is_tracked: bool,
#[serde(rename = "locationGameIconUrl")]
location_game_icon_url: Option<String>,
premium: bool,
badges: HashMap<String, u64>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PlayerProfile {
pub user_id: u64,
pub terminated: bool,
pub privated: bool,
pub is_online: bool,
pub last_online: u64,
pub premium: bool,
pub presence_type: PresenceType,
pub badges: Vec<Badge>,
pub inventory: Vec<PlayerAsset>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Badge {
pub name: String,
pub timestamp_earned: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Copy)]
pub enum PresenceType {
Unavailable,
Website,
InGame,
InStudio,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
pub struct PlayerAsset {
pub item_id: u64,
pub uaids: Vec<u64>,
}
impl PlayerSearchResult {
fn from_raw(codes: Vec<Code>) -> Result<Self, RoliError> {
if codes.len() != 2 && codes.len() != 3 {
return Err(RoliError::MalformedResponse);
}
let user_id = codes[0].to_i64()? as u64;
let username = codes[1].to_string();
Ok(Self { user_id, username })
}
}
impl PresenceType {
fn from_u8(value: u8) -> Self {
match value {
0 => Self::Unavailable,
1 => Self::Website,
2 => Self::InGame,
3 => Self::InStudio,
_ => Self::Unavailable,
}
}
}
impl Client {
pub async fn player_search(
&self,
username: &str,
) -> Result<Vec<PlayerSearchResult>, RoliError> {
let formatted_url = format!("{}?searchstring={}", PLAYER_SEARCH_API, username);
let request_result = self
.reqwest_client
.get(formatted_url)
.header(header::USER_AGENT, crate::USER_AGENT)
.send()
.await;
match request_result {
Ok(response) => {
let status_code = response.status().as_u16();
match status_code {
200 => {
let raw = match response.json::<PlayerSearchResponse>().await {
Ok(x) => x,
Err(_) => return Err(RoliError::MalformedResponse),
};
if !raw.success {
return Err(RoliError::RequestReturnedUnsuccessful);
}
let mut search_outputs = Vec::new();
for player in raw.players {
search_outputs.push(PlayerSearchResult::from_raw(player)?);
}
Ok(search_outputs)
}
429 => Err(RoliError::TooManyRequests),
500 => Err(RoliError::InternalServerError),
_ => Err(RoliError::UnidentifiedStatusCode(status_code)),
}
}
Err(e) => Err(RoliError::ReqwestError(e)),
}
}
pub async fn player_profile(&self, user_id: u64) -> Result<PlayerProfile, RoliError> {
let formatted_url = format!("{}{}", PLAYER_API, user_id);
let request_result = self
.reqwest_client
.get(formatted_url)
.header(header::USER_AGENT, crate::USER_AGENT)
.send()
.await;
match request_result {
Ok(response) => {
let status_code = response.status().as_u16();
match status_code {
200 => {
let raw = match response.json::<PlayerProfileResponse>().await {
Ok(x) => x,
Err(_) => return Err(RoliError::MalformedResponse),
};
if !raw.success {
return Err(RoliError::RequestReturnedUnsuccessful);
}
let mut badges = Vec::new();
for (name, timestamp) in raw.badges {
badges.push(Badge {
name,
timestamp_earned: timestamp,
});
}
let mut inventory = Vec::new();
for (item_id, uaids) in raw.player_assets {
let item_id_u64 = match item_id.parse::<u64>() {
Ok(x) => x,
Err(_) => return Err(RoliError::MalformedResponse),
};
inventory.push(PlayerAsset {
item_id: item_id_u64,
uaids,
});
}
Ok(PlayerProfile {
user_id: raw.player_id,
terminated: raw.player_terminated,
privated: raw.player_privacy_enabled,
inventory,
is_online: raw.is_online,
presence_type: PresenceType::from_u8(raw.presence_type),
last_online: raw.last_online,
premium: raw.premium,
badges,
})
}
429 => Err(RoliError::TooManyRequests),
500 => Err(RoliError::InternalServerError),
_ => Err(RoliError::UnidentifiedStatusCode(status_code)),
}
}
Err(e) => Err(RoliError::ReqwestError(e)),
}
}
}