improve calculator, improve logging, fix auto role
Run cargo test / Run tests (push) Successful in 3m25s
Build Docker Package / build (push) Successful in 6m6s

This commit is contained in:
Elias Wendland committed 2026-07-05 12:13:41 +02:00
1 parent 24205326bf
commit afa64fd55b
6 files changed
+94 -23

No files matched your search

+1
View File
@@ -14,6 +14,7 @@ dotenv = "0.15.0"
exmex = "0.21.0" exmex = "0.21.0"
f128 = "0.2.9" f128 = "0.2.9"
f256 = "0.11.2" f256 = "0.11.2"
num-bigint = "0.5.1"
num-traits = "0.2.19" num-traits = "0.2.19"
poise = "0.6.2" poise = "0.6.2"
regex = "1.12.4" regex = "1.12.4"
@@ -0,0 +1 @@
ALTER TABLE auto_roles ADD CONSTRAINT auto_roles_guild_id_key UNIQUE (guild_id);
+85 -17
View File
@@ -1,12 +1,12 @@
use crate::{Context, Error}; use crate::{Context, Error};
use tracing::{debug, trace, info}; use tracing::{debug, trace, warn};
use crate::shared_functions::trace_message; use crate::shared_functions::trace_message;
use regex::Regex; use regex::Regex;
use std::sync::LazyLock; use std::sync::LazyLock;
use std::str::FromStr; use std::str::FromStr;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use f128::f128; use f128::f128;
use exmex::{Express, FlatEx, MakeOperators, Operator, BinOp}; use exmex::{Express, MakeOperators, Operator, BinOp};
use num_traits::float::Float; use num_traits::float::Float;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@@ -142,22 +142,27 @@ pub async fn calc(ctx: Context<'_>,
let response_message = ctx.say(msg).await?; let response_message = ctx.say(msg).await?;
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
trace!("Saving start time: {:?}", start_time); trace!("Saving start time: {:?}", start_time);
let processed_expr = FIND_SCI_NOTATION_RE.replace_all(&expression, "($1 * 10^($2))"); if expression.contains('=') {
let error_msg = format!("Invalid expression: {}\nYou are not allowed to have an equal sign in the expression.", expression);
debug!("Invalid expression detected: {}", expression);
trace_message(error_msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await;
edit_response_message(&response_message, ctx, error_msg, false).await?;
return Ok(());
}
let processed_expr : String = FIND_SCI_NOTATION_RE.replace_all(&expression, "$1 * 10^($2)").to_string();
trace!("Processed expression: {}", processed_expr); trace!("Processed expression: {}", processed_expr);
let value = match precision { let value = match evaluate_expression(processed_expr, &precision) {
Some(Precision::Bits32) => exmex::eval_str::<f32>(&processed_expr).map_err(|err| err.to_string())?.to_string(), Ok(v) => v,
Some(Precision::Bits64) => exmex::eval_str::<f64>(&processed_expr).map_err(|err| err.to_string())?.to_string(), Err(err_msg) => {
Some(Precision::Bits128) => { let error_msg = format!("Failed to parse or evaluate expression: `{}`", err_msg);
let expr = exmex::FlatEx::<F128, F128OpsFactory>::parse(&processed_expr).map_err(|err| err.to_string())?; warn!("Failed to parse or evaluate expression: `{}` in guild {} channel {} by {}", err_msg, ctx.guild_id().unwrap().get(), ctx.channel_id().get(), ctx.author().name);
expr.eval(&[]).map_err(|err| err.to_string())?.to_string() trace_message(error_msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await;
}, edit_response_message(&response_message, ctx, error_msg, false).await?;
Some(Precision::Bits256) => { return Ok(());
let expr = exmex::FlatEx::<f256::f256, F256OpsFactory>::parse(&processed_expr).map_err(|err| err.to_string())?; }
expr.eval(&[]).map_err(|err| err.to_string())?.to_string()
},
None => exmex::eval_str::<f64>(&processed_expr).map_err(|err| err.to_string())?.to_string(),
}; };
trace!("Result: {}", value);
let end_time = std::time::Instant::now(); let end_time = std::time::Instant::now();
trace!("Saving end time: {:?}", end_time); trace!("Saving end time: {:?}", end_time);
let duration = end_time - start_time; let duration = end_time - start_time;
@@ -169,10 +174,17 @@ pub async fn calc(ctx: Context<'_>,
Some(Precision::Bits256) => "256-bit".to_string(), Some(Precision::Bits256) => "256-bit".to_string(),
None => "64-bit".to_string(), None => "64-bit".to_string(),
}; };
let msg = format!("{} = {} \n-# Precision: {} Compute time : {:?}", expression, value, precision_str, duration); // Turn to scientifc notation if number has more than 30 digits
let value_string = if value.len() > 30 {
to_scientific_notation(value)
} else {
value
};
let msg = format!("{} = {} \n-# Precision: {} Compute time : {:?}", expression, value_string, precision_str, duration);
trace_message(msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await; trace_message(msg.clone(), ctx.channel_id().to_string(), ctx.guild_id().unwrap().to_string()).await;
edit_response_message(&response_message, ctx, msg, false).await?; edit_response_message(&response_message, ctx, msg, false).await?;
debug!("Calculation perfomed for {} with result {} in {:?} by {}", expression, value, duration, ctx.author().name); debug!("Calculation perfomed for {} with result {} in {:?} by {}", expression, value_string, duration, ctx.author().name);
Ok(()) Ok(())
} }
@@ -183,4 +195,60 @@ async fn edit_response_message<'a>(response_message: &poise::ReplyHandle<'a>, ct
response_message.edit(ctx, poise::CreateReply::default().content(content)).await?; response_message.edit(ctx, poise::CreateReply::default().content(content)).await?;
} }
Ok(()) Ok(())
}
fn to_scientific_notation(value: String) -> String {
trace!("Converting {} to scientific notation", value);
if value.contains('e') || value.contains('E') {
trace!("Already in scientific notation");
return value;
}
let mut is_negative = false;
let mut val_str = value.as_str();
if val_str.starts_with('-') {
is_negative = true;
val_str = &val_str[1..];
}
let parts: Vec<&str> = val_str.split('.').collect();
let integer_part = parts[0];
let fractional_part = if parts.len() > 1 { parts[1] } else { "" };
if integer_part.len() <= 1 {
return value;
}
let first_digit = &integer_part[0..1];
let rest_integer = &integer_part[1..];
let exponent = integer_part.len() - 1;
let mut fractional_combined = format!("{}{}", rest_integer, fractional_part);
fractional_combined = fractional_combined.trim_end_matches('0').to_string();
let sign = if is_negative { "-" } else { "" };
if fractional_combined.is_empty() {
format!("{}{}e{}", sign, first_digit, exponent)
} else {
format!("{}{}.{}e{}", sign, first_digit, fractional_combined, exponent)
}
}
fn evaluate_expression(expr: String, precision: &Option<Precision>) -> Result<String, String> {
let value = match precision {
Some(Precision::Bits32) => exmex::eval_str::<f32>(&expr).map_err(|err| err.to_string())?.to_string(),
Some(Precision::Bits64) => exmex::eval_str::<f64>(&expr).map_err(|err| err.to_string())?.to_string(),
Some(Precision::Bits128) => {
let parsed_expr = exmex::FlatEx::<F128, F128OpsFactory>::parse(&expr).map_err(|err| err.to_string())?;
parsed_expr.eval(&[]).map_err(|err| err.to_string())?.to_string()
},
Some(Precision::Bits256) => {
let parsed_expr = exmex::FlatEx::<f256::f256, F256OpsFactory>::parse(&expr).map_err(|err| err.to_string())?;
parsed_expr.eval(&[]).map_err(|err| err.to_string())?.to_string()
},
None => exmex::eval_str::<f64>(&expr).map_err(|err| err.to_string())?.to_string(),
};
Ok(value)
} }
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::{Context, Error}; use crate::{Context, Error};
use crate::shared_functions::get_all_admin_users; use crate::shared_functions::get_all_admin_users;
use poise::serenity_prelude as sere; use poise::serenity_prelude as sere;
use tracing::{error, warn, info, debug, trace}; use tracing::{error, info, debug, trace};
#[derive(Debug, poise::ChoiceParameter)] #[derive(Debug, poise::ChoiceParameter)]
pub enum AdminOperationType { pub enum AdminOperationType {
+4 -4
View File
@@ -1,7 +1,7 @@
use crate::{Context, Error}; use crate::{Context, Error};
use crate::shared_functions::get_all_admin_users; use crate::shared_functions::get_all_admin_users;
use poise::serenity_prelude as sere; use poise::serenity_prelude as sere;
use tracing::{error, warn, info, debug, trace}; use tracing::{error, info, debug, trace};
#[derive(Debug, poise::ChoiceParameter)] #[derive(Debug, poise::ChoiceParameter)]
pub enum AutoRoleOperationType { pub enum AutoRoleOperationType {
@@ -97,7 +97,7 @@ async fn enable_auto_role(ctx: Context<'_>, response_message: &poise::ReplyHandl
let query = sqlx::query!("UPDATE auto_roles SET enabled = true WHERE guild_id = $1 AND role_id = $2;", ctx.guild_id().unwrap().get() as i64, role.id.get() as i64).execute(&ctx.data().pool).await; let query = sqlx::query!("UPDATE auto_roles SET enabled = true WHERE guild_id = $1 AND role_id = $2;", ctx.guild_id().unwrap().get() as i64, role.id.get() as i64).execute(&ctx.data().pool).await;
match query { match query {
Ok(_) => { Ok(_) => {
info!("Enabled auto role {} in guild {}", role.name, ctx.guild_id().unwrap().get()); debug!("Enabled auto role {} in guild {}", role.name, ctx.guild_id().unwrap().get());
edit_response_message(response_message, ctx, format!("Enabled auto role: <@&{}>", role.id.get()), false).await?; edit_response_message(response_message, ctx, format!("Enabled auto role: <@&{}>", role.id.get()), false).await?;
} }
Err(e) => { Err(e) => {
@@ -137,7 +137,7 @@ async fn set_auto_role(ctx: Context<'_>, response_message: &poise::ReplyHandle<'
let query = sqlx::query!("INSERT INTO auto_roles (guild_id, role_id, enabled) VALUES ($1, $2, false) ON CONFLICT (guild_id) DO UPDATE SET role_id = $2;", ctx.guild_id().unwrap().get() as i64, role.id.get() as i64).execute(&ctx.data().pool).await; let query = sqlx::query!("INSERT INTO auto_roles (guild_id, role_id, enabled) VALUES ($1, $2, false) ON CONFLICT (guild_id) DO UPDATE SET role_id = $2;", ctx.guild_id().unwrap().get() as i64, role.id.get() as i64).execute(&ctx.data().pool).await;
match query { match query {
Ok(_) => { Ok(_) => {
info!("Set {} as auto role in guild {}", role.name, ctx.guild_id().unwrap().get()); debug!("Set {} as auto role in guild {}", role.name, ctx.guild_id().unwrap().get());
edit_response_message(response_message, ctx, format!("Set {} as auto role", role.name), false).await?; edit_response_message(response_message, ctx, format!("Set {} as auto role", role.name), false).await?;
} }
Err(e) => { Err(e) => {
@@ -165,7 +165,7 @@ async fn disable_auto_role(ctx: Context<'_>, response_message: &poise::ReplyHand
let query = sqlx::query!("UPDATE auto_roles SET enabled = false WHERE guild_id = $1 AND role_id = $2;", ctx.guild_id().unwrap().get() as i64, role.id.get() as i64).execute(&ctx.data().pool).await; let query = sqlx::query!("UPDATE auto_roles SET enabled = false WHERE guild_id = $1 AND role_id = $2;", ctx.guild_id().unwrap().get() as i64, role.id.get() as i64).execute(&ctx.data().pool).await;
match query { match query {
Ok(_) => { Ok(_) => {
info!("Disabled auto role {} in guild {}", role.name, ctx.guild_id().unwrap().get()); debug!("Disabled auto role {} in guild {}", role.name, ctx.guild_id().unwrap().get());
edit_response_message(response_message, ctx, format!("Disabled auto role: {}", role.name), false).await?; edit_response_message(response_message, ctx, format!("Disabled auto role: {}", role.name), false).await?;
} }
Err(e) => { Err(e) => {
+2 -1
View File
@@ -1,5 +1,5 @@
use poise::serenity_prelude as sere; use poise::serenity_prelude as sere;
use tracing::{error, warn, info, debug, trace}; use tracing::{error, debug, trace};
pub async fn user_join(ctx: &sere::Context, new_member: &sere::Member, pool: &sqlx::PgPool) { pub async fn user_join(ctx: &sere::Context, new_member: &sere::Member, pool: &sqlx::PgPool) {
trace!("User joined event handler called for guild {}", new_member.guild_id); trace!("User joined event handler called for guild {}", new_member.guild_id);
@@ -35,6 +35,7 @@ pub async fn user_join(ctx: &sere::Context, new_member: &sere::Member, pool: &sq
async fn give_role_to_user(ctx: &sere::Context, member: &sere::Member, role_id: u64) { async fn give_role_to_user(ctx: &sere::Context, member: &sere::Member, role_id: u64) {
if let Err(e) = member.add_role(&ctx.http, sere::RoleId::new(role_id)).await { if let Err(e) = member.add_role(&ctx.http, sere::RoleId::new(role_id)).await {
error!("Error adding role to member: {:?}", e); error!("Error adding role to member: {:?}", e);
return;
} }
debug!("Role {} added to user {} in guild {}", role_id, member.user.name, member.guild_id); debug!("Role {} added to user {} in guild {}", role_id, member.user.name, member.guild_id);
} }