Add consequences for being dead or canceled

This commit is contained in:
Joey Hines 2025-08-09 17:46:19 -06:00
parent 5b4c6b50ae
commit c6786a77f7
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
11 changed files with 154 additions and 49 deletions

2
Cargo.lock generated
View File

@ -1093,7 +1093,7 @@ dependencies = [
[[package]] [[package]]
name = "fren" name = "fren"
version = "2.3.0" version = "2.4.0"
dependencies = [ dependencies = [
"axum 0.8.1", "axum 0.8.1",
"base64 0.22.1", "base64 0.22.1",

View File

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

View File

@ -1,8 +1,9 @@
use crate::discord::Context; use crate::discord::Context;
use crate::discord::is_not_cancelled;
use crate::error::Error; use crate::error::Error;
/// Are you sure? /// Are you sure?
#[poise::command(prefix_command, category = "Celery Man")] #[poise::command(prefix_command, category = "Celery Man", check = "is_not_cancelled")]
pub async fn nudetayne(ctx: Context<'_>) -> Result<(), Error> { pub async fn nudetayne(ctx: Context<'_>) -> Result<(), Error> {
if ctx.invoked_command_name() == "nudetayne" { if ctx.invoked_command_name() == "nudetayne" {
ctx.reply("Not computing, please repeat.").await?; ctx.reply("Not computing, please repeat.").await?;

View File

@ -1,4 +1,5 @@
use crate::discord::Context; use crate::discord::Context;
use crate::discord::is_not_cancelled;
use crate::error::Error; use crate::error::Error;
use crate::models::race; use crate::models::race;
use crate::models::race::{Bet, NUMBER_OF_RACERS, RACE_SIZE, RaceError, Racer}; use crate::models::race::{Bet, NUMBER_OF_RACERS, RACE_SIZE, RaceError, Racer};
@ -40,7 +41,12 @@ async fn add_bet(ctx: Context<'_>, bet: Bet, racers: &[Racer]) -> Result<(), Rac
} }
/// Setup an emoji race to bet on /// Setup an emoji race to bet on
#[poise::command(prefix_command, guild_only, category = "Race")] #[poise::command(
prefix_command,
guild_only,
category = "Race",
check = "is_not_cancelled"
)]
pub async fn race(ctx: Context<'_>) -> Result<(), Error> { pub async fn race(ctx: Context<'_>) -> Result<(), Error> {
cleanup_race(&ctx.data().db)?; cleanup_race(&ctx.data().db)?;
@ -154,7 +160,12 @@ pub async fn start_race(ctx: Context<'_>) -> Result<(), Error> {
} }
/// Bet on a race of emojis, a sound idea /// Bet on a race of emojis, a sound idea
#[poise::command(prefix_command, guild_only, category = "Race")] #[poise::command(
prefix_command,
guild_only,
category = "Race",
check = "is_not_cancelled"
)]
pub async fn bet( pub async fn bet(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Racer to bet on"] racer: EmojiIdentifier, #[description = "Racer to bet on"] racer: EmojiIdentifier,

View File

@ -1,4 +1,4 @@
use crate::discord::Context; use crate::discord::{Context, is_alive};
use crate::error::Error; use crate::error::Error;
use crate::user::{User, UserError}; use crate::user::{User, UserError};
use j_db::database::Database; use j_db::database::Database;
@ -28,7 +28,12 @@ pub async fn balance(
} }
/// Gift someone the gift that keeps on giving, Fren Coins! /// Gift someone the gift that keeps on giving, Fren Coins!
#[poise::command(prefix_command, category = "Fren Coin", aliases("audit"))] #[poise::command(
prefix_command,
category = "Fren Coin",
aliases("audit"),
check = "is_alive"
)]
pub async fn gift( pub async fn gift(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "User to gift coin to"] target: poise::serenity_prelude::User, #[description = "User to gift coin to"] target: poise::serenity_prelude::User,

View File

@ -1,4 +1,4 @@
use crate::discord::Context; use crate::discord::{Context, is_not_cancelled};
use crate::error::Error; use crate::error::Error;
use crate::models::lil_fren; use crate::models::lil_fren;
use crate::models::lil_fren::{AliveState, LilFren, LilFrenState}; use crate::models::lil_fren::{AliveState, LilFren, LilFrenState};
@ -8,18 +8,23 @@ use rand::prelude::IndexedRandom;
use rand::{Rng, rng}; use rand::{Rng, rng};
/// Adopt a new little buddy! /// Adopt a new little buddy!
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] #[poise::command(
prefix_command,
guild_only,
category = "Lil Buddy",
check = "is_not_cancelled"
)]
pub async fn adopt( pub async fn adopt(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Lil buddy to adopt"] lil_buddy: EmojiIdentifier, #[description = "Lil buddy to adopt"] lil_buddy: EmojiIdentifier,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let Some(lil_fren) = LilFren::get_lil_fren(&ctx.data().db)? { if let Some(lil_fren) = LilFren::get_lil_fren(&ctx.data().db)?
if lil_fren.is_alive() == AliveState::Alive { && lil_fren.is_alive() == AliveState::Alive
{
ctx.reply("Your buddy is still alive, please take care of him :)") ctx.reply("Your buddy is still alive, please take care of him :)")
.await?; .await?;
return Ok(()); return Ok(());
} }
}
LilFren::create_new_lil_fren(&ctx.data().db, lil_buddy.id)?; LilFren::create_new_lil_fren(&ctx.data().db, lil_buddy.id)?;
let guild = ctx.guild_id().unwrap(); let guild = ctx.guild_id().unwrap();
@ -36,7 +41,12 @@ pub async fn adopt(
} }
/// Checkup on your little buddy! /// Checkup on your little buddy!
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] #[poise::command(
prefix_command,
guild_only,
category = "Lil Buddy",
check = "is_not_cancelled"
)]
pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> { pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> {
let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
@ -89,7 +99,12 @@ pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> {
} }
/// Feed your little buddy something totally normal! /// Feed your little buddy something totally normal!
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] #[poise::command(
prefix_command,
guild_only,
category = "Lil Buddy",
check = "is_not_cancelled"
)]
pub async fn feed( pub async fn feed(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Item to feed lil buddy"] food: String, #[description = "Item to feed lil buddy"] food: String,
@ -125,7 +140,12 @@ pub async fn feed(
} }
/// Give your little buddy the juice of life. /// Give your little buddy the juice of life.
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] #[poise::command(
prefix_command,
guild_only,
category = "Lil Buddy",
check = "is_not_cancelled"
)]
pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> { pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> {
let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
@ -158,7 +178,12 @@ pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> {
} }
/// Play with little buddy before he shreds up the remote again /// Play with little buddy before he shreds up the remote again
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] #[poise::command(
prefix_command,
guild_only,
category = "Lil Buddy",
check = "is_not_cancelled"
)]
pub async fn play(ctx: Context<'_>) -> Result<(), Error> { pub async fn play(ctx: Context<'_>) -> Result<(), Error> {
let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
@ -185,7 +210,12 @@ pub async fn play(ctx: Context<'_>) -> Result<(), Error> {
} }
/// Give your little buddy medicine /// Give your little buddy medicine
#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] #[poise::command(
prefix_command,
guild_only,
category = "Lil Buddy",
check = "is_not_cancelled"
)]
pub async fn give_medicine(ctx: Context<'_>) -> Result<(), Error> { pub async fn give_medicine(ctx: Context<'_>) -> Result<(), Error> {
let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;

View File

@ -1,3 +1,4 @@
use rand::seq::IteratorRandom;
mod admin; mod admin;
mod album; mod album;
mod birthday; mod birthday;
@ -22,11 +23,10 @@ use crate::error::Error;
use crate::event_listener::{Listener, TriggerEvent, TriggerType}; use crate::event_listener::{Listener, TriggerEvent, TriggerType};
use crate::models::social_credit::SocialCreditPhrase; use crate::models::social_credit::SocialCreditPhrase;
use crate::models::task::Task; use crate::models::task::Task;
use crate::user::User; use crate::user::{User, UserRole};
use log::{debug, error, info}; use log::{debug, error, info};
use poise::serenity_prelude::{GuildId, Http, Message, MessageBuilder, ReactionType, RoleId}; use poise::serenity_prelude::{GuildId, Http, Message, MessageBuilder, ReactionType, RoleId};
use poise::{FrameworkOptions, find_command, serenity_prelude as serenity}; use poise::{FrameworkOptions, find_command, serenity_prelude as serenity};
use rand::prelude::IteratorRandom;
use rand::{Rng, rng}; use rand::{Rng, rng};
use songbird::SerenityInit; use songbird::SerenityInit;
use std::sync::Arc; use std::sync::Arc;
@ -83,13 +83,13 @@ async fn handle_message(
|| new_message.content.eq_ignore_ascii_case("mhmm") || new_message.content.eq_ignore_ascii_case("mhmm")
{ {
let mut bot_state = data.bot_state.lock().await; let mut bot_state = data.bot_state.lock().await;
if let Some(u) = bot_state.accepted_nsfw { if let Some(u) = bot_state.accepted_nsfw
if new_message.author.id == u { && new_message.author.id == u
{
new_message.reply(&ctx.http, "||https://cdn.discordapp.com/attachments/614891432079130625/1041545254362423368/unknown.png||").await.unwrap(); new_message.reply(&ctx.http, "||https://cdn.discordapp.com/attachments/614891432079130625/1041545254362423368/unknown.png||").await.unwrap();
bot_state.accepted_nsfw = None; bot_state.accepted_nsfw = None;
} }
} }
}
if new_message.content.to_lowercase().contains("good bot") { if new_message.content.to_lowercase().contains("good bot") {
let recv_coin = give_coin(&data.db, new_message.author.id, 0.50, 25).await?; let recv_coin = give_coin(&data.db, new_message.author.id, 0.50, 25).await?;
@ -445,3 +445,46 @@ pub async fn get_role(
.map(|(role_id, _)| role_id) .map(|(role_id, _)| role_id)
.copied()) .copied())
} }
async fn check_has_user_role(ctx: Context<'_>, user_role: UserRole) -> Result<bool, Error> {
let role = get_role(ctx.http(), ctx.data().cfg.guild_id, &user_role.to_string()).await?;
if let Some(role) = role {
Ok(ctx
.author()
.has_role(&ctx, ctx.data().cfg.guild_id, role)
.await?)
} else {
Ok(false)
}
}
pub async fn is_alive(ctx: Context<'_>) -> Result<bool, Error> {
let is_ghoul = check_has_user_role(ctx, UserRole::Ghoul).await?;
if check_has_user_role(ctx, UserRole::Dead).await? && !is_ghoul {
ctx.reply("Sorry there ghosty looking fella, you appear to be unalived. I only give service to those who are alive.").await?;
Ok(false)
} else {
if is_ghoul && rng().random_bool(0.50) {
let ghoul_responses = [
"Oh god, what's wrong with your face? Looks like someone took it through a blender and lit it on fire",
"You kiss your mother with that mouth?",
"Oh! I love your cosplay, chic fallout is so IN right now frfr",
"Have you tried moisturizer? Seems like it couldn't umm... hurt...",
"OH damn, can I get a warning next time before you jump scare me with that",
];
let resp = ghoul_responses.iter().choose(&mut rng()).unwrap();
ctx.reply(*resp).await?;
}
Ok(true)
}
}
pub async fn is_not_cancelled(ctx: Context<'_>) -> Result<bool, Error> {
if check_has_user_role(ctx, UserRole::Cancelled).await? {
ctx.reply("You're CANCELLED?? Yikes! I can't interact with you, I don't want to become a conservative influencer.").await?;
Ok(false)
} else {
Ok(true)
}
}

View File

@ -1,5 +1,6 @@
use crate::config::GlobalData; use crate::config::GlobalData;
use crate::discord::Context; use crate::discord::Context;
use crate::discord::is_alive;
use crate::error::Error; use crate::error::Error;
use crate::event_listener::{Action, Expiration, Listener, TriggerType}; use crate::event_listener::{Action, Expiration, Listener, TriggerType};
use crate::image_manipulation::create_motivation_image; use crate::image_manipulation::create_motivation_image;
@ -15,7 +16,7 @@ use std::sync::Arc;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
/// Shop at a real virtual shop in the Metaverse! /// Shop at a real virtual shop in the Metaverse!
#[poise::command(prefix_command, category = "Shop")] #[poise::command(prefix_command, category = "Shop", check = "is_alive")]
pub async fn shop(ctx: Context<'_>) -> Result<(), Error> { pub async fn shop(ctx: Context<'_>) -> Result<(), Error> {
let bot_user = User::get_user(&ctx.data().db, ctx.cache().current_user().id)?; let bot_user = User::get_user(&ctx.data().db, ctx.cache().current_user().id)?;
@ -56,7 +57,7 @@ pub async fn inventory(ctx: Context<'_>) -> Result<(), Error> {
} }
/// Buy an item from the shop with your hard-earned cash /// Buy an item from the shop with your hard-earned cash
#[poise::command(prefix_command, category = "Shop")] #[poise::command(prefix_command, category = "Shop", check = "is_alive")]
pub async fn buy( pub async fn buy(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Item to buy"] #[description = "Item to buy"]
@ -127,7 +128,7 @@ pub async fn license_to_be_horny(ctx: Context<'_>) -> Result<(), Error> {
} }
/// Use a kill gun to kill someone /// Use a kill gun to kill someone
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")]
pub async fn kill_gun( pub async fn kill_gun(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "target to kill"] target: poise::serenity_prelude::User, #[description = "target to kill"] target: poise::serenity_prelude::User,
@ -137,7 +138,7 @@ pub async fn kill_gun(
} }
/// Use a cancel ray to cancel someone /// Use a cancel ray to cancel someone
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")]
pub async fn cancel_ray( pub async fn cancel_ray(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "target to cancel"] target: poise::serenity_prelude::User, #[description = "target to cancel"] target: poise::serenity_prelude::User,
@ -154,14 +155,14 @@ pub async fn helmet(ctx: Context<'_>) -> Result<(), Error> {
} }
/// Deploy a tactical emp /// Deploy a tactical emp
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")]
pub async fn emp(ctx: Context<'_>) -> Result<(), Error> { pub async fn emp(ctx: Context<'_>) -> Result<(), Error> {
use_item(ctx, ItemType::EMP, Target::Everyone).await?; use_item(ctx, ItemType::EMP, Target::Everyone).await?;
Ok(()) Ok(())
} }
/// Deploy a land mine /// Deploy a land mine
#[poise::command(prefix_command, category = "Shop")] #[poise::command(prefix_command, category = "Shop", check = "is_alive")]
pub async fn land_mine( pub async fn land_mine(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Channel to land mine"] channel: Channel, #[description = "Channel to land mine"] channel: Channel,
@ -195,7 +196,7 @@ pub async fn land_mine(
} }
/// Use your Phrase Canceler /// Use your Phrase Canceler
#[poise::command(prefix_command, category = "Shop")] #[poise::command(prefix_command, category = "Shop", check = "is_alive")]
pub async fn phrase_canceler( pub async fn phrase_canceler(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Phrase to cancel"] #[description = "Phrase to cancel"]
@ -231,7 +232,7 @@ pub async fn phrase_canceler(
} }
/// Drop a nuke on this discord /// Drop a nuke on this discord
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")]
pub async fn nuke(ctx: Context<'_>) -> Result<(), Error> { pub async fn nuke(ctx: Context<'_>) -> Result<(), Error> {
if use_item(ctx, ItemType::Nuke, Target::Everyone).await? { if use_item(ctx, ItemType::Nuke, Target::Everyone).await? {
Listener::add_listener(&ctx.data().db, { Listener::add_listener(&ctx.data().db, {
@ -283,18 +284,18 @@ pub async fn use_item(ctx: Context<'_>, item: ItemType, target: Target) -> Resul
User::use_item_on_user(ctx, *target_user, ctx.guild_id().unwrap(), item, &item_data) User::use_item_on_user(ctx, *target_user, ctx.guild_id().unwrap(), item, &item_data)
.await?; .await?;
if let Some(outcome) = outcome { if let Some(outcome) = outcome
if ndx + 1 == target_users.len() { && ndx + 1 == target_users.len()
{
ctx.reply(outcome).await?; ctx.reply(outcome).await?;
} }
} }
}
Ok(true) Ok(true)
} }
/// Sell an item for profit /// Sell an item for profit
#[poise::command(prefix_command, category = "Shop", aliases("sell"))] #[poise::command(prefix_command, category = "Shop", aliases("sell"), check = "is_alive")]
pub async fn sell_item( pub async fn sell_item(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Item to sell"] #[description = "Item to sell"]

View File

@ -1,10 +1,15 @@
use crate::discord::Context; use crate::discord::{Context, is_alive};
use crate::error::Error; use crate::error::Error;
use crate::models::gogurt_reserves::GogurtReserves; use crate::models::gogurt_reserves::GogurtReserves;
use poise::serenity_prelude::{Mentionable, MessageBuilder}; use poise::serenity_prelude::{Mentionable, MessageBuilder};
/// Contribute to the Gogurt Reserve /// Contribute to the Gogurt Reserve
#[poise::command(prefix_command, category = "Stonks", aliases("cgr"))] #[poise::command(
prefix_command,
category = "Stonks",
aliases("cgr"),
check = "is_alive"
)]
pub async fn contribute_to_gogurt_reserve( pub async fn contribute_to_gogurt_reserve(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "amount of FCs to spend"] contribute_amount: u64, #[description = "amount of FCs to spend"] contribute_amount: u64,
@ -21,7 +26,12 @@ pub async fn contribute_to_gogurt_reserve(
} }
/// Sell gogurt from the Gogurt Reserve /// Sell gogurt from the Gogurt Reserve
#[poise::command(prefix_command, category = "Stonks", aliases("sgr"))] #[poise::command(
prefix_command,
category = "Stonks",
aliases("sgr"),
check = "is_alive"
)]
pub async fn sell_from_gogurt_reserve( pub async fn sell_from_gogurt_reserve(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "number of pounds of gogurt to sell"] gogurt_to_sell: f64, #[description = "number of pounds of gogurt to sell"] gogurt_to_sell: f64,

View File

@ -9,16 +9,16 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
const OPENING_HOUR: u32 = 8; const OPENING_HOUR: u32 = 7;
const UPDATE_HOUR: u32 = 12; const UPDATE_HOUR: u32 = 12;
const CLOSING_HOUR: u32 = 16; const CLOSING_HOUR: u32 = 18;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum GogurtError { pub enum GogurtError {
#[error("Wow, you really don't have enough gogurt. What are you 12?? Just got buy some poor.")] #[error("Wow, you really don't have enough gogurt. What are you 12?? Just got buy some poor.")]
NotEnoughGogurt, NotEnoughGogurt,
#[error( #[error(
"Sorry, gogurt can only be bought and sold between the hours of 8AM to 4PM Naperville Time." "Sorry, gogurt can only be bought and sold between the hours of 7AM to 6PM Naperville Time."
)] )]
OutsideOfGogurtTradingHours, OutsideOfGogurtTradingHours,
} }

View File

@ -209,12 +209,16 @@ impl Task {
let midday_update_time = GogurtReserves::market_update_time(); let midday_update_time = GogurtReserves::market_update_time();
let next_check = if chicago_time.time() >= midday_update_time { let next_check = if chicago_time.time() >= midday_update_time {
chicago_time.with_time(market_open_time) chicago_time
.with_time(market_open_time)
.unwrap()
.checked_add_days(Days::new(1))
.unwrap()
} else { } else {
chicago_time.with_time(midday_update_time) chicago_time.with_time(midday_update_time).unwrap()
}; };
let next_check = next_check.unwrap().with_timezone(&Utc); let next_check = next_check.with_timezone(&Utc);
Task::add_task(&data.db, TaskType::UpdateGogurtRate, next_check)?; Task::add_task(&data.db, TaskType::UpdateGogurtRate, next_check)?;
} }