diff --git a/Cargo.lock b/Cargo.lock index 51548e3..e979a55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2469,10 +2469,12 @@ dependencies = [ "chrono", "clap", "env_logger", + "futures", "git-version", "log", "oj_polariton_auth", "oj_rc_core", + "oj_rc_plugins", "oj_serdes", "polariton", "polariton_server", @@ -2602,6 +2604,13 @@ dependencies = [ "tokio", ] +[[package]] +name = "oj_rc_plugins" +version = "1.1.0" +dependencies = [ + "log", +] + [[package]] name = "oj_rc_services" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index bc7a947..99623c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "rc_lobby", "rc_lobby_room", "rc_core", "rc_database", "rc_factory", "rc_multiplayer", + "rc_plugins", ] [workspace.dependencies] diff --git a/rc_chat_room/Cargo.toml b/rc_chat_room/Cargo.toml index 285d131..71b3298 100644 --- a/rc_chat_room/Cargo.toml +++ b/rc_chat_room/Cargo.toml @@ -23,3 +23,5 @@ async-trait.workspace = true git-version.workspace = true chrono.workspace = true oj_serdes.workspace = true +oj_rc_plugins = { version = "*", path = "../rc_plugins" } +futures.workspace = true diff --git a/rc_chat_room/src/main.rs b/rc_chat_room/src/main.rs index e6ffea0..5d08fcf 100644 --- a/rc_chat_room/src/main.rs +++ b/rc_chat_room/src/main.rs @@ -3,6 +3,8 @@ mod cli; mod state; mod op_handler; pub use op_handler::SimpleChatFunc; +mod plugin_wrapper; +pub use plugin_wrapper::{PluginWrapper, ProviderWrapper}; mod data; mod operations; diff --git a/rc_chat_room/src/plugin_wrapper.rs b/rc_chat_room/src/plugin_wrapper.rs new file mode 100644 index 0000000..b99e552 --- /dev/null +++ b/rc_chat_room/src/plugin_wrapper.rs @@ -0,0 +1,53 @@ +pub struct PluginWrapper { + plugins: Vec>>, +} + +impl PluginWrapper { + pub fn new(plugins: impl IntoIterator>) -> Self { + Self { + plugins: plugins.into_iter().map(std::sync::Arc::new).collect(), + } + } + + pub fn set_provider(&self, provider: std::sync::Arc>) { + for plugin in self.plugins.iter() { + plugin.set_provider(provider.clone()); + } + } + + pub async fn on_message(&self, message: &str, channel: &str, username: &str) { + let mut futures = Vec::with_capacity(self.plugins.len()); + for plugin in self.plugins.iter() { + let owned_plugin = plugin.to_owned(); + let owned_message = message.to_owned(); + let owned_channel = channel.to_owned(); + let owned_username = username.to_owned(); + let handle = tokio::task::spawn_blocking(move || owned_plugin.on_message(&owned_message, &owned_channel, &owned_username)); + futures.push(handle); + } + futures::future::join_all(futures).await; + } +} + +pub struct ProviderWrapper { + provider: std::sync::Arc>, +} + +impl ProviderWrapper { + pub fn new(provider: std::sync::Arc>) -> Self { + Self { + provider, + } + } + + async fn do_send_message(provider: std::sync::Arc>, message: String, channel: String, username: String) { + provider.read().await + .send_fake_message(message, channel, username).await; + } +} + +impl oj_rc_plugins::chat::ChatProvider for ProviderWrapper { + fn send_message(&self, message: &str, channel: &str, username: &str) { + tokio::task::spawn(Self::do_send_message(self.provider.clone(), message.to_owned(), channel.to_owned(), username.to_owned())); + } +} diff --git a/rc_chat_room/src/state/chat/chat.rs b/rc_chat_room/src/state/chat/chat.rs index 6994831..374e902 100644 --- a/rc_chat_room/src/state/chat/chat.rs +++ b/rc_chat_room/src/state/chat/chat.rs @@ -19,12 +19,18 @@ impl ChatProvider { pub async fn system_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, crate::state::chat::ChatSystem> { self.chat_system.write().await } + + pub async fn init_provider(&self) { + let provider = crate::ProviderWrapper::new(self.chat_system.clone()); + self.chat_system.read().await.plugin.set_provider(std::sync::Arc::new(Box::new(provider))); + } } pub struct ChatSystem { chats: HashMap, online_users: HashMap, config: super::ChatSystemConfig, + plugin: crate::PluginWrapper, } impl ChatSystem { @@ -53,6 +59,10 @@ impl ChatSystem { total_removed_users + to_be_removed.len() } + pub(crate) fn get_channel(&self, channel: &str) -> Option<&super::ChatRoom> { + self.chats.get(&channel.to_lowercase()) + } + pub fn connect_user(&mut self, display_name: String, channels: Vec, event_tx: tokio::sync::mpsc::UnboundedSender) { self.cleanup(); let handle = super::UserHandle::from_strong_sender(event_tx, display_name.clone()); @@ -92,11 +102,11 @@ impl ChatSystem { if let Some(user_handle) = self.online_users.get(user.public_id()) { self.handle_public_command(user, text, user_handle, channel, channel_ty).await; } - } else if let Some(room) = self.chats.get(&channel.to_lowercase()) { + } else if let Some(room) = self.get_channel(&channel) { let event_params = crate::events::chat_message::PublicMessage { sender_name: user.public_id().to_owned(), sender_display_name: user.display_name().to_owned(), - text, + text: text.clone(), is_dev: user.is_dev(), is_mod: user.is_mod(), is_admin: user.is_admin(), @@ -104,11 +114,32 @@ impl ChatSystem { channel_ty, }; room.send_public_message(event_params); + self.plugin.on_message(&text, &room.canon_name(), user.display_name()).await; } else { log::warn!("Got message for non-existent chat room {} (variant: {:?})", channel, channel_ty); } } + pub async fn send_fake_message(&self, message: String, channel: String, username: String) -> bool { + if let Some(room) = self.get_channel(&channel) { + let event_params = crate::events::chat_message::PublicMessage { + sender_name: username.clone(), + sender_display_name: username.clone(), + text: message.clone(), + is_dev: false, + is_mod: false, + is_admin: false, + channel_name: room.canon_name(), + channel_ty: crate::data::channel::ChatChannelType::Public, + }; + room.send_public_message(event_params); + self.plugin.on_message(&message, &room.canon_name(), &username).await; + true + } else { + false + } + } + 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(), @@ -172,6 +203,7 @@ impl ChatSystem { chats: HashMap::new(), online_users: HashMap::new(), config: super::ChatSystemConfig::from_persist(config)?, + plugin: crate::PluginWrapper::new(vec![]), // TODO construct plugins }) } diff --git a/rc_plugins/Cargo.toml b/rc_plugins/Cargo.toml new file mode 100644 index 0000000..80ddf36 --- /dev/null +++ b/rc_plugins/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "oj_rc_plugins" +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true +authors.workspace = true +readme.workspace = true + +[dependencies] +log.workspace = true diff --git a/rc_plugins/src/chat/mod.rs b/rc_plugins/src/chat/mod.rs new file mode 100644 index 0000000..6da659a --- /dev/null +++ b/rc_plugins/src/chat/mod.rs @@ -0,0 +1,2 @@ +mod plugin; +pub use plugin::{ChatProvider, ChatPlugin}; diff --git a/rc_plugins/src/chat/plugin.rs b/rc_plugins/src/chat/plugin.rs new file mode 100644 index 0000000..66ac373 --- /dev/null +++ b/rc_plugins/src/chat/plugin.rs @@ -0,0 +1,8 @@ +pub trait ChatPlugin: crate::Plugin { + fn set_provider(&self, provider: std::sync::Arc>); + fn on_message(&self, message: &str, channel: &str, username: &str); +} + +pub trait ChatProvider: Send + Sync { + fn send_message(&self, message: &str, channel: &str, username: &str); +} diff --git a/rc_plugins/src/lib.rs b/rc_plugins/src/lib.rs new file mode 100644 index 0000000..949c695 --- /dev/null +++ b/rc_plugins/src/lib.rs @@ -0,0 +1,3 @@ +pub mod chat; + +pub trait Plugin: Send + Sync {}