use crate::{Context, Error}; use cached::proc_macro::cached; use log::warn; use poise::serenity_prelude as serenity; use poise::serenity_prelude::CreateSelectMenuOption; use reqwest::header::AUTHORIZATION; use serde::Deserialize; async fn autocomplete_season<'a>( _ctx: Context<'_>, partial: &'a str, ) -> impl Iterator + 'a { (1981..2023) .map(|n: u32| n.to_string()) .rev() .filter(move |e| e.starts_with(&partial)) .map(|e| e.to_string()) } #[derive(Deserialize, Clone)] struct MSReq { containers: Vec, } #[derive(Deserialize, Clone)] struct MSEvent { id: 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) -> String { format!( "[{id}](https://morningstreams.com/hightier/f1/session/{id})\nDuration: {duration}", id = self.id, duration = self.metadata.duration ) } } #[derive(Deserialize, Clone)] struct MSMetadata { title: String, #[serde(rename = "emfAttributes")] attributes: EmfAttributes, #[serde(rename = "titleBrief")] brief: String, #[serde(rename = "uiDuration")] duration: String, } #[derive(Deserialize, Clone)] struct EmfAttributes { #[serde(rename = "Series")] series: String, } #[derive(Deserialize, Clone)] struct Season { #[serde(rename = "containers")] events: Vec, } #[derive(Deserialize, Clone)] struct EventData { metadata: SeasonMetadata, } #[derive(Deserialize, Clone)] struct SeasonMetadata { #[serde(rename = "emfAttributes")] attributes: SeasonAttributes, } #[derive(Deserialize, Clone)] struct SeasonAttributes { #[serde(rename = "MeetingKey")] id: String, #[serde(rename = "Meeting_Official_Name")] name: String, #[serde(rename = "Meeting_Name")] short_name: String, } #[cached(time = 18000)] async fn get_event(session: String) -> 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/f1/event/{}", session )) .header(AUTHORIZATION, token) .send() .await; let result: Option = match req { Err(e) => { warn!("Error getting F1TV schedule {}", e); None } Ok(req) if req.status().as_u16() == 404 => { warn!("404 on getting F1TV 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 F1TV schedule {}", e); None } } } Ok(req) => { warn!( "Unhandled status when parsing F1TV request {}", req.status() ); None } }; result } #[cached(time = 18000)] async fn get_sessions(season: String) -> 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/f1/season/{}", season )) .header(AUTHORIZATION, token) .send() .await; let result: Option = match req { Err(e) => { warn!("Error getting F1TV schedule {}", e); None } Ok(req) if req.status().as_u16() == 404 => { warn!("404 on getting F1TV 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 F1TV schedule {}", e); None } } } Ok(req) => { warn!( "Unhandled status when parsing F1TV request {} text: {}", req.status(), req.text().await.expect( "Whoops not getting the text out of this one, F1TV status error unknown" ) ); None } }; result } // F1TV links throughout the seasons #[poise::command(slash_command, ephemeral)] pub async fn f1( ctx: Context<'_>, #[description = "Which season to pull from?"] #[autocomplete = "autocomplete_season"] season: String, ) -> Result<(), Error> { let sessions: Option = get_sessions(season).await; match sessions { None => { ctx.say("Unable to find data for selected season").await?; } Some(ses) if ses.events.len() == 0 => { ctx.say("Found 0 events for this season :(").await?; } Some(sessions) => { let content_id = ctx.id(); let options: Vec<_> = sessions .events .iter() .map(|e| { CreateSelectMenuOption::default() .label(&e.metadata.attributes.short_name) .description(&e.metadata.attributes.name) .value(&e.metadata.attributes.id) .to_owned() }) .collect(); ctx.send(|m| { m.content("Please select a session").components(|c| { c.create_action_row(|ar| { ar.create_select_menu(|menu| { menu.custom_id(content_id); menu.options(|opt| opt.set_options(options)) }) }) }) }) .await?; while let Some(mci) = serenity::CollectComponentInteraction::new(ctx) .channel_id(ctx.channel_id()) .filter(move |mci| mci.data.custom_id == content_id.to_string()) .await { println!("Got new interaction event"); let session = mci.data.values[0].parse::().unwrap(); let ses = get_event(session).await; println!("Now responding to interaction"); match mci .create_interaction_response(ctx, |ir| { ir.kind(serenity::InteractionResponseType::UpdateMessage) .interaction_response_data(|m| match ses { None => m.content("Unable to get these events :("), Some(evs) if evs.containers.len() == 0 => { m.content("Somehow no sessions found for this event :(") } Some(evs) => { println!("editing message"); m.content(evs.containers[0].metadata.title.to_string()); m.embed(|e| { e.title(format!( "F1 schedule: {}", evs.containers[0].metadata.title )); for event in evs.containers { e.field(event.get_title(), event.get_value(), true); } e }) } }) }) .await { Err(e) => { println!("Error on interaction response {}", e); } // Ok(o) => {println!("No error on interaction response, here's what we got back: {}", o);}, _ => { (); } }; println!("Reached end of while loop iteration, going back?"); } println!("Exited while loop for F1TV links"); } } Ok(()) }