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
2026-07-05 12:13:41 +02:00
parent 24205326bf
commit afa64fd55b
6 changed files with 94 additions and 23 deletions
+1
View File
@@ -14,6 +14,7 @@ dotenv = "0.15.0"
exmex = "0.21.0"
f128 = "0.2.9"
f256 = "0.11.2"
num-bigint = "0.5.1"
num-traits = "0.2.19"
poise = "0.6.2"
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 tracing::{debug, trace, info};
use tracing::{debug, trace, warn};
use crate::shared_functions::trace_message;
use regex::Regex;
use std::sync::LazyLock;
use std::str::FromStr;
use std::fmt::{Display, Formatter};
use f128::f128;
use exmex::{Express, FlatEx, MakeOperators, Operator, BinOp};
use exmex::{Express, MakeOperators, Operator, BinOp};
use num_traits::float::Float;
#[derive(Clone, Copy, Debug)]
@@ -142,22 +142,27 @@ pub async fn calc(ctx: Context<'_>,
let response_message = ctx.say(msg).await?;
let start_time = std::time::Instant::now();
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);
let value = match precision {
Some(Precision::Bits32) => exmex::eval_str::<f32>(&processed_expr).map_err(|err| err.to_string())?.to_string(),
Some(Precision::Bits64) => exmex::eval_str::<f64>(&processed_expr).map_err(|err| err.to_string())?.to_string(),
Some(Precision::Bits128) => {
let expr = exmex::FlatEx::<F128, F128OpsFactory>::parse(&processed_expr).map_err(|err| err.to_string())?;
expr.eval(&[]).map_err(|err| err.to_string())?.to_string()
},
Some(Precision::Bits256) => {
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(),
let value = match evaluate_expression(processed_expr, &precision) {
Ok(v) => v,
Err(err_msg) => {
let error_msg = format!("Failed to parse or evaluate expression: `{}`", err_msg);
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);
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(());
}
};
trace!("Result: {}", value);
let end_time = std::time::Instant::now();
trace!("Saving end time: {:?}", end_time);
let duration = end_time - start_time;
@@ -169,10 +174,17 @@ pub async fn calc(ctx: Context<'_>,
Some(Precision::Bits256) => "256-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;
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(())
}
@@ -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?;
}
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::shared_functions::get_all_admin_users;
use poise::serenity_prelude as sere;
use tracing::{error, warn, info, debug, trace};
use tracing::{error, info, debug, trace};
#[derive(Debug, poise::ChoiceParameter)]
pub enum AdminOperationType {
+4 -4
View File
@@ -1,7 +1,7 @@
use crate::{Context, Error};
use crate::shared_functions::get_all_admin_users;
use poise::serenity_prelude as sere;
use tracing::{error, warn, info, debug, trace};
use tracing::{error, info, debug, trace};
#[derive(Debug, poise::ChoiceParameter)]
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;
match query {
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?;
}
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;
match query {
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?;
}
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;
match query {
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?;
}
Err(e) => {
+2 -1
View File
@@ -1,5 +1,5 @@
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) {
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) {
if let Err(e) = member.add_role(&ctx.http, sere::RoleId::new(role_id)).await {
error!("Error adding role to member: {:?}", e);
return;
}
debug!("Role {} added to user {} in guild {}", role_id, member.user.name, member.guild_id);
}