407 lines
11 KiB
Rust
407 lines
11 KiB
Rust
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<String, String>,
|
|
}
|
|
|
|
impl RandomCtx {
|
|
pub async fn new(user_name: &str, global_data: &GlobalData) -> Result<Self, Error> {
|
|
let mut random_image: HashMap<String, String> = HashMap::new();
|
|
|
|
let albums: Vec<Album> = 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<String, Error> {
|
|
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 the responses from a random set
|
|
#[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::<Random>(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(())
|
|
}
|
|
|
|
/// List all random sets
|
|
#[poise::command(prefix_command, category = "Joke")]
|
|
pub async fn list_randoms(ctx: Context<'_>) -> Result<(), Error> {
|
|
let randoms: Vec<RandomConfig> = ctx
|
|
.data()
|
|
.db
|
|
.filter(|_, _random: &RandomConfig| true)?
|
|
.collect();
|
|
|
|
let mut msg_build = MessageBuilder::new();
|
|
|
|
msg_build.push_line("List of all randoms:");
|
|
|
|
for random in &randoms {
|
|
msg_build.push_line(format!("* {}", random.name));
|
|
}
|
|
|
|
ctx.reply(msg_build.build()).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::<ndm::Dice>();
|
|
|
|
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 {target}s, mr freeman??")).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_unicode_emojis() -> Vec<String> {
|
|
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<String> = 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<String> = emojis.sample(&mut rng(), 3).cloned().collect();
|
|
|
|
ctx.reply(emoji_msg.join("")).await?;
|
|
|
|
Ok(())
|
|
}
|