From 91e195ddbf42dc3268eaf7834ec18f78249088ac Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 18 Aug 2022 02:30:11 +0200 Subject: [PATCH] Update to the new website api for schedule --- src/commands/schedule.rs | 467 +++++++++------------------------------ 1 file changed, 106 insertions(+), 361 deletions(-) diff --git a/src/commands/schedule.rs b/src/commands/schedule.rs index 8e8606e..f155c38 100644 --- a/src/commands/schedule.rs +++ b/src/commands/schedule.rs @@ -1,205 +1,91 @@ -use crate::{Context, Error}; +use crate::{commands::utils, Context, Error}; use cached::proc_macro::cached; -use futures::{Stream, StreamExt}; -use log::*; - -use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; -use poise::serenity_prelude::CreateEmbed; +use chrono::{DateTime, Utc}; +use log::{warn}; use reqwest::header::AUTHORIZATION; -use reqwest::Error as reqError; use serde::Deserialize; -use std::cmp::Ordering::{Greater, Less}; -#[derive(Debug, Clone)] -struct Event { - name: String, +#[derive(Deserialize, Clone)] +struct MSReq { + containers: Vec, + #[serde(rename = "eventTitle")] + event_title: String, +} + +#[derive(Deserialize, Clone)] +struct MSEvent { + id: String, + // #[serde(rename = "longDescription")] + // description: String, + // country: String, + metadata: MSMetadata, +} + +impl MSEvent { + fn get_title(&self) -> String { + let title = &self.metadata.attributes.series.replace("FORMULA", "F"); + format!("**{}: {}**", title, self.metadata.brief) + // format!("", sport=self.content.format.sport, title=self.content.title, synopsis=self.content.synopsis, start=self.times.start.timestamp(), end=self.times.end.timestamp(), desc=self.content.description, id=self.system.product_key) + } + + fn get_value(&self, high_tier: bool) -> String { + let link = if high_tier { + format!("[{id}](https://morningstreams.com/hightier/f1/session/{id})\n", id=self.id) + } else { + "".to_string() + }; + format!("{link}Start: \nEnd:  ", link=link, start=self.metadata.attributes.start.timestamp(), end=self.metadata.attributes.end.timestamp()) + } +} + +#[derive(Deserialize, Clone)] +struct MSMetadata { + // id: String, + // system: System, + #[serde(rename="emfAttributes")] + attributes: EmfAttributes, + #[serde(rename="titleBrief")] + brief: String, + // #[serde(rename="Series")] + // series: String, +} + + +#[derive(Deserialize, Clone)] +struct EmfAttributes { + #[serde(with = "ms_date")] + #[serde(rename="sessionStartDate")] + start: DateTime, + #[serde(with = "ms_date")] + #[serde(rename="sessionEndDate")] + end: DateTime, + #[serde(rename="Series")] series: String, - _lower_series: String, - session: String, - _lower_session: String, - date: DateTime, } -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct F1Data { - #[serde(rename = "seasonContext")] - season_context: SeasonContext, - race: Race, -} -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct SeasonContext { - timetables: Vec, -} -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct Race { - #[serde(rename = "meetingOfficialName")] - name: String, - #[serde(rename = "meetingCountryName")] - country: String, -} -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct TimeTable { - state: String, - session: String, - #[serde(rename = "gmtOffset")] - offset: String, - #[serde(rename = "startTime")] - start: String, - #[serde(rename = "endTime")] - end: String, -} -impl Event { - fn create(name: String, series: String, session: String, date: DateTime) -> Event { - Event { - name, - _lower_series: series.to_ascii_lowercase(), - series, - _lower_session: session.to_ascii_lowercase(), - session, - date, - } - } - fn deref(&self) -> Event { - Event { - name: self.name.to_string(), - _lower_series: self._lower_series.to_string(), - series: self.series.to_string(), - _lower_session: self._lower_session.to_string(), - session: self.session.to_string(), - date: self.date.clone(), - } - } +mod ms_date { + use chrono::{DateTime, Utc, NaiveDateTime}; + use serde::{self, Deserialize, Deserializer}; - fn check_series(&self, series: String) -> bool { - return self._lower_series.contains(&series); - } + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let n = i64::deserialize(deserializer)?/1000; + // let s = String::deserialize(deserializer)?; + + Ok(DateTime::from_utc(NaiveDateTime::from_timestamp(n, 0), Utc)) - fn check_session(&self, session: String) -> bool { - return self._lower_session.contains(&session); - } - #[allow(dead_code)] - fn check(&self, series: String, session: String) -> bool { - if self._lower_session.contains(&session) { - if self._lower_series.contains(&series) { - return true; - } - } - return false; } } -fn parse_time(time: String) -> Option> { - let tim = NaiveDateTime::parse_from_str(&*time, "%Y-%m-%dT%H:%M:%S.%fZ"); - match tim { - Ok(t) => Some(Utc.from_utc_datetime(&t)), - Err(e) => { - warn!("Error on parsing time: {}", e); - None - } - } -} -fn parse_f1_time(mut time: String, offset: &String) -> Option> { - time.push_str(offset); - let tim = DateTime::parse_from_str(&time, "%Y-%m-%dT%H:%M:%S%:z"); - match tim { - Ok(t) => Some(t.with_timezone(&Utc)), - Err(e) => { - warn!("Error on parsing time: {}", e); - None - } - } -} - -// Use cached to cache the requests, don't repeatedly redo this call. Caches for 1 hour. #[cached(time = 3600)] -async fn get_api_events() -> Vec { - let token = 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/events")) - .header(AUTHORIZATION, token) - .send() - .await; - #[derive(Debug, Deserialize)] - struct Data { - category: String, - name: String, - session: String, - date: String, - } - - let result: Option> = match req { - Err(e) => { - warn!("Error getting schedule {}", e); - None - } - Ok(req) => { - info!("Did MS event request status code {}", req.status().as_u16()); - if req.status().as_u16() == 404 { - warn!("404 on getting MS events"); - None - } else if req.status().as_u16() == 200 { - let data: Result, reqError> = req.json().await; - match data { - Err(e) => { - warn!("Error parsing morningstreams event: {}", e); - None - } - Ok(dat) => { - let mut result: Vec = Vec::new(); - for d in dat { - let t = parse_time(d.date.to_string()); - if let Some(tim) = t { - result.push(Event::create( - d.name.to_string(), - d.category.to_string(), - d.session.to_string(), - tim, - )); - } - } - if result.len() > 0 { - result.sort_by(|a, b| a.date.cmp(&b.date)); - Some(result) - } else { - None - } - } - } - } else { - None - } - } - }; - match result { - None => { - return Vec::new(); - } - Some(events) => return events, - } -} - -#[allow(dead_code)] -#[cached(time = 3600)] -async fn get_f1_events() -> Option> { +async fn get_schedule() -> Option { let token = super::super::SETTINGS .read() .unwrap() @@ -213,202 +99,61 @@ async fn get_f1_events() -> Option> { let client = reqwest::Client::new(); let req = client .get(format!( - "https://api.morningstreams.com/api/events/f1/event-tracker" + "https://api.morningstreams.com/api/hightier/f1/next-event" )) .header(AUTHORIZATION, token) .send() .await; - let result: Option> = match req { + let result: Option = match req { Err(e) => { - warn!("Error getting schedule {}", e); + warn!("Error getting Viaplay schedule {}", e); None } - Ok(req) => { - info!("Did MS F1 request status code {}", req.status().as_u16()); - if req.status().as_u16() == 404 { - warn!("404 on getting F1 events"); - return None; - } else if req.status().as_u16() == 200 { - let data: Result = req.json().await; - match data { - Err(e) => { - warn!("Error parsing morningstreams event: {}", e); - None - } - Ok(dat) => { - // return Some(dat); - let mut events: Vec = Vec::new(); - // let mut sessions: Vec = Vec::new(); - for ses in dat.season_context.timetables { - if let Some(start) = parse_f1_time(ses.start, &ses.offset) { - events.push(Event::create( - dat.race.name.to_string(), - "Formula 1".to_string(), - ses.session, - start, - )) - } - } - return Some(events); - } + Ok(req) if req.status().as_u16() == 404 => { + warn!("404 on getting VP 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 Viaplay schedule {}", e); + None } - } else { - None } } + Ok(req) => { + warn!( + "Unhandled status when parsing viaplay request {}", + req.status() + ); + None + } }; - return result; + result } -#[cached(size = 5, time = 3600)] -async fn filter_events(series: String, session: String) -> (Option, Option) { - let mut events: Vec = get_api_events().await; - - if let Some(mut e) = get_f1_events().await { - events.append(&mut e); - } - if events.len() == 0 { - return (None, None); - } else { - let mut next_event: Option<&Event> = None; - let mut previous_event: Option<&Event> = None; - let now = Utc::now(); - for e in &events { - if e.check_series(series.to_string()) && e.check_session(session.to_string()) { - match now.cmp(&e.date) { - // Now is greater (after) event - Greater => { - if let Some(p) = previous_event { - if p.date.cmp(&e.date) == Less { - previous_event = Some(e) - }; - } else { - previous_event = Some(e); - } - } - Less => { - if let Some(f) = next_event { - if f.date.cmp(&e.date) == Greater { - next_event = Some(e) - } - } else { - next_event = Some(e); - } - } - _ => { - next_event = Some(e); - previous_event = Some(e); - } - }; - } - } - - let first: Option = match previous_event { - None => None, - Some(e) => Some(e.deref()), - }; - let second: Option = match next_event { - None => None, - Some(e) => Some(e.deref()), - }; - (first, second) - } -} - -#[allow(dead_code)] -fn build_embed<'a>(event: Event, e: &'a mut CreateEmbed) -> &'a mut CreateEmbed { - e.title(format!("{} | {}", event.series, event.name)); - - // e.description(format!("{}", event.session)); - e.field("Session", &event.session, true); - e.field( - "Starts in", - format!("", &event.date.timestamp()), - true, - ); - e.field( - "Date and time", - format!("", &event.date.timestamp()), - true, - ); - - e.timestamp(event.date); - e -} - -async fn autocomplete_series<'a>( - _ctx: Context<'_>, - partial: &'a str, -) -> impl Stream + 'a { - futures::stream::iter(&["Formula 1", "MotoGP", "IndyCar"]) - .filter(move |name| futures::future::ready(name.starts_with(&partial))) - .map(|name| name.to_string()) -} - -async fn autocomplete_session<'a>( - _ctx: Context<'_>, - partial: &'a str, -) -> impl Stream + 'a { - futures::stream::iter(&[ - "Race", - "Qualifying", - "Free practice 3", - "Free practice 2", - "Free practice 1", - "Sprint race", - ]) - .filter(move |name| futures::future::ready(name.starts_with(&partial))) - .map(|name| name.to_string()) -} - -/// F1 schedules #[poise::command(slash_command)] pub async fn schedule( ctx: Context<'_>, - #[description = "Which series to look for"] - #[autocomplete = "autocomplete_series"] - series: Option, - #[description = "Which session to look for"] - #[autocomplete = "autocomplete_session"] - session: Option, ) -> Result<(), Error> { - let serie: String = match series { - None => "".to_string(), - Some(ser) => { - if vec![ - "f1".to_string(), - "formula 1".to_string(), - "formula1".to_string(), - ] - .contains(&ser.to_ascii_lowercase()) - { - "formula 1".to_string() - } else { - ser.to_ascii_lowercase() - } + let events: Option = get_schedule().await; + let ht: bool = utils::high_tier(ctx).await; + match events { + None => {ctx.say("Error on fetching events :(").await?;}, + Some(evs) => { + ctx.send(|b| {b.embed(|e| { + e.title(format!("F1 schedule: {}", evs.event_title)); + for event in evs.containers { + e.field(event.get_title(), event.get_value(ht), true); + }; + e + })}).await?; } }; - let session: String = match session { - None => "".to_string(), - Some(s) => s.to_ascii_lowercase(), - }; - // Get the events (This will hopefully be cached) - let (previous_event, next_event) = filter_events(serie, session.to_string()).await; - - // Do the event sending thingy... - if let Some(e) = next_event { - ctx.send(|b| b.embed(|em| build_embed(e, em))).await?; - } else if let Some(e) = previous_event { - ctx.send(|b| { - b.embed(|em| build_embed(e, em)); - b.content("No future events found, showing most recent") - }) - .await?; - } else { - ctx.say("No future events found, showing most recent") - .await?; - }; Ok(()) -} + +} \ No newline at end of file