From 35b0d8f065dd9d5e3c55d02adb5cc806ff806d48 Mon Sep 17 00:00:00 2001 From: Joey Pollack Date: Thu, 12 Oct 2023 14:55:57 -0400 Subject: [PATCH] adds racetime.gg race polling and tracking --- Cargo.toml | 2 + src/commands/test.rs | 5 +++ src/main.rs | 13 ++++-- src/racetime/mod.rs | 100 +++++++++++++++++++++++++++++++++++++++++++ src/racetime/race.rs | 56 ++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/racetime/mod.rs create mode 100644 src/racetime/race.rs diff --git a/Cargo.toml b/Cargo.toml index 33863d7..2cc5d7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ edition = "2021" rand = "0.8.5" serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } tokio = { version = "1.21.1", features = ["full"] } +reqwest = { version = "0.11", features = ["json"] } +serde_json = "1.0" diff --git a/src/commands/test.rs b/src/commands/test.rs index 3e44989..3674992 100644 --- a/src/commands/test.rs +++ b/src/commands/test.rs @@ -55,4 +55,9 @@ pub async fn parse_command(ctx: Context, msg: Message, server_id: GuildId) return; } + + if msg.content == ".race_test" + { + // TODO: Get race data from ctx.data and print it to main log channel + } } diff --git a/src/main.rs b/src/main.rs index ed09a37..3ea2d10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,11 @@ mod commands; mod data_loader; mod utils; +mod racetime; -// use std::collections::HashSet; +use racetime::{race::Races}; + +use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::{AtomicBool, Ordering}; @@ -11,7 +14,6 @@ use std::time::Duration; use serenity::async_trait; use serenity::prelude::*; -// use serenity::model::prelude::*; use serenity::model::channel::Message; use serenity::model::gateway::Ready; use serenity::model::id::GuildId; @@ -86,8 +88,9 @@ impl EventHandler for Handler // TODO: function we want to run called here // Run racetime.gg and twitch api checks // Post new stuff to discord channel + racetime::poll_races(Arc::clone(&ctx1)).await; - tokio::time::sleep(Duration::from_secs(120)).await; + tokio::time::sleep(Duration::from_secs(60)).await; } }); @@ -116,12 +119,13 @@ async fn main() }; - + //let race = Race { name: String::from("Test"), game: String::from("Test"), goal: String::from("Test"), status: Status::Open }; println!("Connecting..."); let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::DIRECT_MESSAGES + | GatewayIntents::GUILDS | GatewayIntents::MESSAGE_CONTENT; // Create a new instance of the Client, logging in as a bot. This will @@ -140,6 +144,7 @@ async fn main() let mut data = client.data.write().await; data.insert::(Arc::new(AtomicU64::new(owner.into()))); + data.insert::(Arc::new(RwLock::new(HashMap::new()))); } diff --git a/src/racetime/mod.rs b/src/racetime/mod.rs new file mode 100644 index 0000000..b3878ee --- /dev/null +++ b/src/racetime/mod.rs @@ -0,0 +1,100 @@ + +#![allow(dead_code)] + +pub mod race; +use race::{Race, Races}; + +use std::sync::Arc; +use serenity::prelude::*; +use serde_json::Value; +use crate::utils; + +pub async fn poll_races(ctx: Arc) +{ + let race_data = match fetch_race_data(ctx.clone()).await + { + Ok(v) => v, + Err(why) => { utils::Logger::log_error((*ctx).clone(), &format!("Failed to query racetime.gg: {}", why)).await; return; } + }; + + let race_array = match race_data["races"].as_array() + { + Some(arr) => arr, + None => { utils::Logger::log_error((*ctx).clone(), &format!("Race data was not an array")).await; return; } + }; + + + { + let mut data = ctx.data.write().await; + let mut races = match data.get_mut::() + { + Some(r) => r.write().await, + None => { utils::Logger::log_error((*ctx).clone(), &format!("Couldn't get Races HashMap from the data type map")).await; return; } + }; + + for race in race_array + { + //println!("\tGame: {}\n\tCategory: {}", race["category"]["name"], race["goal"]["name"]); + let key = race["name"].to_string().strip_prefix("\"").unwrap().to_string(); + if key.starts_with("sm") || key.starts_with("smr") || key.starts_with("smz3") + { + let race = Race::from_json(race); + if !races.contains_key(&key) + { + races.insert(key.clone(), race.clone()); + + // TODO: Send announcement to sm server + + // TEST ANNOUNCEMENT + // utils::Logger::log_message((*ctx).clone(), &format!("New race started for: {} - **goal:** {} - **url:** {}", races[&key].game, races[&key].goal, races[&key].url)).await; + } + else + { + if races[&key].status != race.status + { + races.get_mut(&key).unwrap().status = race.status; + } + + if races[&key].goal != race.goal + { + races.get_mut(&key).unwrap().goal = race.goal; + + + // TODO: Send announcement to sm server + + // TEST ANNOUNCEMENT + //utils::Logger::log_message((*ctx).clone(), &format!("Goal set: {} for: {}", races[&key].goal, races[&key].url)).await; + } + } + } + } + } +} + + +//////////// HELPERS +async fn fetch_race_data(ctx: Arc) -> Result +{ + let result = reqwest::get("https://racetime.gg/races/data").await; + let response = match result + { + Ok(r) => r, + Err(why) => { return Err(format!("Failed to query racetime.gg: {}", why)); } + }; + + // let response: Value = serde_json::from_str(res.text().await.expect("reqwest::text() failed").as_str()).expect("Failed to convert to json"); + let body_result = response.text().await; + let body = match body_result + { + Ok(b) => b, + Err(why) => { return Err(format!("Failed to query racetime.gg: {}", why)); } + }; + + let body_json: Value = match serde_json::from_str(body.as_str()) + { + Ok(j) => j, + Err(why) => {return Err(format!("Failed to convert racetime query to json: {}", why)); } + }; + + Ok(body_json) +} diff --git a/src/racetime/race.rs b/src/racetime/race.rs new file mode 100644 index 0000000..420929e --- /dev/null +++ b/src/racetime/race.rs @@ -0,0 +1,56 @@ + + +use std::sync::Arc; +use serenity::prelude::*; +use std::collections::HashMap; +use tokio::sync::RwLock; +use serde_json::Value; + +#[derive(Copy, Clone, PartialEq)] +pub enum Status +{ + Open, + Invitational, + Pending, + InProgress, + Finished, + Cancelled +} + +#[derive(Clone)] +pub struct Race +{ + pub name: String, + pub game: String, + pub goal: String, + pub status: String, + pub url: String, +} + +impl Race +{ + pub fn new(name: String, game: String, goal: String, status: String, url: String) -> Race + { + Race { name, game, goal, status, url } + } + + pub fn from_json(json: &Value) -> Race + { + let url = String::from("https://racetime.gg") + json["url"].as_str().unwrap(); + Race { + name: json["name"].to_string(), + game: json["category"]["name"].to_string(), + goal: json["goal"]["name"].to_string(), + status: json["status"]["value"].to_string(), + url: url + } + } +} + +pub struct Races; + +impl TypeMapKey for Races +{ + // HashMap key is the name of the race + type Value = Arc>>; +} \ No newline at end of file