diff --git a/Cargo.lock b/Cargo.lock index b31b96e..9cecc4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,7 +1093,7 @@ dependencies = [ [[package]] name = "fren" -version = "1.4.0" +version = "1.5.0" dependencies = [ "axum 0.8.1", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 29fd9ed..913311f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fren" -version = "1.4.0" +version = "1.5.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/discord/admin.rs b/src/discord/admin.rs index 9aa6a83..09c7f41 100644 --- a/src/discord/admin.rs +++ b/src/discord/admin.rs @@ -7,8 +7,10 @@ use crate::models::lil_fren::{ draw_resonance_cascade, draw_sick, draw_sleep, draw_standing, draw_tax_fraud, }; use crate::models::managed_roles::ManagedRole; +use crate::models::social_credit::SocialCreditPhrase; use crate::models::task::Task; use crate::user::User; +use j_db::model::JdbModel; use json::JsonValue; use log::info; use poise::serenity_prelude::{ @@ -263,3 +265,66 @@ pub async fn remove_role( Ok(()) } + +/// Add a phrase to check for social credit +#[poise::command(prefix_command, category = "Admin", check = "is_admin")] +pub async fn add_social_credit_phrase( + ctx: Context<'_>, + #[description = "Phrase to look for, wrap in \"s if you want multiple words"] phrase: String, + #[description = "Upper limit of points to add/take away"] upper_limit: i64, + #[description = "Lower limit of points to take away"] lower_limit: i64, +) -> Result<(), Error> { + let social_credit_phrase = + SocialCreditPhrase::add_new_phrase(&ctx.data().db, phrase, upper_limit, lower_limit)?; + + ctx.reply(format!( + "Added new phrase '{}' with id `{}`", + social_credit_phrase.phrase, + social_credit_phrase.id().unwrap() + )) + .await?; + + Ok(()) +} + +/// Remove a social credit phrase +#[poise::command(prefix_command, category = "Admin", check = "is_admin")] +pub async fn remove_social_credit_phrase( + ctx: Context<'_>, + #[description = "ID of the phrase to remove"] id: u64, +) -> Result<(), Error> { + let social_credit_phrase = ctx.data().db.remove::(id)?; + + ctx.reply(format!( + "Removed phrase '{}' with id `{}`", + social_credit_phrase.phrase, + social_credit_phrase.id().unwrap() + )) + .await?; + + Ok(()) +} + +/// List social credit phrases +#[poise::command(prefix_command, category = "Admin", check = "is_admin")] +pub async fn list_social_credit_phrases(ctx: Context<'_>) -> Result<(), Error> { + let social_credit_phrase = SocialCreditPhrase::get_phrases(&ctx.data().db)?; + + let mut list = MessageBuilder::new(); + + list.push_line("The following phrases are EXAMINED for their value for the state:"); + + for phrase in &social_credit_phrase { + list.push_line(format!( + "* id={} phrase={} upper_bound={} lower_bound={}", + phrase.id().unwrap(), + phrase.phrase, + phrase.upper_bound, + phrase.lower_bound + )); + } + + ctx.reply(list.build()).await?; + + Ok(()) +} diff --git a/src/discord/fren_coin.rs b/src/discord/fren_coin.rs index 87de4ba..26f068d 100644 --- a/src/discord/fren_coin.rs +++ b/src/discord/fren_coin.rs @@ -64,6 +64,51 @@ pub async fn gift( Ok(()) } +/// Find your social worth in the eyes of the glorious part +#[poise::command(prefix_command, category = "Fren Coin")] +pub async fn social_credit( + ctx: Context<'_>, + #[description = "User to get the social credit of"] user: Option, +) -> Result<(), Error> { + let user_id = user.map(|u| u.id).unwrap_or(ctx.author().id); + + let user = User::get_user(&ctx.data().db, user_id)?; + + let msg = if user.social_credit < -2000 { + "||REDACTED||" + } else if user.social_credit < -1000 { + "Very bad person they are!" + } else if user.social_credit < -500 { + "Likely a capitalist!" + } else if user.social_credit < -100 { + "Tread carefully..." + } else if user.social_credit < 0 { + "There is always a chance, before its too late..." + } else if user.social_credit < 100 { + "Okay, but be cautious with your words." + } else if user.social_credit < 500 { + "There is only winners and losers." + } else if user.social_credit < 1000 { + "On the up and up!" + } else if user.social_credit < 2000 { + "A great member of our glorious society!" + } else if user.social_credit >= 2000 { + "Perfect in every way, may your deeds shine light in our world!!!" + } else { + "There is no god for you." + }; + + ctx.reply(format!( + "{}'s social credit is {}. {}", + user_id.mention(), + user.social_credit, + msg + )) + .await?; + + Ok(()) +} + pub async fn give_coin( db: &Database, user: UserId, diff --git a/src/discord/mod.rs b/src/discord/mod.rs index 5a1b40b..2be72dd 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -18,12 +18,14 @@ use crate::discord::fren_coin::give_coin; use crate::discord::joke::random; use crate::error::Error; use crate::models::lil_fren::lil_fren_task; +use crate::models::social_credit::SocialCreditPhrase; use crate::models::task::Task; -use log::{error, info}; +use crate::user::User; +use log::{debug, error, info}; use poise::serenity_prelude::{GuildId, Http, Message, MessageBuilder, ReactionType, RoleId}; use poise::{FrameworkOptions, find_command, serenity_prelude as serenity}; use rand::prelude::IteratorRandom; -use rand::rng; +use rand::{Rng, rng}; use songbird::SerenityInit; use std::sync::Arc; use std::time::Duration; @@ -81,6 +83,18 @@ async fn handle_message( data: &Arc, new_message: &Message, ) -> Result<(), Error> { + if new_message.content.eq_ignore_ascii_case("yes") + || new_message.content.eq_ignore_ascii_case("mhmm") + { + let mut bot_state = data.bot_state.lock().await; + if let Some(u) = bot_state.accepted_nsfw { + if new_message.author.id == u { + new_message.reply(&ctx.http, "||https://cdn.discordapp.com/attachments/614891432079130625/1041545254362423368/unknown.png||").await.unwrap(); + bot_state.accepted_nsfw = None; + } + } + } + if new_message.content.to_lowercase().contains("good bot") { let recv_coin = give_coin(&data.db, new_message.author.id, 0.50, 25).await?; @@ -113,6 +127,18 @@ async fn handle_message( give_coin(&data.db, new_message.author.id, 0.05, 10).await?; + if let Some(phrase) = SocialCreditPhrase::check_if_match(&data.db, &new_message.content)? { + debug!( + "{} matched phrase '{}' for social credit checking", + new_message.author.name, phrase.phrase + ); + + let social_credit_change = + rand::rng().random_range(phrase.lower_bound..=phrase.upper_bound); + + User::update_social_credit(&data.db, new_message.author.id, social_credit_change)?; + } + Ok(()) } @@ -275,6 +301,9 @@ pub async fn run_bot(global_data: GlobalData) { admin::list_tasks(), admin::add_role(), admin::remove_role(), + admin::add_social_credit_phrase(), + admin::remove_social_credit_phrase(), + admin::list_social_credit_phrases(), album::add_image(), album::list_albums(), birthday::add_birthday(), @@ -289,6 +318,7 @@ pub async fn run_bot(global_data: GlobalData) { emoji_race::start_race(), fren_coin::balance(), fren_coin::gift(), + fren_coin::social_credit(), joke::add_random(), joke::dad_joke(), joke::bad_apple(), diff --git a/src/migrations/migration_6_add_social_credit.rs b/src/migrations/migration_6_add_social_credit.rs new file mode 100644 index 0000000..3f346ef --- /dev/null +++ b/src/migrations/migration_6_add_social_credit.rs @@ -0,0 +1,30 @@ +use j_db::database::Database; +use j_db::migration::Migration; + +pub struct Migration6AddSocialCredit {} + +impl Migration for Migration6AddSocialCredit { + fn up(&self, db: &Database) -> j_db::error::Result<()> { + let tree = db.db.open_tree("Users")?; + + for user_entry in tree.iter() { + let (id, user) = user_entry?; + + let mut user = json::parse(std::str::from_utf8(&user).unwrap()).unwrap(); + + user["social_credit"] = json::JsonValue::Number(json::number::Number::from(1000)); + + tree.insert(id, user.to_string().as_bytes())?; + } + + Ok(()) + } + + fn down(&self, _db: &Database) -> j_db::error::Result<()> { + Ok(()) + } + + fn version(&self) -> u64 { + 6 + } +} diff --git a/src/migrations/mod.rs b/src/migrations/mod.rs index 8a8170e..35a7425 100644 --- a/src/migrations/mod.rs +++ b/src/migrations/mod.rs @@ -1,5 +1,6 @@ use crate::migrations::migration_4_update_random::Migration4UpdateRandoms; use crate::migrations::migration_5_update_motivation::Migration5UpdateMotivation; +use crate::migrations::migration_6_add_social_credit::Migration6AddSocialCredit; use crate::migrations::migration2_remove_imgur::Migration2RemoveImgur; use crate::migrations::migration3_remove_img::Migration3RemoveImage; use j_db::database::Database; @@ -10,8 +11,9 @@ mod migration2_remove_imgur; mod migration3_remove_img; mod migration_4_update_random; mod migration_5_update_motivation; +mod migration_6_add_social_credit; -pub const CURRENT_DB_VERSION: u64 = 5; +pub const CURRENT_DB_VERSION: u64 = 6; #[allow(clippy::single_match)] pub fn do_migration(db: &Database) { @@ -48,6 +50,12 @@ pub fn do_migration(db: &Database) { Direction::Up, ) .unwrap(), + 6 => migration::do_migration::( + db, + Migration6AddSocialCredit {}, + Direction::Up, + ) + .unwrap(), _ => {} } } diff --git a/src/models/mod.rs b/src/models/mod.rs index 9f82883..6c3a3ff 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -6,4 +6,5 @@ pub mod managed_roles; pub mod motivation; pub mod race; pub mod random; +pub mod social_credit; pub mod task; diff --git a/src/models/social_credit.rs b/src/models/social_credit.rs new file mode 100644 index 0000000..02d1644 --- /dev/null +++ b/src/models/social_credit.rs @@ -0,0 +1,62 @@ +use crate::error::Error; +use j_db::database::Database; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct SocialCreditPhrase { + id: Option, + pub phrase: String, + pub lower_bound: i64, + pub upper_bound: i64, +} + +impl j_db::model::JdbModel for SocialCreditPhrase { + fn id(&self) -> Option { + self.id + } + + fn set_id(&mut self, id: u64) { + self.id = Some(id) + } + + fn tree() -> String { + "SocialCreditPhrase".to_string() + } + + fn check_unique(&self, other: &Self) -> bool { + other.phrase.to_lowercase() == self.phrase.to_lowercase() + } +} + +impl SocialCreditPhrase { + pub fn check_if_match(db: &Database, msg: &str) -> Result, Error> { + let lowercase_msg = msg.to_lowercase(); + let matches = db + .filter(|_, phrase: &Self| lowercase_msg.contains(&phrase.phrase.to_lowercase()))? + .next(); + + Ok(matches) + } + + pub fn add_new_phrase( + db: &Database, + phrase: String, + lower_bound: i64, + upper_bound: i64, + ) -> Result { + let new_phrase = db.insert(Self { + id: None, + phrase, + lower_bound, + upper_bound, + })?; + + Ok(new_phrase) + } + + pub fn get_phrases(db: &Database) -> Result, Error> { + let phrases = db.filter(|_, _phrase: &Self| true)?.collect(); + + Ok(phrases) + } +} diff --git a/src/user/mod.rs b/src/user/mod.rs index 0343bf2..3bd80e7 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -35,6 +35,7 @@ pub struct User { pub id: Option, pub user_id: UserId, pub coin_count: i64, + pub social_credit: i64, #[serde(default)] pub inventory: InventoryManager, } @@ -46,6 +47,7 @@ impl User { id: None, user_id, coin_count: 0, + social_credit: 1000, inventory: Default::default(), } } @@ -201,6 +203,15 @@ impl User { db.insert::(buyer)?; Ok(()) } + + pub fn update_social_credit(db: &Database, user: UserId, amount: i64) -> Result<(), Error> { + let mut user = User::get_user(db, user)?; + + user.social_credit += amount; + + db.insert::(user)?; + Ok(()) + } } impl JdbModel for User {