diff --git a/Cargo.lock b/Cargo.lock index 4a509523..f0e4bd78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -516,6 +516,7 @@ dependencies = [ "chrono", "config", "futures-util", + "gwevent", "indexmap", "kittycat", "log", diff --git a/core/rust.bot_modules.core/Cargo.toml b/core/rust.bot_modules.core/Cargo.toml index 4731ddd8..9d6eec0a 100644 --- a/core/rust.bot_modules.core/Cargo.toml +++ b/core/rust.bot_modules.core/Cargo.toml @@ -27,6 +27,7 @@ modules_ext = { path = "../rust.modules_ext" } module_settings = { path = "../rust.module_settings" } splashcore_rs = { path = "../rust.std" } templating = { path = "../rust.templating" } +gwevent = { path = "../rust.gwevent" } log = "0.4" [dependencies.serenity] diff --git a/core/rust.bot_modules.core/src/settings.rs b/core/rust.bot_modules.core/src/settings.rs index 50367053..efbaf2dd 100644 --- a/core/rust.bot_modules.core/src/settings.rs +++ b/core/rust.bot_modules.core/src/settings.rs @@ -976,6 +976,18 @@ pub static GUILD_TEMPLATES: LazyLock = LazyLock::new(|| { ignored_for: vec![], secret: false, }, + Column { + id: "events", + name: "Events", + description: "The events that this template can be dispatched on. If empty, this template is never dispatched.", + column_type: ColumnType::new_array(InnerColumnType::String { min_length: None, max_length: None, allowed_values: vec![], kind: InnerColumnTypeStringKind::Normal }), + nullable: true, + default: None, + unique: false, + suggestions: ColumnSuggestion::Static { suggestions: gwevent::core::event_list().to_vec() }, + ignored_for: vec![], + secret: false, + }, module_settings::common_columns::created_at(), module_settings::common_columns::created_by(), module_settings::common_columns::last_updated_at(), diff --git a/core/rust.bot_modules.hooks/src/cache.rs b/core/rust.bot_modules.hooks/src/cache.rs deleted file mode 100644 index 5b1d961a..00000000 --- a/core/rust.bot_modules.hooks/src/cache.rs +++ /dev/null @@ -1,42 +0,0 @@ -use moka::future::Cache; -use serenity::all::GuildId; -use sqlx::types::Uuid; -use sqlx::PgPool; -use std::sync::Arc; -use std::sync::LazyLock; -use std::time::Duration; - -#[derive(Debug, Clone)] -pub struct Sink { - pub id: Uuid, - pub sink: String, - pub events: Option>, - pub template: String, -} - -pub static SINKS_CACHE: LazyLock>>> = LazyLock::new(|| { - Cache::builder() - .support_invalidation_closures() - .time_to_idle(Duration::from_secs(60 * 5)) // Expire the audit log sink cache after 5 minutes - .build() -}); - -pub async fn get_sinks(guild_id: GuildId, pool: &PgPool) -> Result>, sqlx::Error> { - if let Some(sinks) = SINKS_CACHE.get(&guild_id).await { - return Ok(sinks.clone()); - } - - let sinks = sqlx::query_as!( - Sink, - "SELECT id, sink, events, template FROM hooks__sinks WHERE guild_id = $1 AND broken = false", - guild_id.to_string(), - ) - .fetch_all(pool) - .await?; - - let sinks = Arc::new(sinks); - - SINKS_CACHE.insert(guild_id, sinks.clone()).await; - - Ok(sinks) -} diff --git a/core/rust.bot_modules.hooks/src/events.rs b/core/rust.bot_modules.hooks/src/events.rs index fb86db26..a765e384 100644 --- a/core/rust.bot_modules.hooks/src/events.rs +++ b/core/rust.bot_modules.hooks/src/events.rs @@ -224,18 +224,18 @@ async fn dispatch_audit_log( event_data: serde_json::Value, guild_id: serenity::model::id::GuildId, ) -> Result<(), silverpelt::Error> { - let sinks = super::cache::get_sinks(guild_id, &data.pool).await?; + let templates = templating::cache::get_all_guild_templates(guild_id, &data.pool).await?; - if sinks.is_empty() { + if templates.is_empty() { return Ok(()); } - for sink in sinks.iter() { + for template in templates.iter() { // Verify event dispatch if !should_dispatch_event(event_name, { // False positive, unwrap_or_default cannot be used here as it moves the event out of the sink #[allow(clippy::manual_unwrap_or_default)] - if let Some(ref events) = sink.events { + if let Some(ref events) = template.events { events } else { &[] @@ -248,7 +248,7 @@ async fn dispatch_audit_log( templating::execute::<_, Option<()>>( guild_id, - templating::Template::Named(sink.template.clone()), + templating::Template::Named(template.name.clone()), data.pool.clone(), ctx.clone(), data.reqwest.clone(), @@ -256,8 +256,7 @@ async fn dispatch_audit_log( event_titlename: event_titlename.to_string(), event_name: event_name.to_string(), event_data: event_data.clone(), - sink_id: sink.id.to_string(), - sink: sink.sink.clone(), + template: template.clone(), }, ) .await?; @@ -273,8 +272,7 @@ struct HookContext { pub event_titlename: String, pub event_name: String, pub event_data: serde_json::Value, - pub sink_id: String, - pub sink: String, + pub template: templating::GuildTemplate, } #[typetag::serde] diff --git a/core/rust.bot_modules.hooks/src/lib.rs b/core/rust.bot_modules.hooks/src/lib.rs index c48adc31..0f001a2a 100644 --- a/core/rust.bot_modules.hooks/src/lib.rs +++ b/core/rust.bot_modules.hooks/src/lib.rs @@ -1,6 +1,4 @@ -mod cache; mod events; -mod settings; pub struct Module; @@ -25,10 +23,6 @@ impl silverpelt::module::Module for Module { Some(Box::new(EventHandler)) } - fn config_options(&self) -> Vec { - vec![(*settings::SINK).clone()] - } - fn full_command_list(&self) -> Vec { modules_ext::create_full_command_list(self) } diff --git a/core/rust.bot_modules.hooks/src/settings.rs b/core/rust.bot_modules.hooks/src/settings.rs deleted file mode 100644 index 053f2652..00000000 --- a/core/rust.bot_modules.hooks/src/settings.rs +++ /dev/null @@ -1,138 +0,0 @@ -use module_settings::data_stores::PostgresDataStore; -use module_settings::state::State; -use module_settings::types::{ - settings_wrap, Column, ColumnSuggestion, ColumnType, ConfigOption, HookContext, - InnerColumnType, InnerColumnTypeStringKind, NoOpValidator, OperationSpecific, OperationType, - PostAction, SettingsError, -}; -use splashcore_rs::value::Value; -use std::sync::LazyLock; - -pub static SINK: LazyLock = LazyLock::new(|| { - ConfigOption { - id: "sinks", - name: "Sinks", - description: "Sinks are triggered on given events and can be used for a customizable and flexible automoderation system", - table: "hooks__sinks", - common_filters: indexmap::indexmap! {}, - default_common_filters: indexmap::indexmap! { - "guild_id" => "{__guild_id}" - }, - primary_key: "id", - max_return: 15, - max_entries: Some(10), - data_store: settings_wrap(PostgresDataStore {}), - columns: settings_wrap(vec![ - Column { - id: "id", - name: "Sink ID", - description: "The unique identifier for the sink.", - column_type: ColumnType::new_scalar(InnerColumnType::Uuid {}), - nullable: false, - default: None, - unique: true, - suggestions: ColumnSuggestion::None {}, - ignored_for: vec![OperationType::Create], - secret: false, - }, - module_settings::common_columns::guild_id("guild_id", "Guild ID", "The Guild ID the sink belongs to"), - Column { - id: "sink", - name: "Sink", - description: "Any extra context for the sink", - column_type: ColumnType::new_scalar(InnerColumnType::String { min_length: None, max_length: None, allowed_values: vec![], kind: InnerColumnTypeStringKind::Normal }), - nullable: false, - default: None, - unique: false, - suggestions: ColumnSuggestion::None {}, - ignored_for: vec![], - secret: false, - }, - Column { - id: "events", - name: "Events", - description: "The events that are sent to the sink. If empty, all events are sent. Prefix with R/ for regex (rust style regex) matching.", - column_type: ColumnType::new_array(InnerColumnType::String { min_length: None, max_length: None, allowed_values: vec![], kind: InnerColumnTypeStringKind::Normal }), - nullable: true, - default: None, - unique: false, - suggestions: ColumnSuggestion::Static { suggestions: gwevent::core::event_list().to_vec() }, - ignored_for: vec![], - secret: false, - }, - Column { - id: "template", - name: "Template", - description: "The template for the embed. This template will be executed when an event is sent to the sink. If empty, falls back to default handling", - column_type: ColumnType::new_scalar(InnerColumnType::String { min_length: None, max_length: None, allowed_values: vec![], kind: InnerColumnTypeStringKind::TemplateRef { kind: "message", ctx: "HookContext" }}), - ignored_for: vec![], - secret: false, - nullable: false, - default: None, - unique: false, - suggestions: ColumnSuggestion::None {}, - }, - Column { - id: "broken", - name: "Marked as Broken", - description: "If the sink is marked as broken, it will not be used for sending logs. This can be useful in debugging too!", - column_type: ColumnType::new_scalar(InnerColumnType::Boolean {}), - ignored_for: vec![OperationType::Create], - secret: false, - nullable: false, - default: Some(|_| Value::Boolean(false)), - unique: false, - suggestions: ColumnSuggestion::None {}, - }, - module_settings::common_columns::created_at(), - module_settings::common_columns::created_by(), - module_settings::common_columns::last_updated_at(), - module_settings::common_columns::last_updated_by(), - ]), - title_template: "{type} {sink} [{id}]", - operations: indexmap::indexmap! { - OperationType::View => OperationSpecific { - columns_to_set: indexmap::indexmap! {}, - }, - OperationType::Create => OperationSpecific { - columns_to_set: indexmap::indexmap! { - "created_at" => "{__now}", - "created_by" => "{__author}", - "last_updated_at" => "{__now}", - "last_updated_by" => "{__author}", - }, - }, - OperationType::Update => OperationSpecific { - columns_to_set: indexmap::indexmap! { - "last_updated_at" => "{__now}", - "last_updated_by" => "{__author}", - }, - }, - OperationType::Delete => OperationSpecific { - columns_to_set: indexmap::indexmap! {}, - }, - }, - validator: settings_wrap(NoOpValidator {}), - post_action: settings_wrap(ClearCachePostAction {}), - } -}); - -/// Post action to clear the cache -pub struct ClearCachePostAction; - -#[async_trait::async_trait] -impl PostAction for ClearCachePostAction { - async fn post_action<'a>( - &self, - ctx: HookContext<'a>, - _state: &'a mut State, - ) -> Result<(), SettingsError> { - if ctx.operation_type == OperationType::View { - return Ok(()); - } - - super::cache::SINKS_CACHE.invalidate(&ctx.guild_id).await; - - Ok(()) - } -} diff --git a/core/rust.templating/src/lib.rs b/core/rust.templating/src/lib.rs index 6a084f5a..2feb39f5 100644 --- a/core/rust.templating/src/lib.rs +++ b/core/rust.templating/src/lib.rs @@ -107,6 +107,7 @@ pub struct GuildTemplate { pub name: String, pub description: Option, pub shop_name: Option, + pub events: Option>, pub content: String, pub created_by: String, pub created_at: chrono::DateTime, @@ -135,6 +136,7 @@ async fn get_template( name: shop_template.name, description: Some(shop_template.description), shop_name: Some(template.to_string()), + events: None, // TODO content: shop_template.content, created_by: shop_template.created_by, created_at: shop_template.created_at, @@ -145,7 +147,7 @@ async fn get_template( } } else { let rec = sqlx::query!( - "SELECT content, created_at, created_by, last_updated_at, last_updated_by FROM guild_templates WHERE guild_id = $1 AND name = $2", + "SELECT events, content, created_at, created_by, last_updated_at, last_updated_by FROM guild_templates WHERE guild_id = $1 AND name = $2", guild_id.to_string(), template ) @@ -157,6 +159,7 @@ async fn get_template( name: template.to_string(), description: None, shop_name: None, + events: rec.events, content: rec.content, created_by: rec.created_by, created_at: rec.created_at,