Merge pull request #6 from ethanrusz/session-handling

Bot cleanup
This commit is contained in:
Em (Ethan) Ruszanowski 2023-03-08 13:28:33 -05:00 committed by GitHub
commit 7d360ab091
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 113 additions and 73 deletions

1
.gitignore vendored
View file

@ -14,3 +14,4 @@ Cargo.lock
/target /target
/.idea/ /.idea/
/quotes.txt

View file

@ -1,6 +1,6 @@
# echbot # echbot
A trash bot for Smite fun. A helper bot for Smite fun.
## Environment ## Environment
@ -13,8 +13,3 @@ environment:
- DEV_ID=your_hi-rez_dev_id - DEV_ID=your_hi-rez_dev_id
- AUTH_KEY=your_hi-rez_auth_key - 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.

View file

@ -19,9 +19,25 @@ pub struct God {
ret_msg: Option<String>, ret_msg: Option<String>,
} }
async fn get_utc_timestamp() -> Result<String, Error> { #[derive(Deserialize, Debug)]
let timestamp: String = chrono::Utc::now().format("%Y%m%d%H%M%S").to_string(); #[allow(dead_code)]
Ok(timestamp) pub struct Profile {
#[serde(rename = "Name")]
pub name: Option<String>,
#[serde(rename = "Personal_Status_Message")]
pub personal_status_message: Option<String>,
pub hz_player_name: Option<String>,
#[serde(rename = "HoursPlayed")]
pub hours_played: i32,
#[serde(rename = "Losses")]
pub losses: i32,
#[serde(rename = "Wins")]
pub wins: i32,
pub ret_msg: Option<String>,
}
async fn get_utc_timestamp() -> String {
chrono::Utc::now().format("%Y%m%d%H%M%S").to_string()
} }
async fn get_signature( async fn get_signature(
@ -29,24 +45,20 @@ async fn get_signature(
method: &str, method: &str,
auth_key: &String, auth_key: &String,
timestamp: &String, timestamp: &String,
) -> Result<String, Error> { ) -> String {
let hash: Digest = md5::compute(format!("{}{}{}{}", dev_id, method, auth_key, timestamp)); let hash: Digest = md5::compute(format!("{dev_id}{method}{auth_key}{timestamp}"));
let signature: String = format!("{:x}", hash); format!("{:x}", hash)
Ok(signature)
} }
async fn create_session() -> Result<Session, Error> { async fn create_session() -> Result<Session, Error> {
let dev_id: String = std::env::var("DEV_ID").expect("Missing DEV_ID"); 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 auth_key: String = std::env::var("AUTH_KEY").expect("Missing AUTH_KEY");
let timestamp: String = get_utc_timestamp().await?; let timestamp: String = get_utc_timestamp().await;
let signature: String = get_signature(&dev_id, "createsession", &auth_key, &timestamp).await?; let signature: String = get_signature(&dev_id, "createsession", &auth_key, &timestamp).await;
let request: String = format!( let request: String = format!(
"https://api.smitegame.com/smiteapi.svc/createsessionJson/{dev_id}/{signature}/{timestamp}", "https://api.smitegame.com/smiteapi.svc/createsessionJson/{dev_id}/{signature}/{timestamp}"
dev_id = dev_id,
signature = signature,
timestamp = timestamp
); );
let response: Response = reqwest::get(&request).await?; let response: Response = reqwest::get(&request).await?;
@ -60,15 +72,11 @@ async fn get_gods() -> Result<Vec<God>, Error> {
let session_id: String = create_session().await?.session_id; let session_id: String = create_session().await?.session_id;
let timestamp: String = get_utc_timestamp().await?; let timestamp: String = get_utc_timestamp().await;
let signature: String = get_signature(&dev_id, "getgods", &auth_key, &timestamp).await?; let signature: String = get_signature(&dev_id, "getgods", &auth_key, &timestamp).await;
let request: String = format!( let request: String = format!(
"https://api.smitegame.com/smiteapi.svc/getgodsJson/{id}/{signature}/{session}/{timestamp}/1", "https://api.smitegame.com/smiteapi.svc/getgodsJson/{dev_id}/{signature}/{session_id}/{timestamp}/1"
id = dev_id,
signature = signature,
session = session_id,
timestamp = timestamp,
); );
let response: Response = reqwest::get(&request).await?; let response: Response = reqwest::get(&request).await?;
@ -78,9 +86,25 @@ async fn get_gods() -> Result<Vec<God>, Error> {
pub async fn get_random_god() -> Result<String, Error> { pub async fn get_random_god() -> Result<String, Error> {
let gods: Vec<God> = get_gods().await?; let gods: Vec<God> = get_gods().await?;
let god: &God = gods let god: &God = gods.choose(&mut rand::thread_rng()).unwrap();
.choose(&mut rand::thread_rng())
.expect("Couldn't pick random god.");
let name: String = god.name.clone(); let name: String = god.name.clone();
Ok(name) Ok(name)
} }
pub async fn get_player(player: String) -> Result<Vec<Profile>, 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, &timestamp).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<Profile> = response.json().await?;
Ok(profiles)
}

View file

@ -1,6 +1,7 @@
// Group all commands for registration // Group all commands for registration
mod api; mod api;
pub mod ping;
pub mod profile;
pub mod random; pub mod random;
pub mod register; pub mod register;
pub mod slur;
pub mod team; pub mod team;

48
src/commands/profile.rs Normal file
View file

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

View file

@ -15,7 +15,7 @@ pub async fn god(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(|f| { ctx.send(|f| {
f.embed(|f| { f.embed(|f| {
f.title("Random God") f.title("Random God")
.description(format!("Try not to throw with **{}**, idiot.", god)) .description(format!("Your random god is **{god}**!"))
.color(serenity::Colour::BLUE) .color(serenity::Colour::BLUE)
}) })
}) })

View file

@ -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<File> = 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(())
}

View file

@ -9,11 +9,10 @@ use crate::{Context, Error};
/// Return a string of pingable IDs from a slice of string UserIds /// Return a string of pingable IDs from a slice of string UserIds
fn team_to_ping(team: &[&String]) -> String { fn team_to_ping(team: &[&String]) -> String {
return team team.iter()
.iter() .map(|o| format!("<@{o}>"))
.map(|o| format!("<@{}>", o))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ")
} }
/// Splits up players for custom matches /// Splits up players for custom matches
@ -39,12 +38,12 @@ pub async fn team(
// Make sure there are enough members in the voice channel // Make sure there are enough members in the voice channel
ctx.send(|f| { ctx.send(|f| {
f.embed(|f| { f.embed(|f| {
f.title(format!("Custom {}v{} Teams", size, size)) f.title(format!("Custom {size}v{size} Teams"))
.description("You don't have enough friends for that, idiot.") .description("There are not enough members in the call!")
.color(serenity::Colour::RED) .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 return Ok(()); // Break out early if there are not enough members
} }
@ -57,8 +56,8 @@ pub async fn team(
ctx.send(|f| { ctx.send(|f| {
f.embed(|f| { f.embed(|f| {
f.title(format!("Custom {}v{} Teams", size, size)) f.title(format!("Custom {size}v{size} Teams"))
.description("VER") .description("Click the button below to move the Chaos players.")
.field("Order", team_to_ping(order), false) .field("Order", team_to_ping(order), false)
.field("Chaos", team_to_ping(chaos), false) .field("Chaos", team_to_ping(chaos), false)
.color(serenity::Colour::DARK_GREEN) .color(serenity::Colour::DARK_GREEN)
@ -83,7 +82,7 @@ pub async fn team(
{ {
let guild: Guild = ctx.guild().unwrap(); // Grab guild from context let guild: Guild = ctx.guild().unwrap(); // Grab guild from context
for user in chaos { 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 member
.move_to_voice_channel(ctx, chaos_channel.id()) .move_to_voice_channel(ctx, chaos_channel.id())
.await?; // Move the member to the correct voice channel .await?; // Move the member to the correct voice channel
@ -94,8 +93,8 @@ pub async fn team(
ir.kind(serenity::InteractionResponseType::UpdateMessage) ir.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|f| { .interaction_response_data(|f| {
f.embed(|f| { f.embed(|f| {
f.title(format!("Custom {}v{} Teams", size, size)) f.title(format!("Custom {size}v{size} Teams"))
.description("VVGO VVW VVX") .description("Good luck! Have fun!")
.field("Order", team_to_ping(order), false) .field("Order", team_to_ping(order), false)
.field("Chaos", team_to_ping(chaos), false) .field("Chaos", team_to_ping(chaos), false)
.color(serenity::Colour::DARK_GREEN) .color(serenity::Colour::DARK_GREEN)
@ -109,7 +108,6 @@ pub async fn team(
.disabled(true) // with disabled button .disabled(true) // with disabled button
.style(serenity::ButtonStyle::Primary) .style(serenity::ButtonStyle::Primary)
.label("Quit Sibelius") // and new text .label("Quit Sibelius") // and new text
.custom_id(uuid_team)
}, // Use the context ID as button ID }, // Use the context ID as button ID
) )
}) })

View file

@ -12,10 +12,11 @@ async fn main() {
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
commands: vec![ commands: vec![
commands::slur::slur(), commands::ping::ping(),
commands::team::team(), commands::team::team(),
commands::random::random(), commands::random::random(),
commands::register::register(), commands::register::register(),
commands::profile::profile(),
], // IntelliJ doesn't like this, but it's fine. ], // IntelliJ doesn't like this, but it's fine.
..Default::default() ..Default::default()
}) })