use crate::{
errors::ParseError, http::login::login_error::LoginError, utils::agent_access::AgentAccess,
};
use glam::{Vec2, Vec3};
use serde::{Deserialize, Serialize};
use serde_llsd_benthic::converter::{get, get_nested_vec, get_opt, get_vec, FromLLSDValue};
use serde_llsd_benthic::{auto_from_str, LLSDValue};
use std::collections::HashMap;
use uuid::Uuid;
pub enum LoginStatus {
Success(Box<LoginResponse>),
Failure(LoginError),
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct LoginResponse {
pub first_name: String,
pub last_name: String,
pub agent_id: Uuid,
pub session_id: Uuid,
pub sim_ip: String,
pub sim_port: u16,
pub seed_capability: Option<String>,
pub inventory_root: Option<Uuid>,
pub inventory_skeleton: Option<Vec<InventorySkeletonValues>>,
pub inventory_lib_root: Option<Uuid>,
pub inventory_skeleton_lib: Option<Vec<InventorySkeletonValues>>,
pub inventory_lib_owner: Option<Uuid>,
pub home: Option<HomeValues>,
pub look_at: Option<(String, String, String)>,
pub agent_access: Option<AgentAccess>,
pub agent_access_max: Option<AgentAccess>,
pub http_port: Option<u16>,
pub start_location: Option<String>,
pub region_x: Option<i64>,
pub region_y: Option<i64>,
pub region_size_x: Option<i64>,
pub region_size_y: Option<i64>,
pub circuit_code: u32,
pub secure_session_id: Option<Uuid>,
pub map_server_url: Option<String>,
pub buddy_list: Option<Vec<BuddyListValues>>,
pub gestures: Option<Vec<GesturesValues>>,
pub initial_outfit: Option<Vec<InitialOutfit>>,
pub global_textures: Option<Vec<GlobalTextures>>,
pub login: Option<bool>,
pub login_flags: Option<Vec<LoginFlags>>,
pub message: Option<String>,
pub ui_config: Option<Vec<UiConfig>>,
pub event_categories: Option<String>,
pub classified_categories: Option<Vec<ClassifiedCategoriesValues>>,
pub real_id: Option<String>,
pub search: Option<String>,
pub destination_guide_url: Option<String>,
pub event_notifications: Option<String>,
pub max_agent_groups: Option<i64>,
pub seconds_since_epoch: Option<i64>,
}
impl LoginResponse {
pub fn from_xmlrpc(data: &str) -> Result<LoginStatus, ParseError> {
match auto_from_str(data) {
Ok(xml) => match xml {
LLSDValue::Map(ref map) => {
let login: bool = get("login", map);
if !login {
return Ok(LoginStatus::Failure(LoginError::from_llsd(&xml)));
}
Ok(LoginStatus::Success(Box::new(LoginResponse {
first_name: get("first_name", map),
last_name: get("last_name", map),
agent_id: get("agent_id", map),
session_id: get("session_id", map),
sim_ip: get("sim_ip", map),
sim_port: get("sim_port", map),
circuit_code: get("circuit_code", map),
seed_capability: get_opt("seed_capability", map),
http_port: get_opt("http_port", map),
login: get_opt("login", map),
message: get_opt("message", map),
ui_config: get_nested_vec("ui_config", map),
event_notifications: get_opt("event_notifications", map),
inventory_root: get_inventory_root(map),
inventory_lib_root: get_nested_value("inventory-lib-root", map),
inventory_lib_owner: get_inventory_lib_owner(map),
inventory_skeleton: get_nested_vec("inventory-skeleton", map),
inventory_skeleton_lib: get_nested_vec("inventory-skel-lib", map),
classified_categories: get_nested_vec("classified_categories", map),
initial_outfit: get_nested_vec("initial-outfit", map),
region_x: get_opt("region_x", map),
region_y: get_opt("region_y", map),
start_location: get_opt("start_location", map),
event_categories: get_opt("event_categories", map),
buddy_list: get_vec("buddy_list", map),
region_size_x: get_opt("region_size_x", map),
region_size_y: get_opt("region_size_y", map),
gestures: get_nested_vec("gestures", map),
seconds_since_epoch: get_opt("seconds_since_epoch", map),
login_flags: get_nested_vec("login-flags", map),
map_server_url: get_opt("map_server_url", map),
agent_access_max: get_opt("agent_access_max", map),
secure_session_id: get_opt("secure_session_id", map),
home: get_opt("home", map),
global_textures: get_nested_vec("global_textures", map),
..Default::default()
})))
}
err => Err(err)?,
},
Err(e) => Err(e)?,
}
}
}
fn get_inventory_root(map: &HashMap<String, LLSDValue>) -> Option<Uuid> {
map.get("inventory-root").and_then(|v| {
if let LLSDValue::Array(arr) = v {
arr.iter().find_map(|item| {
if let LLSDValue::Map(inner_map) = item {
get_opt::<Uuid>("folder_id", inner_map)
} else {
None
}
})
} else {
None
}
})
}
pub fn get_inventory_lib_owner(map: &HashMap<String, LLSDValue>) -> Option<Uuid> {
map.get("inventory-lib-owner").and_then(|v| {
if let LLSDValue::Array(arr) = v {
for item in arr {
if let LLSDValue::Map(inner_map) = item {
if let Some(uuid) = get_opt::<Uuid>("agent_id", inner_map) {
return Some(uuid); }
}
}
}
None
})
}
pub fn get_nested_value<T: FromLLSDValue>(
key: &str,
map: &HashMap<String, LLSDValue>,
) -> Option<T> {
map.get(key).and_then(|v| {
if let LLSDValue::Array(arr) = v {
for inner in arr {
if let LLSDValue::Array(inner_arr) = inner {
for item in inner_arr {
if let Some(parsed) = T::from_llsd(item) {
return Some(parsed); }
}
}
}
}
None
})
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClassifiedCategoriesValues {
pub category_id: i32,
pub category_name: String,
}
impl FromLLSDValue for ClassifiedCategoriesValues {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(ClassifiedCategoriesValues {
category_id: get("category_id", map),
category_name: get("category_name", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UiConfig {
pub allow_first_life: bool,
}
impl FromLLSDValue for UiConfig {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(UiConfig {
allow_first_life: get("allow_first_life", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LoginFlags {
pub stipend_since_login: String,
pub ever_logged_in: bool,
pub seconds_since_epoch: Option<i64>,
pub daylight_savings: bool,
pub gendered: bool,
}
impl FromLLSDValue for LoginFlags {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(LoginFlags {
stipend_since_login: get("stipend_since_login", map),
ever_logged_in: get("ever_logged_in", map),
seconds_since_epoch: map.get("seconds_since_epoch").and_then(|v| {
if let LLSDValue::Integer(i) = v {
Some(*i as i64)
} else if let LLSDValue::Undefined = v {
None
} else {
None
}
}),
daylight_savings: get("daylight_savings", map),
gendered: get("gendered", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GlobalTextures {
pub cloud_texture_id: Uuid,
pub sun_texture_id: Uuid,
pub moon_texture_id: Uuid,
}
impl FromLLSDValue for GlobalTextures {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(GlobalTextures {
cloud_texture_id: get("cloud_texture_id", map),
sun_texture_id: get("sun_texture_id", map),
moon_texture_id: get("moon_texture_id", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InitialOutfit {
pub folder_name: Uuid,
pub gender: String,
}
impl FromLLSDValue for InitialOutfit {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(InitialOutfit {
folder_name: get("folder_name", map),
gender: get("gender", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GesturesValues {
pub item_id: String,
pub asset_id: String,
}
impl FromLLSDValue for GesturesValues {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(GesturesValues {
item_id: get("item_id", map),
asset_id: get("asset_id", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BuddyListValues {
pub buddy_id: String,
pub buddy_rights_given: FriendsRights,
pub buddy_rights_has: FriendsRights,
}
impl FromLLSDValue for BuddyListValues {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(BuddyListValues {
buddy_id: get("buddy_id", map),
buddy_rights_given: get("buddy_rights_given", map),
buddy_rights_has: get("buddy_rights_has", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct FriendsRights {
pub can_see_online: bool,
pub can_see_on_map: bool,
pub can_modify_objects: bool,
}
impl FriendsRights {
fn _from_int(rights: u8) -> Self {
match rights {
1 => FriendsRights {
can_see_online: true,
can_see_on_map: false,
can_modify_objects: false,
},
2 => FriendsRights {
can_see_online: true,
can_see_on_map: true,
can_modify_objects: false,
},
4 => FriendsRights {
can_see_online: true,
can_see_on_map: false,
can_modify_objects: true,
},
_ => FriendsRights {
can_see_online: false,
can_see_on_map: false,
can_modify_objects: false,
},
}
}
}
impl FromLLSDValue for FriendsRights {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(FriendsRights {
can_see_online: get("can_see_online", map),
can_see_on_map: get("can_see_on_map", map),
can_modify_objects: get("can_modify_objects", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InventorySkeletonValues {
pub folder_id: Uuid,
pub parent_id: Uuid,
pub name: String,
pub type_default: InventoryType,
pub version: i32,
}
impl FromLLSDValue for InventorySkeletonValues {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Map(map) = value {
Some(InventorySkeletonValues {
folder_id: get("folder_id", map),
parent_id: get("parent_id", map),
name: get("name", map),
type_default: get("type_default", map),
version: get("version", map),
})
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub enum InventoryType {
#[default]
Unknown,
Texture,
Sound,
CallingCard,
Landmark,
Object,
Notecard,
Category,
Folder,
RootCategory,
LSL,
Snapshot,
Attachment,
Wearable,
Animation,
Gesture,
Mesh,
}
impl FromLLSDValue for InventoryType {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::Integer(i) = value {
match *i {
-1 => Some(InventoryType::Unknown),
0 => Some(InventoryType::Texture),
2 => Some(InventoryType::Sound),
3 => Some(InventoryType::CallingCard),
4 => Some(InventoryType::Landmark),
6 => Some(InventoryType::Object),
7 => Some(InventoryType::Notecard),
8 => Some(InventoryType::Category),
9 => Some(InventoryType::Folder),
10 => Some(InventoryType::RootCategory),
11 => Some(InventoryType::LSL),
15 => Some(InventoryType::Snapshot),
17 => Some(InventoryType::Attachment),
18 => Some(InventoryType::Wearable),
19 => Some(InventoryType::Animation),
20 => Some(InventoryType::Gesture),
22 => Some(InventoryType::Mesh),
_ => None, }
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HomeValues {
pub region_handle: Vec2,
pub position: Vec3,
pub look_at: Vec3,
}
impl FromLLSDValue for HomeValues {
fn from_llsd(value: &LLSDValue) -> Option<Self> {
if let LLSDValue::String(s) = value {
let s = s.trim_matches(|c| c == '{' || c == '}').trim();
let s = s
.replace("region_handle", "")
.replace("position", "")
.replace("look_at", "")
.replace("[", "")
.replace("]", "")
.replace("'", "")
.replace(" ", "");
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 8 {
eprintln!(
"Unexpected home string format, got {} parts: {:?}",
parts.len(),
parts
);
return None;
}
let nums: Vec<f32> = parts
.iter()
.map(|p| p.trim_start_matches(['r', ':']))
.map(|p| p.parse::<f32>().ok())
.collect::<Option<Vec<f32>>>()?;
let home = HomeValues {
region_handle: Vec2::new(nums[0], nums[1]),
position: Vec3::new(nums[2], nums[3], nums[4]),
look_at: Vec3::new(nums[5], nums[6], nums[7]),
};
Some(home)
} else {
None
}
}
}