diff --git a/crates/aionbot-core/src/lib.rs b/crates/aionbot-core/src/lib.rs index 33d5b34..023c973 100644 --- a/crates/aionbot-core/src/lib.rs +++ b/crates/aionbot-core/src/lib.rs @@ -4,6 +4,35 @@ pub mod handler; pub mod plugin; pub mod prelude; pub mod queue; -pub mod router; +pub mod router { + use crate::event::Event; + + pub trait Router: Send + Sync { + fn matches(&self, event: &dyn Event) -> bool; + } + + impl Router for T + where + T: Send + Sync + AsRef + 'static, + { + fn matches(&self, event: &dyn Event) -> bool { + if let Ok(val) = event.content().downcast::<&str>() { + *val == self.as_ref() + } else { + false + } + } + } + + mod command; + mod error; + mod logic; + mod matcher; + + pub use command::CommandRouter; + pub use error::ErrorRouter; + pub use logic::{AllRouter, AnyRouter}; + pub use matcher::{ContainsRouter, EndsWithRouter, ExactMatchRouter, StartsWithRouter}; +} pub mod runtime; pub mod types; diff --git a/crates/aionbot-core/src/router.rs b/crates/aionbot-core/src/router.rs deleted file mode 100644 index 27d1135..0000000 --- a/crates/aionbot-core/src/router.rs +++ /dev/null @@ -1,257 +0,0 @@ -use std::{error::Error, marker::PhantomData}; - -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; - -use crate::event::Event; - -pub trait Router: Send + Sync { - fn matches(&self, event: &dyn Event) -> bool; -} - -impl Router for T -where - T: Send + Sync + AsRef + 'static, -{ - fn matches(&self, event: &dyn Event) -> bool { - if let Ok(val) = event.content().downcast::<&str>() { - *val == self.as_ref() - } else { - false - } - } -} - -pub struct ExactMatchRouter -where - T: Send + Sync + PartialEq + 'static, -{ - pub pattern: T, -} - -impl Router for ExactMatchRouter -where - T: Send + Sync + PartialEq + 'static, -{ - fn matches(&self, event: &dyn Event) -> bool { - if let Ok(val) = event.content().downcast::() { - *val == self.pattern - } else { - false - } - } -} - -impl ExactMatchRouter -where - T: Send + Sync + PartialEq + 'static, -{ - pub fn new(pattern: T) -> Self { - Self { pattern } - } -} - -pub struct StartsWithRouter -where - T: Send + Sync + AsRef + 'static, -{ - pub pattern: T, -} - -impl Router for StartsWithRouter<&str> { - fn matches(&self, event: &dyn Event) -> bool { - if let Ok(val) = event.content().downcast::<&str>() { - val.starts_with(self.pattern) - } else { - false - } - } -} - -impl StartsWithRouter -where - T: Send + Sync + AsRef + 'static, -{ - pub fn new(pattern: T) -> Self { - Self { pattern } - } -} - -pub struct ContainsRouter -where - T: Send + Sync + AsRef + 'static, -{ - pub pattern: T, -} - -impl Router for ContainsRouter<&str> { - fn matches(&self, event: &dyn Event) -> bool { - if let Ok(val) = event.content().downcast::<&str>() { - val.contains(self.pattern) - } else { - false - } - } -} - -impl ContainsRouter -where - T: Send + Sync + AsRef + 'static, -{ - pub fn new(pattern: T) -> Self { - Self { pattern } - } -} - -pub struct EndsWithRouter -where - T: Send + Sync + AsRef + 'static, -{ - pub pattern: T, -} - -impl Router for EndsWithRouter<&str> { - fn matches(&self, event: &dyn Event) -> bool { - if let Ok(val) = event.content().downcast::<&str>() { - val.ends_with(self.pattern) - } else { - false - } - } -} - -impl EndsWithRouter -where - T: Send + Sync + AsRef + 'static, -{ - pub fn new(pattern: T) -> Self { - Self { pattern } - } -} - -#[derive(Default)] -pub struct AllRouter; - -impl Router for AllRouter { - fn matches(&self, _event: &dyn Event) -> bool { - true - } -} - -pub struct AnyRouter { - pub routers: Vec>, -} - -impl Router for AnyRouter { - fn matches(&self, event: &dyn Event) -> bool { - self.routers.par_iter().any(|r| r.matches(event)) - } -} - -impl AnyRouter { - pub fn new(routers: Vec>) -> Self { - Self { routers } - } -} - -pub struct CommandRouter { - pub prefixes: Vec, - pub command: Vec, -} - -impl Default for CommandRouter { - fn default() -> Self { - Self { - prefixes: vec!["/".into()], - command: ["help".into()].to_vec(), - } - } -} - -impl CommandRouter { - pub fn new, C: IntoIterator>( - prefixes: Vec, - command: C, - ) -> Self { - Self { - prefixes, - command: command.into_iter().map(Into::into).collect(), - } - } - - pub fn command, C: IntoIterator>(command: C) -> Self { - Self { - command: command.into_iter().map(Into::into).collect(), - ..Default::default() - } - } -} - -impl Router for CommandRouter { - fn matches(&self, event: &dyn Event) -> bool { - if let Ok(val) = event.content().downcast::<&str>() { - for prefix in &self.prefixes { - if val.starts_with(prefix) { - let command = val.strip_prefix(prefix).unwrap(); - if self.command.iter().any(|c| command.starts_with(c)) { - return true; - } - } - } - false - } else { - false - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_command_router() { - let router = CommandRouter::default(); - assert!(!router.matches(&"help".to_string())); - assert!(router.matches(&"/help".to_string())); - assert!(router.matches(&"/help@bot".to_string())); - assert!(!router.matches(&"/not help".to_string())); - - let router = CommandRouter::command(["cmd", "command"]); - assert!(!router.matches(&"help".to_string())); - assert!(router.matches(&"/cmd".to_string())); - assert!(router.matches(&"/cmd@bot".to_string())); - assert!(!router.matches(&"/not cmd".to_string())); - assert!(router.matches(&"/command".to_string())); - - let router = CommandRouter::new(vec!["!".to_string()], ["cmd"]); - assert!(!router.matches(&"help".to_string())); - assert!(router.matches(&"!cmd".to_string())); - assert!(router.matches(&"!cmd@bot".to_string())); - assert!(!router.matches(&"!not cmd".to_string())); - assert!(!router.matches(&"/cmd arg1 arg2".to_string())) - } -} - -pub struct ErrorRouter { - marker: PhantomData, -} - -impl ErrorRouter { - pub fn new() -> Self { - Self::default() - } -} - -impl Default for ErrorRouter { - fn default() -> Self { - Self { - marker: PhantomData, - } - } -} - -impl Router for ErrorRouter { - fn matches(&self, event: &dyn Event) -> bool { - event.content().downcast::().is_ok() - } -} diff --git a/crates/aionbot-core/src/router/command.rs b/crates/aionbot-core/src/router/command.rs new file mode 100644 index 0000000..4bf9be0 --- /dev/null +++ b/crates/aionbot-core/src/router/command.rs @@ -0,0 +1,83 @@ +use crate::event::Event; + +use super::Router; + + +pub struct CommandRouter { + pub prefixes: Vec, + pub command: Vec, +} + +impl Default for CommandRouter { + fn default() -> Self { + Self { + prefixes: vec!["/".into()], + command: ["help".into()].to_vec(), + } + } +} + +impl CommandRouter { + pub fn new, C: IntoIterator>( + prefixes: Vec, + command: C, + ) -> Self { + Self { + prefixes, + command: command.into_iter().map(Into::into).collect(), + } + } + + pub fn command, C: IntoIterator>(command: C) -> Self { + Self { + command: command.into_iter().map(Into::into).collect(), + ..Default::default() + } + } +} + +impl Router for CommandRouter { + fn matches(&self, event: &dyn Event) -> bool { + if let Ok(val) = event.content().downcast::<&str>() { + for prefix in &self.prefixes { + if val.starts_with(prefix) { + let command = val.strip_prefix(prefix).unwrap(); + if self.command.iter().any(|c| command.starts_with(c)) { + return true; + } + } + } + false + } else { + false + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_command_router() { + let router = CommandRouter::default(); + assert!(!router.matches(&"help".to_string())); + assert!(router.matches(&"/help".to_string())); + assert!(router.matches(&"/help@bot".to_string())); + assert!(!router.matches(&"/not help".to_string())); + + let router = CommandRouter::command(["cmd", "command"]); + assert!(!router.matches(&"help".to_string())); + assert!(router.matches(&"/cmd".to_string())); + assert!(router.matches(&"/cmd@bot".to_string())); + assert!(!router.matches(&"/not cmd".to_string())); + assert!(router.matches(&"/command".to_string())); + + let router = CommandRouter::new(vec!["!".to_string()], ["cmd"]); + assert!(!router.matches(&"help".to_string())); + assert!(router.matches(&"!cmd".to_string())); + assert!(router.matches(&"!cmd@bot".to_string())); + assert!(!router.matches(&"!not cmd".to_string())); + assert!(!router.matches(&"/cmd arg1 arg2".to_string())) + } +} diff --git a/crates/aionbot-core/src/router/error.rs b/crates/aionbot-core/src/router/error.rs new file mode 100644 index 0000000..96a1c75 --- /dev/null +++ b/crates/aionbot-core/src/router/error.rs @@ -0,0 +1,29 @@ +use std::{error::Error, marker::PhantomData}; + +use crate::event::Event; + +use super::Router; + +pub struct ErrorRouter { + marker: PhantomData, +} + +impl ErrorRouter { + pub fn new() -> Self { + Self::default() + } +} + +impl Default for ErrorRouter { + fn default() -> Self { + Self { + marker: PhantomData, + } + } +} + +impl Router for ErrorRouter { + fn matches(&self, event: &dyn Event) -> bool { + event.content().downcast::().is_ok() + } +} diff --git a/crates/aionbot-core/src/router/logic.rs b/crates/aionbot-core/src/router/logic.rs new file mode 100644 index 0000000..1ddef9a --- /dev/null +++ b/crates/aionbot-core/src/router/logic.rs @@ -0,0 +1,30 @@ +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; + +use crate::event::Event; + +use super::Router; + +#[derive(Default)] +pub struct AllRouter; + +impl Router for AllRouter { + fn matches(&self, _event: &dyn Event) -> bool { + true + } +} + +pub struct AnyRouter { + pub routers: Vec>, +} + +impl Router for AnyRouter { + fn matches(&self, event: &dyn Event) -> bool { + self.routers.par_iter().any(|r| r.matches(event)) + } +} + +impl AnyRouter { + pub fn new(routers: Vec>) -> Self { + Self { routers } + } +} diff --git a/crates/aionbot-core/src/router/matcher.rs b/crates/aionbot-core/src/router/matcher.rs new file mode 100644 index 0000000..c52db3f --- /dev/null +++ b/crates/aionbot-core/src/router/matcher.rs @@ -0,0 +1,110 @@ +use crate::event::Event; + +use super::Router; + +pub struct ExactMatchRouter +where + T: Send + Sync + PartialEq + 'static, +{ + pub pattern: T, +} + +impl Router for ExactMatchRouter +where + T: Send + Sync + PartialEq + 'static, +{ + fn matches(&self, event: &dyn Event) -> bool { + if let Ok(val) = event.content().downcast::() { + *val == self.pattern + } else { + false + } + } +} + +impl ExactMatchRouter +where + T: Send + Sync + PartialEq + 'static, +{ + pub fn new(pattern: T) -> Self { + Self { pattern } + } +} + +pub struct StartsWithRouter +where + T: Send + Sync + AsRef + 'static, +{ + pub pattern: T, +} + +impl Router for StartsWithRouter<&str> { + fn matches(&self, event: &dyn Event) -> bool { + if let Ok(val) = event.content().downcast::<&str>() { + val.starts_with(self.pattern) + } else { + false + } + } +} + +impl StartsWithRouter +where + T: Send + Sync + AsRef + 'static, +{ + pub fn new(pattern: T) -> Self { + Self { pattern } + } +} + +pub struct ContainsRouter +where + T: Send + Sync + AsRef + 'static, +{ + pub pattern: T, +} + +impl Router for ContainsRouter<&str> { + fn matches(&self, event: &dyn Event) -> bool { + if let Ok(val) = event.content().downcast::<&str>() { + val.contains(self.pattern) + } else { + false + } + } +} + +impl ContainsRouter +where + T: Send + Sync + AsRef + 'static, +{ + pub fn new(pattern: T) -> Self { + Self { pattern } + } +} + +pub struct EndsWithRouter +where + T: Send + Sync + AsRef + 'static, +{ + pub pattern: T, +} + +impl Router for EndsWithRouter<&str> { + fn matches(&self, event: &dyn Event) -> bool { + if let Ok(val) = event.content().downcast::<&str>() { + val.ends_with(self.pattern) + } else { + false + } + } +} + +impl EndsWithRouter +where + T: Send + Sync + AsRef + 'static, +{ + pub fn new(pattern: T) -> Self { + Self { pattern } + } +}