Skip to content

Commit

Permalink
Custom error (#12)
Browse files Browse the repository at this point in the history
* Create a custom error type to give better answers on failure

* bump version number
  • Loading branch information
CodingDepot authored Feb 16, 2024
1 parent bb13d1d commit fc4e5fd
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "boot_bot"
version = "0.1.1"
version = "0.1.2"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
56 changes: 31 additions & 25 deletions src/discord/commands/boots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,40 +32,46 @@ pub fn run(options: &Vec<ResolvedOption>, calling_user: &User) -> CreateInteract
// A new thread is necessary here:
// https://stackoverflow.com/questions/62536566/how-can-i-create-a-tokio-runtime-inside-another-tokio-runtime-without-getting-th
let suggestion = thread::spawn(move || {
if let Some(prediction) = predict(&snowflake) {
return Some(prediction);
}
None
predict(&snowflake)
}
).join();

let mut response_message: CreateInteractionResponseMessage;
if let Ok(Some(boots)) = suggestion {
let content = format!("You should definitely buy {}!", boots);
match suggestion {
Ok(Ok(boots)) => {
let content = format!("You should definitely buy {}!", boots);

response_message =
response_message =
CreateInteractionResponseMessage::new()
.content(content)
.ephemeral(true);

let id = crate::constants::BOOT_IDS.iter()
.filter(|tuple| tuple.1 == boots)
.map(|tuple| tuple.0)
.nth(0);

if let Some(boot_id) = id {
let game_version = env::var("GAME_VERSION").expect("Could not fetch the game version");

response_message = response_message.add_embed(
CreateEmbed::new()
.image(format!("https://ddragon.leagueoflegends.com/cdn/{}/img/item/{}.png", game_version, boot_id as u32))
);
}
},
Ok(Err(error)) => {
response_message =
CreateInteractionResponseMessage::new()
.content(content)
.content(format!("{}", error.message))
.ephemeral(true);

let id = crate::constants::BOOT_IDS.iter()
.filter(|tuple| tuple.1 == boots)
.map(|tuple| tuple.0)
.nth(0);

if let Some(boot_id) = id {
let game_version = env::var("GAME_VERSION").expect("Could not fetch the game version");

response_message = response_message.add_embed(
CreateEmbed::new()
.image(format!("https://ddragon.leagueoflegends.com/cdn/{}/img/item/{}.png", game_version, boot_id as u32))
);
}
} else {
response_message =
},
_ => {
response_message =
CreateInteractionResponseMessage::new()
.content("I could not make a prediction for you...")
.content(format!("Error: Thread failed"))
.ephemeral(true);
}
}

CreateInteractionResponse::Message(response_message)
Expand Down
38 changes: 38 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::fmt;

#[derive(Debug, Clone)]

pub(crate) enum Kind {
REQWUEST,
SERDE,
INTERNAL,
}

pub(crate) struct BootError {
pub(crate) kind: Kind,
pub(crate) message: String,
}

impl fmt::Display for BootError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} ERROR: {}", self.kind, self.message)
}
}

impl From<reqwest::Error> for BootError {
fn from(error: reqwest::Error) -> Self {
BootError {
kind: Kind::REQWUEST,
message: error.to_string(),
}
}
}

impl From<serde_json::Error> for BootError {
fn from(error: serde_json::Error) -> Self {
BootError {
kind: Kind::SERDE,
message: error.to_string(),
}
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod prediction;
mod discord;
mod constants;
mod error;

fn main() {
discord::main();
Expand Down
30 changes: 24 additions & 6 deletions src/prediction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use linfa_trees::{DecisionTree, SplitQuality};
use ndarray::{s, Array1, Array2, ArrayBase, Axis, Dim, OwnedRepr};
use bincode;

use crate::error::{BootError, Kind};

use self::data::get_current_match_data;

pub mod data;
Expand Down Expand Up @@ -107,20 +109,36 @@ pub fn recreate_model(game_count: usize) {
save_model(&tree_model, MODEL_FILE_NAME);
}

pub fn predict(snowflake: &str) -> Option<String> {
pub fn predict(snowflake: &str) -> Result<String, BootError> {
let token: &str = &env::var("RIOT_TOKEN")
.expect("Could not fetch the Riot token");
let base_path = env::var("CONFIG_PATH").unwrap_or(String::new());
let snowflake_map = create_snowflake_puuid_map(&format!("{}{}", base_path, crate::constants::MAPPING_FILE));

if let Some(puuid) = snowflake_map.get(snowflake) {
if let Some(model) = load_model(MODEL_FILE_NAME) {
if let Some(data) = get_current_match_data(puuid, &token) {
return Some(predict_one(&model, &data));
match snowflake_map.get(snowflake) {
Some(puuid) => {
match load_model(MODEL_FILE_NAME) {
Some(model) => {
match get_current_match_data(puuid, &token) {
Ok(data) => return Ok(predict_one(&model, &data)),
Err(err) => return Err(err),
}
},
None => {
return Err(BootError {
kind: Kind::INTERNAL,
message: String::from("I have not been properly trained yet"),
})
}
}
},
None => {
return Err(BootError {
kind: Kind::INTERNAL,
message: String::from("You are not a registered user"),
})
}
}
None
}

fn create_snowflake_puuid_map(file_name: &str) -> HashMap<String, String> {
Expand Down
45 changes: 29 additions & 16 deletions src/prediction/data.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ndarray::{Array1, Array2};
use reqwest::blocking;
use serde_json::{Value, from_str, from_value};
use crate::error::{BootError, Kind};
use std::io::Write;
use std::time::Duration;
use std::thread::sleep;
Expand All @@ -11,7 +12,7 @@ const PAST_GAMES: u32 = 20;
const DEFAULT_RETRY_TIME: u64 = 40;

// https://developer.riotgames.com/apis#spectator-v4/GET_getCurrentGameInfoBySummoner
pub fn get_current_match_data(puuid: &str, token: &str) -> Option<Array1<f32>> {
pub fn get_current_match_data(puuid: &str, token: &str) -> Result<Array1<f32>, BootError> {
let client = blocking::Client::new();
let id_uri: String = ["https://euw1.api.riotgames.com/lol/summoner/v4/summoners/by-puuid/", puuid].join("");
let summoner_id: String;
Expand All @@ -26,8 +27,8 @@ pub fn get_current_match_data(puuid: &str, token: &str) -> Option<Array1<f32>> {
match response {
Ok(res) => {
if res.status().is_success() {
let body = res.text().unwrap();
let parsed_value: Value = from_str(&body).unwrap();
let body = res.text()?;
let parsed_value: Value = from_str(&body)?;
summoner_id = parsed_value.get("id").unwrap().as_str().unwrap().to_string();
break;
} else if res.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
Expand All @@ -43,11 +44,14 @@ pub fn get_current_match_data(puuid: &str, token: &str) -> Option<Array1<f32>> {
sleep(Duration::from_secs(DEFAULT_RETRY_TIME));
}
} else {
return None;
return Err(BootError {
kind: Kind::INTERNAL,
message: String::from("Invalid PUUID"),
});
}
}
Err(_) => {
return None
Err(e) => {
return Err(BootError::from(e));
}
}
}
Expand All @@ -64,33 +68,42 @@ pub fn get_current_match_data(puuid: &str, token: &str) -> Option<Array1<f32>> {
match response {
Ok(res) => {
if res.status().is_success() {
let body = res.text().unwrap();
let parsed_value: Value = from_str(&body).unwrap();
let body = res.text()?;
let parsed_value: Value = from_str(&body)?;
game_info = parsed_value;
break;
} else if res.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
if let Some(retry_after) = res.headers().get("Retry-After") {
if let Ok(retry_seconds) = retry_after.to_str().unwrap_or(&DEFAULT_RETRY_TIME.to_string()).parse::<u64>() {
print!(" /!\\ ({:?})", retry_seconds);
let _ = std::io::stdout().flush();
// print!(" /!\\ ({:?})", retry_seconds);
// let _ = std::io::stdout().flush();
sleep(Duration::from_secs(retry_seconds));
}
} else {
print!(" /!\\ ({:?})", DEFAULT_RETRY_TIME);
let _ = std::io::stdout().flush();
// print!(" /!\\ ({:?})", DEFAULT_RETRY_TIME);
// let _ = std::io::stdout().flush();
sleep(Duration::from_secs(DEFAULT_RETRY_TIME));
}
} else if res.status() == reqwest::StatusCode::NOT_FOUND {
return None;
return Err(BootError {
kind: Kind::INTERNAL,
message: String::from("You are not currently in a game"),
});
}
}
Err(_) => {
return None;
Err(e) => {
return Err(BootError::from(e));
}
}
}

prepare_game_data(game_info, &summoner_id)
match prepare_game_data(game_info, &summoner_id) {
Some(data) => Ok(data),
None => Err(BootError {
kind: Kind::INTERNAL,
message: String::from("I can only make good predictions for ARAM games")
})
}
}

pub fn get_training_data(starting_puuids: Vec<String>, count: usize, token: &str) -> Array2<f32> {
Expand Down

0 comments on commit fc4e5fd

Please sign in to comment.