diff --git a/.gitignore b/.gitignore index bfcd5e3..5c11875 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ /target /data/Secrets.toml /data/rustbot.sqlite +.env +.idea/ +.sqlx/ *.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..80d1f5a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + - repo: local + hooks: + - id: rustfmt + name: rustfmt + entry: bash -c 'cargo fmt --all -- --check || (echo "Code is not formatted. Run, cargo fmt --all" && exit 1)' + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] + + - id: clippy + name: clippy + entry: bash -c 'cargo clippy --all-targets --all-features -- -D warnings || exit 1' + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] diff --git a/Cargo.lock b/Cargo.lock index a6d125a..67d5236 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "RustBot" -version = "0.4.5" +version = "0.4.6" dependencies = [ "chrono", "poise", diff --git a/Cargo.toml b/Cargo.toml index 0bd610c..39cd070 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,31 @@ [package] name = "RustBot" -version = "0.4.5" +version = "0.4.6" authors = ["Tim Hillier tim.r.hillier@gmail.com"] edition = "2024" [dependencies] -serenity = { version = "0.12.4", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache", "standard_framework", "framework", "utils", "voice"] } +serenity = { version = "0.12.4", default-features = false, features = [ + "client", + "gateway", + "rustls_backend", + "model", + "cache", + "standard_framework", + "framework", + "utils", + "voice", +] } tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] } serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.145" toml = "0.9.8" rand = "0.9.2" -sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "tls-rustls", "sqlite"] } +sqlx = { version = "0.8.6", features = [ + "runtime-tokio-rustls", + "tls-rustls", + "sqlite", +] } poise = "0.6.1" thousands = "0.2.0" quickchart-rs = { version = "0.1.1" } diff --git a/README.md b/README.md index 5c65ff3..5b356bf 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ All commands use the `!` prefix. ### Fun Commands -- **`!smash`** - Returns a random "Smash" or "Pass" response. Used as a reply to messages. -- **`!judge`** - Judges a post by adding a random +2 or -2 reaction. Must be used as a reply to another message. Prevents duplicate judgments on the same post. *Note: Marked for rework.* +- **`!smash`** - Returns a random "Smash" or "Pass" response. Used as a reply to messages. +- **`!judge`** - Judges a post by adding a random +2 or -2 reaction. Must be used as a reply to another message. Prevents duplicate judgments on the same post. _Note: Marked for rework._ ### Utility Commands @@ -52,3 +52,36 @@ All commands use the `!` prefix. - **Leaderboards**: Track top performers in the community - **Migration Support**: Database migrations managed through sqlx-cli +## Development + +### Pre-commit Hooks + +This project uses pre-commit hooks to ensure code quality before commits. To set up: + +1. Install pre-commit (requires Python): + + ```bash + pip install pre-commit + ``` + +2. Install the git hooks: + + ```bash + pre-commit install + ``` + +3. The hooks will now run automatically on every commit, checking: + - **rustfmt**: Code formatting with `cargo fmt` + - **clippy**: Linting with `cargo clippy` + +To manually run the hooks: + +```bash +pre-commit run --all-files +``` + +To skip hooks for a single commit (not recommended): + +```bash +git commit --no-verify +``` diff --git a/src/bot_types.rs b/src/bot_types.rs index 7efd1c1..77aa178 100644 --- a/src/bot_types.rs +++ b/src/bot_types.rs @@ -1,3 +1,3 @@ -pub struct Data{} +pub struct Data {} pub type Error = Box; -pub type _Context<'a> = poise::Context<'a, Data, Error>; \ No newline at end of file +pub type _Context<'a> = poise::Context<'a, Data, Error>; diff --git a/src/bot_utils.rs b/src/bot_utils.rs index dafa0ec..42fc93f 100644 --- a/src/bot_utils.rs +++ b/src/bot_utils.rs @@ -4,7 +4,6 @@ use rand::Rng; use serde::Deserialize; use sqlx::{Pool, Sqlite}; use std::fs; -use toml; #[derive(Debug, Deserialize)] struct SecretsToml { @@ -16,13 +15,12 @@ struct SecretsToml { } pub fn get_toml() -> String { - let toml_str = fs::read_to_string("data/Secrets.toml").expect("Failed to read TOML"); - return toml_str; + fs::read_to_string("data/Secrets.toml").expect("Failed to read TOML") } pub fn get_secret() -> String { let toml_str = get_toml(); let secrets_toml: SecretsToml = toml::from_str(&toml_str).expect("Failed to decode toml"); - return secrets_toml.discord_token; + secrets_toml.discord_token } pub fn get_env() -> String { @@ -32,7 +30,7 @@ pub fn get_env() -> String { if environment.is_empty() { return String::from("testing"); } - return environment; + environment } pub fn is_bot(id: String) -> bool { @@ -46,18 +44,12 @@ pub fn is_bot(id: String) -> bool { pub fn get_random_bool(prob: f64) -> bool { let mut rng = rand::rng(); - return rng.random_bool(prob); -} - -#[allow(dead_code)] -pub fn get_random_number() -> i32 { - let mut rng = rand::thread_rng(); - return rng.gen_range(0..999); + rng.random_bool(prob) } // A connection to the database. pub async fn connect_to_database() -> Pool { - let database = sqlx::sqlite::SqlitePoolOptions::new() + sqlx::sqlite::SqlitePoolOptions::new() .max_connections(5) .connect_with( sqlx::sqlite::SqliteConnectOptions::new() @@ -65,9 +57,7 @@ pub async fn connect_to_database() -> Pool { .create_if_missing(true), ) .await - .expect("Couldn't Connect to database."); - - return database; + .expect("Couldn't Connect to database.") } pub async fn score_update(user_id: &str, points: i16) { @@ -200,50 +190,6 @@ pub async fn take_plus_two(user_id: &str, amount_taken: i16) { .expect("Couldn't take plus two"); } -/** -Directly give the user_id plus 2's -**/ -pub async fn give_minus_two(user_id: &str, amount_given: i16) { - let database = connect_to_database().await; - sqlx::query!( - "UPDATE user SET minus_two_received = minus_two_received + ? WHERE user_id = ?", - amount_given, - user_id - ) - .execute(&database) - .await - .expect("Couldn't give minus two"); -} - -/** -Get the current amount of minus 2's the user has. -**/ -pub async fn get_minus_two_received(user_id: &str) { - let database = connect_to_database().await; - let plus_2_amount = sqlx::query!( - "SELECT minus_two_received FROM user WHERE user_id = ?", - user_id - ) - .fetch_all(&database) - .await - .unwrap(); -} - -/** -Directly take the user_id plus 2's -**/ -pub async fn take_minus_two(user_id: &str, amount_taken: i16) { - let database = connect_to_database().await; - sqlx::query!( - "UPDATE user SET minus_two_received = minus_two_received - ? WHERE user_id = ?", - amount_taken, - user_id - ) - .execute(&database) - .await - .expect("Couldn't take minus two"); -} - /** Get the users score formated for userInfo. **/ @@ -254,10 +200,10 @@ pub async fn get_user_info_score(user: &str) -> UserInfo { .await .unwrap(); - return UserInfo { + UserInfo { user_name: user.user_name, score: user.score.unwrap(), - }; + } } /** @@ -270,7 +216,7 @@ pub async fn get_score(user: &str) -> i64 { .await .unwrap(); - return result.score.unwrap(); + result.score.unwrap() } /** @@ -295,7 +241,7 @@ pub(crate) async fn get_top_scores(limit: i8) -> crate::commands::score::UserInf user_vector.0.push(temp_user) } - return user_vector; + user_vector } /** @@ -362,7 +308,7 @@ pub async fn get_shop_items() -> crate::commands::shop::ItemInfoVec { }; item_vector.0.push(temp_item) } - return item_vector; + item_vector } /** @@ -371,10 +317,12 @@ Returns the current number of active Bombs. pub async fn get_count(item: &str) -> i64 { let database = connect_to_database().await; let number_of_mines = sqlx::query!( - "SELECT current_amount FROM shop_items WHERE short_name = ?", item - ).fetch_one(&database) - .await - .unwrap(); + "SELECT current_amount FROM shop_items WHERE short_name = ?", + item + ) + .fetch_one(&database) + .await + .unwrap(); number_of_mines.current_amount } @@ -385,10 +333,12 @@ Resets the current number of active Bombs back to 1. pub async fn reset_count(item: &str) { let database = connect_to_database().await; sqlx::query!( - "UPDATE shop_items SET current_amount = 1 WHERE short_name = ?", item - ).execute(&database) - .await - .unwrap(); + "UPDATE shop_items SET current_amount = 1 WHERE short_name = ?", + item + ) + .execute(&database) + .await + .unwrap(); } pub async fn get_current_bot_id() -> String { @@ -398,5 +348,4 @@ pub async fn get_current_bot_id() -> String { return secrets_toml.live_bot_user_id; } secrets_toml.testing_bot_user_id - } diff --git a/src/commands/help.rs b/src/commands/help.rs index 4b49bea..ef5f810 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -1,13 +1,13 @@ -use poise::builtins::HelpConfiguration; use crate::bot_types::{_Context as Context, Error}; +use poise::builtins::HelpConfiguration; /// Display Help & Command menu. #[poise::command(prefix_command)] -pub async fn help ( +pub async fn help( ctx: Context<'_>, #[description = "Command to get help for."] #[rest] - mut command: Option + mut command: Option, ) -> Result<(), Error> { if ctx.invoked_command_name() != "help" { command = match command { @@ -30,4 +30,4 @@ pub async fn help ( }; poise::builtins::help(ctx, command.as_deref(), config).await?; Ok(()) -} \ No newline at end of file +} diff --git a/src/commands/judge.rs b/src/commands/judge.rs index b80e7a1..12ce8e6 100644 --- a/src/commands/judge.rs +++ b/src/commands/judge.rs @@ -1,15 +1,13 @@ +use crate::bot_types::{_Context as Context, Error}; +use crate::bot_utils::connect_to_database; use crate::{bot_utils, emoji}; -use crate::bot_utils::{connect_to_database}; -use rand::seq::IndexedRandom; -use crate::bot_types::{ Error, _Context as Context}; -use poise::{ serenity_prelude as serenity}; +use poise::serenity_prelude as serenity; +use rand::seq::IteratorRandom; use serenity::model::channel::ReactionType; -/// Use as a reply to have the bot judge a users post. -/// *Noted for deprecation. +/// **Noted for deprecation.** #[poise::command(prefix_command)] pub async fn judge(ctx: Context<'_>) -> Result<(), Error> { - let msg = ctx.channel_id().message(&ctx.http(), ctx.id()).await?; // Add Emojis to Judge Command. let mut emojis: Vec = vec![]; @@ -21,7 +19,7 @@ pub async fn judge(ctx: Context<'_>) -> Result<(), Error> { emojis.push(emoji::get_emoji("manny")); emojis.push(emoji::get_emoji("doot")); } - let reaction = emojis.choose(&mut rand::rng()).unwrap().clone(); + let reaction = emojis.into_iter().choose(&mut rand::rng()).unwrap(); if msg.referenced_message.is_none() { if let Err(why) = ctx.reply("Command can only be used as a reply.").await { @@ -29,12 +27,18 @@ pub async fn judge(ctx: Context<'_>) -> Result<(), Error> { } Ok(()) } else if has_been_judged(&msg.referenced_message.clone().unwrap().id.to_string()).await { - if let Err(why) = ctx.reply("Post has already been judged.").await { - println!("Error sending message: {:?}", why); - } - Ok(()) - } else { - if let Err(why) = msg.referenced_message.clone().unwrap().react(&ctx.http(), reaction.clone()).await { + if let Err(why) = ctx.reply("Post has already been judged.").await { + println!("Error sending message: {:?}", why); + } + Ok(()) + } else { + if let Err(why) = msg + .referenced_message + .clone() + .unwrap() + .react(&ctx.http(), reaction.clone()) + .await + { println!("Error sending message: {:?}", why); } @@ -47,10 +51,14 @@ pub async fn judge(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } - } -async fn insert_into_judged(message_id:&str, message_owner:&str, command_caller: &str, result: &str) { +async fn insert_into_judged( + message_id: &str, + message_owner: &str, + command_caller: &str, + result: &str, +) { let database = connect_to_database().await; sqlx::query!( "INSERT INTO judgedPosts (message_id, message_owner, command_caller, result) VALUES (?, ?, ?, ?)", @@ -64,18 +72,18 @@ async fn insert_into_judged(message_id:&str, message_owner:&str, command_caller: .unwrap(); } -async fn has_been_judged(message_id:&str) -> bool { +async fn has_been_judged(message_id: &str) -> bool { let database = connect_to_database().await; let is_judged = sqlx::query!( - "SELECT message_id FROM judgedPosts WHERE message_id = ?", - message_id, - ) - .fetch_all(&database) - .await - .unwrap(); + "SELECT message_id FROM judgedPosts WHERE message_id = ?", + message_id, + ) + .fetch_all(&database) + .await + .unwrap(); - if is_judged.len() >= 1 { - return true; - } - return false; + if !is_judged.is_empty() { + return true; + } + false } diff --git a/src/commands/ping.rs b/src/commands/ping.rs index d1a7579..62fffe0 100644 --- a/src/commands/ping.rs +++ b/src/commands/ping.rs @@ -1,18 +1,16 @@ -use crate::bot_types::{Error, _Context as Context }; +use crate::bot_types::{_Context as Context, Error}; use poise::serenity_prelude as serenity; /// Just a test command. Does nothing. #[poise::command(prefix_command)] -pub async fn ping(ctx: Context<'_>, - #[description = "Selected User"] user: Option, - )-> Result<(), Error> { +pub async fn ping( + ctx: Context<'_>, + #[description = "Selected User"] user: Option, +) -> Result<(), Error> { let u = user.as_ref().unwrap_or_else(|| ctx.author()); let response = format!("{}'s account was created at {}", u.name, u.created_at()); let embed = serenity::CreateEmbed::default().title(response); - let reply = { - poise::CreateReply::default() - .embed(embed) - }; + let reply = { poise::CreateReply::default().embed(embed) }; ctx.send(reply).await?; Ok(()) diff --git a/src/commands/score.rs b/src/commands/score.rs index 08016ec..0988b4e 100644 --- a/src/commands/score.rs +++ b/src/commands/score.rs @@ -1,10 +1,6 @@ -/** -Commands for displaying scores. -**/ - +use crate::bot_types::{_Context as Context, Error}; +use crate::bot_utils::{get_plus_two_received, get_top_scores, get_user_info_score}; use std::fmt::{Display, Formatter, Result as fmtResult}; -use crate::bot_types::{ Error, _Context as Context }; -use crate::bot_utils::{get_user_info_score, get_top_scores, get_plus_two_received}; /** Struct used for displaying Information @@ -16,21 +12,21 @@ pub struct UserInfo { impl Display for UserInfo { fn fmt(&self, f: &mut Formatter) -> fmtResult { - write!(f, " - {} > {}\n", self.user_name, self.score) + writeln!(f, " - {} > {}", self.user_name, self.score) } } pub(crate) struct UserInfoVec(pub Vec); impl Display for UserInfoVec { fn fmt(&self, f: &mut Formatter) -> fmtResult { - let mut comma_seperated = String::new(); + let mut comma_seperated = String::new(); - for val in &self.0[0..self.0.len()-1] { + for val in &self.0[0..self.0.len() - 1] { comma_seperated.push_str(val.user_name.to_string().as_str()); comma_seperated.push_str(val.score.to_string().as_str()); comma_seperated.push_str(", "); } - comma_seperated.push_str(&self.0[self.0.len() -1].to_string()); + comma_seperated.push_str(&self.0[self.0.len() - 1].to_string()); write!(f, "{}", comma_seperated) } } @@ -39,16 +35,16 @@ impl Display for UserInfoVec { Returns the Top Scoring User. **/ #[poise::command(prefix_command)] -pub async fn top(ctx: Context<'_>) -> Result<(), Error>{ +pub async fn top(ctx: Context<'_>) -> Result<(), Error> { let top_scores = get_top_scores(1).await; let mut reply_string: String = String::new(); for (i, value) in top_scores.0.iter().enumerate() { - reply_string.push_str((i+1).to_string().as_str()); + reply_string.push_str((i + 1).to_string().as_str()); reply_string.push_str(value.to_string().as_str()); } - if let Err(why) = ctx.reply( reply_string).await { + if let Err(why) = ctx.reply(reply_string).await { println!("Error sending message: {:?}", why); } Ok(()) @@ -58,13 +54,12 @@ pub async fn top(ctx: Context<'_>) -> Result<(), Error>{ Returns the top 10 scoring users. **/ #[poise::command(prefix_command, aliases("board", "leaderboard", "lb"))] -pub async fn leader(ctx: Context<'_>) -> Result<(), Error>{ +pub async fn leader(ctx: Context<'_>) -> Result<(), Error> { let top_scores = get_top_scores(10).await; - let mut reply_string: String = String::new(); for (i, value) in top_scores.0.iter().enumerate() { - reply_string.push_str((i+1).to_string().as_str()); + reply_string.push_str((i + 1).to_string().as_str()); reply_string.push_str(value.to_string().as_str()); } @@ -79,20 +74,17 @@ Returns the score of a specific user. **/ #[poise::command(prefix_command)] pub async fn score(ctx: Context<'_>) -> Result<(), Error> { - let msg = ctx.channel_id().message(&ctx.http(), ctx.id()).await?; let search_user = if msg.referenced_message.is_none() { msg.author.id } else { msg.referenced_message.clone().unwrap().author.id - }; - let return_user = get_user_info_score(search_user.to_string().as_str()).await; - if let Err(why) = ctx.reply( return_user.to_string()).await { + if let Err(why) = ctx.reply(return_user.to_string()).await { println!("Error sending message: {:?}", why); } @@ -103,10 +95,14 @@ pub async fn score(ctx: Context<'_>) -> Result<(), Error> { Returns the users +2 count **/ #[poise::command(prefix_command, aliases("balance", "bank"))] -pub async fn wallet(ctx: Context<'_>) -> Result<(), Error>{ - let number_of_plus_twos = get_plus_two_received(ctx.author().id.to_string()).await.unwrap(); - - ctx.reply(format!("Current Balance: {}", number_of_plus_twos)).await.expect("Error: Getting current Balance."); +pub async fn wallet(ctx: Context<'_>) -> Result<(), Error> { + let number_of_plus_twos = get_plus_two_received(ctx.author().id.to_string()) + .await + .unwrap(); + + ctx.reply(format!("Current Balance: {}", number_of_plus_twos)) + .await + .expect("Error: Getting current Balance."); Ok(()) } diff --git a/src/commands/shop.rs b/src/commands/shop.rs index 632de0f..288957f 100644 --- a/src/commands/shop.rs +++ b/src/commands/shop.rs @@ -1,10 +1,10 @@ use crate::bot_types::{_Context as Context, Error}; use crate::bot_utils; -use poise::serenity_prelude as serenity; -use std::fmt::{Display, Formatter, Result as fmtResult}; -use crate::bot_utils::{connect_to_database, get_current_bot_id, get_plus_two_received, take_plus_two}; +use crate::bot_utils::{connect_to_database, get_current_bot_id, get_plus_two_received}; use crate::commands::trade::do_transaction; use crate::emoji::get_emoji; +use poise::serenity_prelude as serenity; +use std::fmt::{Display, Formatter, Result as fmtResult}; static ITEM_COL_WIDTH: usize = 70; static PRICE_COL_WIDTH: usize = 15; @@ -44,7 +44,7 @@ impl Display for ItemInfoVec { pub async fn shop(ctx: Context<'_>) -> Result<(), Error> { let shop_items = bot_utils::get_shop_items().await; let mut shop_string: String = String::new(); - for (i, value) in shop_items.0.iter().enumerate() { + for value in shop_items.0.iter() { shop_string.push_str(&value.to_string()); } @@ -90,20 +90,30 @@ pub async fn buy( #[description = "The symbol of the item you want"] symbol: String, ) -> Result<(), Error> { let database = connect_to_database().await; - let selected_item = sqlx::query!( - "SELECT * FROM shop_items WHERE short_name = ?", symbol, - ).fetch_one(&database) - .await?; - - if get_plus_two_received(ctx.author().id.to_string()).await.unwrap() < selected_item.price { - ctx.reply(format!("Not Enough {}", get_emoji("plus_two"))).await?; + let selected_item = sqlx::query!("SELECT * FROM shop_items WHERE short_name = ?", symbol,) + .fetch_one(&database) + .await?; + + if get_plus_two_received(ctx.author().id.to_string()) + .await + .unwrap() + < selected_item.price + { + ctx.reply(format!("Not Enough {}", get_emoji("plus_two"))) + .await?; return Ok(()); } let current_bot_id = get_current_bot_id().await.to_string(); - do_transaction(&ctx.author().id.to_string(), ¤t_bot_id , selected_item.price as i16).await; + do_transaction( + &ctx.author().id.to_string(), + ¤t_bot_id, + selected_item.price as i16, + ) + .await; update_shop_count(selected_item.short_name, 1, 1).await; - ctx.reply(format!("Bought {}", selected_item.item_name)).await?; + ctx.reply(format!("Bought {}", selected_item.item_name)) + .await?; Ok(()) } @@ -116,8 +126,6 @@ pub async fn update_shop_count(item_name: String, current_increase: i16, total_i total_increase, item_name ).execute(&database).await.expect("Failed to update shop count"); - - } /// Returns the current amount of an item. @@ -127,6 +135,7 @@ pub async fn count( #[description = "The symbol of the item you want"] symbol: String, ) -> Result<(), Error> { let count = bot_utils::get_count(&symbol).await; - ctx.reply(format!("current {} count: {}", symbol, count)).await?; + ctx.reply(format!("current {} count: {}", symbol, count)) + .await?; Ok(()) } diff --git a/src/commands/smash.rs b/src/commands/smash.rs index 415053d..3403c5b 100644 --- a/src/commands/smash.rs +++ b/src/commands/smash.rs @@ -1,22 +1,30 @@ +use crate::bot_types::{_Context as Context, Error}; /** Returns smash or pass. - Requires image? - Should return in reply. **/ use crate::bot_utils; -use crate::bot_types::{Error, _Context as Context}; /// Smash or Pass a message. Used as a reply. #[poise::command(prefix_command)] -pub async fn smash(ctx: Context<'_>) -> Result<(), Error>{ - let mut reply = if bot_utils::get_random_bool(0.5) {"Smash"} else {"Pass"}; +pub async fn smash(ctx: Context<'_>) -> Result<(), Error> { + let mut reply = if bot_utils::get_random_bool(0.5) { + "Smash" + } else { + "Pass" + }; if bot_utils::get_random_bool(0.2) { - reply = if bot_utils::get_random_bool(0.5) {"Easy smash"} else {"Hard pass"}; + reply = if bot_utils::get_random_bool(0.5) { + "Easy smash" + } else { + "Hard pass" + }; } if let Err(why) = ctx.reply(reply).await { println!("Error sending message: {:?}", why); } Ok(()) -} \ No newline at end of file +} diff --git a/src/commands/trade.rs b/src/commands/trade.rs index 4a6f236..ebeb4c7 100644 --- a/src/commands/trade.rs +++ b/src/commands/trade.rs @@ -2,8 +2,8 @@ use crate::bot_types::{_Context as Context, Error}; use crate::bot_utils; use crate::emoji; use crate::emoji::get_emoji; -use poise::serenity_prelude as serenity; use bot_utils::get_score; +use poise::serenity_prelude as serenity; /** Trades +2's from the caller, to the reviver. @@ -53,7 +53,13 @@ pub async fn trade( number_amount.clone().unwrap(), ) .await; - bot_utils::add_trade_log(message_id, from_user, receiving_user.clone(), amount.clone()).await; + bot_utils::add_trade_log( + message_id, + from_user, + receiving_user.clone(), + amount.clone(), + ) + .await; ctx.reply(format!( "{} has traded {} {} {}. The updated scores are {}: {} and {}: {}", from_user.clone(), @@ -61,10 +67,12 @@ pub async fn trade( amount.clone(), get_emoji("plus_two"), from_user, - get_score(from_user_id.as_str()).await.to_string(), + get_score(from_user_id.as_str()).await, receiving_user, - get_score(receiving_user_id.as_str()).await.to_string() - )).await.expect("Error Updating Score"); + get_score(receiving_user_id.as_str()).await + )) + .await + .expect("Error Updating Score"); Ok(()) } diff --git a/src/emoji.rs b/src/emoji.rs index e3fbe71..cd3f9c9 100644 --- a/src/emoji.rs +++ b/src/emoji.rs @@ -1,7 +1,7 @@ -use std::string::ToString; +use crate::bot_utils; use serenity::model::channel::ReactionType; use serenity::model::id::EmojiId; -use crate::bot_utils; +use std::string::ToString; fn get_plus_two() -> ReactionType { let plus_two: ReactionType = ReactionType::Custom { @@ -9,46 +9,44 @@ fn get_plus_two() -> ReactionType { id: EmojiId::new(924536822472802337), name: Some("p2".to_string()), }; - return plus_two; + plus_two } fn get_minus_two() -> ReactionType { - let minus_two:ReactionType = ReactionType::Custom { + let minus_two: ReactionType = ReactionType::Custom { animated: false, id: EmojiId::new(924536784191365120), name: Some("m2".to_string()), }; - return minus_two; + minus_two } fn get_manny() -> ReactionType { let manny: ReactionType = ReactionType::Custom { animated: false, id: EmojiId::new(929987409360343051), - name: Some("manny".to_string()) + name: Some("manny".to_string()), }; - return manny; - + manny } fn get_doot() -> ReactionType { - let doot: ReactionType = ReactionType::Custom { - animated: false, - id: EmojiId::new(929985012554682469), - name: Some("doot".to_string()) - } ; - return doot; + let doot: ReactionType = ReactionType::Custom { + animated: false, + id: EmojiId::new(929985012554682469), + name: Some("doot".to_string()), + }; + doot } fn get_winner() -> ReactionType { let winner: ReactionType = ReactionType::Custom { animated: false, id: EmojiId::new(1348181039779938366), - name: Some("winner".to_string()) - } ; + name: Some("winner".to_string()), + }; winner } - pub fn get_emoji(emoji_name: &str) -> ReactionType { let current_env = bot_utils::get_env(); if String::from("live").eq(¤t_env) { diff --git a/src/main.rs b/src/main.rs index ba4d406..389d07b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,9 +29,9 @@ use serenity::model::channel::{Message, Reaction, ReactionType}; use serenity::model::gateway::Ready; use serenity::model::id::{ChannelId, GuildId, MessageId}; use serenity::prelude::*; -use std::collections::HashSet; struct Handler; +const MAX_BOMB_RANGE: i64 = 300; #[async_trait] impl EventHandler for Handler { @@ -49,7 +49,7 @@ impl EventHandler for Handler { return; } - let mut _rng = rand::rng().random_range(0..500); + let mut _rng = rand::rng().random_range(0..MAX_BOMB_RANGE); let current_number_of_bombs = get_count("mine").await; if _rng <= current_number_of_bombs { let mut member = get_member(_ctx.clone(), msg.clone()).await; @@ -62,8 +62,10 @@ impl EventHandler for Handler { msg.reply( &_ctx.http, format!( - "{} You're our lucky loser! See you in 10 minutes. :3", - get_emoji("winner") + "{} You're our lucky loser! See you in 10 minutes. :3 {}/{}", + get_emoji("winner"), + current_number_of_bombs, + MAX_BOMB_RANGE, ), ) .await @@ -79,13 +81,6 @@ impl EventHandler for Handler { .author; let score = get_points_from_emoji(reaction); - if _add_reaction.user_id.unwrap().to_string() == message.id.to_string() { - // if (_add_reaction.user_id.unwrap().to_string() == "180083924414758912") { - // let mut memeber = get_member(_ctx.clone(), message.clone()).await; - // } - return; - } - if score == 2 { bot_utils::plus_two( &_add_reaction.user_id.unwrap().to_string(), @@ -138,7 +133,7 @@ impl EventHandler for Handler { .await; } - bot_utils::score_update(&message.id.to_string(), score * -1).await; + bot_utils::score_update(&message.id.to_string(), -score).await; } async fn ready(&self, _: Context, ready: Ready) { @@ -164,8 +159,7 @@ Returns the Member of the message sent. **/ async fn get_member(_ctx: Context, msg: Message) -> Member { let guild_id = msg.guild_id.unwrap(); - let member = guild_id.member(&_ctx.http, msg.author.id).await.unwrap(); - member + guild_id.member(&_ctx.http, msg.author.id).await.unwrap() } fn get_points_from_emoji(reaction: ReactionType) -> i16 { @@ -176,7 +170,7 @@ fn get_points_from_emoji(reaction: ReactionType) -> i16 { if reaction == emoji::get_emoji("minus_two") || reaction == emoji::get_emoji("doot") { score = -2; } - return score; + score } #[hook] @@ -187,7 +181,6 @@ async fn unknown_command(_ctx: &Context, _msg: &Message, unknown_command_name: & #[tokio::main] async fn main() { let token = bot_utils::get_secret(); - let http = Http::new(&token); // Set gateway intents, which decides what events the bot will be notified about let intents = GatewayIntents::GUILD_MESSAGES @@ -197,22 +190,6 @@ async fn main() { | GatewayIntents::GUILD_VOICE_STATES | GatewayIntents::MESSAGE_CONTENT; - let (owners, bot_id) = match http.get_current_application_info().await { - Ok(info) => { - let mut owners = HashSet::new(); - if let Some(team) = info.team { - owners.insert(team.owner_user_id); - } else { - owners.insert(info.owner.unwrap().id); - } - match http.get_current_user().await { - Ok(bot_id) => (owners, bot_id.id), - Err(why) => panic!("Could not access the bot id: {:?}", why), - } - } - Err(why) => panic!("Could not access application info: {:?}", why), - }; - // TODO make commands combine vectors from all the command files. let framework = poise::Framework::::builder() .options(poise::FrameworkOptions { diff --git a/src/runescape_utils/rs_client.rs b/src/runescape_utils/rs_client.rs index 6f83fe8..ab07298 100644 --- a/src/runescape_utils/rs_client.rs +++ b/src/runescape_utils/rs_client.rs @@ -1,5 +1,5 @@ use reqwest::{Client, Url}; -use serde::{de::Error, Deserialize}; +use serde::{Deserialize, de::Error}; use std::collections::HashMap; use thiserror::Error; @@ -15,8 +15,6 @@ pub struct RSClient { #[derive(Debug, Deserialize, Clone)] pub struct RSItemPrice { pub item: String, - pub id: String, - pub timestamp: TimeStampValue, pub price: u64, pub volume: u64, } @@ -36,7 +34,6 @@ pub struct RSItemPriceHistory { #[derive(Debug, Deserialize, Clone)] pub struct RSPrice { - pub id: String, pub timestamp: TimeStampValue, pub price: u64, pub volume: u64, @@ -47,11 +44,11 @@ pub type RSPriceHistoryMapResponse = HashMap>; #[derive(Error, Debug)] pub enum RSError { #[error("HTTP error: {0}")] - HttpError(#[from] reqwest::Error), + Http(#[from] reqwest::Error), #[error("Failed to parse JSON response: {0}")] - JsonParseError(#[from] serde_json::Error), + JsonParse(#[from] serde_json::Error), #[error("Failed to parse URL: {0}")] - UrlParseError(#[from] url::ParseError), + UrlParse(#[from] url::ParseError), } impl Default for RSClient { @@ -86,31 +83,24 @@ impl RSClient { encoded_name ); - let url = self.base_url.join(&path).map_err(RSError::UrlParseError)?; - let response = self - .client - .get(url) - .send() - .await - .map_err(RSError::HttpError)?; + let url = self.base_url.join(&path).map_err(RSError::UrlParse)?; + let response = self.client.get(url).send().await.map_err(RSError::Http)?; - let body_text = response.text().await.map_err(RSError::HttpError)?; + let body_text = response.text().await.map_err(RSError::Http)?; // The API returns a HashMap of item names to price data let price_map: RSPriceMapResponse = - serde_json::from_str(&body_text).map_err(RSError::JsonParseError)?; + serde_json::from_str(&body_text).map_err(RSError::JsonParse)?; // Extract the first (and typically only) entry from the map let (item_name, price_data) = price_map .into_iter() .next() - .ok_or_else(|| RSError::JsonParseError(serde_json::Error::custom("empty response")))?; + .ok_or_else(|| RSError::JsonParse(serde_json::Error::custom("empty response")))?; // Convert RSPrice to RSItemPrice by adding the item name let price_response = RSItemPrice { item: item_name, - id: price_data.id, - timestamp: price_data.timestamp, price: price_data.price, volume: price_data.volume, }; @@ -125,29 +115,24 @@ impl RSClient { encoded_name ); - let url = self.base_url.join(&path).map_err(RSError::UrlParseError)?; - let response = self - .client - .get(url) - .send() - .await - .map_err(RSError::HttpError)?; + let url = self.base_url.join(&path).map_err(RSError::UrlParse)?; + let response = self.client.get(url).send().await.map_err(RSError::Http)?; - let body_text = response.text().await.map_err(RSError::HttpError)?; + let body_text = response.text().await.map_err(RSError::Http)?; // The API returns a HashMap of item names to arrays of price history data let price_history_map: RSPriceHistoryMapResponse = serde_json::from_str(&body_text) .map_err(|e| { eprintln!("JSON parse error at position: {}", e); eprintln!("Response body: {}", body_text); - RSError::JsonParseError(e) + RSError::JsonParse(e) })?; // Extract the first (and typically only) entry from the map let (item_name, price_history) = price_history_map .into_iter() .next() - .ok_or_else(|| RSError::JsonParseError(serde_json::Error::custom("empty response")))?; + .ok_or_else(|| RSError::JsonParse(serde_json::Error::custom("empty response")))?; let item_price_history = RSItemPriceHistory { item: item_name,