diff --git a/.gitignore b/.gitignore index f7a8089..4f33c95 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ Cargo.lock /target /.idea/ +/quotes.txt diff --git a/README.md b/README.md index bf6c315..21ad1b8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # echbot -A trash bot for Smite fun. +A helper bot for Smite fun. ## Environment @@ -13,8 +13,3 @@ environment: - DEV_ID=your_hi-rez_dev_id - AUTH_KEY=your_hi-rez_auth_key ``` - -## Quotes - -A `quotes.txt` file must be present in the root directory for the ping command to function. The format is one quote per -line. diff --git a/src/commands/api.rs b/src/commands/api.rs index 582525b..5918127 100644 --- a/src/commands/api.rs +++ b/src/commands/api.rs @@ -19,9 +19,25 @@ pub struct God { ret_msg: Option, } -async fn get_utc_timestamp() -> Result { - let timestamp: String = chrono::Utc::now().format("%Y%m%d%H%M%S").to_string(); - Ok(timestamp) +#[derive(Deserialize, Debug)] +#[allow(dead_code)] +pub struct Profile { + #[serde(rename = "Name")] + pub name: Option, + #[serde(rename = "Personal_Status_Message")] + pub personal_status_message: Option, + pub hz_player_name: Option, + #[serde(rename = "HoursPlayed")] + pub hours_played: i32, + #[serde(rename = "Losses")] + pub losses: i32, + #[serde(rename = "Wins")] + pub wins: i32, + pub ret_msg: Option, +} + +async fn get_utc_timestamp() -> String { + chrono::Utc::now().format("%Y%m%d%H%M%S").to_string() } async fn get_signature( @@ -29,24 +45,20 @@ async fn get_signature( method: &str, auth_key: &String, timestamp: &String, -) -> Result { - let hash: Digest = md5::compute(format!("{}{}{}{}", dev_id, method, auth_key, timestamp)); - let signature: String = format!("{:x}", hash); - Ok(signature) +) -> String { + let hash: Digest = md5::compute(format!("{dev_id}{method}{auth_key}{timestamp}")); + format!("{:x}", hash) } async fn create_session() -> Result { let dev_id: String = std::env::var("DEV_ID").expect("Missing DEV_ID"); let auth_key: String = std::env::var("AUTH_KEY").expect("Missing AUTH_KEY"); - let timestamp: String = get_utc_timestamp().await?; - let signature: String = get_signature(&dev_id, "createsession", &auth_key, ×tamp).await?; + let timestamp: String = get_utc_timestamp().await; + let signature: String = get_signature(&dev_id, "createsession", &auth_key, ×tamp).await; let request: String = format!( - "https://api.smitegame.com/smiteapi.svc/createsessionJson/{dev_id}/{signature}/{timestamp}", - dev_id = dev_id, - signature = signature, - timestamp = timestamp + "https://api.smitegame.com/smiteapi.svc/createsessionJson/{dev_id}/{signature}/{timestamp}" ); let response: Response = reqwest::get(&request).await?; @@ -60,15 +72,11 @@ async fn get_gods() -> Result, Error> { let session_id: String = create_session().await?.session_id; - let timestamp: String = get_utc_timestamp().await?; - let signature: String = get_signature(&dev_id, "getgods", &auth_key, ×tamp).await?; + let timestamp: String = get_utc_timestamp().await; + let signature: String = get_signature(&dev_id, "getgods", &auth_key, ×tamp).await; let request: String = format!( - "https://api.smitegame.com/smiteapi.svc/getgodsJson/{id}/{signature}/{session}/{timestamp}/1", - id = dev_id, - signature = signature, - session = session_id, - timestamp = timestamp, + "https://api.smitegame.com/smiteapi.svc/getgodsJson/{dev_id}/{signature}/{session_id}/{timestamp}/1" ); let response: Response = reqwest::get(&request).await?; @@ -78,9 +86,25 @@ async fn get_gods() -> Result, Error> { pub async fn get_random_god() -> Result { let gods: Vec = get_gods().await?; - let god: &God = gods - .choose(&mut rand::thread_rng()) - .expect("Couldn't pick random god."); + let god: &God = gods.choose(&mut rand::thread_rng()).unwrap(); let name: String = god.name.clone(); Ok(name) } + +pub async fn get_player(player: String) -> Result, Error> { + let dev_id: String = std::env::var("DEV_ID").expect("Missing DEV_ID"); + let auth_key: String = std::env::var("AUTH_KEY").expect("Missing AUTH_KEY"); + + let session_id: String = create_session().await?.session_id; + + let timestamp: String = get_utc_timestamp().await; + let signature: String = get_signature(&dev_id, "getplayer", &auth_key, ×tamp).await; + + let request: String = format!( + "https://api.smitegame.com/smiteapi.svc/getplayerJson/{dev_id}/{signature}/{session_id}/{timestamp}/{player}" + ); + + let response: Response = reqwest::get(&request).await?; + let profiles: Vec = response.json().await?; + Ok(profiles) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 17f3f8d..aab0d7e 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,6 +1,7 @@ // Group all commands for registration mod api; +pub mod ping; +pub mod profile; pub mod random; pub mod register; -pub mod slur; pub mod team; diff --git a/src/commands/profile.rs b/src/commands/profile.rs new file mode 100644 index 0000000..02ac873 --- /dev/null +++ b/src/commands/profile.rs @@ -0,0 +1,48 @@ +use crate::commands::api::get_player; +use crate::serenity; +use crate::{Context, Error}; + +/// Looks up a player's profile +#[poise::command(slash_command)] +pub async fn profile( + ctx: Context<'_>, + #[rename = "player"] player_name: String, +) -> Result<(), Error> { + let profiles = get_player(player_name).await?; + let profile = profiles.first().unwrap(); + if profile.name.is_none() { + ctx.send(|f| { + f.embed(|f| { + f.title("Hidden") + .description("This profile is hidden.") + .color(serenity::Colour::RED) + }) + }) + .await?; + return Ok(()); + } + + let winrate = + (profile.wins as f32 / (profile.wins as f32 + profile.losses as f32)) * 100 as f32; + ctx.send(|f| { + f.embed(|f| { + f.title(format!("{}", profile.name.as_ref().unwrap())) + .description(format!( + "{}'s statistics.", + profile.hz_player_name.as_ref().unwrap() + )) + .field( + "Status", + format!("{}", profile.personal_status_message.as_ref().unwrap()), + false, + ) + .field("Hours played", format!("{}", profile.hours_played), false) + .field("Wins", format!("{}", profile.wins), true) + .field("Losses", format!("{}", profile.losses), true) + .field("Winrate", format!("{:.2}%", winrate), true) + .color(serenity::Colour::BLURPLE) + }) + }) + .await?; + Ok(()) +} diff --git a/src/commands/random.rs b/src/commands/random.rs index 64b94c0..91936ec 100644 --- a/src/commands/random.rs +++ b/src/commands/random.rs @@ -15,7 +15,7 @@ pub async fn god(ctx: Context<'_>) -> Result<(), Error> { ctx.send(|f| { f.embed(|f| { f.title("Random God") - .description(format!("Try not to throw with **{}**, idiot.", god)) + .description(format!("Your random god is **{god}**!")) .color(serenity::Colour::BLUE) }) }) diff --git a/src/commands/slur.rs b/src/commands/slur.rs deleted file mode 100644 index 7308759..0000000 --- a/src/commands/slur.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::serenity; -use crate::{Context, Error}; -use rand::seq::IteratorRandom; -use std::{ - fs::File, - io::{BufRead, BufReader}, -}; - -/// Basically a ping command -#[poise::command(slash_command, prefix_command)] -pub async fn slur(ctx: Context<'_>) -> Result<(), Error> { - let file: File = File::open("quotes.txt").unwrap_or_else(|_e| panic!("Quote file missing.")); // Open the quotes file - let file: BufReader = BufReader::new(file); // Read the quotes file - let quotes = file.lines().map(|res| res.expect("Failed to read line.")); - let quote: String = quotes - .choose(&mut rand::thread_rng()) - .expect("No lines in file."); // Pick a random quote - - ctx.send(|f| { - f.embed(|f| { - f.title("DMBrandon Sez") - .description(format!("\"{}\"", quote)) - .color(serenity::Colour::GOLD) - }) - }) - .await?; // Send embed with team picks - Ok(()) -} diff --git a/src/commands/team.rs b/src/commands/team.rs index f403e5b..b488847 100644 --- a/src/commands/team.rs +++ b/src/commands/team.rs @@ -9,11 +9,10 @@ use crate::{Context, Error}; /// Return a string of pingable IDs from a slice of string UserIds fn team_to_ping(team: &[&String]) -> String { - return team - .iter() - .map(|o| format!("<@{}>", o)) + team.iter() + .map(|o| format!("<@{o}>")) .collect::>() - .join(", "); + .join(", ") } /// Splits up players for custom matches @@ -39,12 +38,12 @@ pub async fn team( // Make sure there are enough members in the voice channel ctx.send(|f| { f.embed(|f| { - f.title(format!("Custom {}v{} Teams", size, size)) - .description("You don't have enough friends for that, idiot.") + f.title(format!("Custom {size}v{size} Teams")) + .description("There are not enough members in the call!") .color(serenity::Colour::RED) }) }) - .await?; // Insult the user for not having enough friends + .await?; return Ok(()); // Break out early if there are not enough members } @@ -57,8 +56,8 @@ pub async fn team( ctx.send(|f| { f.embed(|f| { - f.title(format!("Custom {}v{} Teams", size, size)) - .description("VER") + f.title(format!("Custom {size}v{size} Teams")) + .description("Click the button below to move the Chaos players.") .field("Order", team_to_ping(order), false) .field("Chaos", team_to_ping(chaos), false) .color(serenity::Colour::DARK_GREEN) @@ -83,7 +82,7 @@ pub async fn team( { let guild: Guild = ctx.guild().unwrap(); // Grab guild from context for user in chaos { - let member: Member = guild.member(ctx, UserId(user.parse().unwrap())).await?; // Get the member in the correct guild + let member: Member = guild.member(ctx, UserId(user.parse()?)).await?; // Get the member in the correct guild member .move_to_voice_channel(ctx, chaos_channel.id()) .await?; // Move the member to the correct voice channel @@ -94,8 +93,8 @@ pub async fn team( ir.kind(serenity::InteractionResponseType::UpdateMessage) .interaction_response_data(|f| { f.embed(|f| { - f.title(format!("Custom {}v{} Teams", size, size)) - .description("VVGO VVW VVX") + f.title(format!("Custom {size}v{size} Teams")) + .description("Good luck! Have fun!") .field("Order", team_to_ping(order), false) .field("Chaos", team_to_ping(chaos), false) .color(serenity::Colour::DARK_GREEN) @@ -109,7 +108,6 @@ pub async fn team( .disabled(true) // with disabled button .style(serenity::ButtonStyle::Primary) .label("Quit Sibelius") // and new text - .custom_id(uuid_team) }, // Use the context ID as button ID ) }) diff --git a/src/main.rs b/src/main.rs index 4df5668..c4b6a9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,10 +12,11 @@ async fn main() { let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands: vec![ - commands::slur::slur(), + commands::ping::ping(), commands::team::team(), commands::random::random(), commands::register::register(), + commands::profile::profile(), ], // IntelliJ doesn't like this, but it's fine. ..Default::default() })