From 8b9d1da206bdb52ea293d319ab915e4d110d5e05 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Sat, 22 Mar 2025 18:59:14 -0600 Subject: [PATCH] Add back in lil fren commands, delete story module --- src/discord/little_fren.rs | 247 +++++++++++++++---------------------- src/discord/mod.rs | 15 +++ src/discord/story.rs | 207 ------------------------------- src/models/lil_fren.rs | 22 ++-- 4 files changed, 124 insertions(+), 367 deletions(-) delete mode 100644 src/discord/story.rs diff --git a/src/discord/little_fren.rs b/src/discord/little_fren.rs index 24d601d..e3990ae 100644 --- a/src/discord/little_fren.rs +++ b/src/discord/little_fren.rs @@ -1,68 +1,45 @@ -use crate::config::GlobalData; +use crate::discord::Context; +use crate::error::Error; use crate::models::lil_fren; use crate::models::lil_fren::{AliveState, LilFren, LilFrenState}; -use crate::{command, group}; +use poise::serenity_prelude::model::misc::EmojiIdentifier; +use poise::serenity_prelude::utils::MessageBuilder; use rand::prelude::IndexedRandom; use rand::{rng, Rng}; -use serenity::client::Context; -use serenity::framework::standard::{Args, CommandResult}; -use serenity::model::channel::Message; -use serenity::model::misc::EmojiIdentifier; -use serenity::utils::MessageBuilder; -#[group] -#[commands(adopt, checkup, feed, give_water, play, give_medicine)] -pub struct Buddy; - -#[command] -#[description("Adopt a new lil buddy")] -#[only_in(guilds)] -async fn adopt(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); - - if let Ok(emoji) = args.parse::() { - if let Some(lil_fren) = LilFren::get_lil_fren(&global_data.db)? { - if lil_fren.is_alive() == AliveState::Alive { - msg.reply( - &ctx.http, - "Your buddy is still alive, please take care of him :)", - ) +#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +pub async fn adopt( + ctx: Context<'_>, + #[description = "Lil buddy to adopt"] lil_buddy: EmojiIdentifier, +) -> Result<(), Error> { + if let Some(lil_fren) = LilFren::get_lil_fren(&ctx.data().db)? { + if lil_fren.is_alive() == AliveState::Alive { + ctx.reply("Your buddy is still alive, please take care of him :)") .await?; - return Ok(()); - } + return Ok(()); } - - LilFren::create_new_lil_fren(&global_data.db, emoji.id)?; - let guild = msg.guild_id.unwrap(); - let emoji = guild.emoji(&ctx.http, emoji.id).await?; - - let mut msg_builder = MessageBuilder::new(); - - msg_builder.push("Congrats on adopting "); - msg_builder.emoji(&emoji); - msg_builder.push("!"); - - msg.reply(&ctx.http, msg_builder.build()).await?; - } else { - msg.reply(&ctx.http, "Sorry whatever that is not adoptable.") - .await?; } + LilFren::create_new_lil_fren(&ctx.data().db, lil_buddy.id)?; + let guild = ctx.guild_id().unwrap(); + let emoji = guild.emoji(&ctx, lil_buddy.id).await?; + + let mut msg_builder = MessageBuilder::new(); + + msg_builder.push("Congrats on adopting "); + msg_builder.emoji(&emoji); + msg_builder.push("!"); + ctx.reply(msg_builder.build()).await?; + Ok(()) } -#[command] -#[only_in(guilds)] -#[description("Checkup on your lil buddy")] -async fn checkup(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); - - let lil_fren = LilFren::get_lil_fren(&global_data.db)?; +#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> { + let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; if let Some(lil_fren) = lil_fren { - let guild = msg.guild_id.unwrap(); + let guild = ctx.guild_id().unwrap(); let alive_state = lil_fren.is_alive(); if alive_state == AliveState::Alive { @@ -85,9 +62,8 @@ async fn checkup(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { } }; - msg.reply(&ctx.http, state_msg).await?; - msg.reply(&ctx.http, lil_fren.draw(ctx, &guild).await) - .await?; + ctx.reply(state_msg).await?; + ctx.reply(lil_fren.draw(ctx, &guild).await).await?; } else { let resp = match alive_state { AliveState::Alive => "", @@ -96,142 +72,116 @@ async fn checkup(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { AliveState::DiedOfThirst => "Your little buddy died of thirst", }; - msg.reply(&ctx.http, resp).await?; + ctx.reply(resp).await?; } } else { - msg.reply( - &ctx.http, - "Sorry you have no little buddy right now. Please adopt!", - ) - .await?; - } - - Ok(()) -} - -#[command] -#[description("Feed lil buddy")] -async fn feed(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); - - if let Ok(food_emoji) = args.parse::() { - let lil_fren = LilFren::get_lil_fren(&global_data.db)?; - - if let Some(mut lil_fren) = lil_fren { - if lil_fren.is_alive() == AliveState::Alive { - let feed: f32 = rng().gen_range(-0.1..=1.0); - lil_fren.hunger = (lil_fren.hunger + feed).clamp(-1.0, 1.0); - let guild = msg.guild_id.unwrap(); - - let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; - - if feed > 0.0 { - msg.reply(&ctx.http, "Lil buddy seemed to like that!") - .await?; - } else { - msg.reply(&ctx.http, "Lil buddy DID not like that!").await?; - } - - msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, &food_emoji)) - .await?; - - global_data.db.insert(lil_fren)?; - } else { - msg.reply(&ctx.http, "You may want to check on your buddy...") - .await?; - } - } else { - msg.reply(&ctx.http, "Sorry no little buddy found!").await?; - } - } else { - msg.reply(&ctx.http, "Please provide something to feed lil buddy") + ctx.reply("Sorry you have no little buddy right now. Please adopt!") .await?; } Ok(()) } -#[command] -#[description("Give lil buddy some water")] -async fn give_water(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); +#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +pub async fn feed( + ctx: Context<'_>, + #[description = "Item to feed lil buddy"] food: String, +) -> Result<(), Error> { + let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; - let lil_fren = LilFren::get_lil_fren(&global_data.db)?; + if let Some(mut lil_fren) = lil_fren { + if lil_fren.is_alive() == AliveState::Alive { + let feed: f32 = rng().gen_range(-0.1..=1.0); + lil_fren.hunger = (lil_fren.hunger + feed).clamp(-1.0, 1.0); + let guild = ctx.guild_id().unwrap(); + + let lil_fren_emoji = guild.emoji(&ctx, lil_fren.emoji).await?; + + if feed > 0.0 { + ctx.reply("Lil buddy seemed to like that!").await?; + } else { + ctx.reply("Lil buddy DID not like that!").await?; + } + + ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, &food)) + .await?; + + ctx.data().db.insert(lil_fren)?; + } else { + ctx.reply("You may want to check on your buddy...").await?; + } + } else { + ctx.reply("Sorry no little buddy found!").await?; + } + + Ok(()) +} + +#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> { + let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; if let Some(mut lil_fren) = lil_fren { if lil_fren.is_alive() == AliveState::Alive { lil_fren.thirst = (lil_fren.thirst + 0.5).clamp(-1.0, 1.0); - let guild = msg.guild_id.unwrap(); + let guild = ctx.guild_id().unwrap(); - let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; + let lil_fren_emoji = guild.emoji(ctx, lil_fren.emoji).await?; - msg.reply(&ctx.http, "Lil buddy be drinking!").await?; - global_data.db.insert(lil_fren)?; + ctx.reply("Lil buddy be drinking!").await?; + ctx.data().db.insert(lil_fren)?; - let drink = if rng().gen_bool(0.20) { + let drink = if rng().random_bool(0.20) { ":beer:" } else { ":cup_with_straw:" }; - msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, drink)) + ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, drink)) .await?; } else { - msg.reply(&ctx.http, "You may want to check on your buddy...") - .await?; + ctx.reply("You may want to check on your buddy...").await?; } } else { - msg.reply(&ctx.http, "Sorry no little buddy found!").await?; + ctx.reply("Sorry no little buddy found!").await?; } Ok(()) } -#[command] -#[description("Play with buddy")] -async fn play(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); - - let lil_fren = LilFren::get_lil_fren(&global_data.db)?; +#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +pub async fn play(ctx: Context<'_>) -> Result<(), Error> { + let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; if let Some(mut lil_fren) = lil_fren { if lil_fren.is_alive() == AliveState::Alive { lil_fren.entertainment = - (lil_fren.entertainment + rng().gen_range(0.10..1.0)).clamp(-1.0, 1.0); - let guild = msg.guild_id.unwrap(); + (lil_fren.entertainment + rng().random_range(0.10..1.0)).clamp(-1.0, 1.0); + let guild = ctx.guild_id().unwrap(); - let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; + let lil_fren_emoji = guild.emoji(ctx, lil_fren.emoji).await?; - global_data.db.insert(lil_fren)?; + ctx.data().db.insert(lil_fren)?; - let toy = global_data.cfg.toys.choose(&mut rng()).unwrap(); - msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, toy)) - .await?; + let toy = ctx.data().cfg.toys.choose(&mut rng()).unwrap(); + ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, toy)).await?; } else { - msg.reply(&ctx.http, "You may want to check on your buddy...") - .await?; + ctx.reply("You may want to check on your buddy...").await?; } } else { - msg.reply(&ctx.http, "Sorry no little buddy found!").await?; + ctx.reply("Sorry no little buddy found!").await?; } Ok(()) } -#[command] -#[description("Help heal buddy")] -async fn give_medicine(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); - - let lil_fren = LilFren::get_lil_fren(&global_data.db)?; +#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +pub async fn give_medicine(ctx: Context<'_>) -> Result<(), Error> { + let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; if let Some(mut lil_fren) = lil_fren { if lil_fren.is_alive() == AliveState::Alive { - let guild = msg.guild_id.unwrap(); + let guild = ctx.guild_id().unwrap(); let reply = if lil_fren.state == LilFrenState::Sick { lil_fren.state = LilFrenState::Standing; @@ -240,19 +190,18 @@ async fn give_medicine(ctx: &Context, msg: &Message, _args: Args) -> CommandResu "Little buddy is now addicted to cough drops, I hope you are happy" }; - let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; + let lil_fren_emoji = guild.emoji(ctx, lil_fren.emoji).await?; - global_data.db.insert(lil_fren)?; + ctx.data().db.insert(lil_fren)?; - msg.reply(&ctx.http, reply).await?; - msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, ":pill:")) + ctx.reply(reply).await?; + ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, ":pill:")) .await?; } else { - msg.reply(&ctx.http, "You may want to check on your buddy...") - .await?; + ctx.reply("You may want to check on your buddy...").await?; } } else { - msg.reply(&ctx.http, "Sorry no little buddy found!").await?; + ctx.reply("Sorry no little buddy found!").await?; } Ok(()) diff --git a/src/discord/mod.rs b/src/discord/mod.rs index 55df4c6..dad88b8 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -6,6 +6,7 @@ mod color; mod emoji_race; mod fren_coin; mod joke; +mod little_fren; use crate::config::GlobalData; use crate::error::Error; @@ -15,6 +16,7 @@ use std::time::Duration; use crate::discord::fren_coin::give_coin; use crate::discord::joke::random; +use crate::models::lil_fren::lil_fren_task; use crate::models::task::Task; use poise::serenity_prelude::{Message, ReactionType}; use poise::{find_command, serenity_prelude as serenity, FrameworkOptions}; @@ -34,6 +36,7 @@ async fn event_handler( info!("Bot ready, and logged in as {}", data_about_bot.user.name); { + info!("Starting tasks handler..."); let data = data.clone(); let ctx = ctx.clone(); tokio::spawn(async move { @@ -44,6 +47,12 @@ async fn event_handler( } }); } + + { + info!("Starting lil buddy task..."); + let data = data.clone(); + tokio::spawn(async move { lil_fren_task(data) }); + } } serenity::FullEvent::Message { new_message } => { if new_message.content.starts_with("!") { @@ -186,6 +195,12 @@ pub async fn run_bot(global_data: GlobalData) { joke::list_random(), joke::real_roll(), joke::roll(), + little_fren::adopt(), + little_fren::checkup(), + little_fren::feed(), + little_fren::give_medicine(), + little_fren::give_water(), + little_fren::play(), ], event_handler: |ctx, event, framework, data| { Box::pin(event_handler(ctx, event, framework, data)) diff --git a/src/discord/story.rs b/src/discord/story.rs deleted file mode 100644 index d5d334e..0000000 --- a/src/discord/story.rs +++ /dev/null @@ -1,207 +0,0 @@ -use crate::config::{BotConfig, Channel}; -use crate::{command, group, GlobalData}; -use rand::prelude::{IndexedRandom, SliceRandom}; -use rand::thread_rng; -use regex::Regex; -use serenity::client::Context; -use serenity::framework::standard::{Args, CommandResult}; -use serenity::model::channel::{Attachment, Message}; -use serenity::utils::MessageBuilder; -use std::collections::HashMap; -use std::path::PathBuf; - -#[group] -#[commands(story, word, list_stories, upload_story)] -pub struct Story; - -async fn get_all_stories(cfg: &BotConfig) -> Vec { - let mut dir = tokio::fs::read_dir(&cfg.story_path).await.unwrap(); - - let mut stories = Vec::new(); - - while let Some(file) = &dir.next_entry().await.unwrap() { - stories.push(file.path()); - } - - stories -} - -fn get_all_blanks(story: &str) -> Vec { - let re = Regex::new(r"\{%(?P.*?)%}").unwrap(); - let mut globals: Vec = Vec::new(); - - for cap in re.captures_iter(story) { - if let Some(global) = cap.name("global") { - if !globals.contains(&global.as_str().to_string()) { - globals.push(global.as_str().to_string()) - } - } - } - - globals -} - -#[command] -#[only_in(guilds)] -#[description("List Stories")] -async fn list_stories(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let data = ctx.data.read().await; - let global_data = data.get::().unwrap(); - - let stories = get_all_stories(&global_data.cfg).await; - - let mut resp = MessageBuilder::default(); - - resp.push_line("**Stories I know:**"); - - for story in stories { - resp.push_line( - story - .file_stem() - .unwrap() - .to_str() - .unwrap() - .replace('_', " "), - ); - } - - msg.reply(&ctx.http, resp.build()).await?; - - Ok(()) -} - -#[command] -#[only_in(guilds)] -#[description("Let me tell you a tail")] -async fn story(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let story_channel = { - let data = ctx.data.read().await; - data.get::>().unwrap().clone() - }; - - let mut story_recv = match story_channel.recv.try_lock() { - Ok(story_recv) => story_recv, - Err(_) => { - msg.reply(&ctx.http, "Let me finish this story bub").await?; - return Ok(()); - } - }; - - let stories = { - let data = ctx.data.read().await; - - let global_data = data.get::().unwrap(); - get_all_stories(&global_data.cfg).await - }; - - let story_path = if args.is_empty() { - let mut rng = thread_rng(); - - stories.choose(&mut rng) - } else { - let story_name = args.rest().to_lowercase().replace(' ', "_"); - - stories.iter().find(|story_p| { - let story_p_name = story_p.file_stem().unwrap().to_str().unwrap(); - story_p_name == story_name - }) - }; - - let story_path = match story_path { - None => { - msg.reply(&ctx.http, "No story found :(").await?; - return Ok(()); - } - Some(story) => story, - }; - - let mut story_contents = tokio::fs::read_to_string(story_path).await?; - - let mut story_globals: HashMap = HashMap::new(); - - let msg_channel = msg.channel(&ctx.http).await?.id(); - - for global in get_all_blanks(&story_contents) { - msg_channel - .say(&ctx.http, format!("Give me {}", global)) - .await?; - - story_globals.insert(global, story_recv.recv().await.unwrap()); - } - - for (prompt, response) in story_globals { - story_contents = story_contents.replace(&format!("{{%{}%}}", prompt), &response) - } - - let mut msg_builder = MessageBuilder::default(); - for part in story_contents.split('\n') { - if (msg_builder.0.len() + part.len()) > serenity::constants::MESSAGE_CODE_LIMIT { - msg_channel.say(&ctx.http, msg_builder.build()).await?; - msg_builder = MessageBuilder::default(); - } - - msg_builder.push_line(part); - } - - if !msg_builder.0.is_empty() { - msg_channel.say(&ctx.http, msg_builder.build()).await?; - } - - Ok(()) -} - -#[command] -#[only_in(guilds)] -#[description("Give me a word")] -async fn word(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let data = ctx.data.read().await; - let story_channel = data.get::>().unwrap(); - - if story_channel.recv.try_lock().is_ok() { - msg.reply(&ctx.http, "No story in progress!").await?; - return Ok(()); - } - - let story_send = story_channel.send.lock().await; - - let resp = MessageBuilder::default().push_safe(args.rest()).build(); - - story_send.send(resp).await?; - - Ok(()) -} - -#[command] -#[description("Attach stories as .txt files")] -async fn upload_story(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let data = ctx.data.read().await; - let global = data.get::().unwrap(); - - let stories: Vec<&Attachment> = msg - .attachments - .iter() - .filter(|f| f.filename.ends_with(".txt")) - .collect(); - - for story in stories { - let story_txt = String::from_utf8(story.download().await?)?; - - let globals = get_all_blanks(&story_txt); - - let mut resp = MessageBuilder::new(); - - resp.push_line(format!("{} contains the following blanks:", story.filename)); - - for blank in globals { - resp.push_line(format!("* {}", blank)); - } - - msg.reply(&ctx.http, resp.build()).await?; - - let story_path = global.cfg.story_path.join(&story.filename); - - tokio::fs::write(&story_path, story_txt).await?; - } - - Ok(()) -} diff --git a/src/models/lil_fren.rs b/src/models/lil_fren.rs index d460903..d19b8eb 100644 --- a/src/models/lil_fren.rs +++ b/src/models/lil_fren.rs @@ -1,14 +1,16 @@ use crate::config::GlobalData; +use crate::discord::Context; use crate::error::Error; use j_db::database::Database; use j_db::model::JdbModel; +use log::{debug, info}; use poise::serenity_prelude::model::guild::Emoji; use poise::serenity_prelude::model::id::{EmojiId, GuildId}; -use poise::serenity_prelude::prelude::Context; use poise::serenity_prelude::utils::MessageBuilder; use rand::distr::{Distribution, StandardUniform}; use rand::{rng, Rng}; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use std::time::Duration; pub fn draw_standing(emoji: &Emoji) -> String { @@ -296,8 +298,8 @@ impl LilFren { lil_fren.thirst = (lil_fren.entertainment + entertainment_diff * self.smarts).clamp(-1.0, 1.0); - if rng().gen_bool(0.75) { - println!("fren is now {:?}", self.state); + if rng().random_bool(0.75) { + info!("fren is now {:?}", self.state); lil_fren.state = rng().random(); } @@ -334,8 +336,8 @@ impl LilFren { } } - pub async fn draw(&self, ctx: &Context, guild: &GuildId) -> String { - let emoji = guild.emoji(&ctx.http, self.emoji).await.unwrap(); + pub async fn draw(&self, ctx: Context<'_>, guild: &GuildId) -> String { + let emoji = guild.emoji(&ctx, self.emoji).await.unwrap(); let alive_state = self.is_alive(); if let AliveState::Alive = alive_state { @@ -346,14 +348,12 @@ impl LilFren { } } -pub async fn lil_fren_task(ctx: &Context) { +pub async fn lil_fren_task(data: Arc) { tokio::time::sleep(Duration::from_secs(60)).await; - let data = ctx.data.read().await; - let global_data = data.get::().unwrap(); - - let fren = LilFren::get_lil_fren(&global_data.db).unwrap(); + let fren = LilFren::get_lil_fren(&data.db).unwrap(); if let Some(mut fren) = fren { - fren.update_fren(&global_data.db).unwrap(); + debug!("Updating Lil Fren state..."); + fren.update_fren(&data.db).unwrap(); } }