diff --git a/assets/robocraft/chat.json b/assets/robocraft/chat.json deleted file mode 100644 index 5bf791d..0000000 --- a/assets/robocraft/chat.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "commands": [ - { - "regex": "\\?online", - "op": { - "type": "BuiltIn", - "built_in": "OnlineUsers" - } - }, - { - "regex": "\\?users", - "op": { - "type": "BuiltIn", - "built_in": "TotalUsers" - } - } - ] -} diff --git a/assets/robocraft/config.json b/assets/robocraft/config.json index acd49a7..8f71bb3 100644 --- a/assets/robocraft/config.json +++ b/assets/robocraft/config.json @@ -14462,6 +14462,13 @@ "type": "BuiltIn", "built_in": "TotalUsers" } + }, + { + "regex": "\\?help", + "op": { + "type": "BuiltIn", + "built_in": "Help" + } } ] }, @@ -14539,4 +14546,4 @@ } ] } -} \ No newline at end of file +} diff --git a/rc_chat_room/src/operations/send_message.rs b/rc_chat_room/src/operations/send_message.rs index cb8d6f4..2445276 100644 --- a/rc_chat_room/src/operations/send_message.rs +++ b/rc_chat_room/src/operations/send_message.rs @@ -76,7 +76,7 @@ impl SimpleOperation for PrivateMessageSender { }; let chat_system = self.chat.system().await; log::debug!("Got message `{}` from user {} (@ {} to {})", message_text.string, user.public_id(), chat_loc, username.string); - chat_system.handle_private_message(user.as_ref().as_ref(), message_text.string, username.string); + chat_system.handle_private_message(user.as_ref().as_ref(), message_text.string, username.string).await; } } Ok(params.into()) diff --git a/rc_chat_room/src/state/chat/chat.rs b/rc_chat_room/src/state/chat/chat.rs index 93b91e4..cf09eca 100644 --- a/rc_chat_room/src/state/chat/chat.rs +++ b/rc_chat_room/src/state/chat/chat.rs @@ -90,7 +90,7 @@ impl ChatSystem { pub async fn handle_public_message(&self, user: &(dyn oj_rc_core::persist::user::User<()> + Send + Sync), text: String, channel: String, channel_ty: crate::data::channel::ChatChannelType) { if self.config.is_command_channel(&channel) { if let Some(user_handle) = self.online_users.get(user.public_id()) { - self.handle_public_command(user, text, user_handle, channel, channel_ty); + self.handle_public_command(user, text, user_handle, channel, channel_ty).await; } } else if let Some(room) = self.chats.get(&channel) { let event_params = crate::events::chat_message::PublicMessage { @@ -109,11 +109,11 @@ impl ChatSystem { } } - fn handle_public_command(&self, user: &dyn oj_rc_core::persist::user::User<()>, text: String, handle: &super::UserHandle, channel: String, channel_ty: crate::data::channel::ChatChannelType) { + async fn handle_public_command(&self, user: &dyn oj_rc_core::persist::user::User<()>, text: String, handle: &super::UserHandle, channel: String, channel_ty: crate::data::channel::ChatChannelType) { let event_params = crate::events::chat_message::PublicMessage { sender_name: self.config.command_username().to_owned(), sender_display_name: self.config.command_username().to_owned(), - text: self.config.perform_command(&text, self, user), + text: self.config.perform_command(&text, self, user).await, is_dev: false, is_mod: false, is_admin: false, @@ -132,10 +132,10 @@ impl ChatSystem { handle.send(polariton_server::ToSend::Data { data: polariton::packet::Data::Event(event), encrypt: true, channel: 0, reliable: true }); } - pub fn handle_private_message(&self, user: &dyn oj_rc_core::persist::user::User<()>, text: String, recipient: String) { + pub async fn handle_private_message(&self, user: &dyn oj_rc_core::persist::user::User<()>, text: String, recipient: String) { if self.config.is_command_user(&recipient) { if let Some(user_handle) = self.online_users.get(user.public_id()) { - self.handle_private_command(user, text, user_handle); + self.handle_private_command(user, text, user_handle).await; } } else if let Some(recipient_handle) = self.online_users.get(&recipient) { let private_msg = crate::events::chat_message::PrivateMessage { @@ -150,11 +150,11 @@ impl ChatSystem { } } - fn handle_private_command(&self, user: &dyn oj_rc_core::persist::user::User<()>, text: String, handle: &super::UserHandle) { + async fn handle_private_command(&self, user: &dyn oj_rc_core::persist::user::User<()>, text: String, handle: &super::UserHandle) { let event_params = crate::events::chat_message::PrivateMessage { sender_name: self.config.command_username().to_owned(), sender_display_name: self.config.command_username().to_owned(), - text: self.config.perform_command(&text, self, user), + text: self.config.perform_command(&text, self, user).await, is_dev: false, is_mod: false, is_admin: false, @@ -182,4 +182,8 @@ impl ChatSystem { pub fn is_user_online(&self, display_name: &str) -> bool { self.config.is_command_user(display_name) || self.online_users.get(display_name).map(|x| x.is_online()).unwrap_or(false) } + + pub fn chat_config(&self) -> &'_ super::ChatSystemConfig { + &self.config + } } diff --git a/rc_chat_room/src/state/chat/config.rs b/rc_chat_room/src/state/chat/config.rs index 818cf4f..e3a1231 100644 --- a/rc_chat_room/src/state/chat/config.rs +++ b/rc_chat_room/src/state/chat/config.rs @@ -7,7 +7,7 @@ pub struct ChatSystemConfig { #[derive(Clone, Copy)] struct CommandContext<'a, 'b> { chat_system: &'a super::ChatSystem, - user: &'b dyn oj_rc_core::persist::user::User<()>, + user: &'b dyn oj_rc_core::persist::user::ChatUser, } impl ChatSystemConfig { @@ -26,13 +26,13 @@ impl ChatSystemConfig { }) } - pub fn perform_command(&self, text: &str, chat_system: &super::ChatSystem, user: &dyn oj_rc_core::persist::user::User<()>,) -> String { + pub async fn perform_command(&self, text: &str, chat_system: &super::ChatSystem, user: &dyn oj_rc_core::persist::user::ChatUser,) -> String { let ctx = CommandContext { chat_system, user, }; for cmd in self.commands.iter() { - if let Some(result) = cmd.perform_if_match(text, ctx) { + if let Some(result) = cmd.perform_if_match(text, ctx).await { return result; } } @@ -65,8 +65,12 @@ impl ChatCommand { }) } - fn perform_if_match(&self, text: &str, ctx: CommandContext) -> Option { - self.regex.captures(text).map(|cap| self.op.perform_command(cap, ctx)) + async fn perform_if_match<'b, 'c>(&self, text: &str, ctx: CommandContext<'b, 'c>) -> Option { + if let Some(cap) = self.regex.captures(text) { + Some(self.op.perform_command(cap, ctx).await) + } else { + None + } } } @@ -85,18 +89,27 @@ impl ChatOperation { } } - fn perform_command<'a>(&self, _captures: regex::Captures<'a>, ctx: CommandContext) -> String { + async fn perform_command<'a, 'b, 'c>(&self, _captures: regex::Captures<'a>, ctx: CommandContext<'b, 'c>) -> String { match self { - Self::BuiltIn(b_in) => b_in.do_command(ctx), + Self::BuiltIn(b_in) => b_in.do_command(ctx).await, Self::Custom => "{not implemented}".to_owned(), Self::Nop => "{no op}".to_owned(), } } + + fn help_str(&self) -> String { + match self { + Self::BuiltIn(b_in) => b_in.do_help(), + Self::Custom => "{not implemented}".to_owned(), + Self::Nop => "does nothing".to_owned(), + } + } } enum BuiltIn { OnlineUsers, TotalUsers, + Help, } impl BuiltIn { @@ -104,10 +117,15 @@ impl BuiltIn { match b_in { oj_rc_core::persist::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers, oj_rc_core::persist::BuiltInChatOperation::TotalUsers => Self::TotalUsers, + oj_rc_core::persist::BuiltInChatOperation::Help => Self::Help, } } - fn do_command(&self, ctx: CommandContext) -> String { + fn prettify_re<'a>(regex: &'a str) -> &'a str { + regex.trim_start_matches("\\") + } + + async fn do_command<'b, 'c>(&self, ctx: CommandContext<'b, 'c>) -> String { match self { Self::OnlineUsers => { let online_count = ctx.chat_system.user_count(); @@ -118,8 +136,35 @@ impl BuiltIn { } }, Self::TotalUsers => { - "User count is not supported".to_string() + match ctx.user.get_total_registered_users().await { + Ok(count) => if count == 1 { + "1 user registered".to_owned() + } else { + format!("{} users registered", count) + }, + Err(e) => e.error_msg().map(|x| x.to_owned()).unwrap_or_else(|| "Failed to retrieve registered users".to_owned()), + } }, + Self::Help => { + use core::fmt::Write; + let mut msg = String::new(); + for command in ctx.chat_system.chat_config().commands.iter() { + let raw_re = command.regex.to_string(); + let pretty_name = Self::prettify_re(&raw_re); + if let Err(e) = write!(msg, "\n{}: {}", pretty_name, command.op.help_str()) { + log::warn!("Failed to construct help for command `{}`: {}", pretty_name, e); + } + } + msg + } + } + } + + fn do_help(&self) -> String { + match self { + Self::OnlineUsers => "Show total users online".to_owned(), + Self::TotalUsers => "Show total users registered".to_owned(), + Self::Help => "Display this message".to_owned(), } } } diff --git a/rc_core/src/persist/chat.rs b/rc_core/src/persist/chat.rs index ddd8396..29beba4 100644 --- a/rc_core/src/persist/chat.rs +++ b/rc_core/src/persist/chat.rs @@ -33,6 +33,7 @@ pub enum ChatOperation { pub enum BuiltInChatOperation { OnlineUsers, TotalUsers, + Help, } diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 8c4fb5a..99325d8 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -1337,4 +1337,15 @@ impl super::ChatUser for UserData { Err(crate::data::error_codes::ChatErrorCodes::DoesNotExist as i16) } } + + async fn get_total_registered_users(&self) -> Result { + self.db.user_count().await + .map_err(|e| { + log::error!("Failed to retrieve total user count for {}: {}", self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16, + format!("Failed to retrieve total user count: {}", e), + ) + }) + } } diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 4e5a4db..2f9cf26 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -211,6 +211,7 @@ pub trait ChatUser: CommonUser { //async fn has_pending_sanctions(&self) -> Result; async fn get_sanctions(&self, username: String) -> Result, i16>; async fn set_sanction(&self, sanction: SetSanction) -> Result<(), i16>; + async fn get_total_registered_users(&self) -> Result; } pub struct SetSanction { diff --git a/rc_database/src/wrapper.rs b/rc_database/src/wrapper.rs index 1e95f40..50c2a23 100644 --- a/rc_database/src/wrapper.rs +++ b/rc_database/src/wrapper.rs @@ -15,6 +15,12 @@ impl Database { }) } + pub async fn user_count(&self) -> Result { + crate::schema::user::Entity::find() + .count(&self.orm) + .await + } + pub async fn user_by_display_name(&self, public_id: String) -> Result, sea_orm::DbErr> { crate::schema::user::Entity::find() .filter(crate::schema::user::Column::DisplayName.eq(public_id)) diff --git a/utils/cube_gen.py b/utils/cube_gen.py index 51b112a..2b9767c 100755 --- a/utils/cube_gen.py +++ b/utils/cube_gen.py @@ -472,6 +472,13 @@ def main(asset_in, cubes=None, weapons=None, movement=None): "type": "BuiltIn", "built_in": "TotalUsers" } + }, + { + "regex": "\\?help", + "op": { + "type": "BuiltIn", + "built_in": "Help" + } } ] },