From 95b965a4a9f75cbf4d8aab12908830146cd12af7 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Wed, 18 Jan 2023 19:10:15 -0700 Subject: [PATCH] Ported randoms to the DB + Also added commands for adding and listing randoms + fmt, clippy failing in a library... --- src/config.rs | 24 -------- src/discord/joke.rs | 140 +++++++++++++++++++++++++++++++++++-------- src/main.rs | 1 + src/models/mod.rs | 1 + src/models/random.rs | 60 +++++++++++++++++++ 5 files changed, 177 insertions(+), 49 deletions(-) create mode 100644 src/models/mod.rs create mode 100644 src/models/random.rs diff --git a/src/config.rs b/src/config.rs index 0de9882..7bd5f88 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,6 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; use structopt::StructOpt; -use tera::Tera; use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::Mutex; @@ -29,12 +28,6 @@ pub struct AlbumConfig { pub album_id: String, } -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct RandomConfig { - pub name: String, - pub responses: Vec, -} - #[derive(Debug, Deserialize, Serialize, Clone)] pub struct MotivationConfig { pub album: Vec, @@ -57,9 +50,6 @@ pub struct BotConfig { #[serde(default)] pub albums: Vec, - #[serde(default)] - pub randoms: Vec, - pub motivation: MotivationConfig, pub insults: Vec, @@ -88,7 +78,6 @@ impl BotConfig { #[derive(Debug)] pub struct BotState { pub accepted_nsfw: Option, - pub templates: HashMap, pub albums: HashMap>, pub bad_apple_running: bool, } @@ -96,18 +85,6 @@ pub struct BotState { impl BotState { pub async fn new(cfg: &BotConfig) -> Result { let mut albums: HashMap> = HashMap::new(); - let mut templates = HashMap::new(); - - for random_config in cfg.randoms.iter() { - let mut templates_set = Tera::default(); - for (idx, response) in random_config.responses.iter().enumerate() { - templates_set - .add_raw_template(&idx.to_string(), response) - .unwrap(); - } - - templates.insert(random_config.name.clone(), templates_set); - } for album in &cfg.albums { albums.insert( @@ -118,7 +95,6 @@ impl BotState { Ok(Self { accepted_nsfw: None, - templates, albums, bad_apple_running: false, }) diff --git a/src/discord/joke.rs b/src/discord/joke.rs index 4f84444..bac1f66 100644 --- a/src/discord/joke.rs +++ b/src/discord/joke.rs @@ -1,10 +1,12 @@ use crate::error::Error; +use crate::models::random::RandomConfig; use crate::{command, group, GlobalData, BAD_APPLE}; use rand::prelude::IteratorRandom; use rand::thread_rng; use reqwest::Client; use serde::{Deserialize, Serialize}; use serenity::client::Context; +use serenity::constants::MESSAGE_CODE_LIMIT; use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; use serenity::utils::MessageBuilder; @@ -12,7 +14,7 @@ use std::collections::HashMap; use std::time::Duration; #[group] -#[commands(dad_joke, roll, bad_apple, insult)] +#[commands(dad_joke, roll, bad_apple, insult, add_random, list_random)] pub struct Joke; #[derive(Clone, Serialize, Deserialize)] @@ -49,10 +51,7 @@ struct RandomCtx { } impl RandomCtx { - pub async fn new( - user: serenity::model::guild::Member, - global_data: &GlobalData, - ) -> Result { + pub fn new(user_name: &str, global_data: &GlobalData) -> Result { let mut random_image: HashMap = HashMap::new(); for album in &global_data.cfg.albums { @@ -64,28 +63,42 @@ impl RandomCtx { } Ok(Self { - user: (*user.display_name()).clone(), + user: user_name.to_string(), random_image, }) } } +pub fn render_random( + author_name: &str, + global_data: &GlobalData, + template: &str, +) -> Result { + let random_ctx = RandomCtx::new(author_name, global_data)?; + + Ok(tera::Tera::one_off( + template, + &tera::Context::from_serialize(&random_ctx)?, + false, + )?) +} + pub async fn random(ctx: &Context, msg: &Message, random_name: &str) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); - let templates = global_data.bot_state.templates.get(random_name); - - let templates = if let Some(templates) = templates { - templates - } else { - return Ok(()); + let random = match RandomConfig::get_random(&global_data.db, random_name)? { + None => return Ok(()), + Some(r) => r, }; - let template = { - let mut rng = thread_rng(); - - templates.get_template_names().choose(&mut rng) + let response_template_str = match random.get_response()? { + None => { + msg.reply(&ctx, format!("I'm all out of material for {}", random_name)) + .await?; + return Ok(()); + } + Some(resp) => resp, }; let guild_member = msg @@ -94,13 +107,11 @@ pub async fn random(ctx: &Context, msg: &Message, random_name: &str) -> CommandR .member(&ctx.http, msg.author.id) .await?; - let random_ctx = RandomCtx::new(guild_member, global_data).await?; - - let reply = if let Some(template) = template { - templates.render(template, &tera::Context::from_serialize(&random_ctx)?)? - } else { - "Sorry kid, all out of messages.".to_string() - }; + let reply = render_random( + &guild_member.display_name(), + global_data, + response_template_str, + )?; msg.reply(&ctx.http, reply).await?; @@ -108,7 +119,87 @@ pub async fn random(ctx: &Context, msg: &Message, random_name: &str) -> CommandR } #[command] -#[only_in(guilds)] +#[example("8ball Funny Response haha")] +pub async fn add_random(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { + if args.len() < 2 { + msg.reply( + &ctx.http, + "Look kid, you need to provide both the random name and response", + ) + .await?; + return Ok(()); + } + + let data = ctx.data.read().await; + let global_data = data.get::().unwrap(); + + let random_name = args.parse::()?; + args.advance(); + let random_response = args.rest(); + + if let Err(err) = render_random(&msg.author.name, global_data, random_response) { + msg.reply( + &ctx.http, + format!("Template failed test render, try again: {}", err), + ) + .await?; + } else { + RandomConfig::add_random(&global_data.db, &random_name, random_response)?; + msg.reply(&ctx.http, "New random added!").await?; + } + + Ok(()) +} + +#[command] +#[example("8ball Funny Response haha")] +pub async fn list_random(ctx: &Context, msg: &Message, args: Args) -> CommandResult { + if args.len() < 1 { + msg.reply( + &ctx.http, + "Look kid, you need to provide both the random name", + ) + .await?; + return Ok(()); + } + + let data = ctx.data.read().await; + let global_data = data.get::().unwrap(); + + let random_name = args.parse::()?; + + let random = RandomConfig::get_random(&global_data.db, &random_name)?; + + if let Some(random) = random { + let mut msg_builder = MessageBuilder::new(); + msg_builder.push_line(format!("All possible responses for {}:", random_name)); + + for resp in random.responses { + let line_msg = format!("* {}", resp); + + if (msg_builder.0.len() + line_msg.len()) > MESSAGE_CODE_LIMIT { + msg.channel_id.say(&ctx.http, msg_builder.build()).await?; + msg_builder.0.clear(); + } + + msg_builder.push_line(line_msg); + } + + if !msg_builder.0.is_empty() { + msg.channel_id.say(&ctx.http, msg_builder.build()).await?; + } + } else { + msg.reply( + &ctx.http, + "*glances to the back room*, nope we ain't got that random", + ) + .await?; + } + + Ok(()) +} + +#[command] #[aliases("roll")] #[description("Roll a die!")] async fn roll(ctx: &Context, msg: &Message, args: Args) -> CommandResult { @@ -168,7 +259,6 @@ async fn bad_apple(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { #[command] #[aliases("compliment")] -#[only_in(guilds)] async fn insult(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let data = ctx.data.read().await; let global = data.get::().unwrap(); diff --git a/src/main.rs b/src/main.rs index 8130caf..78664ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod error; mod imgur; mod insult_compliment; mod inventory; +mod models; mod user; use crate::config::{Args, BotConfig, Channel, GlobalData}; diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..7bcbfe0 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1 @@ +pub mod random; diff --git a/src/models/random.rs b/src/models/random.rs new file mode 100644 index 0000000..1a78365 --- /dev/null +++ b/src/models/random.rs @@ -0,0 +1,60 @@ +use crate::error::Error; +use j_db::database::Database; +use j_db::model::JdbModel; +use rand::prelude::SliceRandom; +use rand::thread_rng; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct RandomConfig { + id: Option, + pub name: String, + pub responses: Vec, +} + +impl JdbModel for RandomConfig { + fn id(&self) -> Option { + self.id + } + + fn set_id(&mut self, id: u64) { + self.id = Some(id) + } + + fn tree() -> String { + "randoms".to_string() + } + + fn check_unique(&self, other: &Self) -> bool { + !self.name.eq_ignore_ascii_case(&other.name) + } +} + +impl RandomConfig { + pub fn add_random(db: &Database, name: &str, response: &str) -> Result<(), Error> { + let mut random = match Self::get_random(db, name)? { + None => db.insert::(Self { + id: None, + name: name.to_string(), + responses: vec![], + })?, + Some(random) => random, + }; + + random.responses.push(response.to_string()); + + db.insert::(random)?; + + Ok(()) + } + + pub fn get_random(db: &Database, name: &str) -> Result, Error> { + Ok(db + .filter(|_, random: &RandomConfig| random.name.eq_ignore_ascii_case(name))? + .next()) + } + + pub fn get_response(&self) -> Result, Error> { + Ok(self.responses.choose(&mut thread_rng())) + } +}