use crate::{commands::utils, Context, Error}; use cached::proc_macro::cached; use chrono::{DateTime, Duration, Utc}; use log::{info, warn}; use reqwest::header::AUTHORIZATION; use serde::Deserialize; use super::Timeframe; mod str_to_u8 { use serde::{self, Deserialize, Deserializer}; pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; Ok(s.parse::().unwrap()) } } mod nfl_date { use chrono::{DateTime, TimeZone, Utc}; use serde::{self, Deserialize, Deserializer}; const FORMAT: &'static str = "%Y-%m-%dT%H:%M:%S.%fZ"; pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; Utc.datetime_from_str(&s, FORMAT) .map_err(serde::de::Error::custom) } } #[derive(Deserialize, Clone)] #[serde(rename_all = "camelCase")] struct NFLContext { // current_season: String, // current_season_type: String, #[serde(with = "str_to_u8")] current_week: u8, } #[derive(Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct NFLEvent { home_nick_name: String, visitor_nick_name: String, // game_id: String, #[serde(with = "nfl_date")] game_date_time_utc: DateTime, video: NFLVideo, } #[derive(Deserialize, Clone)] #[serde(rename_all = "camelCase")] struct NFLVideo { title: String, // video_status: String, video_id: String, // schedule_date: String } impl NFLEvent { pub fn filter(&self, filter: &str) -> bool { if filter.is_empty() { return true; }; if self.video.title.contains(filter) { return true; } false } pub fn comp(&self, when: &Timeframe) -> bool { let now = Utc::now(); match when { Timeframe::Everything => true, Timeframe::Current => { self.game_date_time_utc <= now && (self.game_date_time_utc + Duration::minutes(240)) >= now } Timeframe::Future => (self.game_date_time_utc + Duration::minutes(240)) >= now, Timeframe::Past => self.game_date_time_utc <= now, // _ => (self.game_date_time_utc + Duration::minutes(240)) >= now, } } pub fn to_string(&self) -> String { format!( "```fix\n{home}-{away} ||{title}``` https://tom.al/ms/nfl/{id}", title = self.video.title, id = self.video.video_id, time = self.game_date_time_utc.timestamp(), home = self.home_nick_name, away = self.visitor_nick_name, ) } pub fn get_key(&self) -> (DateTime, String) { (self.game_date_time_utc, self.video.title.to_owned()) } } #[cached(time = 3600)] async fn get_week() -> Option { let token = super::super::super::SETTINGS .read() .unwrap() .get_table("morningstreams") .unwrap() .get("token") .expect("Config error, please set the morningstreams[token] value") .clone() .into_string() .expect("Config error, please make sure morningstreams[token] is a string"); let client = reqwest::Client::new(); let req = client .get("https://api.morningstreams.com/api/hightier/nfl/current-context") .header(AUTHORIZATION, token) .send() .await; match req { Err(e) => { warn!("Error getting NFL context {}", e); None } Ok(req) => match req.json::().await { Ok(data) => Some(data), Err(e) => { warn!("Error parsing NFL context {}", e); None } }, } } pub async fn get_current_schedule() -> Option> { match get_week().await { None => return None, Some(w) => return get_schedule(w.current_week).await, } } #[cached(time = 3600)] async fn get_schedule(week: u8) -> Option> { let token = super::super::super::SETTINGS .read() .unwrap() .get_table("morningstreams") .unwrap() .get("token") .expect("Config error, please set the morningstreams[token] value") .clone() .into_string() .expect("Config error, please make sure morningstreams[token] is a string"); let client = reqwest::Client::new(); let req = client .get(format!( "https://api.morningstreams.com/api/hightier/nfl/schedule/{}", week )) .header(AUTHORIZATION, token) .send() .await; let result: Option> = match req { Err(e) => { warn!("Error getting NFL schedule {}", e); None } Ok(req) if req.status().as_u16() == 404 => { warn!("404 on getting NFL events"); None } Ok(req) if req.status().as_u16() == 200 => { let data = req.json::>().await; match data { Ok(d) => Some(d), Err(e) => { warn!("Error getting NFL schedule {}", e); None } } } Ok(req) => { warn!("Unhandled status when parsing NFL request {}", req.status()); None } }; result } //NFL events listing #[poise::command(slash_command, ephemeral)] pub async fn nfl( ctx: Context<'_>, #[description = "Filter sessions for when they are/were happening, defaults to future"] timeframe: Option, #[description = "Content to filter on"] filter: Option, #[description = "Which game week? (Defaults to current)"] week: Option, ) -> Result<(), Error> { let tf = match timeframe { None => Timeframe::Future, Some(tf) => tf, }; let get_week: u8 = match week { Some(w) => w, None => match get_week().await { None => { ctx.say("Error getting current week data, try setting one manually") .await?; return Ok(()); } Some(w) => w.current_week, }, }; let events: Option> = get_schedule(get_week).await; match events { None => { ctx.say("Unable to get the events, try a different game week or try again later (it's cached so wait a bit...)") .await?; } Some(evs) => { info!("Found {} events from NFL", evs.len()); let filtered: Vec = evs .into_iter() .filter(|e| e.comp(&tf)) .filter(|e| match &filter { None => true, Some(f) => e.filter(f.as_str()), }) .map(|e| e.to_string()) .collect(); let pages = utils::paginator(filtered, 1900, "\n".to_string()); utils::paginate_string(ctx, pages).await?; } }; Ok(()) }