From c22eef5d0eba1ffeab7c13a1a5af45da48a574de Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Mon, 10 Aug 2026 18:35:09 +0000 Subject: [PATCH] Implement social and group managers (#72) --- api/SHIM-COVERAGE.md | 2 +- crates/libremetaverse/src/client_core.rs | 88 + crates/libremetaverse/src/friends_manager.rs | 886 ++++++++++ crates/libremetaverse/src/generated.rs | 1556 +++-------------- crates/libremetaverse/src/group_manager.rs | 1610 ++++++++++++++++++ crates/libremetaverse/src/lib.rs | 2 + docs/social.md | 9 + tools/generate_api_shims.py | 44 + 8 files changed, 2852 insertions(+), 1345 deletions(-) create mode 100644 crates/libremetaverse/src/friends_manager.rs create mode 100644 crates/libremetaverse/src/group_manager.rs create mode 100644 docs/social.md diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 4adc09e..c1d38f8 100644 --- a/api/SHIM-COVERAGE.md +++ b/api/SHIM-COVERAGE.md @@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand. | Assembly | Types | Members | Status | |---|---:|---:|---| -| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 326 types / 16,464 members; remaining surface is callable failure-only shims | +| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 351 types / 16,636 members; remaining surface is callable failure-only shims | | `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain | | `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain | | `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim | diff --git a/crates/libremetaverse/src/client_core.rs b/crates/libremetaverse/src/client_core.rs index 5b8962e..f11872e 100644 --- a/crates/libremetaverse/src/client_core.rs +++ b/crates/libremetaverse/src/client_core.rs @@ -126,6 +126,8 @@ struct ClientRuntime { grid_manager: Mutex>>, directory_manager: Mutex>>, interest_list_manager: Mutex>>, + friends_manager: Mutex>>, + group_manager: Mutex>>, appearance_manager: Mutex>>, avatar_manager: Mutex>>, animesh_manager: Mutex>>, @@ -168,6 +170,8 @@ impl ClientRuntime { grid_manager: Mutex::new(None), directory_manager: Mutex::new(None), interest_list_manager: Mutex::new(None), + friends_manager: Mutex::new(None), + group_manager: Mutex::new(None), appearance_manager: Mutex::new(None), avatar_manager: Mutex::new(None), animesh_manager: Mutex::new(None), @@ -270,6 +274,14 @@ impl ClientRuntime { .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) .take(); + self.friends_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take(); + self.group_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .take(); self.http_caps_client .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) @@ -977,6 +989,82 @@ impl GridClient { .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); } + pub(crate) fn cached_friends_manager_inner( + &self, + ) -> Option> { + self.runtime + .friends_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + pub(crate) fn install_friends_manager_inner( + &self, + candidate: Arc, + ) -> Arc { + let mut cached = self + .runtime + .friends_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Arc::clone(inner); + } + *cached = Some(Arc::clone(&candidate)); + candidate + } + pub(crate) fn native_friends(&self) -> Result { + if let Some(inner) = self.cached_friends_manager_inner() { + Ok(crate::FriendsManager { inner }) + } else { + crate::friends_manager::FriendsManager::native_new(Arc::new(self.clone())) + } + } + pub(crate) fn native_set_friends(&self, value: crate::FriendsManager) { + *self + .runtime + .friends_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); + } + pub(crate) fn cached_group_manager_inner( + &self, + ) -> Option> { + self.runtime + .group_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) + .clone() + } + pub(crate) fn install_group_manager_inner( + &self, + candidate: Arc, + ) -> Arc { + let mut cached = self + .runtime + .group_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(inner) = cached.as_ref() { + return Arc::clone(inner); + } + *cached = Some(Arc::clone(&candidate)); + candidate + } + pub(crate) fn native_groups(&self) -> crate::GroupManager { + if let Some(inner) = self.cached_group_manager_inner() { + crate::GroupManager { inner } + } else { + crate::group_manager::GroupManager::native_new(Arc::new(self.clone())) + } + } + pub(crate) fn native_set_groups(&self, value: crate::GroupManager) { + *self + .runtime + .group_manager + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(value.inner); + } pub(crate) fn native_set_caps_rate_limiter( &mut self, diff --git a/crates/libremetaverse/src/friends_manager.rs b/crates/libremetaverse/src/friends_manager.rs new file mode 100644 index 0000000..1098fa1 --- /dev/null +++ b/crates/libremetaverse/src/friends_manager.rs @@ -0,0 +1,886 @@ +//! Friend presence, rights, offers, calling cards, and map tracking. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] +#![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString method. + +use crate::agent_manager::EventRegistry; +use crate::network_manager::RawPacketReceivedEventArgs; +use crate::packet_catalog::{GeneratedPacket, PacketType}; +use crate::{Error, FriendRights, GridClient, InstantMessageDialog}; +use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser}; +use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription}; +use libremetaverse_types::{FolderType, UUID, Vector3}; +use std::collections::{BTreeMap, HashMap}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; + +const MAX_FRIENDS: usize = 100_000; + +fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { + value + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} +fn read(value: &RwLock) -> std::sync::RwLockReadGuard<'_, T> { + value + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} +fn write(value: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { + value + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} +fn wire_string(value: &[u8]) -> String { + String::from_utf8_lossy(value.split(|b| *b == 0).next().unwrap_or_default()).into_owned() +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FriendInfo { + id: UUID, + name: String, + online: bool, + their_rights: FriendRights, + my_rights: FriendRights, +} +impl FriendInfo { + pub(crate) fn native_new( + id: UUID, + their_rights: FriendRights, + my_rights: FriendRights, + ) -> Self { + Self { + id, + name: String::new(), + online: false, + their_rights, + my_rights, + } + } + pub fn uuid(&self) -> UUID { + self.id + } + pub fn name(&self) -> String { + self.name.clone() + } + pub fn set_name(&mut self, value: String) { + self.name = value; + } + pub fn is_online(&self) -> bool { + self.online + } + pub fn set_is_online(&mut self, value: bool) { + self.online = value; + } + pub fn their_friend_rights(&self) -> FriendRights { + self.their_rights + } + pub fn set_their_friend_rights(&mut self, value: FriendRights) { + self.their_rights = value; + } + pub fn my_friend_rights(&self) -> FriendRights { + self.my_rights + } + pub fn set_my_friend_rights(&mut self, value: FriendRights) { + self.my_rights = value; + } + pub fn can_see_me_online(&self) -> bool { + self.their_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0 + } + pub fn set_can_see_me_online(&mut self, value: bool) { + set_right(&mut self.their_rights, FriendRights::CAN_SEE_ONLINE, value); + if !value { + set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, false); + } + } + pub fn can_see_me_on_map(&self) -> bool { + self.their_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0 + } + pub fn set_can_see_me_on_map(&mut self, value: bool) { + if self.can_see_me_online() { + set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, value); + } + } + pub fn can_modify_my_objects(&self) -> bool { + self.their_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0 + } + pub fn set_can_modify_my_objects(&mut self, value: bool) { + set_right( + &mut self.their_rights, + FriendRights::CAN_MODIFY_OBJECTS, + value, + ); + } + pub fn can_see_them_online(&self) -> bool { + self.my_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0 + } + pub fn set_can_see_them_online(&mut self, value: bool) { + set_right(&mut self.my_rights, FriendRights::CAN_SEE_ONLINE, value); + } + pub fn can_see_them_on_map(&self) -> bool { + self.my_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0 + } + pub fn set_can_see_them_on_map(&mut self, value: bool) { + set_right(&mut self.my_rights, FriendRights::CAN_SEE_ON_MAP, value); + } + pub fn can_modify_their_objects(&self) -> bool { + self.my_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0 + } + pub fn set_can_modify_their_objects(&mut self, value: bool) { + set_right(&mut self.my_rights, FriendRights::CAN_MODIFY_OBJECTS, value); + } + pub fn to_string(&self) -> String { + let who = if self.name.is_empty() { + self.id.to_string() + } else { + self.name.clone() + }; + format!( + "{who} (Their Rights: {}, My Rights: {})", + self.their_rights.0, self.my_rights.0 + ) + } +} +fn set_right(rights: &mut FriendRights, flag: FriendRights, enabled: bool) { + if enabled { + rights.0 |= flag.0; + } else { + rights.0 &= !flag.0; + } +} + +macro_rules! simple_event { + ($name:ident { $($field:ident : $ty:ty => $getter:ident),+ $(,)? }) => { + #[derive(Clone)] pub struct $name { $( $field: $ty ),+ } + impl $name { + pub fn new($($field: $ty),+) -> Result { Ok(Self { $($field),+ }) } + $(pub fn $getter(&self) -> $ty { self.$field.clone() })+ + } + }; +} +simple_event!(CallingCardOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, transaction_id: UUID => transaction_id }); +simple_event!(FriendFoundReplyEventArgs { agent_id: UUID => agent_id, region_handle: u64 => region_handle, location: Vector3 => location }); +simple_event!(FriendInfoEventArgs { friend: FriendInfo => friend }); +simple_event!(FriendNamesEventArgs { names: HashMap => names }); +simple_event!(FriendsReadyEventArgs { count: i32 => count }); +simple_event!(FriendshipOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, im_session_id: UUID => session_id }); +simple_event!(FriendshipResponseEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, accepted: bool => accepted }); +simple_event!(FriendshipTerminatedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name }); + +#[derive(Default)] +struct FriendEvents { + calling_card: EventRegistry, + found: EventRegistry, + names: EventRegistry, + offline: EventRegistry, + online: EventRegistry, + rights: EventRegistry, + offered: EventRegistry, + response: EventRegistry, + terminated: EventRegistry, + ready: EventRegistry, +} +pub(crate) struct FriendsManagerInner { + client: Arc, + friends: RwLock>, + requests: RwLock>, + events: FriendEvents, + subscriptions: Mutex>, + login_callback: Mutex>, + disposed: AtomicBool, +} +#[derive(Clone)] +pub struct FriendsManager { + pub(crate) inner: Arc, +} + +impl FriendsManager { + pub(crate) fn native_new(client: Arc) -> Result { + if let Some(inner) = client.cached_friends_manager_inner() { + return Ok(Self { inner }); + } + let inner = Arc::new(FriendsManagerInner { + client: Arc::clone(&client), + friends: RwLock::new(HashMap::new()), + requests: RwLock::new(HashMap::new()), + events: FriendEvents::default(), + subscriptions: Mutex::new(Vec::new()), + login_callback: Mutex::new(None), + disposed: AtomicBool::new(false), + }); + let weak = Arc::downgrade(&inner); + mutex(&inner.subscriptions).push(client.network().subscribe_raw_packet(Arc::new( + move |e| { + if let Some(inner) = weak.upgrade() { + inner.handle_packet(e); + } + }, + ))); + let weak = Arc::downgrade(&inner); + let mut callback_client = (*client).clone(); + mutex(&inner.subscriptions).push(callback_client.self_().subscribe_im(Arc::new( + move |e| { + if let Some(inner) = weak.upgrade() { + inner.handle_im(e); + } + }, + ))); + let weak = Arc::downgrade(&inner); + let callback = crate::NetworkManagerLoginResponseCallback::from_handler( + move |success, _, _, _, response| { + if success && let Some(inner) = weak.upgrade() { + inner.load_login(response); + } + }, + ); + client.network().native_register_login_response_callback( + callback.clone(), + Some(vec!["buddy-list".into()]), + )?; + *mutex(&inner.login_callback) = Some(callback); + Ok(Self { + inner: client.install_friends_manager_inner(inner), + }) + } + fn ids(&self) -> (UUID, UUID) { + let mut c = (*self.inner.client).clone(); + let a = c.self_(); + (a.agent_id(), a.session_id()) + } + fn send(&self, p: &T, ty: PacketType) -> Result<(), Error> { + if self.inner.disposed.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let sim = self + .inner + .client + .network() + .current_sim() + .ok_or(Error::InvalidOperation)?; + let bytes = p.encode_packet()?; + let result = sim.native_send_packet_data( + bytes.clone(), + i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, + ty, + false, + ); + match result { + Err(Error::InvalidOperation) if !sim.connected() => Ok(()), + result => result, + } + } + pub fn friend_list(&self) -> HashMap { + read(&self.inner.friends).clone() + } + pub fn friend_requests(&self) -> HashMap { + read(&self.inner.requests).clone() + } + pub fn dispose(&self) -> Result<(), Error> { + self.inner.dispose(); + Ok(()) + } + pub fn subscribe_calling_card_offered( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.calling_card.subscribe(h) + } + pub fn subscribe_friend_found_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.found.subscribe(h) + } + pub fn subscribe_friend_names(&self, h: EventHandler) -> Subscription { + self.inner.events.names.subscribe(h) + } + pub fn subscribe_friend_offline(&self, h: EventHandler) -> Subscription { + self.inner.events.offline.subscribe(h) + } + pub fn subscribe_friend_online(&self, h: EventHandler) -> Subscription { + self.inner.events.online.subscribe(h) + } + pub fn subscribe_friend_rights_update( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.rights.subscribe(h) + } + pub fn subscribe_friendship_offered( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.offered.subscribe(h) + } + pub fn subscribe_friendship_response( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.response.subscribe(h) + } + pub fn subscribe_friendship_terminated( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.terminated.subscribe(h) + } + pub fn subscribe_friends_list_ready( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.ready.subscribe(h) + } + pub fn accept_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::AcceptFriendshipPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.transaction_block.transaction_id = transaction; + let mut folder = + crate::packets::AcceptFriendshipPacketFolderDataBlock::new_with_constructor()?; + folder.folder_id = self + .inner + .client + .inventory() + .find_folder_for_type_with_folder_type(FolderType::CallingCard)?; + p.folder_data.push(folder); + self.send(&p, PacketType::AcceptFriendship)?; + self.inner.accept(from); + Ok(()) + } + pub fn decline_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::DeclineFriendshipPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.transaction_block.transaction_id = transaction; + self.send(&p, PacketType::DeclineFriendship)?; + write(&self.inner.requests).remove(&from); + Ok(()) + } + pub async fn accept_friendship_via_cap( + &self, + from: UUID, + ct: Option, + ) -> Result<(), Error> { + self.friend_cap("AcceptFriendship", "POST", from, ct) + .await?; + self.inner.accept(from); + Ok(()) + } + pub async fn decline_friendship_via_cap( + &self, + from: UUID, + ct: Option, + ) -> Result<(), Error> { + self.friend_cap("DeclineFriendship", "DELETE", from, ct) + .await?; + write(&self.inner.requests).remove(&from); + Ok(()) + } + async fn friend_cap( + &self, + cap: &str, + method: &str, + from: UUID, + ct: Option, + ) -> Result<(), Error> { + let token = ct.unwrap_or_else(|| self.inner.client.cancellation_token()); + token.throw_if_cancellation_requested()?; + let caps = self + .inner + .client + .network() + .current_sim() + .and_then(|s| s.native_caps()) + .ok_or(Error::InvalidOperation)?; + let uri = caps + .capability_uri(cap.into())? + .ok_or(Error::InvalidOperation)?; + let mut url = reqwest::Url::parse(&uri.0).map_err(|_| Error::Argument)?; + url.query_pairs_mut().append_pair("from", &from.to_string()); + let http = self.inner.client.native_http_caps_client(); + let (response, bytes) = if method == "POST" { + http.post_with_uri_osd_format_osd_cancellation_token_i_progress( + libremetaverse_types::compat::Uri(url.into()), + OSDFormat::Xml, + OSD::Undefined, + token, + None, + ) + .await? + } else { + http.send_custom( + "DELETE", + libremetaverse_types::compat::Uri(url.into()), + BTreeMap::new(), + None, + Vec::new(), + token, + ) + .await? + }; + if !response.is_success_status_code() || bytes.len() > 1024 * 1024 { + return Err(Error::InvalidOperation); + } + if !bytes.is_empty() { + let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else { + return Err(Error::Argument); + }; + if !map + .get("success") + .and_then(|v| v.as_boolean().ok()) + .unwrap_or(false) + { + return Err(Error::InvalidOperation); + } + } + Ok(()) + } + pub fn offer_friendship_with_uuid(&self, id: UUID) -> Result<(), Error> { + self.offer_friendship_with_uuid_string(id, "Do ya wanna be my buddy?".into()) + } + pub fn offer_friendship_with_uuid_string( + &self, + id: UUID, + message: String, + ) -> Result<(), Error> { + if message.len() > 1024 { + return Err(Error::Argument); + } + let mut client = (*self.inner.client).clone(); + let region = client + .network() + .current_sim() + .map_or_else(UUID::zero, |s| s.id); + let me = client.self_(); + me.native_instant_message_full( + me.name(), + id, + message, + UUID::random()?, + InstantMessageDialog::FriendshipOffered, + crate::InstantMessageOnline::Offline, + me.sim_position(), + region, + Vec::new(), + ) + } + pub fn terminate_friendship(&self, id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::TerminateFriendshipPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.ex_block.other_id = id; + self.send(&p, PacketType::TerminateFriendship)?; + write(&self.inner.friends).remove(&id); + Ok(()) + } + pub fn grant_rights(&self, id: UUID, rights: FriendRights) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::GrantUserRightsPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + let mut b = crate::packets::GrantUserRightsPacketRightsBlock::new_with_constructor()?; + b.agent_related = id; + b.related_rights = rights.0; + p.rights.push(b); + self.send(&p, PacketType::GrantUserRights) + } + pub fn map_friend(&self, id: UUID) -> Result<(), Error> { + let (agent, _) = self.ids(); + let mut p = crate::packets::FindAgentPacket::new_with_constructor()?; + p.agent_block.hunter = agent; + p.agent_block.prey = id; + let b = crate::packets::FindAgentPacketLocationBlockBlock::new_with_constructor()?; + p.location_block.push(b); + self.send(&p, PacketType::FindAgent) + } + pub fn track_friend(&self, id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::TrackAgentPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.target_data.prey_id = id; + self.send(&p, PacketType::TrackAgent) + } + pub fn request_online_notification(&self, id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::GenericMessagePacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.method_data.method = b"requestonlinenotification".to_vec(); + let mut b = crate::packets::GenericMessagePacketParamListBlock::new_with_constructor()?; + b.parameter = id.to_string().into_bytes(); + p.param_list.push(b); + self.send(&p, PacketType::GenericMessage) + } + pub fn give_calling_card(&self, id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::OfferCallingCardPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.agent_block.dest_id = id; + p.agent_block.transaction_id = UUID::random()?; + self.send(&p, PacketType::OfferCallingCard) + } + pub fn accept_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::AcceptCallingCardPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.transaction_block.transaction_id = transaction; + let mut b = crate::packets::AcceptCallingCardPacketFolderDataBlock::new_with_constructor()?; + b.folder_id = self + .inner + .client + .inventory() + .find_folder_for_type_with_folder_type(FolderType::CallingCard)?; + p.folder_data.push(b); + self.send(&p, PacketType::AcceptCallingCard) + } + pub fn decline_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut p = crate::packets::DeclineCallingCardPacket::new_with_constructor()?; + p.agent_data.agent_id = agent; + p.agent_data.session_id = session; + p.transaction_block.transaction_id = transaction; + self.send(&p, PacketType::DeclineCallingCard) + } + pub fn on_find_agent_reply_handler( + &self, + _sender: Option, + e: crate::PacketReceivedEventArgs, + ) -> Result<(), Error> { + // The native network pipeline dispatches the payload-bearing raw packet + // before its metadata-only compatibility callback. `handle_packet` + // performs the decode and event emission; this public callback retains + // the upstream type guard without emitting a duplicate event. + if e.packet().type_ == PacketType::FindAgent { + Ok(()) + } else { + Err(Error::Argument) + } + } +} + +impl FriendsManagerInner { + fn accept(&self, id: UUID) { + let mut friends = write(&self.friends); + if friends.len() < MAX_FRIENDS || friends.contains_key(&id) { + friends.entry(id).or_insert_with(|| { + FriendInfo::native_new( + id, + FriendRights::CAN_SEE_ONLINE, + FriendRights::CAN_SEE_ONLINE, + ) + }); + } + drop(friends); + write(&self.requests).remove(&id); + } + fn dispose(&self) { + if self.disposed.swap(true, Ordering::AcqRel) { + return; + } + mutex(&self.subscriptions).clear(); + if let Some(cb) = mutex(&self.login_callback).take() { + let _ = self + .client + .network() + .native_unregister_login_response_callback(&cb); + } + } + fn load_login(&self, response: Option) { + let Some(response) = response else { return }; + let mut friends = write(&self.friends); + for b in response.buddy_list().into_iter().take(MAX_FRIENDS) { + if let Ok(id) = UUID::parse(b.buddy_id.chars().take(36).collect::()) { + friends.entry(id).or_insert_with(|| { + FriendInfo::native_new( + id, + FriendRights(b.buddy_rights_given), + FriendRights(b.buddy_rights_has), + ) + }); + } + } + let count = i32::try_from(friends.len()).unwrap_or(i32::MAX); + drop(friends); + self.events.ready.emit(FriendsReadyEventArgs { count }); + } + fn handle_im(&self, e: crate::InstantMessageEventArgs) { + let im = e.im(); + match im.dialog { + InstantMessageDialog::FriendshipOffered => { + let mut requests = write(&self.requests); + if requests.len() < MAX_FRIENDS || requests.contains_key(&im.from_agent_id) { + requests.insert(im.from_agent_id, im.im_session_id); + } + drop(requests); + self.events.offered.emit(FriendshipOfferedEventArgs { + agent_id: im.from_agent_id, + agent_name: im.from_agent_name, + im_session_id: im.im_session_id, + }); + } + InstantMessageDialog::FriendshipAccepted => { + let mut f = FriendInfo::native_new( + im.from_agent_id, + FriendRights::CAN_SEE_ONLINE, + FriendRights::CAN_SEE_ONLINE, + ); + f.name.clone_from(&im.from_agent_name); + f.online = true; + write(&self.friends).insert(f.id, f.clone()); + self.events.response.emit(FriendshipResponseEventArgs { + agent_id: f.id, + agent_name: f.name, + accepted: true, + }); + } + InstantMessageDialog::FriendshipDeclined => { + self.events.response.emit(FriendshipResponseEventArgs { + agent_id: im.from_agent_id, + agent_name: im.from_agent_name, + accepted: false, + }); + } + _ => {} + } + } + #[allow( + clippy::too_many_lines, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + clippy::cast_precision_loss + )] + fn handle_packet(&self, e: RawPacketReceivedEventArgs) { + if self.disposed.load(Ordering::Acquire) || e.data.len() > 8 * 1024 * 1024 { + return; + } + let mut pos = 0; + match e.packet_type { + PacketType::OnlineNotification | PacketType::OfflineNotification => { + let online = e.packet_type == PacketType::OnlineNotification; + let ids = if online { + crate::packets::OnlineNotificationPacket::new_from_bytes(&e.data, &mut pos).map( + |p| { + p.agent_block + .into_iter() + .map(|b| b.agent_id) + .collect::>() + }, + ) + } else { + crate::packets::OfflineNotificationPacket::new_from_bytes(&e.data, &mut pos) + .map(|p| { + p.agent_block + .into_iter() + .map(|b| b.agent_id) + .collect::>() + }) + }; + if let Ok(ids) = ids { + for id in ids.into_iter().take(MAX_FRIENDS) { + let event = { + let mut fs = write(&self.friends); + let f = fs.entry(id).or_insert_with(|| { + FriendInfo::native_new( + id, + FriendRights::CAN_SEE_ONLINE, + FriendRights::CAN_SEE_ONLINE, + ) + }); + let changed = f.online != online; + f.online = online; + changed.then(|| f.clone()) + }; + if let Some(friend) = event { + if online { + self.events.online.emit(FriendInfoEventArgs { friend }); + } else { + self.events.offline.emit(FriendInfoEventArgs { friend }); + } + } + } + } + } + PacketType::TerminateFriendship => { + if let Ok(p) = + crate::packets::TerminateFriendshipPacket::new_from_bytes(&e.data, &mut pos) + { + let id = p.ex_block.other_id; + let name = write(&self.friends) + .remove(&id) + .map_or_else(String::new, |f| f.name); + self.events.terminated.emit(FriendshipTerminatedEventArgs { + agent_id: id, + agent_name: name, + }); + } + } + PacketType::ChangeUserRights => { + if let Ok(p) = + crate::packets::ChangeUserRightsPacket::new_from_bytes(&e.data, &mut pos) + { + let self_id = { + let mut client = (*self.client).clone(); + client.self_().agent_id() + }; + for b in p.rights.into_iter().take(MAX_FRIENDS) { + let id = if b.agent_related == self_id { + p.agent_data.agent_id + } else { + b.agent_related + }; + let event = { + let mut fs = write(&self.friends); + fs.get_mut(&id).map(|f| { + if b.agent_related == id { + f.their_rights = FriendRights(b.related_rights); + } else { + f.my_rights = FriendRights(b.related_rights); + } + f.clone() + }) + }; + if let Some(friend) = event { + self.events.rights.emit(FriendInfoEventArgs { friend }); + } + } + } + } + PacketType::FindAgent => { + if let Ok(p) = crate::packets::FindAgentPacket::new_from_bytes(&e.data, &mut pos) + && let Some(loc) = p.location_block.first() + { + let gx = loc.global_x.max(0.0) as u32; + let gy = loc.global_y.max(0.0) as u32; + let handle = (u64::from(gx & !255) << 32) | u64::from(gy & !255); + self.events.found.emit(FriendFoundReplyEventArgs { + agent_id: p.agent_block.prey, + region_handle: handle, + location: Vector3 { + x: (gx & 255) as f32, + y: (gy & 255) as f32, + z: 0.0, + }, + }); + } + } + PacketType::OfferCallingCard => { + if let Ok(p) = + crate::packets::OfferCallingCardPacket::new_from_bytes(&e.data, &mut pos) + { + let id = p.agent_data.agent_id; + let name = read(&self.friends).get(&id).map_or_else( + || id.to_string(), + |f| { + if f.name.is_empty() { + id.to_string() + } else { + f.name.clone() + } + }, + ); + self.events.calling_card.emit(CallingCardOfferedEventArgs { + agent_id: id, + agent_name: name, + transaction_id: p.agent_block.transaction_id, + }); + } + } + PacketType::UUIDNameReply => { + if let Ok(p) = + crate::packets::UUIDNameReplyPacket::new_from_bytes(&e.data, &mut pos) + { + let mut changed = HashMap::new(); + { + let mut fs = write(&self.friends); + for b in p.uuid_name_block.into_iter().take(MAX_FRIENDS) { + if let Some(f) = fs.get_mut(&b.id) { + let name = format!( + "{} {}", + wire_string(&b.first_name), + wire_string(&b.last_name) + ) + .trim() + .to_owned(); + if f.name != name { + f.name.clone_from(&name); + changed.insert(b.id, name); + } + } + } + } + if !changed.is_empty() { + self.events + .names + .emit(FriendNamesEventArgs { names: changed }); + } + } + } + _ => {} + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + + #[test] + fn rights_enforce_online_before_map() { + let mut f = FriendInfo::native_new(UUID::zero(), FriendRights::NONE, FriendRights::NONE); + f.set_can_see_me_on_map(true); + assert!(!f.can_see_me_on_map()); + f.set_can_see_me_online(true); + f.set_can_see_me_on_map(true); + assert!(f.can_see_me_on_map()); + f.set_can_see_me_online(false); + assert!(!f.can_see_me_on_map()); + } + + #[test] + fn presence_packet_updates_state_before_callback() { + let client = GridClient::new().unwrap(); + let manager = FriendsManager::native_new(Arc::new(client.clone())).unwrap(); + let friend_id = + UUID::new_with_string("77777777-7777-7777-7777-777777777777".into()).unwrap(); + write(&manager.inner.friends).insert( + friend_id, + FriendInfo::native_new(friend_id, FriendRights::NONE, FriendRights::NONE), + ); + let callback_manager = manager.clone(); + let observed = Arc::new(AtomicBool::new(false)); + let observed_copy = Arc::clone(&observed); + let _subscription = manager.subscribe_friend_online(Arc::new(move |event| { + assert!(callback_manager.friend_list()[&event.friend().uuid()].is_online()); + observed_copy.store(true, Ordering::Release); + })); + let mut packet = crate::packets::OnlineNotificationPacket::new_with_constructor().unwrap(); + let mut row = + crate::packets::OnlineNotificationPacketAgentBlockBlock::new_with_constructor() + .unwrap(); + row.agent_id = friend_id; + packet.agent_block = vec![row]; + let simulator = crate::Simulator::native_new( + client, + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13973), + 0, + Some(256), + Some(256), + ) + .unwrap(); + manager.inner.handle_packet(RawPacketReceivedEventArgs { + packet_type: PacketType::OnlineNotification, + data: packet.encode_packet().unwrap(), + simulator, + }); + assert!(observed.load(Ordering::Acquire)); + } +} diff --git a/crates/libremetaverse/src/generated.rs b/crates/libremetaverse/src/generated.rs index 334575f..1152cbc 100644 --- a/crates/libremetaverse/src/generated.rs +++ b/crates/libremetaverse/src/generated.rs @@ -4572,37 +4572,11 @@ impl BakeType { pub use crate::agent_messages::BalanceEventArgs; /// C# type: `T:LibreMetaverse.BannedAgentsEventArgs`. -pub struct BannedAgentsEventArgs; -impl BannedAgentsEventArgs { - /// C# member: `M:LibreMetaverse.BannedAgentsEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.DateTime})`. - pub fn new( - group_id: libremetaverse_types::UUID, - success: bool, - banned_agents: Option< - std::collections::HashMap, - >, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.BannedAgentsEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.DateTime})", - ) - } - /// C# member: `P:LibreMetaverse.BannedAgentsEventArgs.BannedAgents`. - pub fn banned_agents( - &self, - ) -> Option> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.BannedAgentsEventArgs.BannedAgents" - ) - } - /// C# member: `P:LibreMetaverse.BannedAgentsEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.BannedAgentsEventArgs.GroupID") - } - /// C# member: `P:LibreMetaverse.BannedAgentsEventArgs.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.BannedAgentsEventArgs.Success") - } -} +/// C# member: `M:LibreMetaverse.BannedAgentsEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.DateTime})`. +/// C# member: `P:LibreMetaverse.BannedAgentsEventArgs.BannedAgents`. +/// C# member: `P:LibreMetaverse.BannedAgentsEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.BannedAgentsEventArgs.Success`. +pub use crate::group_manager::BannedAgentsEventArgs; /// C# type: `T:LibreMetaverse.BinBVHAnimationReader`. pub use crate::animation::BinBVHAnimationReader; @@ -4773,37 +4747,11 @@ pub enum Bumpiness { } /// C# type: `T:LibreMetaverse.CallingCardOfferedEventArgs`. -pub struct CallingCardOfferedEventArgs; -impl CallingCardOfferedEventArgs { - /// C# member: `M:LibreMetaverse.CallingCardOfferedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)`. - pub fn new( - agent_id: libremetaverse_types::UUID, - agent_name: String, - transaction_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.CallingCardOfferedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.CallingCardOfferedEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CallingCardOfferedEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.CallingCardOfferedEventArgs.AgentName`. - pub fn agent_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CallingCardOfferedEventArgs.AgentName" - ) - } - /// C# member: `P:LibreMetaverse.CallingCardOfferedEventArgs.TransactionID`. - pub fn transaction_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.CallingCardOfferedEventArgs.TransactionID" - ) - } -} +/// C# member: `M:LibreMetaverse.CallingCardOfferedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.CallingCardOfferedEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.CallingCardOfferedEventArgs.AgentName`. +/// C# member: `P:LibreMetaverse.CallingCardOfferedEventArgs.TransactionID`. +pub use crate::friends_manager::CallingCardOfferedEventArgs; /// C# type: `T:LibreMetaverse.CameraConstraintEventArgs`. /// C# member: `M:LibreMetaverse.CameraConstraintEventArgs.#ctor(LibreMetaverse.Vector4)`. @@ -5283,23 +5231,9 @@ impl CoordinateFrame { } /// C# type: `T:LibreMetaverse.CurrentGroupsEventArgs`. -pub struct CurrentGroupsEventArgs; -impl CurrentGroupsEventArgs { - /// C# member: `M:LibreMetaverse.CurrentGroupsEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.Group})`. - pub fn new( - groups: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.CurrentGroupsEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.Group})", - ) - } - /// C# member: `P:LibreMetaverse.CurrentGroupsEventArgs.Groups`. - pub fn groups( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.CurrentGroupsEventArgs.Groups") - } -} +/// C# member: `M:LibreMetaverse.CurrentGroupsEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.Group})`. +/// C# member: `P:LibreMetaverse.CurrentGroupsEventArgs.Groups`. +pub use crate::group_manager::CurrentGroupsEventArgs; /// C# type: `T:LibreMetaverse.DeRezDestination`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -7050,166 +6984,36 @@ pub use crate::inventory_manager::FolderUpdatedEventArgs; pub use crate::parcel_manager::ForceSelectObjectsReplyEventArgs; /// C# type: `T:LibreMetaverse.FriendFoundReplyEventArgs`. -pub struct FriendFoundReplyEventArgs; -impl FriendFoundReplyEventArgs { - /// C# member: `M:LibreMetaverse.FriendFoundReplyEventArgs.#ctor(LibreMetaverse.UUID,System.UInt64,LibreMetaverse.Vector3)`. - pub fn new( - agent_id: libremetaverse_types::UUID, - region_handle: u64, - location: libremetaverse_types::Vector3, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendFoundReplyEventArgs.#ctor(LibreMetaverse.UUID,System.UInt64,LibreMetaverse.Vector3)", - ) - } - /// C# member: `P:LibreMetaverse.FriendFoundReplyEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendFoundReplyEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.FriendFoundReplyEventArgs.Location`. - pub fn location(&self) -> libremetaverse_types::Vector3 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendFoundReplyEventArgs.Location" - ) - } - /// C# member: `P:LibreMetaverse.FriendFoundReplyEventArgs.RegionHandle`. - pub fn region_handle(&self) -> u64 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendFoundReplyEventArgs.RegionHandle" - ) - } -} +/// C# member: `M:LibreMetaverse.FriendFoundReplyEventArgs.#ctor(LibreMetaverse.UUID,System.UInt64,LibreMetaverse.Vector3)`. +/// C# member: `P:LibreMetaverse.FriendFoundReplyEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.FriendFoundReplyEventArgs.Location`. +/// C# member: `P:LibreMetaverse.FriendFoundReplyEventArgs.RegionHandle`. +pub use crate::friends_manager::FriendFoundReplyEventArgs; /// C# type: `T:LibreMetaverse.FriendInfo`. -pub struct FriendInfo; -impl FriendInfo { - /// C# member: `M:LibreMetaverse.FriendInfo.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.FriendInfo.ToString") - } - /// C# member: `P:LibreMetaverse.FriendInfo.CanModifyMyObjects`. - pub fn can_modify_my_objects(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanModifyMyObjects") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.CanModifyMyObjects`. - pub fn set_can_modify_my_objects(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanModifyMyObjects") - } - /// C# member: `P:LibreMetaverse.FriendInfo.CanModifyTheirObjects`. - pub fn can_modify_their_objects(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendInfo.CanModifyTheirObjects" - ) - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.CanModifyTheirObjects`. - pub fn set_can_modify_their_objects(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendInfo.CanModifyTheirObjects" - ) - } - /// C# member: `P:LibreMetaverse.FriendInfo.CanSeeMeOnMap`. - pub fn can_see_me_on_map(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeMeOnMap") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.CanSeeMeOnMap`. - pub fn set_can_see_me_on_map(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeMeOnMap") - } - /// C# member: `P:LibreMetaverse.FriendInfo.CanSeeMeOnline`. - pub fn can_see_me_online(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeMeOnline") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.CanSeeMeOnline`. - pub fn set_can_see_me_online(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeMeOnline") - } - /// C# member: `P:LibreMetaverse.FriendInfo.CanSeeThemOnMap`. - pub fn can_see_them_on_map(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeThemOnMap") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.CanSeeThemOnMap`. - pub fn set_can_see_them_on_map(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeThemOnMap") - } - /// C# member: `P:LibreMetaverse.FriendInfo.CanSeeThemOnline`. - pub fn can_see_them_online(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeThemOnline") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.CanSeeThemOnline`. - pub fn set_can_see_them_online(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.CanSeeThemOnline") - } - /// C# member: `P:LibreMetaverse.FriendInfo.IsOnline`. - pub fn is_online(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.IsOnline") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.IsOnline`. - pub fn set_is_online(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.IsOnline") - } - /// C# member: `P:LibreMetaverse.FriendInfo.MyFriendRights`. - pub fn my_friend_rights(&self) -> libremetaverse::FriendRights { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.MyFriendRights") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.MyFriendRights`. - pub fn set_my_friend_rights(&mut self, value: libremetaverse::FriendRights) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.MyFriendRights") - } - /// C# member: `P:LibreMetaverse.FriendInfo.Name`. - pub fn name(&self) -> String { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.Name") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.Name`. - pub fn set_name(&mut self, value: String) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.Name") - } - /// C# member: `P:LibreMetaverse.FriendInfo.TheirFriendRights`. - pub fn their_friend_rights(&self) -> libremetaverse::FriendRights { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.TheirFriendRights") - } - /// Setter for C# member: `P:LibreMetaverse.FriendInfo.TheirFriendRights`. - pub fn set_their_friend_rights(&mut self, value: libremetaverse::FriendRights) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.TheirFriendRights") - } - /// C# member: `P:LibreMetaverse.FriendInfo.UUID`. - pub fn uuid(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfo.UUID") - } -} +/// C# member: `M:LibreMetaverse.FriendInfo.ToString`. +/// C# member: `P:LibreMetaverse.FriendInfo.CanModifyMyObjects`. +/// C# member: `P:LibreMetaverse.FriendInfo.CanModifyTheirObjects`. +/// C# member: `P:LibreMetaverse.FriendInfo.CanSeeMeOnMap`. +/// C# member: `P:LibreMetaverse.FriendInfo.CanSeeMeOnline`. +/// C# member: `P:LibreMetaverse.FriendInfo.CanSeeThemOnMap`. +/// C# member: `P:LibreMetaverse.FriendInfo.CanSeeThemOnline`. +/// C# member: `P:LibreMetaverse.FriendInfo.IsOnline`. +/// C# member: `P:LibreMetaverse.FriendInfo.MyFriendRights`. +/// C# member: `P:LibreMetaverse.FriendInfo.Name`. +/// C# member: `P:LibreMetaverse.FriendInfo.TheirFriendRights`. +/// C# member: `P:LibreMetaverse.FriendInfo.UUID`. +pub use crate::friends_manager::FriendInfo; /// C# type: `T:LibreMetaverse.FriendInfoEventArgs`. -pub struct FriendInfoEventArgs; -impl FriendInfoEventArgs { - /// C# member: `M:LibreMetaverse.FriendInfoEventArgs.#ctor(LibreMetaverse.FriendInfo)`. - pub fn new(friend: libremetaverse::FriendInfo) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendInfoEventArgs.#ctor(LibreMetaverse.FriendInfo)", - ) - } - /// C# member: `P:LibreMetaverse.FriendInfoEventArgs.Friend`. - pub fn friend(&self) -> libremetaverse::FriendInfo { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendInfoEventArgs.Friend") - } -} +/// C# member: `M:LibreMetaverse.FriendInfoEventArgs.#ctor(LibreMetaverse.FriendInfo)`. +/// C# member: `P:LibreMetaverse.FriendInfoEventArgs.Friend`. +pub use crate::friends_manager::FriendInfoEventArgs; /// C# type: `T:LibreMetaverse.FriendNamesEventArgs`. -pub struct FriendNamesEventArgs; -impl FriendNamesEventArgs { - /// C# member: `M:LibreMetaverse.FriendNamesEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.String})`. - pub fn new( - names: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendNamesEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.String})", - ) - } - /// C# member: `P:LibreMetaverse.FriendNamesEventArgs.Names`. - pub fn names(&self) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendNamesEventArgs.Names") - } -} +/// C# member: `M:LibreMetaverse.FriendNamesEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.String})`. +/// C# member: `P:LibreMetaverse.FriendNamesEventArgs.Names`. +pub use crate::friends_manager::FriendNamesEventArgs; /// C# type: `T:LibreMetaverse.FriendRights`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -7227,360 +7031,60 @@ impl FriendRights { } /// C# type: `T:LibreMetaverse.FriendsManager`. -pub struct FriendsManager; -impl FriendsManager { - /// C# member: `E:LibreMetaverse.FriendsManager.CallingCardOffered`. - pub fn subscribe_calling_card_offered( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::CallingCardOfferedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.FriendsManager.CallingCardOffered" - ) - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendFoundReply`. - pub fn subscribe_friend_found_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::FriendFoundReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.FriendsManager.FriendFoundReply") - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendNames`. - pub fn subscribe_friend_names( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.FriendsManager.FriendNames") - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendOffline`. - pub fn subscribe_friend_offline( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.FriendsManager.FriendOffline") - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendOnline`. - pub fn subscribe_friend_online( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.FriendsManager.FriendOnline") - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendRightsUpdate`. - pub fn subscribe_friend_rights_update( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.FriendsManager.FriendRightsUpdate" - ) - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendshipOffered`. - pub fn subscribe_friendship_offered( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::FriendshipOfferedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.FriendsManager.FriendshipOffered" - ) - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendshipResponse`. - pub fn subscribe_friendship_response( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::FriendshipResponseEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.FriendsManager.FriendshipResponse" - ) - } - /// C# member: `E:LibreMetaverse.FriendsManager.FriendshipTerminated`. - pub fn subscribe_friendship_terminated( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::FriendshipTerminatedEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.FriendsManager.FriendshipTerminated" - ) - } - /// C# member: `E:LibreMetaverse.FriendsManager.friendsListReady`. - pub fn subscribe_friends_list_ready( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.FriendsManager.friendsListReady") - } - /// C# member: `M:LibreMetaverse.FriendsManager.AcceptCallingCard(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn accept_calling_card( - &self, - from_agent_id: libremetaverse_types::UUID, - transaction_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.AcceptCallingCard(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.AcceptFriendship(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn accept_friendship( - &self, - from_agent_id: libremetaverse_types::UUID, - im_session_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.AcceptFriendship(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.AcceptFriendshipViaCapAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn accept_friendship_via_cap( - &self, - from_agent_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.AcceptFriendshipViaCapAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.DeclineCallingCard(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn decline_calling_card( - &self, - from_agent_id: libremetaverse_types::UUID, - transaction_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.DeclineCallingCard(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.DeclineFriendship(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn decline_friendship( - &self, - from_agent_id: libremetaverse_types::UUID, - im_session_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.DeclineFriendship(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.DeclineFriendshipViaCapAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn decline_friendship_via_cap( - &self, - from_agent_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.DeclineFriendshipViaCapAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.Dispose`. - pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.FriendsManager.Dispose") - } - /// C# member: `M:LibreMetaverse.FriendsManager.GiveCallingCard(LibreMetaverse.UUID)`. - pub fn give_calling_card( - &self, - agent_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.GiveCallingCard(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.GrantRights(LibreMetaverse.UUID,LibreMetaverse.FriendRights)`. - pub fn grant_rights( - &self, - friend_id: libremetaverse_types::UUID, - rights: libremetaverse::FriendRights, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.GrantRights(LibreMetaverse.UUID,LibreMetaverse.FriendRights)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.MapFriend(LibreMetaverse.UUID)`. - pub fn map_friend(&self, friend_id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.MapFriend(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.OfferFriendship(LibreMetaverse.UUID)`. - pub fn offer_friendship_with_uuid( - &self, - agent_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.OfferFriendship(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.OfferFriendship(LibreMetaverse.UUID,System.String)`. - pub fn offer_friendship_with_uuid_string( - &self, - agent_id: libremetaverse_types::UUID, - message: String, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.OfferFriendship(LibreMetaverse.UUID,System.String)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.OnFindAgentReplyHandler(System.Object,LibreMetaverse.PacketReceivedEventArgs)`. - pub fn on_find_agent_reply_handler( - &self, - sender: Option, - e: libremetaverse::PacketReceivedEventArgs, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.OnFindAgentReplyHandler(System.Object,LibreMetaverse.PacketReceivedEventArgs)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.RequestOnlineNotification(LibreMetaverse.UUID)`. - pub fn request_online_notification( - &self, - friend_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.RequestOnlineNotification(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.TerminateFriendship(LibreMetaverse.UUID)`. - pub fn terminate_friendship( - &self, - agent_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.TerminateFriendship(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.FriendsManager.TrackFriend(LibreMetaverse.UUID)`. - pub fn track_friend(&self, friend_id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsManager.TrackFriend(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.FriendsManager.FriendList`. - pub fn friend_list( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendsManager.FriendList") - } - /// C# member: `P:LibreMetaverse.FriendsManager.FriendRequests`. - pub fn friend_requests( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendsManager.FriendRequests") - } -} +/// C# member: `E:LibreMetaverse.FriendsManager.CallingCardOffered`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendFoundReply`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendNames`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendOffline`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendOnline`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendRightsUpdate`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendshipOffered`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendshipResponse`. +/// C# member: `E:LibreMetaverse.FriendsManager.FriendshipTerminated`. +/// C# member: `E:LibreMetaverse.FriendsManager.friendsListReady`. +/// C# member: `M:LibreMetaverse.FriendsManager.AcceptCallingCard(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.AcceptFriendship(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.AcceptFriendshipViaCapAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.FriendsManager.DeclineCallingCard(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.DeclineFriendship(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.DeclineFriendshipViaCapAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.FriendsManager.Dispose`. +/// C# member: `M:LibreMetaverse.FriendsManager.GiveCallingCard(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.GrantRights(LibreMetaverse.UUID,LibreMetaverse.FriendRights)`. +/// C# member: `M:LibreMetaverse.FriendsManager.MapFriend(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.OfferFriendship(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.OfferFriendship(LibreMetaverse.UUID,System.String)`. +/// C# member: `M:LibreMetaverse.FriendsManager.OnFindAgentReplyHandler(System.Object,LibreMetaverse.PacketReceivedEventArgs)`. +/// C# member: `M:LibreMetaverse.FriendsManager.RequestOnlineNotification(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.TerminateFriendship(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.FriendsManager.TrackFriend(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.FriendsManager.FriendList`. +/// C# member: `P:LibreMetaverse.FriendsManager.FriendRequests`. +pub use crate::friends_manager::FriendsManager; /// C# type: `T:LibreMetaverse.FriendsReadyEventArgs`. -pub struct FriendsReadyEventArgs; -impl FriendsReadyEventArgs { - /// C# member: `M:LibreMetaverse.FriendsReadyEventArgs.#ctor(System.Int32)`. - pub fn new(count: i32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendsReadyEventArgs.#ctor(System.Int32)", - ) - } - /// C# member: `P:LibreMetaverse.FriendsReadyEventArgs.Count`. - pub fn count(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.FriendsReadyEventArgs.Count") - } -} +/// C# member: `M:LibreMetaverse.FriendsReadyEventArgs.#ctor(System.Int32)`. +/// C# member: `P:LibreMetaverse.FriendsReadyEventArgs.Count`. +pub use crate::friends_manager::FriendsReadyEventArgs; /// C# type: `T:LibreMetaverse.FriendshipOfferedEventArgs`. -pub struct FriendshipOfferedEventArgs; -impl FriendshipOfferedEventArgs { - /// C# member: `M:LibreMetaverse.FriendshipOfferedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)`. - pub fn new( - agent_id: libremetaverse_types::UUID, - agent_name: String, - im_session_id: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendshipOfferedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.FriendshipOfferedEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipOfferedEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.FriendshipOfferedEventArgs.AgentName`. - pub fn agent_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipOfferedEventArgs.AgentName" - ) - } - /// C# member: `P:LibreMetaverse.FriendshipOfferedEventArgs.SessionID`. - pub fn session_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipOfferedEventArgs.SessionID" - ) - } -} +/// C# member: `M:LibreMetaverse.FriendshipOfferedEventArgs.#ctor(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.FriendshipOfferedEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.FriendshipOfferedEventArgs.AgentName`. +/// C# member: `P:LibreMetaverse.FriendshipOfferedEventArgs.SessionID`. +pub use crate::friends_manager::FriendshipOfferedEventArgs; /// C# type: `T:LibreMetaverse.FriendshipResponseEventArgs`. -pub struct FriendshipResponseEventArgs; -impl FriendshipResponseEventArgs { - /// C# member: `M:LibreMetaverse.FriendshipResponseEventArgs.#ctor(LibreMetaverse.UUID,System.String,System.Boolean)`. - pub fn new( - agent_id: libremetaverse_types::UUID, - agent_name: String, - accepted: bool, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendshipResponseEventArgs.#ctor(LibreMetaverse.UUID,System.String,System.Boolean)", - ) - } - /// C# member: `P:LibreMetaverse.FriendshipResponseEventArgs.Accepted`. - pub fn accepted(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipResponseEventArgs.Accepted" - ) - } - /// C# member: `P:LibreMetaverse.FriendshipResponseEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipResponseEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.FriendshipResponseEventArgs.AgentName`. - pub fn agent_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipResponseEventArgs.AgentName" - ) - } -} +/// C# member: `M:LibreMetaverse.FriendshipResponseEventArgs.#ctor(LibreMetaverse.UUID,System.String,System.Boolean)`. +/// C# member: `P:LibreMetaverse.FriendshipResponseEventArgs.Accepted`. +/// C# member: `P:LibreMetaverse.FriendshipResponseEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.FriendshipResponseEventArgs.AgentName`. +pub use crate::friends_manager::FriendshipResponseEventArgs; /// C# type: `T:LibreMetaverse.FriendshipTerminatedEventArgs`. -pub struct FriendshipTerminatedEventArgs; -impl FriendshipTerminatedEventArgs { - /// C# member: `M:LibreMetaverse.FriendshipTerminatedEventArgs.#ctor(LibreMetaverse.UUID,System.String)`. - pub fn new( - agent_id: libremetaverse_types::UUID, - agent_name: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.FriendshipTerminatedEventArgs.#ctor(LibreMetaverse.UUID,System.String)", - ) - } - /// C# member: `P:LibreMetaverse.FriendshipTerminatedEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipTerminatedEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.FriendshipTerminatedEventArgs.AgentName`. - pub fn agent_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.FriendshipTerminatedEventArgs.AgentName" - ) - } -} +/// C# member: `M:LibreMetaverse.FriendshipTerminatedEventArgs.#ctor(LibreMetaverse.UUID,System.String)`. +/// C# member: `P:LibreMetaverse.FriendshipTerminatedEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.FriendshipTerminatedEventArgs.AgentName`. +pub use crate::friends_manager::FriendshipTerminatedEventArgs; /// C# type: `T:LibreMetaverse.GLTFMaterialUpdate`. /// C# member: `M:LibreMetaverse.GLTFMaterialUpdate.#ctor`. @@ -7771,11 +7275,14 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Friends`. pub fn friends(&self) -> libremetaverse::FriendsManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Friends") + /* native client-core implementation */ + self.native_friends() + .unwrap_or_else(|_| panic!("failed to construct FriendsManager")) } /// Setter for C# member: `P:LibreMetaverse.GridClient.Friends`. pub fn set_friends(&mut self, value: libremetaverse::FriendsManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Friends") + /* native client-core implementation */ + self.native_set_friends(value) } /// C# member: `P:LibreMetaverse.GridClient.Grid`. pub fn grid(&self) -> libremetaverse::GridManager { @@ -7790,11 +7297,13 @@ impl GridClient { } /// C# member: `P:LibreMetaverse.GridClient.Groups`. pub fn groups(&self) -> libremetaverse::GroupManager { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Groups") + /* native client-core implementation */ + self.native_groups() } /// Setter for C# member: `P:LibreMetaverse.GridClient.Groups`. pub fn set_groups(&mut self, value: libremetaverse::GroupManager) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Groups") + /* native client-core implementation */ + self.native_set_groups(value) } /// C# member: `P:LibreMetaverse.GridClient.HttpCapsClient`. pub fn http_caps_client(&self) -> libremetaverse::HttpCapsClient { @@ -8064,6 +7573,7 @@ pub use crate::grid_manager::GridRegion; pub use crate::grid_manager::GridRegionEventArgs; /// C# type: `T:LibreMetaverse.Group`. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Group { /// C# member: `F:LibreMetaverse.Group.AcceptNotices`. pub accept_notices: bool, @@ -8107,25 +7617,24 @@ pub struct Group { impl Group { /// C# member: `M:LibreMetaverse.Group.Equals(LibreMetaverse.Group)`. pub fn equals_with_group(&self, other: libremetaverse::Group) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.Group.Equals(LibreMetaverse.Group)" - ) + self.native_equals_group(&other) } /// C# member: `M:LibreMetaverse.Group.Equals(System.Object)`. pub fn equals_with_object(&self, obj: Option) -> bool { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Group.Equals(System.Object)") + self.native_equals_object(obj) } /// C# member: `M:LibreMetaverse.Group.GetHashCode`. pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Group.GetHashCode") + self.native_hash_code() } /// C# member: `M:LibreMetaverse.Group.ToString`. pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.Group.ToString") + self.native_to_string() } } /// C# type: `T:LibreMetaverse.GroupAccountSummary`. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GroupAccountSummary { /// C# member: `F:LibreMetaverse.GroupAccountSummary.Balance`. pub balance: i32, @@ -8169,30 +7678,10 @@ pub struct GroupAccountSummary { impl GroupAccountSummary {} /// C# type: `T:LibreMetaverse.GroupAccountSummaryReplyEventArgs`. -pub struct GroupAccountSummaryReplyEventArgs; -impl GroupAccountSummaryReplyEventArgs { - /// C# member: `M:LibreMetaverse.GroupAccountSummaryReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.GroupAccountSummary)`. - pub fn new( - group_id: libremetaverse_types::UUID, - summary: libremetaverse::GroupAccountSummary, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupAccountSummaryReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.GroupAccountSummary)", - ) - } - /// C# member: `P:LibreMetaverse.GroupAccountSummaryReplyEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupAccountSummaryReplyEventArgs.GroupID" - ) - } - /// C# member: `P:LibreMetaverse.GroupAccountSummaryReplyEventArgs.Summary`. - pub fn summary(&self) -> libremetaverse::GroupAccountSummary { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupAccountSummaryReplyEventArgs.Summary" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupAccountSummaryReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.GroupAccountSummary)`. +/// C# member: `P:LibreMetaverse.GroupAccountSummaryReplyEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupAccountSummaryReplyEventArgs.Summary`. +pub use crate::group_manager::GroupAccountSummaryReplyEventArgs; /// C# type: `T:LibreMetaverse.GroupBanAction`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -8213,543 +7702,82 @@ pub enum GroupBanAction { pub use crate::agent_messages::GroupChatJoinedEventArgs; /// C# type: `T:LibreMetaverse.GroupCreatedReplyEventArgs`. -pub struct GroupCreatedReplyEventArgs; -impl GroupCreatedReplyEventArgs { - /// C# member: `M:LibreMetaverse.GroupCreatedReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.String)`. - pub fn new( - group_id: libremetaverse_types::UUID, - success: bool, - message: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupCreatedReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.String)", - ) - } - /// C# member: `P:LibreMetaverse.GroupCreatedReplyEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupCreatedReplyEventArgs.GroupID" - ) - } - /// C# member: `P:LibreMetaverse.GroupCreatedReplyEventArgs.Message`. - pub fn message(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupCreatedReplyEventArgs.Message" - ) - } - /// C# member: `P:LibreMetaverse.GroupCreatedReplyEventArgs.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupCreatedReplyEventArgs.Success" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupCreatedReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean,System.String)`. +/// C# member: `P:LibreMetaverse.GroupCreatedReplyEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupCreatedReplyEventArgs.Message`. +/// C# member: `P:LibreMetaverse.GroupCreatedReplyEventArgs.Success`. +pub use crate::group_manager::GroupCreatedReplyEventArgs; /// C# type: `T:LibreMetaverse.GroupDroppedEventArgs`. -pub struct GroupDroppedEventArgs; -impl GroupDroppedEventArgs { - /// C# member: `M:LibreMetaverse.GroupDroppedEventArgs.#ctor(LibreMetaverse.UUID)`. - pub fn new(group_id: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupDroppedEventArgs.#ctor(LibreMetaverse.UUID)", - ) - } - /// C# member: `P:LibreMetaverse.GroupDroppedEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupDroppedEventArgs.GroupID") - } -} +/// C# member: `M:LibreMetaverse.GroupDroppedEventArgs.#ctor(LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.GroupDroppedEventArgs.GroupID`. +pub use crate::group_manager::GroupDroppedEventArgs; /// C# type: `T:LibreMetaverse.GroupInvitationEventArgs`. -pub struct GroupInvitationEventArgs; -impl GroupInvitationEventArgs { - /// C# member: `M:LibreMetaverse.GroupInvitationEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.String,System.String)`. - pub fn new( - simulator: libremetaverse::Simulator, - agent_id: libremetaverse_types::UUID, - agent_name: String, - message: String, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupInvitationEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.String,System.String)", - ) - } - /// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.Accept`. - pub fn accept(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupInvitationEventArgs.Accept") - } - /// Setter for C# member: `P:LibreMetaverse.GroupInvitationEventArgs.Accept`. - pub fn set_accept(&mut self, value: bool) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupInvitationEventArgs.Accept") - } - /// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.AgentID`. - pub fn agent_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupInvitationEventArgs.AgentID" - ) - } - /// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.FromName`. - pub fn from_name(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupInvitationEventArgs.FromName" - ) - } - /// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.Message`. - pub fn message(&self) -> String { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupInvitationEventArgs.Message" - ) - } - /// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.Simulator`. - pub fn simulator(&self) -> libremetaverse::Simulator { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupInvitationEventArgs.Simulator" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupInvitationEventArgs.#ctor(LibreMetaverse.Simulator,LibreMetaverse.UUID,System.String,System.String)`. +/// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.Accept`. +/// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.AgentID`. +/// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.FromName`. +/// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.Message`. +/// C# member: `P:LibreMetaverse.GroupInvitationEventArgs.Simulator`. +pub use crate::group_manager::GroupInvitationEventArgs; /// C# type: `T:LibreMetaverse.GroupManager`. -pub struct GroupManager; -impl GroupManager { - /// C# member: `E:LibreMetaverse.GroupManager.BannedAgents`. - pub fn subscribe_banned_agents( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.BannedAgents") - } - /// C# member: `E:LibreMetaverse.GroupManager.CurrentGroups`. - pub fn subscribe_current_groups( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.CurrentGroups") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupAccountSummaryReply`. - pub fn subscribe_group_account_summary_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupAccountSummaryReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.GroupManager.GroupAccountSummaryReply" - ) - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupCreatedReply`. - pub fn subscribe_group_created_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupCreatedReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupCreatedReply") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupDropped`. - pub fn subscribe_group_dropped( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupDropped") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupInvitation`. - pub fn subscribe_group_invitation( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupInvitationEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupInvitation") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupJoinedReply`. - pub fn subscribe_group_joined_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupOperationEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupJoinedReply") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupLeaveReply`. - pub fn subscribe_group_leave_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupOperationEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupLeaveReply") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupMemberEjected`. - pub fn subscribe_group_member_ejected( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupOperationEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupMemberEjected") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupMembersReply`. - pub fn subscribe_group_members_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupMembersReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupMembersReply") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupNamesReply`. - pub fn subscribe_group_names_reply( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupNamesReply") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupNoticesListReply`. - pub fn subscribe_group_notices_list_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupNoticesListReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.GroupManager.GroupNoticesListReply" - ) - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupProfile`. - pub fn subscribe_group_profile( - &self, - handler: libremetaverse_types::compat::EventHandler, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupProfile") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupRoleDataReply`. - pub fn subscribe_group_role_data_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupRolesDataReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupRoleDataReply") - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupRoleMembersReply`. - pub fn subscribe_group_role_members_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupRolesMembersReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!( - "E:LibreMetaverse.GroupManager.GroupRoleMembersReply" - ) - } - /// C# member: `E:LibreMetaverse.GroupManager.GroupTitlesReply`. - pub fn subscribe_group_titles_reply( - &self, - handler: libremetaverse_types::compat::EventHandler< - libremetaverse::GroupTitlesReplyEventArgs, - >, - ) -> libremetaverse_types::compat::Subscription { - libremetaverse_types::unimplemented_api!("E:LibreMetaverse.GroupManager.GroupTitlesReply") - } - /// C# member: `M:LibreMetaverse.GroupManager.#ctor(LibreMetaverse.GridClient)`. - pub fn new(client: libremetaverse::GridClient) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.#ctor(LibreMetaverse.GridClient)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.ActivateGroup(LibreMetaverse.UUID)`. - pub fn activate_group(&self, id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.ActivateGroup(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.ActivateTitle(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn activate_title( - &self, - group: libremetaverse_types::UUID, - role: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.ActivateTitle(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.AddToRole(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn add_to_role( - &self, - group: libremetaverse_types::UUID, - role: libremetaverse_types::UUID, - member: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.AddToRole(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.CreateRole(LibreMetaverse.UUID,LibreMetaverse.GroupRole)`. - pub fn create_role( - &self, - group: libremetaverse_types::UUID, - role: libremetaverse::GroupRole, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.CreateRole(LibreMetaverse.UUID,LibreMetaverse.GroupRole)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.DeleteRole(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn delete_role( - &self, - group: libremetaverse_types::UUID, - role_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.DeleteRole(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.Dispose`. - pub fn dispose(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.GroupManager.Dispose") - } - /// C# member: `M:LibreMetaverse.GroupManager.EjectUser(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn eject_user( - &self, - group: libremetaverse_types::UUID, - member: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.EjectUser(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.Invite(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.UUID},LibreMetaverse.UUID)`. - pub fn invite( - &self, - group: libremetaverse_types::UUID, - roles: Vec, - requested_agent: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.Invite(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.UUID},LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.LeaveGroup(LibreMetaverse.UUID)`. - pub fn leave_group(&self, group_id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.LeaveGroup(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RemoveFromRole(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn remove_from_role( - &self, - group: libremetaverse_types::UUID, - role: libremetaverse_types::UUID, - member: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RemoveFromRole(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestBanActionAsync(LibreMetaverse.UUID,LibreMetaverse.GroupBanAction,LibreMetaverse.UUID[],System.Threading.CancellationToken)`. - pub async fn request_ban_action( - &self, - group_id: libremetaverse_types::UUID, - action: libremetaverse::GroupBanAction, - agents: Vec, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestBanActionAsync(LibreMetaverse.UUID,LibreMetaverse.GroupBanAction,LibreMetaverse.UUID[],System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestBannedAgentsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. - pub async fn request_banned_agents( - &self, - group_id: libremetaverse_types::UUID, - cancellation_token: Option, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestBannedAgentsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestCreateGroup(LibreMetaverse.Group)`. - pub fn request_create_group(&self, group: libremetaverse::Group) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestCreateGroup(LibreMetaverse.Group)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestCurrentGroups`. - pub fn request_current_groups(&self) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.GroupManager.RequestCurrentGroups") - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupAccountSummary(LibreMetaverse.UUID,System.Int32,System.Int32)`. - pub fn request_group_account_summary( - &self, - group: libremetaverse_types::UUID, - interval_days: i32, - current_interval: i32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupAccountSummary(LibreMetaverse.UUID,System.Int32,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupMembers(LibreMetaverse.UUID)`. - pub fn request_group_members( - &self, - group: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupMembers(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupName(LibreMetaverse.UUID)`. - pub fn request_group_name( - &self, - group_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupName(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupNames(System.Collections.Generic.List{LibreMetaverse.UUID})`. - pub fn request_group_names( - &self, - group_i_ds: Vec, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupNames(System.Collections.Generic.List{LibreMetaverse.UUID})", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupNotice(LibreMetaverse.UUID)`. - pub fn request_group_notice( - &self, - notice_id: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupNotice(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupNoticesList(LibreMetaverse.UUID)`. - pub fn request_group_notices_list( - &self, - group: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupNoticesList(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupProfile(LibreMetaverse.UUID)`. - pub fn request_group_profile( - &self, - group: libremetaverse_types::UUID, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupProfile(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupRoles(LibreMetaverse.UUID)`. - pub fn request_group_roles( - &self, - group: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupRoles(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupRolesMembers(LibreMetaverse.UUID)`. - pub fn request_group_roles_members( - &self, - group: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupRolesMembers(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestGroupTitles(LibreMetaverse.UUID)`. - pub fn request_group_titles( - &self, - group: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestGroupTitles(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.RequestJoinGroup(LibreMetaverse.UUID)`. - pub fn request_join_group(&self, id: libremetaverse_types::UUID) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.RequestJoinGroup(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.SendGroupNotice(LibreMetaverse.UUID,LibreMetaverse.GroupNotice)`. - pub fn send_group_notice( - &self, - group: libremetaverse_types::UUID, - notice: libremetaverse::GroupNotice, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.SendGroupNotice(LibreMetaverse.UUID,LibreMetaverse.GroupNotice)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.SetGroupAcceptNotices(LibreMetaverse.UUID,System.Boolean,System.Boolean)`. - pub fn set_group_accept_notices( - &self, - group_id: libremetaverse_types::UUID, - accept_notices: bool, - list_in_profile: bool, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.SetGroupAcceptNotices(LibreMetaverse.UUID,System.Boolean,System.Boolean)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.SetGroupContribution(LibreMetaverse.UUID,System.Int32)`. - pub fn set_group_contribution( - &self, - group: libremetaverse_types::UUID, - contribution: i32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.SetGroupContribution(LibreMetaverse.UUID,System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.StartProposal(LibreMetaverse.UUID,LibreMetaverse.GroupProposal)`. - pub fn start_proposal( - &self, - group: libremetaverse_types::UUID, - prop: libremetaverse::GroupProposal, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.StartProposal(LibreMetaverse.UUID,LibreMetaverse.GroupProposal)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.UpdateGroup(LibreMetaverse.UUID,LibreMetaverse.Group)`. - pub fn update_group( - &self, - id: libremetaverse_types::UUID, - group: libremetaverse::Group, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.UpdateGroup(LibreMetaverse.UUID,LibreMetaverse.Group)", - ) - } - /// C# member: `M:LibreMetaverse.GroupManager.UpdateRole(LibreMetaverse.GroupRole)`. - pub fn update_role(&self, role: libremetaverse::GroupRole) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupManager.UpdateRole(LibreMetaverse.GroupRole)", - ) - } - /// C# member: `P:LibreMetaverse.GroupManager.CanJoinMoreGroups`. - pub fn can_join_more_groups(&self) -> Option> { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupManager.CanJoinMoreGroups") - } - /// C# member: `P:LibreMetaverse.GroupManager.CurrentGroupCount`. - pub fn current_group_count(&self) -> i32 { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupManager.CurrentGroupCount") - } - /// C# member: `P:LibreMetaverse.GroupManager.GroupMembershipLimit`. - pub fn group_membership_limit(&self) -> i32 { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupManager.GroupMembershipLimit" - ) - } -} +/// C# member: `E:LibreMetaverse.GroupManager.BannedAgents`. +/// C# member: `E:LibreMetaverse.GroupManager.CurrentGroups`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupAccountSummaryReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupCreatedReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupDropped`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupInvitation`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupJoinedReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupLeaveReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupMemberEjected`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupMembersReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupNamesReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupNoticesListReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupProfile`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupRoleDataReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupRoleMembersReply`. +/// C# member: `E:LibreMetaverse.GroupManager.GroupTitlesReply`. +/// C# member: `M:LibreMetaverse.GroupManager.#ctor(LibreMetaverse.GridClient)`. +/// C# member: `M:LibreMetaverse.GroupManager.ActivateGroup(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.ActivateTitle(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.AddToRole(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.CreateRole(LibreMetaverse.UUID,LibreMetaverse.GroupRole)`. +/// C# member: `M:LibreMetaverse.GroupManager.DeleteRole(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.Dispose`. +/// C# member: `M:LibreMetaverse.GroupManager.EjectUser(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.Invite(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.UUID},LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.LeaveGroup(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RemoveFromRole(LibreMetaverse.UUID,LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestBanActionAsync(LibreMetaverse.UUID,LibreMetaverse.GroupBanAction,LibreMetaverse.UUID[],System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestBannedAgentsAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestCreateGroup(LibreMetaverse.Group)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestCurrentGroups`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupAccountSummary(LibreMetaverse.UUID,System.Int32,System.Int32)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupMembers(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupName(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupNames(System.Collections.Generic.List{LibreMetaverse.UUID})`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupNotice(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupNoticesList(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupProfile(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupRoles(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupRolesMembers(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestGroupTitles(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.RequestJoinGroup(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.GroupManager.SendGroupNotice(LibreMetaverse.UUID,LibreMetaverse.GroupNotice)`. +/// C# member: `M:LibreMetaverse.GroupManager.SetGroupAcceptNotices(LibreMetaverse.UUID,System.Boolean,System.Boolean)`. +/// C# member: `M:LibreMetaverse.GroupManager.SetGroupContribution(LibreMetaverse.UUID,System.Int32)`. +/// C# member: `M:LibreMetaverse.GroupManager.StartProposal(LibreMetaverse.UUID,LibreMetaverse.GroupProposal)`. +/// C# member: `M:LibreMetaverse.GroupManager.UpdateGroup(LibreMetaverse.UUID,LibreMetaverse.Group)`. +/// C# member: `M:LibreMetaverse.GroupManager.UpdateRole(LibreMetaverse.GroupRole)`. +/// C# member: `P:LibreMetaverse.GroupManager.CanJoinMoreGroups`. +/// C# member: `P:LibreMetaverse.GroupManager.CurrentGroupCount`. +/// C# member: `P:LibreMetaverse.GroupManager.GroupMembershipLimit`. +pub use crate::group_manager::GroupManager; /// C# type: `T:LibreMetaverse.GroupMember`. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GroupMember { /// C# member: `F:LibreMetaverse.GroupMember.Contribution`. pub contribution: i32, @@ -8767,58 +7795,19 @@ pub struct GroupMember { impl GroupMember {} /// C# type: `T:LibreMetaverse.GroupMembersReplyEventArgs`. -pub struct GroupMembersReplyEventArgs; -impl GroupMembersReplyEventArgs { - /// C# member: `M:LibreMetaverse.GroupMembersReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupMember})`. - pub fn new( - request_id: libremetaverse_types::UUID, - group_id: libremetaverse_types::UUID, - members: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupMembersReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupMember})", - ) - } - /// C# member: `P:LibreMetaverse.GroupMembersReplyEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupMembersReplyEventArgs.GroupID" - ) - } - /// C# member: `P:LibreMetaverse.GroupMembersReplyEventArgs.Members`. - pub fn members( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupMembersReplyEventArgs.Members" - ) - } - /// C# member: `P:LibreMetaverse.GroupMembersReplyEventArgs.RequestID`. - pub fn request_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupMembersReplyEventArgs.RequestID" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupMembersReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupMember})`. +/// C# member: `P:LibreMetaverse.GroupMembersReplyEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupMembersReplyEventArgs.Members`. +/// C# member: `P:LibreMetaverse.GroupMembersReplyEventArgs.RequestID`. +pub use crate::group_manager::GroupMembersReplyEventArgs; /// C# type: `T:LibreMetaverse.GroupNamesEventArgs`. -pub struct GroupNamesEventArgs; -impl GroupNamesEventArgs { - /// C# member: `M:LibreMetaverse.GroupNamesEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.String})`. - pub fn new( - group_names: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupNamesEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.String})", - ) - } - /// C# member: `P:LibreMetaverse.GroupNamesEventArgs.GroupNames`. - pub fn group_names(&self) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupNamesEventArgs.GroupNames") - } -} +/// C# member: `M:LibreMetaverse.GroupNamesEventArgs.#ctor(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,System.String})`. +/// C# member: `P:LibreMetaverse.GroupNamesEventArgs.GroupNames`. +pub use crate::group_manager::GroupNamesEventArgs; /// C# type: `T:LibreMetaverse.GroupNotice`. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GroupNotice { /// C# member: `F:LibreMetaverse.GroupNotice.AttachmentID`. pub attachment_id: libremetaverse_types::UUID, @@ -8832,11 +7821,12 @@ pub struct GroupNotice { impl GroupNotice { /// C# member: `M:LibreMetaverse.GroupNotice.SerializeAttachment`. pub fn serialize_attachment(&mut self) -> Result, crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.GroupNotice.SerializeAttachment") + self.native_serialize_attachment() } } /// C# type: `T:LibreMetaverse.GroupNoticesListEntry`. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GroupNoticesListEntry { /// C# member: `F:LibreMetaverse.GroupNoticesListEntry.AssetType`. pub asset_type: libremetaverse_types::AssetType, @@ -8854,49 +7844,16 @@ pub struct GroupNoticesListEntry { impl GroupNoticesListEntry {} /// C# type: `T:LibreMetaverse.GroupNoticesListReplyEventArgs`. -pub struct GroupNoticesListReplyEventArgs; -impl GroupNoticesListReplyEventArgs { - /// C# member: `M:LibreMetaverse.GroupNoticesListReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.GroupNoticesListEntry})`. - pub fn new( - group_id: libremetaverse_types::UUID, - notices: Vec, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupNoticesListReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.GroupNoticesListEntry})", - ) - } - /// C# member: `P:LibreMetaverse.GroupNoticesListReplyEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupNoticesListReplyEventArgs.GroupID" - ) - } - /// C# member: `P:LibreMetaverse.GroupNoticesListReplyEventArgs.Notices`. - pub fn notices(&self) -> Vec { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupNoticesListReplyEventArgs.Notices" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupNoticesListReplyEventArgs.#ctor(LibreMetaverse.UUID,System.Collections.Generic.List{LibreMetaverse.GroupNoticesListEntry})`. +/// C# member: `P:LibreMetaverse.GroupNoticesListReplyEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupNoticesListReplyEventArgs.Notices`. +pub use crate::group_manager::GroupNoticesListReplyEventArgs; /// C# type: `T:LibreMetaverse.GroupOperationEventArgs`. -pub struct GroupOperationEventArgs; -impl GroupOperationEventArgs { - /// C# member: `M:LibreMetaverse.GroupOperationEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean)`. - pub fn new(group_id: libremetaverse_types::UUID, success: bool) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupOperationEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean)", - ) - } - /// C# member: `P:LibreMetaverse.GroupOperationEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupOperationEventArgs.GroupID") - } - /// C# member: `P:LibreMetaverse.GroupOperationEventArgs.Success`. - pub fn success(&self) -> bool { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupOperationEventArgs.Success") - } -} +/// C# member: `M:LibreMetaverse.GroupOperationEventArgs.#ctor(LibreMetaverse.UUID,System.Boolean)`. +/// C# member: `P:LibreMetaverse.GroupOperationEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupOperationEventArgs.Success`. +pub use crate::group_manager::GroupOperationEventArgs; /// C# type: `T:LibreMetaverse.GroupPowers`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] @@ -9008,21 +7965,12 @@ impl GroupPowers { } /// C# type: `T:LibreMetaverse.GroupProfileEventArgs`. -pub struct GroupProfileEventArgs; -impl GroupProfileEventArgs { - /// C# member: `M:LibreMetaverse.GroupProfileEventArgs.#ctor(LibreMetaverse.Group)`. - pub fn new(group: libremetaverse::Group) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupProfileEventArgs.#ctor(LibreMetaverse.Group)", - ) - } - /// C# member: `P:LibreMetaverse.GroupProfileEventArgs.Group`. - pub fn group(&self) -> libremetaverse::Group { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GroupProfileEventArgs.Group") - } -} +/// C# member: `M:LibreMetaverse.GroupProfileEventArgs.#ctor(LibreMetaverse.Group)`. +/// C# member: `P:LibreMetaverse.GroupProfileEventArgs.Group`. +pub use crate::group_manager::GroupProfileEventArgs; /// C# type: `T:LibreMetaverse.GroupProposal`. +#[derive(Clone, Debug, PartialEq)] pub struct GroupProposal { /// C# member: `F:LibreMetaverse.GroupProposal.Duration`. pub duration: i32, @@ -9036,6 +7984,7 @@ pub struct GroupProposal { impl GroupProposal {} /// C# type: `T:LibreMetaverse.GroupRole`. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GroupRole { /// C# member: `F:LibreMetaverse.GroupRole.Description`. pub description: String, @@ -9055,7 +8004,7 @@ pub struct GroupRole { impl GroupRole { /// C# member: `M:LibreMetaverse.GroupRole.ToString`. pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.GroupRole.ToString") + self.native_to_string() } } @@ -9088,74 +8037,21 @@ pub enum GroupRoleUpdate { } /// C# type: `T:LibreMetaverse.GroupRolesDataReplyEventArgs`. -pub struct GroupRolesDataReplyEventArgs; -impl GroupRolesDataReplyEventArgs { - /// C# member: `M:LibreMetaverse.GroupRolesDataReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupRole})`. - pub fn new( - request_id: libremetaverse_types::UUID, - group_id: libremetaverse_types::UUID, - roles: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupRolesDataReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupRole})", - ) - } - /// C# member: `P:LibreMetaverse.GroupRolesDataReplyEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupRolesDataReplyEventArgs.GroupID" - ) - } - /// C# member: `P:LibreMetaverse.GroupRolesDataReplyEventArgs.RequestID`. - pub fn request_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupRolesDataReplyEventArgs.RequestID" - ) - } - /// C# member: `P:LibreMetaverse.GroupRolesDataReplyEventArgs.Roles`. - pub fn roles( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupRolesDataReplyEventArgs.Roles" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupRolesDataReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupRole})`. +/// C# member: `P:LibreMetaverse.GroupRolesDataReplyEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupRolesDataReplyEventArgs.RequestID`. +/// C# member: `P:LibreMetaverse.GroupRolesDataReplyEventArgs.Roles`. +pub use crate::group_manager::GroupRolesDataReplyEventArgs; /// C# type: `T:LibreMetaverse.GroupRolesMembersReplyEventArgs`. -pub struct GroupRolesMembersReplyEventArgs; -impl GroupRolesMembersReplyEventArgs { - /// C# member: `M:LibreMetaverse.GroupRolesMembersReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.List{System.Collections.Generic.KeyValuePair{LibreMetaverse.UUID,LibreMetaverse.UUID}})`. - pub fn new( - request_id: libremetaverse_types::UUID, - group_id: libremetaverse_types::UUID, - roles_members: Vec<(libremetaverse_types::UUID, libremetaverse_types::UUID)>, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupRolesMembersReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.List{System.Collections.Generic.KeyValuePair{LibreMetaverse.UUID,LibreMetaverse.UUID}})", - ) - } - /// C# member: `P:LibreMetaverse.GroupRolesMembersReplyEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupRolesMembersReplyEventArgs.GroupID" - ) - } - /// C# member: `P:LibreMetaverse.GroupRolesMembersReplyEventArgs.RequestID`. - pub fn request_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupRolesMembersReplyEventArgs.RequestID" - ) - } - /// C# member: `P:LibreMetaverse.GroupRolesMembersReplyEventArgs.RolesMembers`. - pub fn roles_members(&self) -> Vec<(libremetaverse_types::UUID, libremetaverse_types::UUID)> { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupRolesMembersReplyEventArgs.RolesMembers" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupRolesMembersReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.List{System.Collections.Generic.KeyValuePair{LibreMetaverse.UUID,LibreMetaverse.UUID}})`. +/// C# member: `P:LibreMetaverse.GroupRolesMembersReplyEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupRolesMembersReplyEventArgs.RequestID`. +/// C# member: `P:LibreMetaverse.GroupRolesMembersReplyEventArgs.RolesMembers`. +pub use crate::group_manager::GroupRolesMembersReplyEventArgs; /// C# type: `T:LibreMetaverse.GroupTitle`. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GroupTitle { /// C# member: `F:LibreMetaverse.GroupTitle.GroupID`. pub group_id: libremetaverse_types::UUID, @@ -9169,44 +8065,16 @@ pub struct GroupTitle { impl GroupTitle { /// C# member: `M:LibreMetaverse.GroupTitle.ToString`. pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.GroupTitle.ToString") + self.native_to_string() } } /// C# type: `T:LibreMetaverse.GroupTitlesReplyEventArgs`. -pub struct GroupTitlesReplyEventArgs; -impl GroupTitlesReplyEventArgs { - /// C# member: `M:LibreMetaverse.GroupTitlesReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupTitle})`. - pub fn new( - request_id: libremetaverse_types::UUID, - group_id: libremetaverse_types::UUID, - titles: std::collections::HashMap, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.GroupTitlesReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupTitle})", - ) - } - /// C# member: `P:LibreMetaverse.GroupTitlesReplyEventArgs.GroupID`. - pub fn group_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupTitlesReplyEventArgs.GroupID" - ) - } - /// C# member: `P:LibreMetaverse.GroupTitlesReplyEventArgs.RequestID`. - pub fn request_id(&self) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupTitlesReplyEventArgs.RequestID" - ) - } - /// C# member: `P:LibreMetaverse.GroupTitlesReplyEventArgs.Titles`. - pub fn titles( - &self, - ) -> std::collections::HashMap { - libremetaverse_types::unimplemented_api!( - "P:LibreMetaverse.GroupTitlesReplyEventArgs.Titles" - ) - } -} +/// C# member: `M:LibreMetaverse.GroupTitlesReplyEventArgs.#ctor(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.GroupTitle})`. +/// C# member: `P:LibreMetaverse.GroupTitlesReplyEventArgs.GroupID`. +/// C# member: `P:LibreMetaverse.GroupTitlesReplyEventArgs.RequestID`. +/// C# member: `P:LibreMetaverse.GroupTitlesReplyEventArgs.Titles`. +pub use crate::group_manager::GroupTitlesReplyEventArgs; /// C# type: `T:LibreMetaverse.GroupVoteMajority`. pub struct GroupVoteMajority; diff --git a/crates/libremetaverse/src/group_manager.rs b/crates/libremetaverse/src/group_manager.rs new file mode 100644 index 0000000..ca4d40c --- /dev/null +++ b/crates/libremetaverse/src/group_manager.rs @@ -0,0 +1,1610 @@ +//! Group membership, roles, notices, invitations, and profile synchronization. + +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::must_use_candidate)] +#![allow(clippy::needless_pass_by_value)] + +use crate::agent_manager::EventRegistry; +use crate::network_manager::RawPacketReceivedEventArgs; +use crate::packet_catalog::{GeneratedPacket, PacketType}; +use crate::{ + Error, GridClient, Group, GroupAccountSummary, GroupBanAction, GroupMember, GroupNotice, + GroupNoticesListEntry, GroupProposal, GroupRole, GroupRoleChangeType, GroupRoleUpdate, + GroupTitle, InstantMessageDialog, +}; +use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser}; +use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription}; +use libremetaverse_types::{AssetType, UUID, Vector3}; +use std::collections::{HashMap, HashSet}; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; +use std::sync::{Arc, Mutex, RwLock}; + +const MAX_GROUPS: usize = 10_000; +const MAX_GROUP_ROWS: usize = 100_000; +const MAX_PENDING_REQUESTS: usize = 4_096; + +fn mutex(value: &Mutex) -> std::sync::MutexGuard<'_, T> { + value + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} +fn write(value: &RwLock) -> std::sync::RwLockWriteGuard<'_, T> { + value + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner) +} +fn wire_string(value: &[u8]) -> String { + String::from_utf8_lossy(value.split(|b| *b == 0).next().unwrap_or_default()).into_owned() +} + +macro_rules! event_args { + ($name:ident { $($field:ident : $ty:ty => $getter:ident),+ $(,)? }) => { + #[derive(Clone, Debug)] + pub struct $name { $( $field: $ty ),+ } + impl $name { + pub fn new($($field: $ty),+) -> Result { Ok(Self { $($field),+ }) } + $(pub fn $getter(&self) -> $ty { self.$field.clone() })+ + } + }; +} + +event_args!(BannedAgentsEventArgs { group_id: UUID => group_id, success: bool => success, banned_agents: Option> => banned_agents }); +event_args!(CurrentGroupsEventArgs { groups: HashMap => groups }); +event_args!(GroupAccountSummaryReplyEventArgs { group_id: UUID => group_id, summary: GroupAccountSummary => summary }); +event_args!(GroupCreatedReplyEventArgs { group_id: UUID => group_id, success: bool => success, message: String => message }); +event_args!(GroupDroppedEventArgs { group_id: UUID => group_id }); +event_args!(GroupMembersReplyEventArgs { request_id: UUID => request_id, group_id: UUID => group_id, members: HashMap => members }); +event_args!(GroupNamesEventArgs { group_names: HashMap => group_names }); +event_args!(GroupNoticesListReplyEventArgs { group_id: UUID => group_id, notices: Vec => notices }); +event_args!(GroupOperationEventArgs { group_id: UUID => group_id, success: bool => success }); +event_args!(GroupProfileEventArgs { group: Group => group }); +event_args!(GroupRolesDataReplyEventArgs { request_id: UUID => request_id, group_id: UUID => group_id, roles: HashMap => roles }); +event_args!(GroupRolesMembersReplyEventArgs { request_id: UUID => request_id, group_id: UUID => group_id, roles_members: Vec<(UUID, UUID)> => roles_members }); +event_args!(GroupTitlesReplyEventArgs { request_id: UUID => request_id, group_id: UUID => group_id, titles: HashMap => titles }); + +#[derive(Clone)] +pub struct GroupInvitationEventArgs { + simulator: crate::Simulator, + agent_id: UUID, + from_name: String, + message: String, + accept: Arc, +} +impl GroupInvitationEventArgs { + pub fn new( + simulator: crate::Simulator, + agent_id: UUID, + agent_name: String, + message: String, + ) -> Result { + Ok(Self { + simulator, + agent_id, + from_name: agent_name, + message, + accept: Arc::new(AtomicBool::new(false)), + }) + } + pub fn accept(&self) -> bool { + self.accept.load(Ordering::Acquire) + } + pub fn set_accept(&mut self, value: bool) { + self.accept.store(value, Ordering::Release); + } + pub fn agent_id(&self) -> UUID { + self.agent_id + } + pub fn from_name(&self) -> String { + self.from_name.clone() + } + pub fn message(&self) -> String { + self.message.clone() + } + pub fn simulator(&self) -> crate::Simulator { + self.simulator.clone() + } +} + +#[derive(Default)] +struct GroupEvents { + banned: EventRegistry, + current: EventRegistry, + account: EventRegistry, + created: EventRegistry, + dropped: EventRegistry, + invitation: EventRegistry, + joined: EventRegistry, + left: EventRegistry, + ejected: EventRegistry, + members: EventRegistry, + names: EventRegistry, + notices: EventRegistry, + profile: EventRegistry, + roles: EventRegistry, + role_members: EventRegistry, + titles: EventRegistry, +} + +#[derive(Default)] +struct PendingRows { + members: HashMap)>, + roles: HashMap)>, + role_members: HashMap, + titles: HashMap, + accounts: HashMap, +} +type PendingRoleMembers = (UUID, usize, Vec<(UUID, UUID)>, HashSet<(UUID, UUID)>); + +pub(crate) struct GroupManagerInner { + client: Arc, + groups: RwLock>, + group_count: AtomicI32, + pending: Mutex, + events: GroupEvents, + subscriptions: Mutex>, + disposed: AtomicBool, +} + +#[derive(Clone)] +pub struct GroupManager { + pub(crate) inner: Arc, +} + +impl GroupManager { + pub(crate) fn native_new(client: Arc) -> Self { + if let Some(inner) = client.cached_group_manager_inner() { + return Self { inner }; + } + let inner = Arc::new(GroupManagerInner { + client: Arc::clone(&client), + groups: RwLock::new(HashMap::new()), + group_count: AtomicI32::new(-1), + pending: Mutex::new(PendingRows::default()), + events: GroupEvents::default(), + subscriptions: Mutex::new(Vec::new()), + disposed: AtomicBool::new(false), + }); + let weak = Arc::downgrade(&inner); + mutex(&inner.subscriptions).push(client.network().subscribe_raw_packet(Arc::new( + move |event| { + if let Some(inner) = weak.upgrade() { + inner.handle_packet(event); + } + }, + ))); + let weak = Arc::downgrade(&inner); + mutex(&inner.subscriptions).push(client.network().subscribe_caps( + "AgentGroupDataUpdate".into(), + crate::CapsEventQueueCallback::from_handler(move |_, message, _| { + let Some(inner) = weak.upgrade() else { + return; + }; + let Ok(map) = message.serialize() else { + return; + }; + let Ok(mut decoded) = crate::messages::linden::AgentGroupDataUpdateMessage::new() + else { + return; + }; + if decoded.deserialize(map).is_ok() { + inner.handle_group_update_caps(decoded); + } + }), + )); + let weak = Arc::downgrade(&inner); + mutex(&inner.subscriptions).push(client.network().subscribe_caps( + "AgentDropGroup".into(), + crate::CapsEventQueueCallback::from_handler(move |_, message, _| { + let Some(inner) = weak.upgrade() else { + return; + }; + let Ok(map) = message.serialize() else { + return; + }; + let Ok(mut decoded) = crate::messages::linden::AgentDropGroupMessage::new() else { + return; + }; + if decoded.deserialize(map).is_ok() { + for row in decoded.agent_data_block.into_iter().take(MAX_GROUPS) { + inner.drop_group(row.group_id); + } + } + }), + )); + let weak = Arc::downgrade(&inner); + let mut callback_client = (*client).clone(); + mutex(&inner.subscriptions).push(callback_client.self_().subscribe_im(Arc::new( + move |event| { + if let Some(inner) = weak.upgrade() { + inner.handle_im(event); + } + }, + ))); + Self { + inner: client.install_group_manager_inner(inner), + } + } + pub fn new(client: GridClient) -> Result { + Ok(Self::native_new(Arc::new(client))) + } + fn ids(&self) -> (UUID, UUID) { + let mut client = (*self.inner.client).clone(); + let agent = client.self_(); + (agent.agent_id(), agent.session_id()) + } + fn send(&self, packet: &T, ty: PacketType) -> Result<(), Error> { + if self.inner.disposed.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let sim = self + .inner + .client + .network() + .current_sim() + .ok_or(Error::InvalidOperation)?; + let bytes = packet.encode_packet()?; + match sim.native_send_packet_data( + bytes.clone(), + i32::try_from(bytes.len()).map_err(|_| Error::Argument)?, + ty, + false, + ) { + Err(Error::InvalidOperation) if !sim.connected() => Ok(()), + result => result, + } + } + pub fn dispose(&self) -> Result<(), Error> { + self.inner.dispose(); + Ok(()) + } + pub fn current_group_count(&self) -> i32 { + self.inner.group_count.load(Ordering::Acquire) + } + pub fn group_membership_limit(&self) -> i32 { + let mut client = (*self.inner.client).clone(); + client.self_().benefits().group_membership_limit() + } + pub fn can_join_more_groups(&self) -> Option> { + let count = self.current_group_count(); + if count < 0 { + Some(None) + } else { + Some(Some(count < self.group_membership_limit())) + } + } + + pub fn subscribe_banned_agents(&self, h: EventHandler) -> Subscription { + self.inner.events.banned.subscribe(h) + } + pub fn subscribe_current_groups( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.current.subscribe(h) + } + pub fn subscribe_group_account_summary_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.account.subscribe(h) + } + pub fn subscribe_group_created_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.created.subscribe(h) + } + pub fn subscribe_group_dropped(&self, h: EventHandler) -> Subscription { + self.inner.events.dropped.subscribe(h) + } + pub fn subscribe_group_invitation( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.invitation.subscribe(h) + } + pub fn subscribe_group_joined_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.joined.subscribe(h) + } + pub fn subscribe_group_leave_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.left.subscribe(h) + } + pub fn subscribe_group_member_ejected( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.ejected.subscribe(h) + } + pub fn subscribe_group_members_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.members.subscribe(h) + } + pub fn subscribe_group_names_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.names.subscribe(h) + } + pub fn subscribe_group_notices_list_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.notices.subscribe(h) + } + pub fn subscribe_group_profile(&self, h: EventHandler) -> Subscription { + self.inner.events.profile.subscribe(h) + } + pub fn subscribe_group_role_data_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.roles.subscribe(h) + } + pub fn subscribe_group_role_members_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.role_members.subscribe(h) + } + pub fn subscribe_group_titles_reply( + &self, + h: EventHandler, + ) -> Subscription { + self.inner.events.titles.subscribe(h) + } +} + +impl GroupManagerInner { + fn dispose(&self) { + if !self.disposed.swap(true, Ordering::AcqRel) { + mutex(&self.subscriptions).clear(); + mutex(&self.pending).members.clear(); + mutex(&self.pending).roles.clear(); + mutex(&self.pending).role_members.clear(); + mutex(&self.pending).titles.clear(); + mutex(&self.pending).accounts.clear(); + } + } + fn drop_group(&self, group_id: UUID) { + let count = { + let mut groups = write(&self.groups); + groups.remove(&group_id); + groups.len() + }; + if self.group_count.load(Ordering::Acquire) >= 0 { + self.group_count + .store(i32::try_from(count).unwrap_or(i32::MAX), Ordering::Release); + } + self.events.dropped.emit(GroupDroppedEventArgs { group_id }); + } + fn handle_group_update_caps( + &self, + message: crate::messages::linden::AgentGroupDataUpdateMessage, + ) { + let mut groups = HashMap::new(); + for (index, row) in message + .group_data_block + .into_iter() + .take(MAX_GROUPS) + .enumerate() + { + groups.insert( + row.group_id, + Group { + accept_notices: row.accept_notices, + allow_publish: false, + charter: String::new(), + contribution: row.contribution, + founder_id: UUID::zero(), + group_membership_count: 0, + group_roles_count: 0, + id: row.group_id, + insignia_id: row.group_insignia_id, + list_in_profile: message + .new_group_data_block + .get(index) + .is_some_and(|item| item.list_in_profile), + mature_publish: false, + member_title: row.group_title, + membership_fee: 0, + money: 0, + name: row.group_name, + open_enrollment: false, + owner_role: UUID::zero(), + powers: row.group_powers, + show_in_list: false, + }, + ); + } + write(&self.groups).clone_from(&groups); + self.group_count.store( + i32::try_from(groups.len()).unwrap_or(i32::MAX), + Ordering::Release, + ); + self.events.current.emit(CurrentGroupsEventArgs { groups }); + } + fn handle_im(&self, event: crate::InstantMessageEventArgs) { + let im = event.im(); + if im.dialog != InstantMessageDialog::GroupInvitation { + return; + } + let Some(simulator) = event.simulator() else { + return; + }; + let args = GroupInvitationEventArgs { + simulator, + agent_id: im.from_agent_id, + from_name: im.from_agent_name, + message: im.message, + accept: Arc::new(AtomicBool::new(false)), + }; + self.events.invitation.emit(args.clone()); + let mut client = (*self.client).clone(); + let _ = client.self_().native_group_invite_respond( + im.from_agent_id, + im.im_session_id, + args.accept(), + ); + } + #[allow(clippy::too_many_lines)] + fn handle_packet(&self, event: RawPacketReceivedEventArgs) { + if self.disposed.load(Ordering::Acquire) || event.data.len() > 8 * 1024 * 1024 { + return; + } + let mut position = 0; + match event.packet_type { + PacketType::AgentGroupDataUpdate => { + if let Ok(packet) = crate::packets::AgentGroupDataUpdatePacket::new_from_bytes( + &event.data, + &mut position, + ) { + let groups = packet + .group_data + .into_iter() + .take(MAX_GROUPS) + .map(|row| { + let group = Group { + accept_notices: row.accept_notices, + allow_publish: false, + charter: String::new(), + contribution: row.contribution, + founder_id: UUID::zero(), + group_membership_count: 0, + group_roles_count: 0, + id: row.group_id, + insignia_id: row.group_insignia_id, + list_in_profile: false, + mature_publish: false, + member_title: String::new(), + membership_fee: 0, + money: 0, + name: wire_string(&row.group_name), + open_enrollment: false, + owner_role: UUID::zero(), + powers: crate::GroupPowers(row.group_powers), + show_in_list: false, + }; + (group.id, group) + }) + .collect::>(); + write(&self.groups).clone_from(&groups); + self.group_count.store( + i32::try_from(groups.len()).unwrap_or(i32::MAX), + Ordering::Release, + ); + self.events.current.emit(CurrentGroupsEventArgs { groups }); + } + } + PacketType::AgentDropGroup => { + if let Ok(packet) = + crate::packets::AgentDropGroupPacket::new_from_bytes(&event.data, &mut position) + { + self.drop_group(packet.agent_data.group_id); + } + } + PacketType::GroupProfileReply => { + if let Ok(packet) = crate::packets::GroupProfileReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + let row = packet.group_data; + let group = Group { + accept_notices: false, + allow_publish: row.allow_publish, + charter: wire_string(&row.charter), + contribution: 0, + founder_id: row.founder_id, + group_membership_count: row.group_membership_count, + group_roles_count: row.group_roles_count, + id: row.group_id, + insignia_id: row.insignia_id, + list_in_profile: false, + mature_publish: row.mature_publish, + member_title: wire_string(&row.member_title), + membership_fee: row.membership_fee, + money: row.money, + name: wire_string(&row.name), + open_enrollment: row.open_enrollment, + owner_role: row.owner_role, + powers: crate::GroupPowers(row.powers_mask), + show_in_list: row.show_in_list, + }; + write(&self.groups).insert(group.id, group.clone()); + self.events.profile.emit(GroupProfileEventArgs { group }); + } + } + PacketType::UUIDGroupNameReply => { + if let Ok(packet) = crate::packets::UUIDGroupNameReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + let names = packet + .uuid_name_block + .into_iter() + .take(MAX_GROUPS) + .map(|row| (row.id, wire_string(&row.group_name))) + .collect(); + self.events + .names + .emit(GroupNamesEventArgs { group_names: names }); + } + } + PacketType::GroupNoticesListReply => { + if let Ok(packet) = crate::packets::GroupNoticesListReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + let notices = packet + .data + .into_iter() + .take(MAX_GROUP_ROWS) + .map(|row| GroupNoticesListEntry { + asset_type: asset_type(row.asset_type), + from_name: wire_string(&row.from_name), + has_attachment: row.has_attachment, + notice_id: row.notice_id, + subject: wire_string(&row.subject), + timestamp: row.timestamp, + }) + .collect(); + self.events.notices.emit(GroupNoticesListReplyEventArgs { + group_id: packet.agent_data.group_id, + notices, + }); + } + } + PacketType::GroupTitlesReply => { + if let Ok(packet) = crate::packets::GroupTitlesReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + let group_id = packet.agent_data.group_id; + if mutex(&self.pending) + .titles + .remove(&packet.agent_data.request_id) + != Some(group_id) + { + return; + } + let titles = packet + .group_data + .into_iter() + .take(MAX_GROUP_ROWS) + .map(|row| { + ( + row.role_id, + GroupTitle { + group_id, + role_id: row.role_id, + selected: row.selected, + title: wire_string(&row.title), + }, + ) + }) + .collect(); + self.events.titles.emit(GroupTitlesReplyEventArgs { + request_id: packet.agent_data.request_id, + group_id, + titles, + }); + } + } + PacketType::GroupMembersReply => self.handle_members(&event.data, &mut position), + PacketType::GroupRoleDataReply => self.handle_roles(&event.data, &mut position), + PacketType::GroupRoleMembersReply => { + self.handle_role_members(&event.data, &mut position); + } + PacketType::GroupAccountSummaryReply => self.handle_account(&event.data, &mut position), + PacketType::CreateGroupReply => { + if let Ok(packet) = crate::packets::CreateGroupReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + self.events.created.emit(GroupCreatedReplyEventArgs { + group_id: packet.reply_data.group_id, + success: packet.reply_data.success, + message: wire_string(&packet.reply_data.message), + }); + } + } + PacketType::JoinGroupReply => { + if let Ok(packet) = + crate::packets::JoinGroupReplyPacket::new_from_bytes(&event.data, &mut position) + { + self.events.joined.emit(GroupOperationEventArgs { + group_id: packet.group_data.group_id, + success: packet.group_data.success, + }); + } + } + PacketType::LeaveGroupReply => { + if let Ok(packet) = crate::packets::LeaveGroupReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + if packet.group_data.success { + let count = { + let mut groups = write(&self.groups); + groups.remove(&packet.group_data.group_id); + groups.len() + }; + if self.group_count.load(Ordering::Acquire) >= 0 { + self.group_count + .store(i32::try_from(count).unwrap_or(i32::MAX), Ordering::Release); + } + } + self.events.left.emit(GroupOperationEventArgs { + group_id: packet.group_data.group_id, + success: packet.group_data.success, + }); + } + } + PacketType::EjectGroupMemberReply => { + if let Ok(packet) = crate::packets::EjectGroupMemberReplyPacket::new_from_bytes( + &event.data, + &mut position, + ) { + self.events.ejected.emit(GroupOperationEventArgs { + group_id: packet.group_data.group_id, + success: packet.eject_data.success, + }); + } + } + _ => {} + } + } + fn handle_members(&self, data: &[u8], position: &mut i32) { + let Ok(packet) = crate::packets::GroupMembersReplyPacket::new_from_bytes(data, position) + else { + return; + }; + let Ok(expected) = usize::try_from(packet.group_data.member_count) else { + return; + }; + if expected > MAX_GROUP_ROWS { + mutex(&self.pending) + .members + .remove(&packet.group_data.request_id); + return; + } + let complete = { + let mut pending = mutex(&self.pending); + let Some((group_id, count, rows)) = + pending.members.get_mut(&packet.group_data.request_id) + else { + return; + }; + if *group_id != packet.group_data.group_id { + return; + } + *count = expected; + for row in packet.member_data.into_iter().take(MAX_GROUP_ROWS) { + rows.entry(row.agent_id).or_insert_with(|| GroupMember { + contribution: row.contribution, + id: row.agent_id, + is_owner: row.is_owner, + online_status: wire_string(&row.online_status), + powers: crate::GroupPowers(row.agent_powers), + title: wire_string(&row.title), + }); + } + if rows.len() >= *count { + pending + .members + .remove(&packet.group_data.request_id) + .map(|(_, _, rows)| rows) + } else { + None + } + }; + if let Some(members) = complete { + self.events.members.emit(GroupMembersReplyEventArgs { + request_id: packet.group_data.request_id, + group_id: packet.group_data.group_id, + members, + }); + } + } + fn handle_roles(&self, data: &[u8], position: &mut i32) { + let Ok(packet) = crate::packets::GroupRoleDataReplyPacket::new_from_bytes(data, position) + else { + return; + }; + let Ok(expected) = usize::try_from(packet.group_data.role_count) else { + return; + }; + if expected > MAX_GROUP_ROWS { + mutex(&self.pending) + .roles + .remove(&packet.group_data.request_id); + return; + } + let complete = { + let mut pending = mutex(&self.pending); + let Some((group_id, count, rows)) = + pending.roles.get_mut(&packet.group_data.request_id) + else { + return; + }; + if *group_id != packet.group_data.group_id { + return; + } + *count = expected; + for row in packet.role_data.into_iter().take(MAX_GROUP_ROWS) { + rows.entry(row.role_id).or_insert_with(|| GroupRole { + description: wire_string(&row.description), + group_id: packet.group_data.group_id, + id: row.role_id, + members: row.members, + name: wire_string(&row.name), + powers: crate::GroupPowers(row.powers), + title: wire_string(&row.title), + }); + } + if rows.len() >= *count { + pending + .roles + .remove(&packet.group_data.request_id) + .map(|(_, _, rows)| rows) + } else { + None + } + }; + if let Some(roles) = complete { + self.events.roles.emit(GroupRolesDataReplyEventArgs { + request_id: packet.group_data.request_id, + group_id: packet.group_data.group_id, + roles, + }); + } + } + fn handle_role_members(&self, data: &[u8], position: &mut i32) { + let Ok(packet) = + crate::packets::GroupRoleMembersReplyPacket::new_from_bytes(data, position) + else { + return; + }; + let Ok(expected) = usize::try_from(packet.agent_data.total_pairs) else { + return; + }; + if expected > MAX_GROUP_ROWS { + mutex(&self.pending) + .role_members + .remove(&packet.agent_data.request_id); + return; + } + let complete = { + let mut pending = mutex(&self.pending); + let Some((group_id, count, rows, seen)) = + pending.role_members.get_mut(&packet.agent_data.request_id) + else { + return; + }; + if *group_id != packet.agent_data.group_id { + return; + } + *count = expected; + for row in packet.member_data.into_iter().take(MAX_GROUP_ROWS) { + let pair = (row.role_id, row.member_id); + if seen.insert(pair) { + rows.push(pair); + } + } + if rows.len() >= *count { + pending + .role_members + .remove(&packet.agent_data.request_id) + .map(|(_, _, rows, _)| rows) + } else { + None + } + }; + if let Some(roles_members) = complete { + self.events + .role_members + .emit(GroupRolesMembersReplyEventArgs { + request_id: packet.agent_data.request_id, + group_id: packet.agent_data.group_id, + roles_members, + }); + } + } + fn handle_account(&self, data: &[u8], position: &mut i32) { + let Ok(packet) = + crate::packets::GroupAccountSummaryReplyPacket::new_from_bytes(data, position) + else { + return; + }; + let row = packet.money_data; + if mutex(&self.pending).accounts.remove(&row.request_id) != Some(packet.agent_data.group_id) + { + return; + } + let summary = GroupAccountSummary { + balance: row.balance, + current_interval: row.current_interval, + group_tax_current: row.group_tax_current, + group_tax_estimate: row.group_tax_estimate, + interval_days: row.interval_days, + land_tax_current: row.land_tax_current, + land_tax_estimate: row.land_tax_estimate, + last_tax_date: wire_string(&row.last_tax_date), + light_tax_current: row.light_tax_current, + light_tax_estimate: row.light_tax_estimate, + non_exempt_members: row.non_exempt_members, + object_tax_current: row.object_tax_current, + object_tax_estimate: row.object_tax_estimate, + parcel_dir_fee_current: row.parcel_dir_fee_current, + parcel_dir_fee_estimate: row.parcel_dir_fee_estimate, + start_date: wire_string(&row.start_date), + tax_date: wire_string(&row.tax_date), + total_credits: row.total_credits, + total_debits: row.total_debits, + }; + self.events.account.emit(GroupAccountSummaryReplyEventArgs { + group_id: packet.agent_data.group_id, + summary, + }); + } +} + +fn asset_type(value: u8) -> AssetType { + match value.cast_signed() { + 0 => AssetType::Texture, + 1 => AssetType::Sound, + 2 => AssetType::CallingCard, + 3 => AssetType::Landmark, + 4 => AssetType::Script, + 5 => AssetType::Clothing, + 6 => AssetType::Object, + 7 => AssetType::Notecard, + 8 => AssetType::Folder, + 10 => AssetType::LSLText, + 11 => AssetType::LSLBytecode, + 12 => AssetType::TextureTGA, + 13 => AssetType::Bodypart, + 17 => AssetType::SoundWAV, + 18 => AssetType::ImageTGA, + 19 => AssetType::ImageJPEG, + 20 => AssetType::Animation, + 21 => AssetType::Gesture, + 22 => AssetType::Simstate, + 24 => AssetType::Link, + 25 => AssetType::LinkFolder, + 40 => AssetType::Widget, + 45 => AssetType::Person, + 49 => AssetType::Mesh, + 56 => AssetType::Settings, + 57 => AssetType::Material, + _ => AssetType::Unknown, + } +} + +impl GroupManager { + pub fn request_current_groups(&self) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::AgentDataUpdateRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + self.send(&packet, PacketType::AgentDataUpdateRequest) + } + pub fn request_group_name(&self, group_id: UUID) -> Result<(), Error> { + self.request_group_names(vec![group_id]) + } + pub fn request_group_names(&self, group_i_ds: Vec) -> Result<(), Error> { + if group_i_ds.is_empty() { + return Ok(()); + } + if group_i_ds.len() > MAX_GROUPS { + return Err(Error::Argument); + } + let mut packet = crate::packets::UUIDGroupNameRequestPacket::new_with_constructor()?; + packet.uuid_name_block = group_i_ds + .into_iter() + .map(|id| crate::packets::UUIDGroupNameRequestPacketUUIDNameBlockBlock { id }) + .collect(); + self.send(&packet, PacketType::UUIDGroupNameRequest) + } + pub fn request_group_profile(&self, group: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::GroupProfileRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = group; + self.send(&packet, PacketType::GroupProfileRequest) + } + pub fn request_group_members(&self, group: UUID) -> Result { + let request_id = UUID::random()?; + let (agent, session) = self.ids(); + { + let mut pending = mutex(&self.inner.pending); + if pending.members.len() >= MAX_PENDING_REQUESTS { + return Err(Error::InvalidOperation); + } + pending + .members + .insert(request_id, (group, usize::MAX, HashMap::new())); + } + let mut packet = crate::packets::GroupMembersRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = group; + packet.group_data.request_id = request_id; + if let Err(error) = self.send(&packet, PacketType::GroupMembersRequest) { + mutex(&self.inner.pending).members.remove(&request_id); + return Err(error); + } + Ok(request_id) + } + pub fn request_group_roles(&self, group: UUID) -> Result { + let request_id = UUID::random()?; + let (agent, session) = self.ids(); + { + let mut pending = mutex(&self.inner.pending); + if pending.roles.len() >= MAX_PENDING_REQUESTS { + return Err(Error::InvalidOperation); + } + pending + .roles + .insert(request_id, (group, usize::MAX, HashMap::new())); + } + let mut packet = crate::packets::GroupRoleDataRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = group; + packet.group_data.request_id = request_id; + if let Err(error) = self.send(&packet, PacketType::GroupRoleDataRequest) { + mutex(&self.inner.pending).roles.remove(&request_id); + return Err(error); + } + Ok(request_id) + } + pub fn request_group_roles_members(&self, group: UUID) -> Result { + let request_id = UUID::random()?; + let (agent, session) = self.ids(); + { + let mut pending = mutex(&self.inner.pending); + if pending.role_members.len() >= MAX_PENDING_REQUESTS { + return Err(Error::InvalidOperation); + } + pending + .role_members + .insert(request_id, (group, usize::MAX, Vec::new(), HashSet::new())); + } + let mut packet = crate::packets::GroupRoleMembersRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = group; + packet.group_data.request_id = request_id; + if let Err(error) = self.send(&packet, PacketType::GroupRoleMembersRequest) { + mutex(&self.inner.pending).role_members.remove(&request_id); + return Err(error); + } + Ok(request_id) + } + pub fn request_group_titles(&self, group: UUID) -> Result { + let request_id = UUID::random()?; + let (agent, session) = self.ids(); + { + let mut pending = mutex(&self.inner.pending); + if pending.titles.len() >= MAX_PENDING_REQUESTS { + return Err(Error::InvalidOperation); + } + pending.titles.insert(request_id, group); + } + let mut packet = crate::packets::GroupTitlesRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.agent_data.group_id = group; + packet.agent_data.request_id = request_id; + if let Err(error) = self.send(&packet, PacketType::GroupTitlesRequest) { + mutex(&self.inner.pending).titles.remove(&request_id); + return Err(error); + } + Ok(request_id) + } + pub fn request_group_account_summary( + &self, + group: UUID, + interval_days: i32, + current_interval: i32, + ) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::GroupAccountSummaryRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.agent_data.group_id = group; + let request_id = UUID::random()?; + { + let mut pending = mutex(&self.inner.pending); + if pending.accounts.len() >= MAX_PENDING_REQUESTS { + return Err(Error::InvalidOperation); + } + pending.accounts.insert(request_id, group); + } + packet.money_data.request_id = request_id; + packet.money_data.current_interval = current_interval; + packet.money_data.interval_days = interval_days; + if let Err(error) = self.send(&packet, PacketType::GroupAccountSummaryRequest) { + mutex(&self.inner.pending).accounts.remove(&request_id); + Err(error) + } else { + Ok(()) + } + } + pub fn request_group_notices_list(&self, group: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::GroupNoticesListRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.data.group_id = group; + self.send(&packet, PacketType::GroupNoticesListRequest) + } + pub fn request_group_notice(&self, notice_id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::GroupNoticeRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.data.group_notice_id = notice_id; + self.send(&packet, PacketType::GroupNoticeRequest) + } + pub fn activate_group(&self, id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::ActivateGroupPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.agent_data.group_id = id; + self.send(&packet, PacketType::ActivateGroup) + } + pub fn activate_title(&self, group: UUID, role: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::GroupTitleUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.agent_data.group_id = group; + packet.agent_data.title_role_id = role; + self.send(&packet, PacketType::GroupTitleUpdate) + } + pub fn set_group_contribution(&self, group: UUID, contribution: i32) -> Result<(), Error> { + if contribution < 0 { + return Err(Error::Argument); + } + let (agent, session) = self.ids(); + let mut packet = crate::packets::SetGroupContributionPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.data.group_id = group; + packet.data.contribution = contribution; + self.send(&packet, PacketType::SetGroupContribution) + } + pub fn set_group_accept_notices( + &self, + group_id: UUID, + accept_notices: bool, + list_in_profile: bool, + ) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::SetGroupAcceptNoticesPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.data.group_id = group_id; + packet.data.accept_notices = accept_notices; + packet.new_data.list_in_profile = list_in_profile; + self.send(&packet, PacketType::SetGroupAcceptNotices) + } + pub fn request_join_group(&self, id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::JoinGroupRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = id; + self.send(&packet, PacketType::JoinGroupRequest) + } + pub fn leave_group(&self, group_id: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::LeaveGroupRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = group_id; + self.send(&packet, PacketType::LeaveGroupRequest) + } + + pub fn invite( + &self, + group: UUID, + roles: Vec, + requested_agent: UUID, + ) -> Result<(), Error> { + if roles.is_empty() || roles.len() > 256 { + return Err(Error::Argument); + } + let (agent, session) = self.ids(); + let mut packet = crate::packets::InviteGroupRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = group; + packet.invite_data = roles + .into_iter() + .map( + |role_id| crate::packets::InviteGroupRequestPacketInviteDataBlock { + invitee_id: requested_agent, + role_id, + }, + ) + .collect(); + self.send(&packet, PacketType::InviteGroupRequest) + } + pub fn request_create_group(&self, group: Group) -> Result<(), Error> { + validate_group(&group)?; + let (agent, session) = self.ids(); + let mut packet = crate::packets::CreateGroupRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.allow_publish = group.allow_publish; + packet.group_data.charter = group.charter.into_bytes(); + packet.group_data.insignia_id = group.insignia_id; + packet.group_data.mature_publish = group.mature_publish; + packet.group_data.membership_fee = group.membership_fee; + packet.group_data.name = group.name.into_bytes(); + packet.group_data.open_enrollment = group.open_enrollment; + packet.group_data.show_in_list = group.show_in_list; + self.send(&packet, PacketType::CreateGroupRequest) + } + pub fn update_group(&self, id: UUID, group: Group) -> Result<(), Error> { + validate_group(&group)?; + let (agent, session) = self.ids(); + let mut packet = crate::packets::UpdateGroupInfoPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = id; + packet.group_data.allow_publish = group.allow_publish; + packet.group_data.charter = group.charter.into_bytes(); + packet.group_data.insignia_id = group.insignia_id; + packet.group_data.mature_publish = group.mature_publish; + packet.group_data.membership_fee = group.membership_fee; + packet.group_data.open_enrollment = group.open_enrollment; + packet.group_data.show_in_list = group.show_in_list; + self.send(&packet, PacketType::UpdateGroupInfo) + } + pub fn eject_user(&self, group: UUID, member: UUID) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::EjectGroupMemberRequestPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.group_data.group_id = group; + let block = + crate::packets::EjectGroupMemberRequestPacketEjectDataBlock { ejectee_id: member }; + packet.eject_data = vec![block]; + self.send(&packet, PacketType::EjectGroupMemberRequest) + } + pub fn update_role(&self, role: GroupRole) -> Result<(), Error> { + self.send_role_update(role.group_id, role, GroupRoleUpdate::UpdateAll) + } + pub fn create_role(&self, group: UUID, mut role: GroupRole) -> Result<(), Error> { + role.id = UUID::random()?; + self.send_role_update(group, role, GroupRoleUpdate::Create) + } + pub fn delete_role(&self, group: UUID, role_id: UUID) -> Result<(), Error> { + let role = GroupRole { + description: String::new(), + group_id: group, + id: role_id, + members: 0, + name: String::new(), + powers: crate::GroupPowers::NONE, + title: String::new(), + }; + self.send_role_update(group, role, GroupRoleUpdate::Delete) + } + fn send_role_update( + &self, + group: UUID, + role: GroupRole, + update: GroupRoleUpdate, + ) -> Result<(), Error> { + if role.name.len() > 256 || role.title.len() > 256 || role.description.len() > 1024 { + return Err(Error::Argument); + } + let (agent, session) = self.ids(); + let mut packet = crate::packets::GroupRoleUpdatePacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.agent_data.group_id = group; + let block = crate::packets::GroupRoleUpdatePacketRoleDataBlock { + role_id: role.id, + name: role.name.into_bytes(), + description: role.description.into_bytes(), + powers: role.powers.0, + title: role.title.into_bytes(), + update_type: update as u8, + }; + packet.role_data = vec![block]; + self.send(&packet, PacketType::GroupRoleUpdate) + } + pub fn add_to_role(&self, group: UUID, role: UUID, member: UUID) -> Result<(), Error> { + self.change_role(group, role, member, GroupRoleChangeType::AddToRole) + } + pub fn remove_from_role(&self, group: UUID, role: UUID, member: UUID) -> Result<(), Error> { + self.change_role(group, role, member, GroupRoleChangeType::RemoveFromRole) + } + fn change_role( + &self, + group: UUID, + role: UUID, + member: UUID, + change: GroupRoleChangeType, + ) -> Result<(), Error> { + let (agent, session) = self.ids(); + let mut packet = crate::packets::GroupRoleChangesPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.agent_data.group_id = group; + let block = crate::packets::GroupRoleChangesPacketRoleChangeBlock { + member_id: member, + role_id: role, + change: change as u32, + }; + packet.role_change = vec![block]; + self.send(&packet, PacketType::GroupRoleChanges) + } + pub fn start_proposal(&self, group: UUID, prop: GroupProposal) -> Result<(), Error> { + if prop.vote_text.len() > 1024 + || prop.duration <= 0 + || prop.quorum < 0 + || !(0.0..=1.0).contains(&prop.majority) + { + return Err(Error::Argument); + } + let (agent, session) = self.ids(); + let mut packet = crate::packets::StartGroupProposalPacket::new_with_constructor()?; + packet.agent_data.agent_id = agent; + packet.agent_data.session_id = session; + packet.proposal_data.group_id = group; + packet.proposal_data.proposal_text = prop.vote_text.into_bytes(); + packet.proposal_data.quorum = prop.quorum; + packet.proposal_data.majority = prop.majority; + packet.proposal_data.duration = prop.duration; + self.send(&packet, PacketType::StartGroupProposal) + } + pub fn send_group_notice(&self, group: UUID, notice: GroupNotice) -> Result<(), Error> { + if notice.subject.len() > 256 || notice.message.len() > 4096 { + return Err(Error::Argument); + } + let bucket = serialize_notice_attachment(¬ice)?; + let mut client = (*self.inner.client).clone(); + let region = client + .network() + .current_sim() + .map_or_else(UUID::zero, |sim| sim.id); + let agent = client.self_(); + agent.native_instant_message_full( + agent.name(), + group, + format!("{}|{}", notice.subject, notice.message), + UUID::zero(), + InstantMessageDialog::GroupNotice, + crate::InstantMessageOnline::Online, + Vector3::zero(), + region, + bucket, + ) + } + + pub async fn request_banned_agents( + &self, + group_id: UUID, + cancellation_token: Option, + ) -> Result<(), Error> { + let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token()); + let result = self.group_api_uri(group_id).map(|uri| (uri, token.clone())); + let Ok((uri, token)) = result else { + self.inner.events.banned.emit(BannedAgentsEventArgs { + group_id, + success: false, + banned_agents: None, + }); + return result.map(|_| ()); + }; + token.throw_if_cancellation_requested()?; + let response = self + .inner + .client + .native_http_caps_client() + .get(uri, token, None) + .await; + let parsed = response.and_then(|(response, bytes)| { + if !response.is_success_status_code() || bytes.len() > 4 * 1024 * 1024 { + return Err(Error::InvalidOperation); + } + let OSD::Map(root) = OSDParser::deserialize_with_bytes(bytes)? else { + return Err(Error::Argument); + }; + let mut agents = HashMap::new(); + if let Some(OSD::Map(list)) = root.get("ban_list") { + if list.len() > MAX_GROUP_ROWS { + return Err(Error::Argument); + } + for (id, value) in list { + let Ok(id) = UUID::new_with_string(id.clone()) else { + continue; + }; + let OSD::Map(fields) = value else { continue }; + if let Some(date) = fields.get("ban_date").and_then(|date| date.as_date().ok()) + { + agents.insert(id, date); + } + } + } + Ok(agents) + }); + match parsed { + Ok(agents) => { + self.inner.events.banned.emit(BannedAgentsEventArgs { + group_id, + success: true, + banned_agents: Some(agents), + }); + Ok(()) + } + Err(error) => { + self.inner.events.banned.emit(BannedAgentsEventArgs { + group_id, + success: false, + banned_agents: None, + }); + Err(error) + } + } + } + pub async fn request_ban_action( + &self, + group_id: UUID, + action: GroupBanAction, + agents: Vec, + cancellation_token: Option, + ) -> Result<(), Error> { + if agents.is_empty() || agents.len() > MAX_GROUP_ROWS { + return Err(Error::Argument); + } + let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token()); + token.throw_if_cancellation_requested()?; + let payload = OSD::Map(HashMap::from([ + ("ban_action".into(), OSD::Integer(action as i32)), + ( + "ban_ids".into(), + OSD::Array(agents.into_iter().map(OSD::UUID).collect()), + ), + ])); + let (response, bytes) = self + .inner + .client + .native_http_caps_client() + .post_with_uri_osd_format_osd_cancellation_token_i_progress( + self.group_api_uri(group_id)?, + OSDFormat::Xml, + payload, + token, + None, + ) + .await?; + if !response.is_success_status_code() || bytes.len() > 1024 * 1024 { + Err(Error::InvalidOperation) + } else { + Ok(()) + } + } + fn group_api_uri(&self, group_id: UUID) -> Result { + let caps = self + .inner + .client + .network() + .current_sim() + .and_then(|sim| sim.native_caps()) + .ok_or(Error::InvalidOperation)?; + let uri = caps + .capability_uri("GroupAPIv1".into())? + .ok_or(Error::InvalidOperation)?; + let mut url = reqwest::Url::parse(&uri.0).map_err(|_| Error::Argument)?; + url.query_pairs_mut() + .append_pair("group_id", &group_id.to_string()); + Ok(libremetaverse_types::compat::Uri(url.into())) + } +} + +fn validate_group(group: &Group) -> Result<(), Error> { + if group.name.is_empty() + || group.name.len() > 256 + || group.charter.len() > 4096 + || group.membership_fee < 0 + { + Err(Error::Argument) + } else { + Ok(()) + } +} + +pub(crate) fn serialize_notice_attachment(notice: &GroupNotice) -> Result, Error> { + if notice.owner_id == UUID::zero() || notice.attachment_id == UUID::zero() { + return Ok(Vec::new()); + } + OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([ + ("item_id".into(), OSD::UUID(notice.attachment_id)), + ("owner_id".into(), OSD::UUID(notice.owner_id)), + ]))) +} + +impl Group { + pub(crate) fn native_equals_group(&self, other: &Self) -> bool { + self == other + } + pub(crate) fn native_equals_object( + &self, + other: Option, + ) -> bool { + other + .as_ref() + .and_then(libremetaverse_types::compat::Object::downcast_ref::) + == Some(self) + } + pub(crate) fn native_hash_code(&self) -> i32 { + let Ok(bytes) = self.id.get_bytes() else { + return 0; + }; + i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + } + pub(crate) fn native_to_string(&self) -> String { + format!("{} (Members: {})", self.name, self.group_membership_count) + } +} +impl GroupNotice { + pub(crate) fn native_serialize_attachment(&self) -> Result, Error> { + serialize_notice_attachment(self) + } +} +impl GroupRole { + pub(crate) fn native_to_string(&self) -> String { + format!("{} ({})", self.name, self.title) + } +} +impl GroupTitle { + pub(crate) fn native_to_string(&self) -> String { + self.title.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + + fn id(value: &str) -> UUID { + UUID::new_with_string(value.into()).unwrap() + } + + fn simulator(client: &GridClient) -> crate::Simulator { + crate::Simulator::native_new( + client.clone(), + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13972), + 0, + Some(256), + Some(256), + ) + .unwrap() + } + + fn member_packet(group_id: UUID, request_id: UUID, member_id: UUID) -> Vec { + let mut packet = crate::packets::GroupMembersReplyPacket::new_with_constructor().unwrap(); + packet.group_data.group_id = group_id; + packet.group_data.request_id = request_id; + packet.group_data.member_count = 2; + let mut member = + crate::packets::GroupMembersReplyPacketMemberDataBlock::new_with_constructor().unwrap(); + member.agent_id = member_id; + member.title = b"Builder".to_vec(); + member.online_status = b"Online".to_vec(); + member.agent_powers = crate::GroupPowers::MEMBER_VISIBLE.0; + packet.member_data = vec![member]; + packet.encode_packet().unwrap() + } + + #[test] + fn fragmented_members_are_correlated_deduplicated_and_emitted_after_unlock() { + let client = GridClient::new().unwrap(); + let manager = GroupManager::native_new(Arc::new(client.clone())); + let simulator = simulator(&client); + let group_id = id("11111111-1111-1111-1111-111111111111"); + let request_id = id("22222222-2222-2222-2222-222222222222"); + let first = id("33333333-3333-3333-3333-333333333333"); + let second = id("44444444-4444-4444-4444-444444444444"); + mutex(&manager.inner.pending) + .members + .insert(request_id, (group_id, usize::MAX, HashMap::new())); + let received = Arc::new(Mutex::new(Vec::new())); + let received_copy = Arc::clone(&received); + let inner = Arc::clone(&manager.inner); + let _subscription = manager.subscribe_group_members_reply(Arc::new(move |event| { + assert!( + !mutex(&inner.pending) + .members + .contains_key(&event.request_id()) + ); + received_copy.lock().unwrap().push(event.members()); + })); + for member in [first, first, second] { + manager.inner.handle_packet(RawPacketReceivedEventArgs { + packet_type: PacketType::GroupMembersReply, + data: member_packet(group_id, request_id, member), + simulator: simulator.clone(), + }); + } + let values = received.lock().unwrap(); + assert_eq!(values.len(), 1); + assert_eq!(values[0].len(), 2); + assert_eq!(values[0][&first].title, "Builder"); + } + + #[test] + fn notice_attachment_is_structured_and_empty_without_both_ids() { + let empty = GroupNotice { + attachment_id: UUID::zero(), + message: String::new(), + owner_id: UUID::zero(), + subject: String::new(), + }; + assert!(serialize_notice_attachment(&empty).unwrap().is_empty()); + let notice = GroupNotice { + attachment_id: id("55555555-5555-5555-5555-555555555555"), + owner_id: id("66666666-6666-6666-6666-666666666666"), + ..empty + }; + let OSD::Map(map) = + OSDParser::deserialize_with_bytes(serialize_notice_attachment(¬ice).unwrap()) + .unwrap() + else { + panic!("attachment must be an LLSD map") + }; + assert_eq!(map["item_id"].as_uuid().unwrap(), notice.attachment_id); + assert_eq!(map["owner_id"].as_uuid().unwrap(), notice.owner_id); + } + + #[test] + fn dispose_releases_callbacks_and_pending_requests() { + let client = GridClient::new().unwrap(); + let manager = GroupManager::native_new(Arc::new(client)); + mutex(&manager.inner.pending) + .members + .insert(UUID::zero(), (UUID::zero(), 1, HashMap::new())); + manager.dispose().unwrap(); + assert!(manager.inner.disposed.load(Ordering::Acquire)); + assert!(mutex(&manager.inner.pending).members.is_empty()); + assert!(mutex(&manager.inner.subscriptions).is_empty()); + } +} diff --git a/crates/libremetaverse/src/lib.rs b/crates/libremetaverse/src/lib.rs index 9e931b0..42ee267 100644 --- a/crates/libremetaverse/src/lib.rs +++ b/crates/libremetaverse/src/lib.rs @@ -28,7 +28,9 @@ mod current_outfit; mod download_manager; mod environment_manager; mod event_queue; +mod friends_manager; mod gesture; +mod group_manager; mod initial_outfit; mod inventory; mod inventory_ais; diff --git a/docs/social.md b/docs/social.md new file mode 100644 index 0000000..3b54cdd --- /dev/null +++ b/docs/social.md @@ -0,0 +1,9 @@ +# Social managers + +`GridClient::friends()` and `GridClient::groups()` are client-owned native Rust managers. Repeated property access returns handles to the same synchronized state, and client shutdown releases packet, CAPS, login, and instant-message subscriptions. + +The friendship manager maintains the login buddy list, presence, names, bidirectional rights, pending offers, calling cards, map requests, and friendship lifecycle operations. Friendship acceptance and rejection use the simulator capabilities when requested and retain LLUDP fallbacks. Capability calls honor the supplied cancellation token and reject oversized or malformed responses. + +The group manager maintains current memberships and supports profiles, titles, members, roles, role-member mappings, invitations, notices, account summaries, proposals, bans, and membership mutations. Fragmented reply packets are correlated by request UUID, bounded, and deduplicated before one completed event is emitted. State is committed and locks are released before callbacks run. + +Group-chat session membership and offline instant-message retrieval remain part of `AgentManager`, because both are instant-message session concerns. Session CAPS events update membership before callbacks, and teardown removes session state deterministically. Social message debug output reports metadata and lengths rather than message bodies, binary buckets, or capability URLs. diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index 11c9613..da3b273 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -182,6 +182,31 @@ NATIVE_TYPES = { "T:LibreMetaverse.PlacesReplyEventArgs": "crate::directory_manager::PlacesReplyEventArgs", "T:LibreMetaverse.InterestListManager": "crate::interest_list::InterestListManager", "T:LibreMetaverse.Messages.Linden.InterestListMessage": "crate::interest_list::InterestListMessage", + "T:LibreMetaverse.CallingCardOfferedEventArgs": "crate::friends_manager::CallingCardOfferedEventArgs", + "T:LibreMetaverse.FriendFoundReplyEventArgs": "crate::friends_manager::FriendFoundReplyEventArgs", + "T:LibreMetaverse.FriendInfo": "crate::friends_manager::FriendInfo", + "T:LibreMetaverse.FriendInfoEventArgs": "crate::friends_manager::FriendInfoEventArgs", + "T:LibreMetaverse.FriendNamesEventArgs": "crate::friends_manager::FriendNamesEventArgs", + "T:LibreMetaverse.FriendsManager": "crate::friends_manager::FriendsManager", + "T:LibreMetaverse.FriendsReadyEventArgs": "crate::friends_manager::FriendsReadyEventArgs", + "T:LibreMetaverse.FriendshipOfferedEventArgs": "crate::friends_manager::FriendshipOfferedEventArgs", + "T:LibreMetaverse.FriendshipResponseEventArgs": "crate::friends_manager::FriendshipResponseEventArgs", + "T:LibreMetaverse.FriendshipTerminatedEventArgs": "crate::friends_manager::FriendshipTerminatedEventArgs", + "T:LibreMetaverse.BannedAgentsEventArgs": "crate::group_manager::BannedAgentsEventArgs", + "T:LibreMetaverse.CurrentGroupsEventArgs": "crate::group_manager::CurrentGroupsEventArgs", + "T:LibreMetaverse.GroupAccountSummaryReplyEventArgs": "crate::group_manager::GroupAccountSummaryReplyEventArgs", + "T:LibreMetaverse.GroupCreatedReplyEventArgs": "crate::group_manager::GroupCreatedReplyEventArgs", + "T:LibreMetaverse.GroupDroppedEventArgs": "crate::group_manager::GroupDroppedEventArgs", + "T:LibreMetaverse.GroupInvitationEventArgs": "crate::group_manager::GroupInvitationEventArgs", + "T:LibreMetaverse.GroupManager": "crate::group_manager::GroupManager", + "T:LibreMetaverse.GroupMembersReplyEventArgs": "crate::group_manager::GroupMembersReplyEventArgs", + "T:LibreMetaverse.GroupNamesEventArgs": "crate::group_manager::GroupNamesEventArgs", + "T:LibreMetaverse.GroupNoticesListReplyEventArgs": "crate::group_manager::GroupNoticesListReplyEventArgs", + "T:LibreMetaverse.GroupOperationEventArgs": "crate::group_manager::GroupOperationEventArgs", + "T:LibreMetaverse.GroupProfileEventArgs": "crate::group_manager::GroupProfileEventArgs", + "T:LibreMetaverse.GroupRolesDataReplyEventArgs": "crate::group_manager::GroupRolesDataReplyEventArgs", + "T:LibreMetaverse.GroupRolesMembersReplyEventArgs": "crate::group_manager::GroupRolesMembersReplyEventArgs", + "T:LibreMetaverse.GroupTitlesReplyEventArgs": "crate::group_manager::GroupTitlesReplyEventArgs", "T:LibreMetaverse.TeleportEventArgs": "crate::agent_movement::TeleportEventArgs", "T:LibreMetaverse.TerrainCompressor": "crate::terrain_codec::TerrainCompressor", "T:LibreMetaverse.TerrainPatch": "crate::terrain_codec::TerrainPatch", @@ -446,6 +471,17 @@ NATIVE_MEMBER_BODIES = { "P:LibreMetaverse.GridClient.Directory#set": "self.native_set_directory(value)", "P:LibreMetaverse.GridClient.InterestList": "self.native_interest_list().unwrap_or_else(|_| panic!(\"failed to construct InterestListManager\"))", "P:LibreMetaverse.GridClient.InterestList#set": "self.native_set_interest_list(value)", + "P:LibreMetaverse.GridClient.Friends": "self.native_friends().unwrap_or_else(|_| panic!(\"failed to construct FriendsManager\"))", + "P:LibreMetaverse.GridClient.Friends#set": "self.native_set_friends(value)", + "P:LibreMetaverse.GridClient.Groups": "self.native_groups()", + "P:LibreMetaverse.GridClient.Groups#set": "self.native_set_groups(value)", + "M:LibreMetaverse.Group.Equals(LibreMetaverse.Group)": "self.native_equals_group(&other)", + "M:LibreMetaverse.Group.Equals(System.Object)": "self.native_equals_object(obj)", + "M:LibreMetaverse.Group.GetHashCode": "self.native_hash_code()", + "M:LibreMetaverse.Group.ToString": "self.native_to_string()", + "M:LibreMetaverse.GroupNotice.SerializeAttachment": "self.native_serialize_attachment()", + "M:LibreMetaverse.GroupRole.ToString": "self.native_to_string()", + "M:LibreMetaverse.GroupTitle.ToString": "self.native_to_string()", "M:LibreMetaverse.Simulator.IsParcelMapFull": "Ok(self.native_is_parcel_map_full())", "M:LibreMetaverse.Simulator.TerrainHeightAtPoint(System.Int32,System.Int32,System.Single@)": "self.native_terrain_height_at_point(x, y, height)", "P:LibreMetaverse.Simulator.ParcelMap": "self.native_parcel_map_snapshot()", @@ -2699,6 +2735,14 @@ VALUE_DERIVES = { "T:LibreMetaverse.ConnectionSettings": "Clone, Eq, PartialEq", "T:LibreMetaverse.LoggingSettings": "Clone, Debug, Eq, PartialEq", "T:LibreMetaverse.HomeInfo": "Clone, Copy, Debug, PartialEq", + "T:LibreMetaverse.Group": "Clone, Debug, Eq, PartialEq", + "T:LibreMetaverse.GroupAccountSummary": "Clone, Debug, Eq, PartialEq", + "T:LibreMetaverse.GroupMember": "Clone, Debug, Eq, PartialEq", + "T:LibreMetaverse.GroupNotice": "Clone, Debug, Eq, PartialEq", + "T:LibreMetaverse.GroupNoticesListEntry": "Clone, Debug, Eq, PartialEq", + "T:LibreMetaverse.GroupProposal": "Clone, Debug, PartialEq", + "T:LibreMetaverse.GroupRole": "Clone, Debug, Eq, PartialEq", + "T:LibreMetaverse.GroupTitle": "Clone, Debug, Eq, PartialEq", "T:LibreMetaverse.InventoryBase": "Clone, Debug", "T:LibreMetaverse.InventoryFolder": "Clone, Debug", "T:LibreMetaverse.Matrix4": "Clone, Copy, Debug",