This commit is contained in:
Joey Hines 2025-03-22 21:30:59 -06:00
parent 1d5b44128b
commit c5cada372e
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
23 changed files with 90 additions and 39 deletions

2
Cargo.lock generated
View File

@ -1080,7 +1080,7 @@ dependencies = [
[[package]]
name = "fren"
version = "0.8.0"
version = "1.0.0"
dependencies = [
"axum 0.8.1",
"base64 0.22.1",

View File

@ -1,7 +1,7 @@
[package]
name = "fren"
version = "0.8.0"
edition = "2021"
version = "1.0.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -69,6 +69,7 @@ impl BotState {
}
pub struct GlobalData {
#[allow(dead_code)]
pub args: Args,
pub cfg: BotConfig,
pub bot_state: Mutex<BotState>,

View File

@ -1,4 +1,3 @@
use crate::config::BotConfig;
use crate::discord::Context;
use crate::error::Error;
use crate::inventory::ItemType;
@ -21,6 +20,7 @@ pub async fn is_admin(ctx: Context<'_>) -> Result<bool, Error> {
Ok(ctx.data().cfg.admins.contains(&ctx.author().id))
}
/// Dumps the bot's internal database as JSON
#[poise::command(prefix_command, category = "Admin", check = "is_admin")]
pub async fn dump_db(ctx: Context<'_>) -> Result<(), Error> {
let db_dump = ctx.data().db.dump_db()?;
@ -45,6 +45,7 @@ pub async fn dump_db(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Loads the bot's database state from the attached file
#[poise::command(prefix_command, category = "Admin", check = "is_admin")]
pub async fn load_db(
ctx: Context<'_>,
@ -73,6 +74,7 @@ pub async fn load_db(
Ok(())
}
/// Add a new token for the bot's API
#[poise::command(prefix_command, category = "Admin", check = "is_admin")]
pub async fn add_key(
ctx: Context<'_>,
@ -102,6 +104,7 @@ pub async fn add_key(
Ok(())
}
/// Get Lil Buddy's Stats
#[poise::command(prefix_command, category = "Admin", check = "is_admin")]
pub async fn debug_buddy(ctx: Context<'_>) -> Result<(), Error> {
let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
@ -128,6 +131,7 @@ pub async fn debug_buddy(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Draw all the Lil Buddy states for debugging
#[poise::command(prefix_command, category = "Admin", check = "is_admin")]
pub async fn draw_buddy_states(
ctx: Context<'_>,
@ -149,6 +153,7 @@ pub async fn draw_buddy_states(
Ok(())
}
/// I'm from planet Minectaft, plz give op
#[poise::command(prefix_command, category = "Admin", check = "is_admin")]
pub async fn op_give(
ctx: Context<'_>,
@ -169,6 +174,7 @@ pub async fn op_give(
Ok(())
}
/// List the tasks the bot is preforming in the background
#[poise::command(prefix_command, category = "Admin")]
pub async fn list_tasks(ctx: Context<'_>) -> Result<(), Error> {
let tasks: Vec<Task> = ctx.data().db.filter(|_, _task: &Task| true)?.collect();

View File

@ -4,6 +4,7 @@ use crate::discord::Context;
use crate::error::Error;
use poise::serenity_prelude::{Attachment, MessageBuilder};
/// Add an image to a new or existing album
#[poise::command(prefix_command, category = "Albums")]
pub async fn add_image(
ctx: Context<'_>,
@ -28,6 +29,7 @@ pub async fn add_image(
Ok(())
}
/// List all albumbs
#[poise::command(prefix_command, category = "Albums")]
pub async fn list_albums(ctx: Context<'_>) -> Result<(), Error> {
let albums = ctx

View File

@ -5,6 +5,7 @@ use poise::serenity_prelude::model::id::UserId;
use poise::serenity_prelude::utils::MessageBuilder;
use rand::{rng, Rng};
/// Add your birthday to the bot, it can probably be trusted with this info
#[poise::command(prefix_command, category = "Birthdays")]
pub async fn add_birthday(
ctx: Context<'_>,
@ -43,6 +44,7 @@ pub async fn add_birthday(
Ok(())
}
/// List birthdays added to the bot
#[poise::command(prefix_command, category = "Birthdays")]
pub async fn list_birthdays(ctx: Context<'_>) -> Result<(), Error> {
let birthdays: Vec<BirthdayEntry> =

View File

@ -1,13 +1,12 @@
use crate::config::GlobalData;
use crate::discord::Context;
use crate::error::Error;
use std::net::ToSocketAddrs;
#[poise::command(prefix_command)]
/// Are you sure?
#[poise::command(prefix_command, category = "Celery Man")]
pub async fn nudetayne(ctx: Context<'_>) -> Result<(), Error> {
if ctx.invoked_command_name() == "nudetayne".to_string() {
if ctx.invoked_command_name() == "nudetayne" {
ctx.reply("Not computing, please repeat.").await?;
} else if ctx.invoked_command_name() == "NUDETAYNE".to_string() {
} else if ctx.invoked_command_name() == "NUDETAYNE" {
ctx.reply("This is not suitable for work are you sure?")
.await?;
@ -19,14 +18,16 @@ pub async fn nudetayne(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
#[poise::command(prefix_command)]
/// Tayne
#[poise::command(prefix_command, category = "Celery Man")]
pub async fn tayne(ctx: Context<'_>) -> Result<(), Error> {
ctx.reply("https://media.tenor.com/115eUl2XUaAAAAAM/flarhgunnstow-paul-rudd.gif")
.await?;
Ok(())
}
#[poise::command(prefix_command)]
/// THE Celeryman
#[poise::command(prefix_command, category = "Celery Man")]
pub async fn celeryman(ctx: Context<'_>) -> Result<(), Error> {
ctx.reply("https://media.tenor.com/1iOUXZFLpBgAAAAM/dance-dancing.gif")
.await?;

View File

@ -3,6 +3,7 @@ use crate::error::Error;
use poise::serenity_prelude::builder::EditRole;
use poise::serenity_prelude::model::Colour;
/// Set your discord name's color
#[poise::command(prefix_command, guild_only, category = "color")]
pub async fn set_color(
ctx: Context<'_>,
@ -45,7 +46,7 @@ pub async fn set_color(
let guild_id = ctx.guild_id().unwrap();
let member = ctx.author_member().await.unwrap();
let roles = member.roles(&ctx.cache()).unwrap();
let roles = member.roles(ctx.cache()).unwrap();
let color_role = roles.iter().find(|role| role.name.contains("COwOlor"));
@ -77,11 +78,12 @@ pub async fn set_color(
Ok(())
}
/// Remove your color (good for you!)
#[poise::command(prefix_command, guild_only, category = "Color")]
pub async fn remove_color(ctx: Context<'_>) -> Result<(), Error> {
let guild_id = ctx.guild_id().unwrap();
let member = ctx.author_member().await.unwrap();
let roles = member.roles(&ctx.cache()).unwrap();
let roles = member.roles(ctx.cache()).unwrap();
let color_role = roles.iter().find(|role| role.name.contains("COwOlor"));
if let Some(role) = color_role {

View File

@ -9,7 +9,7 @@ use poise::serenity_prelude::model::id::UserId;
use poise::serenity_prelude::utils::MessageBuilder;
use poise::serenity_prelude::{EmojiIdentifier, Mentionable};
use rand::seq::IteratorRandom;
use rand::{rng, Rng};
use rand::rng;
use std::time::Duration;
fn cleanup_race(db: &Database) -> Result<(), Error> {
@ -39,6 +39,7 @@ async fn add_bet(ctx: Context<'_>, bet: Bet, racers: &[Racer]) -> Result<(), Rac
Ok(())
}
/// Setup an emoji race to bet on
#[poise::command(prefix_command, guild_only, category = "Race")]
pub async fn race(ctx: Context<'_>) -> Result<(), Error> {
cleanup_race(&ctx.data().db)?;
@ -64,6 +65,7 @@ pub async fn race(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Start the race!
#[poise::command(prefix_command, guild_only, category = "Race")]
pub async fn start_race(ctx: Context<'_>) -> Result<(), Error> {
let mut racers = Racer::get_racers(&ctx.data().db)?;
@ -154,6 +156,7 @@ pub async fn start_race(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Bet on a race of emojis, a sound idea
#[poise::command(prefix_command, guild_only, category = "Race")]
pub async fn bet(
ctx: Context<'_>,

View File

@ -7,7 +7,8 @@ use poise::serenity_prelude::model::id::UserId;
use poise::serenity_prelude::prelude::Mentionable;
use rand::{rng, Rng};
#[poise::command(prefix_command, category = "FrenCoin", aliases("audit"))]
/// Get your balance or someone else's
#[poise::command(prefix_command, category = "Fren Coin", aliases("audit"))]
pub async fn balance(
ctx: Context<'_>,
#[description = "User to get balance of"] user: Option<poise::serenity_prelude::User>,
@ -26,7 +27,8 @@ pub async fn balance(
Ok(())
}
#[poise::command(prefix_command, category = "FrenCoin", aliases("audit"))]
/// Gift someone the gift that keeps on giving, Fren Coins!
#[poise::command(prefix_command, category = "Fren Coin", aliases("audit"))]
pub async fn gift(
ctx: Context<'_>,
#[description = "User to gift coin to"] target: poise::serenity_prelude::User,

View File

@ -9,7 +9,6 @@ use chrono::Utc;
use emojis::Group;
use log::debug;
use poise::serenity_prelude::all::{CreateAttachment, CreateMessage};
use poise::serenity_prelude::builder::EditMessage;
use poise::serenity_prelude::constants::MESSAGE_CODE_LIMIT;
use poise::serenity_prelude::model::channel::Message;
use poise::serenity_prelude::utils::MessageBuilder;
@ -21,7 +20,6 @@ use rand::prelude::IndexedRandom;
use rand::rng;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use songbird::id::UserId;
use std::collections::HashMap;
use std::time::Duration;
@ -32,6 +30,7 @@ struct DadJoke {
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();
@ -136,6 +135,7 @@ pub async fn random(
Ok(())
}
/// Add a random command to the bot
#[poise::command(prefix_command, category = "Joke")]
pub async fn add_random(
ctx: Context<'_>,
@ -143,7 +143,7 @@ pub async fn add_random(
#[description = "Random message"] random_response: String,
) -> Result<(), Error> {
if let Err(err) =
render_random(&ctx.author().display_name(), ctx.data(), &random_response).await
render_random(ctx.author().display_name(), ctx.data(), &random_response).await
{
ctx.reply(format!("Template failed test render, try again: {}", err))
.await?;
@ -155,6 +155,7 @@ pub async fn add_random(
Ok(())
}
/// List all of the random sets
#[poise::command(prefix_command, category = "Joke")]
pub async fn list_random(
ctx: Context<'_>,
@ -195,6 +196,7 @@ pub async fn list_random(
Ok(())
}
/// Roll a totally not loaded digital die!
#[poise::command(prefix_command, category = "Joke")]
pub async fn roll(
ctx: Context<'_>,
@ -214,6 +216,7 @@ pub async fn roll(
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();
@ -271,6 +274,7 @@ pub async fn real_roll(ctx: Context<'_>) -> Result<(), Error> {
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('|');
@ -311,6 +315,7 @@ pub async fn bad_apple(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Insult or compliment your "friends"!
#[poise::command(prefix_command, category = "Joke", aliases("compliment"))]
pub async fn insult(
ctx: Context<'_>,
@ -355,6 +360,7 @@ fn get_unicode_emojis() -> Vec<String> {
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<'_>,

View File

@ -7,6 +7,7 @@ use poise::serenity_prelude::utils::MessageBuilder;
use rand::prelude::IndexedRandom;
use rand::{rng, Rng};
/// Adopt a new little buddy!
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
pub async fn adopt(
ctx: Context<'_>,
@ -34,6 +35,7 @@ pub async fn adopt(
Ok(())
}
/// Checkup on your little buddy!
#[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)?;
@ -82,6 +84,7 @@ pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Feed your little buddy something totally normal!
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
pub async fn feed(
ctx: Context<'_>,
@ -91,7 +94,7 @@ pub async fn feed(
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);
let feed: f32 = rng().random_range(-0.1..=1.0);
lil_fren.hunger = (lil_fren.hunger + feed).clamp(-1.0, 1.0);
let guild = ctx.guild_id().unwrap();
@ -117,6 +120,7 @@ pub async fn feed(
Ok(())
}
/// Give your little buddy the juice of life.
#[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)?;
@ -149,6 +153,7 @@ pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Play with little buddy before he shreds up the remote again
#[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)?;
@ -175,6 +180,7 @@ pub async fn play(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Give your little buddy medicine
#[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)?;

View File

@ -55,7 +55,7 @@ async fn event_handler(
{
info!("Starting lil buddy task...");
let data = data.clone();
tokio::spawn(async move { lil_fren_task(data) });
tokio::spawn(async move { lil_fren_task(data).await });
}
}
serenity::FullEvent::Message { new_message } => {
@ -155,6 +155,18 @@ async fn pre_command(ctx: Context<'_>) {
);
}
#[poise::command(prefix_command)]
pub async fn help(ctx: Context<'_>, command: Option<String>) -> Result<(), Error> {
let configuration = poise::builtins::HelpConfiguration {
extra_text_at_bottom: "Made with 💖 by Joey",
include_description: false,
..Default::default()
};
poise::builtins::help(ctx, command.as_deref(), configuration).await?;
Ok(())
}
pub async fn run_bot(global_data: GlobalData) {
let framework_options: FrameworkOptions<Arc<GlobalData>, Error> = poise::FrameworkOptions {
prefix_options: poise::PrefixFrameworkOptions {
@ -165,6 +177,7 @@ pub async fn run_bot(global_data: GlobalData) {
..Default::default()
},
commands: vec![
help(),
admin::dump_db(),
admin::load_db(),
admin::add_key(),

View File

@ -57,6 +57,7 @@ pub async fn create_motivation_image(motivation: Motivation) -> Result<Vec<u8>,
Ok(wand.write_image_blob("jpg")?)
}
/// Become motivated
#[poise::command(prefix_command, category = "Motivation")]
pub async fn motivation(
ctx: Context<'_>,
@ -87,6 +88,7 @@ pub async fn motivation(
Ok(())
}
/// Add an album to the pool of images to be used for motivations
#[poise::command(prefix_command, category = "Motivation")]
pub async fn motivation_add_album(
ctx: Context<'_>,

View File

@ -9,7 +9,6 @@ use crate::user::{User, UserError};
use poise::serenity_prelude::all::{
parse_user_mention, CreateAttachment, CreateMessage, EditRole, GuildId, Member,
};
use poise::serenity_prelude::model::channel::Message;
use poise::serenity_prelude::model::Colour;
use poise::serenity_prelude::prelude::Mentionable;
use poise::serenity_prelude::utils::MessageBuilder;
@ -20,6 +19,7 @@ use std::hash::Hasher;
use std::sync::Arc;
use tokio::io::AsyncWriteExt;
/// Shop at a real virtual shop in the Metaverse!
#[poise::command(prefix_command, category = "Shop")]
pub async fn shop(ctx: Context<'_>) -> Result<(), Error> {
let bot_user = User::get_user(&ctx.data().db, ctx.cache().current_user().id)?;
@ -40,6 +40,7 @@ pub async fn shop(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Look at your inventory, this button is forced bound to any key other than "B"
#[poise::command(prefix_command, category = "Shop")]
pub async fn inventory(ctx: Context<'_>) -> Result<(), Error> {
let mut inv_msg = MessageBuilder::new();
@ -59,6 +60,7 @@ pub async fn inventory(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Buy an item from the shop with your hard earned cash
#[poise::command(prefix_command, category = "Shop")]
pub async fn buy(
ctx: Context<'_>,
@ -84,7 +86,7 @@ pub async fn buy(
ctx.reply(resp).await?;
} else {
return Err(err.into());
return Err(err);
}
} else {
ctx.reply(format!("Congrats, you now own a '{}'", item))
@ -150,6 +152,7 @@ async fn blockable_item(
}
}
/// Use an item and hope it doesn't use you
#[poise::command(prefix_command, category = "Shop", guild_only, aliases("use"))]
pub async fn use_item(
ctx: Context<'_>,
@ -323,6 +326,7 @@ pub async fn use_item(
Ok(())
}
/// Sell an item for profit
#[poise::command(prefix_command, category = "Shop", aliases("sell"))]
pub async fn sell_item(
ctx: Context<'_>,
@ -359,17 +363,19 @@ pub async fn sell_item(
_ => {}
}
} else {
return Err(err.into());
return Err(err);
}
}
}
Ok(())
}
/// Get help on what an item does
#[poise::command(prefix_command, category = "Shop")]
pub async fn item_help(
ctx: Context<'_>,
#[description = "I"]
#[description = "Item to get help on"]
#[rest]
item: ItemType,
) -> Result<(), Error> {

View File

@ -1,10 +1,8 @@
use crate::discord::Context;
use crate::error::Error;
use magick_rust::bindings::wchar_t;
use poise::async_trait;
use poise::serenity_prelude::all::CreateAttachment;
use poise::serenity_prelude::builder::CreateMessage;
use poise::serenity_prelude::model::channel::Message;
use poise::serenity_prelude::model::id::UserId;
use poise::serenity_prelude::model::prelude::GuildId;
use poise::serenity_prelude::utils::MessageBuilder;
@ -132,7 +130,7 @@ pub async fn speak(
voice: &str,
phrase: &str,
) -> Result<(), VoiceError> {
let _ = ctx.data().speak_lock.lock();
let _ = ctx.data().speak_lock.lock().await;
let voice_path = match find_voice(&ctx.data().cfg.voice_path, voice).await.unwrap() {
None => return Err(VoiceError::VoiceNotFound(voice.to_string())),
@ -231,6 +229,7 @@ pub async fn speak(
Ok(())
}
/// Speak in the language of the gods
#[poise::command(prefix_command, category = "Voice", guild_only)]
pub async fn say(
ctx: Context<'_>,
@ -296,6 +295,7 @@ pub async fn list_words(
Ok(())
}
/// List all voices the bot has
#[poise::command(prefix_command, category = "Voice", guild_only)]
pub async fn list_voices(ctx: Context<'_>) -> Result<(), Error> {
let mut voice_message = MessageBuilder::new();

View File

@ -2,7 +2,7 @@ use crate::discord::voices::VoiceError;
use crate::user;
use magick_rust::MagickError;
use serde::ser::StdError;
use std::fmt::{write, Display, Formatter};
use std::fmt::{Display, Formatter};
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]

View File

@ -9,9 +9,8 @@ mod user;
use crate::config::{Args, BotConfig, GlobalData};
use crate::discord::run_bot;
use log::{error, info, LevelFilter};
use log::{error, info};
use magick_rust::magick_wand_genesis;
use songbird::SerenityInit;
use std::sync::Once;
use structopt::StructOpt;

View File

@ -62,6 +62,8 @@ impl Apikey {
general_purpose::STANDARD_NO_PAD.encode(hash)
}
#[allow(dead_code)]
pub fn find_key_from_secret(db: &Database, secret: &str) -> Result<Option<Self>, Error> {
let hash = Self::hash_secret(secret);

View File

@ -3,7 +3,6 @@ use j_db::database::Database;
use j_db::model::JdbModel;
use rand::prelude::IndexedRandom;
use rand::rng;
use rand::seq::SliceRandom;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tera::{Context, Tera};

View File

@ -194,7 +194,7 @@ pub enum LilFrenState {
impl Distribution<LilFrenState> for StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> LilFrenState {
match rng.gen_range(0..10) {
match rng.random_range(0..10) {
0 => LilFrenState::Standing,
1 => LilFrenState::TaxFraud,
2 => LilFrenState::Sick,
@ -253,8 +253,8 @@ impl LilFren {
thirst: 1.0,
entertainment: 1.0,
state: LilFrenState::Standing,
smarts: rng().gen_range(0.0..0.5),
metabolism: rng().gen_range(0.2..0.75),
smarts: rng().random_range(0.0..0.5),
metabolism: rng().random_range(0.2..0.75),
}
}

View File

@ -1,7 +1,6 @@
use crate::error::Error;
use crate::user::UserError;
use j_db::database::Database;
use j_db::error::JDbError;
use j_db::model::JdbModel;
use poise::serenity_prelude::{Emoji, EmojiId, MessageBuilder, UserId};
use rand::{rng, Rng};
@ -134,7 +133,7 @@ impl Racer {
}
pub fn update_pos(&mut self) {
let speed_boost = rng().gen_range(-0.75..0.75);
let speed_boost = rng().random_range(-0.75..0.75);
self.pos += self.speed + (self.speed * speed_boost);
self.pos = self.pos.clamp(0.0, RACE_SIZE as f32);

View File

@ -3,7 +3,7 @@ use j_db::database::Database;
use j_db::model::JdbModel;
use rand::distr::weighted::WeightedIndex;
use rand::prelude::Distribution;
use rand::thread_rng;
use rand::rng;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
@ -115,7 +115,7 @@ impl RandomConfig {
let dist = WeightedIndex::new(responses.iter().map(|r| r.score)).unwrap();
let mut resp = responses[dist.sample(&mut thread_rng())].clone();
let mut resp = responses[dist.sample(&mut rng())].clone();
resp.score = resp.score.saturating_sub(1);