Add social credit system

This commit is contained in:
Joey Hines 2025-04-20 20:16:28 -06:00
parent 32fa1a11d1
commit 6b2d4641cc
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
10 changed files with 257 additions and 5 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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::<SocialCreditPhrase>(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(())
}

View File

@ -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<poise::serenity_prelude::User>,
) -> 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,

View File

@ -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<GlobalData>,
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(),

View File

@ -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
}
}

View File

@ -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::<Migration6AddSocialCredit>(
db,
Migration6AddSocialCredit {},
Direction::Up,
)
.unwrap(),
_ => {}
}
}

View File

@ -6,4 +6,5 @@ pub mod managed_roles;
pub mod motivation;
pub mod race;
pub mod random;
pub mod social_credit;
pub mod task;

View File

@ -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<u64>,
pub phrase: String,
pub lower_bound: i64,
pub upper_bound: i64,
}
impl j_db::model::JdbModel for SocialCreditPhrase {
fn id(&self) -> Option<u64> {
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<Option<Self>, 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<Self, Error> {
let new_phrase = db.insert(Self {
id: None,
phrase,
lower_bound,
upper_bound,
})?;
Ok(new_phrase)
}
pub fn get_phrases(db: &Database) -> Result<Vec<Self>, Error> {
let phrases = db.filter(|_, _phrase: &Self| true)?.collect();
Ok(phrases)
}
}

View File

@ -35,6 +35,7 @@ pub struct User {
pub id: Option<u64>,
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::<User>(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>(user)?;
Ok(())
}
}
impl JdbModel for User {