Add nuke and canceler

This commit is contained in:
Joey Hines 2025-08-02 21:37:37 -06:00
parent 1669a9cf8f
commit fcde491549
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
6 changed files with 169 additions and 43 deletions

View File

@ -16,9 +16,10 @@ use crate::user::User;
use j_db::model::JdbModel; use j_db::model::JdbModel;
use json::JsonValue; use json::JsonValue;
use log::info; use log::info;
use poise::CreateReply;
use poise::serenity_prelude::{ use poise::serenity_prelude::{
Attachment, CreateAttachment, CreateMessage, EditRole, EmojiIdentifier, FormattedTimestamp, Attachment, CreateAttachment, CreateMessage, EditRole, EmojiIdentifier, FormattedTimestamp,
FormattedTimestampStyle, MessageBuilder, Timestamp, UserId, FormattedTimestampStyle, MESSAGE_CODE_LIMIT, MessageBuilder, Timestamp, UserId,
}; };
use std::borrow::Cow; use std::borrow::Cow;
@ -197,7 +198,18 @@ pub async fn list_tasks(ctx: Context<'_>) -> Result<(), Error> {
)); ));
} }
ctx.reply(resp.build()).await?; let msg = resp.build();
if msg.len() < MESSAGE_CODE_LIMIT {
ctx.reply(msg).await?;
} else {
ctx.send(
CreateReply::default()
.reply(true)
.attachment(CreateAttachment::bytes(msg.as_bytes(), "tasks.md")),
)
.await?;
}
Ok(()) Ok(())
} }
@ -430,7 +442,18 @@ pub async fn list_listeners(ctx: Context<'_>) -> Result<(), Error> {
msg_builder.push_line(format!("\t* Actions: `{:?}`", listener.actions)); msg_builder.push_line(format!("\t* Actions: `{:?}`", listener.actions));
} }
ctx.reply(msg_builder.build()).await?; let msg = msg_builder.build();
if msg.len() < MESSAGE_CODE_LIMIT {
ctx.reply(msg).await?;
} else {
ctx.send(
CreateReply::default()
.reply(true)
.attachment(CreateAttachment::bytes(msg.as_bytes(), "listeners.md")),
)
.await?;
}
Ok(()) Ok(())
} }

View File

@ -135,22 +135,24 @@ async fn handle_message(
User::update_social_credit(&data.db, new_message.author.id, social_credit_change)?; User::update_social_credit(&data.db, new_message.author.id, social_credit_change)?;
} }
let trigger_event = TriggerEvent::new( if !new_message.content.starts_with("!") {
new_message.author.id, let trigger_event = TriggerEvent::new(
TriggerType::OnMessage { new_message.author.id,
channel_id: Some(new_message.channel_id), TriggerType::OnMessage {
content: Some(new_message.content.clone()), channel_id: Some(new_message.channel_id),
}, content: Some(new_message.content.clone()),
new_message.id, },
new_message.channel_id, new_message.id,
); new_message.channel_id,
);
match Listener::process_trigger(ctx, data, trigger_event).await { match Listener::process_trigger(ctx, data, trigger_event).await {
Ok(_) => { Ok(_) => {
debug!("Processed message trigger successfully") debug!("Processed message trigger successfully")
} }
Err(err) => { Err(err) => {
error!("Failed to process message trigger: {err}") error!("Failed to process message trigger: {err}")
}
} }
} }
@ -382,6 +384,8 @@ pub async fn run_bot(global_data: GlobalData) {
shop::helmet(), shop::helmet(),
shop::emp(), shop::emp(),
shop::land_mine(), shop::land_mine(),
shop::phrase_canceler(),
shop::nuke(),
transit::cta_bets(), transit::cta_bets(),
voices::list_voices(), voices::list_voices(),
voices::list_words(), voices::list_words(),
@ -417,6 +421,8 @@ pub async fn run_bot(global_data: GlobalData) {
| serenity::GatewayIntents::DIRECT_MESSAGES | serenity::GatewayIntents::DIRECT_MESSAGES
| serenity::GatewayIntents::GUILDS | serenity::GatewayIntents::GUILDS
| serenity::GatewayIntents::MESSAGE_CONTENT | serenity::GatewayIntents::MESSAGE_CONTENT
| serenity::GatewayIntents::GUILD_MEMBERS
| serenity::GatewayIntents::GUILD_PRESENCES
| serenity::GatewayIntents::GUILD_MESSAGE_REACTIONS; | serenity::GatewayIntents::GUILD_MESSAGE_REACTIONS;
let mut client = serenity::ClientBuilder::new(token, intents) let mut client = serenity::ClientBuilder::new(token, intents)

View File

@ -5,6 +5,7 @@ use crate::event_listener::{Action, Expiration, Listener, TriggerType};
use crate::image_manipulation::create_motivation_image; use crate::image_manipulation::create_motivation_image;
use crate::inventory::{InventoryError, ItemData, ItemType, Operation, Target}; use crate::inventory::{InventoryError, ItemData, ItemType, Operation, Target};
use crate::user::{User, UserError}; use crate::user::{User, UserError};
use chrono::{Duration, Utc};
use poise::serenity_prelude::utils::MessageBuilder; use poise::serenity_prelude::utils::MessageBuilder;
use poise::serenity_prelude::{Channel, CreateMessage, UserId}; use poise::serenity_prelude::{Channel, CreateMessage, UserId};
use rand::{Rng, rng}; use rand::{Rng, rng};
@ -54,7 +55,7 @@ pub async fn inventory(ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// 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")]
pub async fn buy( pub async fn buy(
ctx: Context<'_>, ctx: Context<'_>,
@ -90,36 +91,42 @@ pub async fn buy(
Ok(()) Ok(())
} }
/// Protect yourself from the woke mind virus
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn cancel_insurance(ctx: Context<'_>) -> Result<(), Error> { pub async fn cancel_insurance(ctx: Context<'_>) -> Result<(), Error> {
use_item(ctx, ItemType::CancelInsurance, Target::Myself).await?; use_item(ctx, ItemType::CancelInsurance, Target::Myself).await?;
Ok(()) Ok(())
} }
/// Understand the concept of love
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn the_concept_of_love(ctx: Context<'_>) -> Result<(), Error> { pub async fn the_concept_of_love(ctx: Context<'_>) -> Result<(), Error> {
use_item(ctx, ItemType::TheConceptOfLove, Target::Myself).await?; use_item(ctx, ItemType::TheConceptOfLove, Target::Myself).await?;
Ok(()) Ok(())
} }
/// Get a good fortune
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn good_fortune(ctx: Context<'_>, #[rest] _msg: String) -> Result<(), Error> { pub async fn good_fortune(ctx: Context<'_>, #[rest] _msg: String) -> Result<(), Error> {
use_item(ctx, ItemType::TheConceptOfLove, Target::Myself).await?; use_item(ctx, ItemType::TheConceptOfLove, Target::Myself).await?;
Ok(()) Ok(())
} }
/// Check out your sick NFT
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn nft(ctx: Context<'_>) -> Result<(), Error> { pub async fn nft(ctx: Context<'_>) -> Result<(), Error> {
use_item(ctx, ItemType::Nft, Target::Myself).await?; use_item(ctx, ItemType::Nft, Target::Myself).await?;
Ok(()) Ok(())
} }
/// Present proper id
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn license_to_be_horny(ctx: Context<'_>) -> Result<(), Error> { pub async fn license_to_be_horny(ctx: Context<'_>) -> Result<(), Error> {
use_item(ctx, ItemType::LicenseToBeHorny, Target::Myself).await?; use_item(ctx, ItemType::LicenseToBeHorny, Target::Myself).await?;
Ok(()) Ok(())
} }
/// Use a kill gun to kill someone
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn kill_gun( pub async fn kill_gun(
ctx: Context<'_>, ctx: Context<'_>,
@ -129,6 +136,7 @@ pub async fn kill_gun(
Ok(()) Ok(())
} }
/// Use a cancel ray to cancel someone
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn cancel_ray( pub async fn cancel_ray(
ctx: Context<'_>, ctx: Context<'_>,
@ -138,19 +146,22 @@ pub async fn cancel_ray(
Ok(()) Ok(())
} }
/// Use your helmet
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn helmet(ctx: Context<'_>) -> Result<(), Error> { pub async fn helmet(ctx: Context<'_>) -> Result<(), Error> {
use_item(ctx, ItemType::Helmet, Target::Myself).await?; use_item(ctx, ItemType::Helmet, Target::Myself).await?;
Ok(()) Ok(())
} }
/// Deploy a tactical emp
#[poise::command(prefix_command, category = "Shop", guild_only)] #[poise::command(prefix_command, category = "Shop", guild_only)]
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(())
} }
#[poise::command(prefix_command, category = "Shop", guild_only)] /// Deploy a land mine
#[poise::command(prefix_command, category = "Shop")]
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,
@ -183,6 +194,66 @@ pub async fn land_mine(
Ok(()) Ok(())
} }
/// Use your Phrase Canceler
#[poise::command(prefix_command, category = "Shop")]
pub async fn phrase_canceler(
ctx: Context<'_>,
#[description = "Phrase to cancel"]
#[rest]
phrase: String,
) -> Result<(), Error> {
let used_item = use_item(ctx, ItemType::PhraseCanceler, Target::Other).await?;
if used_item {
Listener::add_listener(&ctx.data().db, {
Listener::new(
TriggerType::OnMessage {
channel_id: None,
content: Some(phrase),
},
vec![Action::Cancel { hours: ctx.data().cfg.effect_role_duration as u16}, Action::Speak {msg: "oof, dude. Don't you know the Christopher Columbus connotations of what you just said? Consider yourself canceled.".to_string()}],
1.0,
Expiration::NumberOfTriggers { triggers: 1 },
false
)
})?;
ctx.author()
.id
.direct_message(
ctx,
CreateMessage::default().content("The video essay has been released, now we wait"),
)
.await?;
}
Ok(())
}
/// Drop a nuke on this discord
#[poise::command(prefix_command, category = "Shop", guild_only)]
pub async fn nuke(ctx: Context<'_>) -> Result<(), Error> {
if use_item(ctx, ItemType::Nuke, Target::Everyone).await? {
Listener::add_listener(&ctx.data().db, {
Listener::new(
TriggerType::OnMessage {
channel_id: Some(ctx.channel_id()),
content: None,
},
vec![
Action::Ghoulify { hours: ctx.data().cfg.effect_role_duration as u16 },
Action::Speak { msg: "As the radiation exposure sets in, you notice changes. Your flesh begins to deteriorate. Your voice becomes raspy. You are now a ghoul. https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdjJpcmtwbXRhZml1djd3ZGI3bjhhb3dxYm1vdmVpN2swN2hiZnNzZiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/83uF5ITtkxbUul6gMB/giphy.gif".to_string()}
],
0.5,
Expiration::Time { time: Utc::now() + Duration::days(5) },
false
)
})?;
}
Ok(())
}
pub async fn use_item(ctx: Context<'_>, item: ItemType, target: Target) -> Result<bool, Error> { pub async fn use_item(ctx: Context<'_>, item: ItemType, target: Target) -> Result<bool, Error> {
let target_users: Vec<UserId> = match target { let target_users: Vec<UserId> = match target {
Target::Myself => { Target::Myself => {
@ -303,17 +374,23 @@ pub async fn restock_shop(
.give_item(ItemType::LicenseToBeHorny, rng().random_range(0..=25), None); .give_item(ItemType::LicenseToBeHorny, rng().random_range(0..=25), None);
bot_user bot_user
.inventory .inventory
.give_item(ItemType::KillGun, rng().random_range(1..=3), None); .give_item(ItemType::KillGun, rng().random_range(1..=5), None);
bot_user bot_user
.inventory .inventory
.give_item(ItemType::CancelRay, rng().random_range(1..=3), None); .give_item(ItemType::CancelRay, rng().random_range(1..=5), None);
bot_user bot_user
.inventory .inventory
.give_item(ItemType::Helmet, rng().random_range(1..=3), None); .give_item(ItemType::Helmet, rng().random_range(1..=3), None);
bot_user bot_user
.inventory .inventory
.give_item(ItemType::EMP, rng().random_range(0..=1), None); .give_item(ItemType::EMP, rng().random_range(1..=2), None);
bot_user
.inventory
.give_item(ItemType::PhraseCanceler, rng().random_range(1..=5), None);
bot_user
.inventory
.give_item(ItemType::LandMine, rng().random_range(1..=5), None);
bot_user.inventory.give_item(ItemType::Nuke, 1, None);
loop { loop {
let mut dir = tokio::fs::read_dir(&global_data.cfg.nft_path).await?; let mut dir = tokio::fs::read_dir(&global_data.cfg.nft_path).await?;

View File

@ -96,6 +96,9 @@ pub enum Action {
Cancel { Cancel {
hours: u16, hours: u16,
}, },
Ghoulify {
hours: u16,
},
UpdateSocialCredit { UpdateSocialCredit {
score_diff: i64, score_diff: i64,
}, },
@ -239,6 +242,17 @@ impl Listener {
Action::Speak { msg } => { Action::Speak { msg } => {
trigger_event.channel_id.say(ctx.http(), msg).await?; trigger_event.channel_id.say(ctx.http(), msg).await?;
} }
Action::Ghoulify { hours } => {
User::add_role(
ctx.http(),
&data.db,
trigger_event.triggerer,
data.cfg.guild_id,
UserRole::Ghoul,
Duration::hours(*hours as i64),
)
.await?;
}
} }
} }

View File

@ -1,3 +1,4 @@
use poise::ChoiceParameter;
use poise::serenity_prelude::UserId; use poise::serenity_prelude::UserId;
use poise::serenity_prelude::utils::MessageBuilder; use poise::serenity_prelude::utils::MessageBuilder;
use rand::prelude::IndexedRandom; use rand::prelude::IndexedRandom;
@ -63,8 +64,12 @@ pub enum ItemType {
Helmet, Helmet,
#[name = "EMP"] #[name = "EMP"]
EMP, EMP,
#[name = "EMP"] #[name = "Land Mine"]
LandMine, LandMine,
#[name = "Phrase Canceler"]
PhraseCanceler,
#[name = "Nuke"]
Nuke,
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
@ -89,7 +94,9 @@ impl ItemType {
ItemType::CancelRay => "Used to cancel people. `!use cancel ray @Austin`".to_string(), ItemType::CancelRay => "Used to cancel people. `!use cancel ray @Austin`".to_string(),
ItemType::Helmet => "Automatically used to block being killed".to_string(), ItemType::Helmet => "Automatically used to block being killed".to_string(),
ItemType::EMP => "Disables weapons and defenses".to_string(), ItemType::EMP => "Disables weapons and defenses".to_string(),
ItemType::LandMine => "A land mine you can setup for someone to step on".to_string(), ItemType::LandMine => "A land mine you can set up for someone to step on".to_string(),
ItemType::PhraseCanceler => "Make a term antiquated".to_string(),
ItemType::Nuke => "A nuclear warhead with a vvery large payload".to_string(),
} }
} }
@ -135,6 +142,12 @@ impl ItemType {
ItemType::LandMine => { ItemType::LandMine => {
None None
} }
ItemType::PhraseCanceler => {
None
}
ItemType::Nuke => {
Some("The Flock Enters A New Age https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmdmMWJuamQxZnQxcWlleHJtdXVtY3VyaDZlNHpwZGxobDl4c251ZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/HhTXt43pk1I1W/giphy.gif".to_string())
}
} }
} }
@ -193,6 +206,10 @@ impl FromStr for ItemType {
Ok(ItemType::EMP) Ok(ItemType::EMP)
} else if item.starts_with("landmine") { } else if item.starts_with("landmine") {
Ok(ItemType::LandMine) Ok(ItemType::LandMine)
} else if item.starts_with("phrasecanceler") {
Ok(ItemType::PhraseCanceler)
} else if item.starts_with("nuke") {
Ok(ItemType::Nuke)
} else { } else {
Err(InventoryError::UnkownItem) Err(InventoryError::UnkownItem)
} }
@ -201,20 +218,7 @@ impl FromStr for ItemType {
impl Display for ItemType { impl Display for ItemType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let name = match self { write!(f, "{}", self.name())
ItemType::CancelInsurance => "Cancel Insurance".to_string(),
ItemType::TheConceptOfLove => "The Concept of Love".to_string(),
ItemType::GoodFortune => "Good Fortune".to_string(),
ItemType::Nft => "NFT".to_string(),
ItemType::LicenseToBeHorny => "License to be Horny".to_string(),
ItemType::KillGun => "Kill Gun".to_string(),
ItemType::Helmet => "Helmet".to_string(),
ItemType::CancelRay => "Cancel Ray".to_string(),
ItemType::EMP => "EMP".to_string(),
ItemType::LandMine => "Land Mine".to_string(),
};
write!(f, "{name}")
} }
} }
@ -243,11 +247,13 @@ impl InventorySlot {
ItemType::GoodFortune => 75, ItemType::GoodFortune => 75,
ItemType::Nft => 250, ItemType::Nft => 250,
ItemType::LicenseToBeHorny => 250, ItemType::LicenseToBeHorny => 250,
ItemType::KillGun => 2_000, ItemType::KillGun => 1_000,
ItemType::Helmet => 10_000, ItemType::Helmet => 10_000,
ItemType::CancelRay => 2_000, ItemType::CancelRay => 1_000,
ItemType::EMP => 50_000, ItemType::EMP => 50_000,
ItemType::LandMine => 3_000, ItemType::LandMine => 3_000,
ItemType::PhraseCanceler => 2_000,
ItemType::Nuke => 100_000,
} }
} }

View File

@ -346,7 +346,7 @@ impl User {
return Ok(Some("Sorry this was a pump and dump".to_string())); return Ok(Some("Sorry this was a pump and dump".to_string()));
} }
} }
ItemType::KillGun => { ItemType::KillGun | ItemType::Nuke => {
Self::add_role( Self::add_role(
ctx.http(), ctx.http(),
&ctx.data().db, &ctx.data().db,