diff --git a/Cargo.lock b/Cargo.lock index 4e41041..b0a1848 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,10 +379,12 @@ dependencies = [ "dotenvy", "openssl", "postgres-openssl", + "rand", "regex", "reqwest", "serde", "serde_json", + "serde_yaml", "serenity", "serial_test", "tokio", @@ -391,6 +393,7 @@ dependencies = [ "utoipa", "utoipa-swagger-ui", "uuid", + "walkdir", ] [[package]] @@ -1856,6 +1859,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serenity" version = "0.12.0" @@ -2392,6 +2408,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 37926ac..a6a6c9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ default-run = "botdiscord" [dependencies] serde = "1.0" +serde_yaml = "0.9" uuid = { version = "1.4", features = ["v4", "serde"] } chrono = { version = "0.4", features = ["serde"] } utoipa = { version = "4", features = ["actix_extras", "chrono", "uuid"] } @@ -40,6 +41,8 @@ serenity = { version = "0.12", default-features = false, features = [ "cache", ] } tokio = { version = "1.35", features = ["macros", "rt-multi-thread"] } +rand = "0.8.5" +walkdir = "2.4.0" [[bin]] diff --git a/src/bot/handler.rs b/src/bot/handler.rs index 9489485..c32ac8a 100644 --- a/src/bot/handler.rs +++ b/src/bot/handler.rs @@ -1,7 +1,17 @@ +use rand::Rng; +use serenity::prelude::*; use serenity::{ all::{Message, Ready}, async_trait, - client::{Context, EventHandler}, + builder::{CreateAttachment, CreateMessage}, + client::Context, +}; +use tokio::fs::File; +use walkdir::{DirEntry, WalkDir}; + +use crate::{ + config::ConfigGlobal, + img::config_file::{ConfigImgGlobal, KeyWordItem}, }; pub struct Handler; @@ -9,7 +19,7 @@ pub struct Handler; #[async_trait] impl EventHandler for Handler { async fn message(&self, ctx: Context, msg: Message) { - if msg.author.bot { + if msg.author.bot || msg.content.starts_with("!") { return; } if msg.content == "&ping" { @@ -18,6 +28,64 @@ impl EventHandler for Handler { } return; } + let (config_img, config) = { + let data_read = ctx.data.read().await; + let config_img = data_read + .get::() + .expect("Config img not found") + .clone(); + let config = data_read + .get::() + .expect("Main config not found") + .clone(); + (config_img, config) + }; + let mut rng = rand::thread_rng(); + let folder_container = match config_img + .keyword + .iter() + .find(|keyword| keyword.does_value_match(msg.content.clone())) + { + Some(keyword_matching) => { + println!("{} match {:?}", msg.content, keyword_matching); + let keyword_path = match keyword_matching.path.len() { + 0 => keyword_matching.path[0].clone(), + _ => { + let id = rng.gen_range(0..keyword_matching.path.len()); + keyword_matching.path[id].clone() + } + }; + + keyword_path.clone() + } + None => return, + }; + let path = format!("{}/{}", config.image.path.clone(), folder_container); + let file_folder = KeyWordItem::output_folder_content(path.clone()); + let id_rand: usize = rng.gen_range(0..file_folder.len()); + let filename = match file_folder.get(id_rand) { + Some(file) => file.file_name().to_str().unwrap(), + None => return, + }; + let file_path = format!("{}/{}", path, filename.clone()); + let file = match File::open(file_path).await { + Ok(file) => file, + Err(why) => { + println!("Error opening file: {:?}", why); + return; + } + }; + let attachment = match CreateAttachment::file(&file, filename).await { + Ok(attachment) => attachment, + Err(why) => { + println!("Error creating attachment: {:?}", why); + return; + } + }; + let builder = CreateMessage::new().add_file(attachment); + if let Err(why) = msg.channel_id.send_message(&ctx.http, builder).await { + println!("Error sending message: {:?}", why); + } } async fn ready(&self, _: Context, ready: Ready) { diff --git a/src/bot/init.rs b/src/bot/init.rs index fc6cce9..efc38e7 100644 --- a/src/bot/init.rs +++ b/src/bot/init.rs @@ -1,6 +1,7 @@ use super::cmd::{help::HELP, ping::PING_COMMAND}; use super::handler::Handler; -use crate::config::Config; +use crate::config::{Config, ConfigGlobal}; +use crate::img::config_file::{ConfigFile, ConfigImgGlobal}; use serenity::framework::standard::Configuration; use serenity::{ all::GatewayIntents, @@ -9,6 +10,7 @@ use serenity::{ Client, }; use std::collections::HashSet; +use std::fs; use tokio::task::spawn_blocking; #[group] @@ -23,6 +25,23 @@ pub fn start_bot(config: Config) { let local = tokio::task::LocalSet::new(); let _ = local .run_until(async move { + let config_img = match fs::read_to_string(format!( + "{}/config.yaml", + config.image.path + )) { + Ok(content) => content, + Err(err) => { + println!("Error while opening config.yaml : {:?}", err); + return; + } + }; + let config_parsed = match ConfigFile::parse_config(config_img) { + Ok(config) => config, + Err(err) => { + println!("Error while parsing config.yaml : {:?}", err); + return; + } + }; let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::DIRECT_MESSAGES | GatewayIntents::MESSAGE_CONTENT @@ -53,7 +72,7 @@ pub fn start_bot(config: Config) { framework.configure( Configuration::new() .with_whitespace(true) - .prefix(config.prefix) + .prefix(config.prefix.clone()) .owners(owners), ); @@ -62,6 +81,14 @@ pub fn start_bot(config: Config) { .framework(framework) .await .expect("Err creating client"); + + { + // Open the data lock in write mode, so keys can be inserted to it. + let mut data = client.data.write().await; + + data.insert::(config_parsed.clone()); + data.insert::(config.clone()); + } if let Err(why) = client.start().await { println!("Client error: {why:?}"); } diff --git a/src/config.rs b/src/config.rs index 5d3dee6..b3154d8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use dotenvy::dotenv; use serde::Deserialize; +use serenity::prelude::TypeMapKey; use std::env; use std::fs::read_to_string; use std::path::PathBuf; @@ -22,6 +23,12 @@ const RUST_ENV: &str = "RUST_ENV"; const PORT: &str = "PORT"; +pub struct ConfigGlobal; + +impl TypeMapKey for ConfigGlobal { + type Value = Config; +} + #[derive(Deserialize, Clone)] pub struct Config { pub bot_name: String, diff --git a/src/img/config_file.rs b/src/img/config_file.rs new file mode 100644 index 0000000..64dd0b3 --- /dev/null +++ b/src/img/config_file.rs @@ -0,0 +1,64 @@ +use regex::Regex; +use serde::Deserialize; +use serenity::prelude::TypeMapKey; +use std::fmt::Debug; +use walkdir::{DirEntry, WalkDir}; + +#[derive(Deserialize, Clone)] +pub struct KeyWordItem { + pub value: Vec, + pub path: Vec, +} + +impl KeyWordItem { + pub fn does_value_match(&self, haystack: String) -> bool { + self.value.clone().into_iter().any(|val| { + let re = Regex::new(&val).unwrap(); + re.is_match(&haystack) + }) + } + pub fn output_folder_content(path: String) -> Vec { + let file_folder: Vec = WalkDir::new(&path) + .into_iter() + .filter_entry(|_e| true) + .filter(|e| { + if let Some(file) = &e.as_ref().ok() { + return !file.metadata().unwrap().is_dir(); + } + return true; + }) + .filter_map(|e| e.ok()) + .collect(); + if file_folder.len() == 0 { + return vec![]; + } + file_folder + } +} + +impl Debug for KeyWordItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KeyWordItem") + .field("value", &self.value) + .field("path", &self.path) + .finish() + } +} + +#[derive(Deserialize, Clone)] +pub struct ConfigFile { + pub keyword: Vec, +} + +impl ConfigFile { + pub fn parse_config(content: String) -> Result { + let config: ConfigFile = serde_yaml::from_str(&content)?; + Ok(config) + } +} + +pub struct ConfigImgGlobal; + +impl TypeMapKey for ConfigImgGlobal { + type Value = ConfigFile; +} diff --git a/src/img/mod.rs b/src/img/mod.rs new file mode 100644 index 0000000..1c8f503 --- /dev/null +++ b/src/img/mod.rs @@ -0,0 +1 @@ +pub mod config_file; diff --git a/src/lib.rs b/src/lib.rs index 5a9b66e..35f7ffd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ +pub mod bot; pub mod config; -pub mod bot; \ No newline at end of file +pub mod img; diff --git a/src/main.rs b/src/main.rs index 815928b..6edc347 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod bot; mod config; +mod img; use actix_cors::Cors; use actix_web::{App, HttpServer};