use crate::BAD_APPLE; use crate::album_manager::{Album, AlbumQuery, ImageQuery, ImageSort}; use crate::config::GlobalData; use crate::discord::Context; use crate::error::Error; use crate::models::insult_compliment::{RandomResponseTemplate, ResponseType}; use crate::models::random::{Random, RandomConfig}; use chrono::Utc; use emojis::Group; use log::debug; use poise::serenity_prelude::all::{CreateAttachment, CreateMessage}; use poise::serenity_prelude::constants::MESSAGE_CODE_LIMIT; use poise::serenity_prelude::model::channel::Message; use poise::serenity_prelude::utils::MessageBuilder; use poise::{CreateReply, serenity_prelude}; use raas_types::raas::bot::roll::{Roll, RollCmd, roll_response}; use raas_types::raas::resp::response::Resp; use raas_types::raas::service::raas_client::RaasClient; use rand::prelude::IndexedRandom; use rand::rng; use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::Duration; #[derive(Clone, Serialize, Deserialize)] struct DadJoke { pub id: String, pub joke: String, pub status: i32, } /// Ask your dad #[poise::command(prefix_command, category = "Joke")] pub async fn dad_joke(ctx: Context<'_>) -> Result<(), Error> { let client = Client::new(); let joke: DadJoke = client .get("https://icanhazdadjoke.com/") .header("Accept", "application/json") .send() .await? .json() .await?; ctx.reply(joke.joke).await?; Ok(()) } #[derive(Serialize, Deserialize)] struct RandomCtx { user: String, random_image: HashMap, } impl RandomCtx { pub async fn new(user_name: &str, global_data: &GlobalData) -> Result { let mut random_image: HashMap = HashMap::new(); let albums: Vec = global_data .picox .query_album(AlbumQuery { album_name: None }) .await?; for album in albums { let images = global_data .picox .query_image(ImageQuery { album: Some(album.album_name.clone()), tags: vec![], order: ImageSort::Random, limit: 1, }) .await?; let image = images.first(); if let Some(image) = image { random_image.insert(album.album_name.clone(), image.link.to_string()); } } Ok(Self { user: user_name.to_string(), random_image, }) } } pub async fn render_random( author_name: &str, global_data: &GlobalData, template: &str, ) -> Result { let random_ctx = RandomCtx::new(author_name, global_data).await?; Ok(tera::Tera::one_off( template, &tera::Context::from_serialize(random_ctx)?, false, )?) } pub async fn random( ctx: &serenity_prelude::Context, global_data: &GlobalData, msg: &Message, random_name: &str, ) -> Result<(), Error> { let random = match RandomConfig::get_random(&global_data.db, random_name)? { None => return Ok(()), Some(r) => r, }; let response_template_str = match random.get_response(&global_data.db)? { None => { msg.reply(&ctx, format!("I'm all out of material for {}", random_name)) .await?; return Ok(()); } Some(resp) => resp, }; let guild_member = msg.guild_id.unwrap().member(&ctx, msg.author.id).await?; let reply = render_random( guild_member.display_name(), global_data, &response_template_str, ) .await?; msg.reply(&ctx.http, reply).await?; Ok(()) } /// Add a random command to the bot #[poise::command(prefix_command, category = "Joke")] pub async fn add_random( ctx: Context<'_>, #[description = "Random collection to add to"] random_name: String, #[description = "Random message"] #[rest] random_response: String, ) -> Result<(), Error> { if let Err(err) = render_random(ctx.author().display_name(), ctx.data(), &random_response).await { ctx.reply(format!("Template failed test render, try again: {}", err)) .await?; } else { RandomConfig::add_random(&ctx.data().db, &random_name, &random_response)?; ctx.reply("New random added!").await?; } Ok(()) } /// List all of the random sets #[poise::command(prefix_command, category = "Joke")] pub async fn list_random( ctx: Context<'_>, #[description = "Random collection to list"] random_name: String, ) -> Result<(), Error> { let random = RandomConfig::get_random(&ctx.data().db, &random_name)?; let dm_channel = ctx.author().id.create_dm_channel(&ctx).await?; if let Some(random) = random { let mut msg_builder = MessageBuilder::new(); msg_builder.push_line(format!( "All possible responses for {} all {} of them..:", random_name, random.responses.len() )); for resp in random.responses { let random = ctx.data().db.get::(resp)?; let line_msg = format!("* ({}) ({}) {}", resp, random.score, random.response); if (msg_builder.0.len() + line_msg.len()) > MESSAGE_CODE_LIMIT { dm_channel.say(&ctx, msg_builder.build()).await?; msg_builder.0.clear(); } msg_builder.push_line(line_msg); } if !msg_builder.0.is_empty() { dm_channel.say(&ctx, msg_builder.build()).await?; } } else { ctx.reply("*glances to the back room*, nope we ain't got that random") .await?; } Ok(()) } /// Roll a totally not loaded digital die! #[poise::command(prefix_command, category = "Joke")] pub async fn roll( ctx: Context<'_>, #[description = "die to roll"] #[rest] die_roll: String, ) -> Result<(), Error> { let roll = die_roll.parse::(); let reply = match roll { Ok(roll) => format!("You rolled: **{}**", roll), Err(err) => format!("Error parsing dice roll: {}", err), }; ctx.reply(reply).await?; Ok(()) } /// Roll a totally not loaded, real actual die! (No joke) #[poise::command(prefix_command, category = "Joke")] pub async fn real_roll(ctx: Context<'_>) -> Result<(), Error> { let addr = ctx.data().cfg.raas_server.clone(); let mut client = RaasClient::connect(addr) .await .map_err(|err| Error::RaasError(err.to_string()))?; let rolls = 3; let roll_request = raas_types::raas::cmd::Request { timestamp: Utc::now().timestamp() as u64, cmd: Some(raas_types::raas::cmd::request::Cmd::RollCmd(RollCmd { cmd: Some(raas_types::raas::bot::roll::roll_cmd::Cmd::Roll(Roll { rotations: rolls, })), })), }; ctx.reply("Sent roll request, please hang on!").await?; let response = client .send_request(roll_request) .await .map_err(|err| Error::RaasError(err.to_string()))?; let raas_response = response.into_inner(); debug!( "Got resp from RaaS: {} @ {}", raas_response.id, raas_response.timestamp ); match raas_response.resp.unwrap() { Resp::RollResp(roll_resp) => { if let roll_response::Response::RollImage(img) = roll_resp.response.unwrap() { ctx.channel_id() .send_message( &ctx, CreateMessage::new() .content("Your roll my friend, hope its good I can't read!") .add_file(CreateAttachment::bytes(img.img, "roll.jpg")), ) .await?; } } Resp::Error(err) => { ctx.reply(format!( "My real flesh encountered an error. Get Dad to fix it. `{}`", err.msg )) .await?; } } Ok(()) } /// Play critically the acclaimed Bad Apple music video as god intended #[poise::command(prefix_command, category = "Joke")] pub async fn bad_apple(ctx: Context<'_>) -> Result<(), Error> { let mut frames = BAD_APPLE.split('|'); let first_frame = frames.next().unwrap(); if first_frame.len() > 2000 { ctx.reply(format!("Frame too big: {}", first_frame.len())) .await?; return Ok(()); } let bad_apple_msg = ctx .reply( MessageBuilder::default() .push_codeblock_safe(first_frame, None) .build(), ) .await?; for (idx, frame) in frames.enumerate() { tokio::time::sleep(Duration::from_millis(100)).await; if (idx % 10) == 0 { bad_apple_msg .edit( ctx, CreateReply::default().content( MessageBuilder::default() .push_codeblock_safe(frame, None) .build(), ), ) .await?; } } Ok(()) } /// Insult or compliment your "friends"! #[poise::command(prefix_command, category = "Joke", aliases("compliment"))] pub async fn insult( ctx: Context<'_>, #[description = "person to compliment/insult"] target: String, ) -> Result<(), Error> { let pool = if ctx.invoked_command_name() == "insult" { ResponseType::Insult } else if ctx.invoked_command_name() == "compliment" { ResponseType::Compliment } else { ctx.reply("The h*ck did you just say to me??").await?; return Ok(()); }; let output = RandomResponseTemplate::get_random_response(&ctx.data().db, pool, &target)?; if let Some(output) = output { ctx.reply(output).await?; } else { ctx.reply(format!("No {}s, mr freeman??", target)).await?; } Ok(()) } fn get_unicode_emojis() -> Vec { let groups = [ Group::SmileysAndEmotion, Group::Activities, Group::Objects, Group::AnimalsAndNature, Group::PeopleAndBody, Group::FoodAndDrink, ]; let mut emoji = Vec::new(); for group in groups { emoji.extend(group.emojis()) } emoji.iter().map(|e| e.to_string()).collect() } /// Stare into the digital black mirror and see what it reflects #[poise::command(prefix_command, category = "Joke", aliases("🎱"))] pub async fn emoji_8ball( ctx: Context<'_>, #[description = "prompt to dissect using magic"] #[rest] _prompt: String, ) -> Result<(), Error> { let guild_emojis = ctx.guild_id().unwrap().emojis(&ctx).await?; let mut emojis: Vec = guild_emojis.iter().map(|e| e.to_string()).collect(); let mut unicode_emojis = get_unicode_emojis(); emojis.append(&mut unicode_emojis); let emoji_msg: Vec = emojis.choose_multiple(&mut rng(), 3).cloned().collect(); ctx.reply(emoji_msg.join("")).await?; Ok(()) }