From 0000312bae84efa0a6bd6822df3e10d6170aa754 Mon Sep 17 00:00:00 2001 From: Xyon Date: Tue, 29 Aug 2023 16:09:27 +0100 Subject: [PATCH 1/6] Experiment; weather forecast rendering --- src/autocomplete_helpers/mod.rs | 0 src/commands/weather.rs | 124 ++++++++++++++------------------ src/helpers/mod.rs | 56 +++++++++++++++ src/lib.rs | 2 +- 4 files changed, 110 insertions(+), 72 deletions(-) delete mode 100644 src/autocomplete_helpers/mod.rs create mode 100644 src/helpers/mod.rs diff --git a/src/autocomplete_helpers/mod.rs b/src/autocomplete_helpers/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/weather.rs b/src/commands/weather.rs index 8ab54ba..79d7899 100644 --- a/src/commands/weather.rs +++ b/src/commands/weather.rs @@ -1,6 +1,7 @@ use poise::serenity_prelude::utils::Colour; use d20::roll_range; use poise::ReplyHandle; +use poise::serenity_prelude::CreateEmbed; use crate::{ManifoldCommand, ManifoldContext}; use crate::models::weather::{Weather, WeatherForecastRequestResponse}; @@ -15,85 +16,66 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo let responses = &ctx.data().responses; + let mut pages = Vec::::new(); + + let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8); + pages.push(CreateEmbed::default() + .colour(card_colour) + .title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country)) + .description(format!("Observations recorded at {}.", weather_forecast.current.last_updated)) + .image(format!("https:{}", weather_forecast.current.condition.icon)) + .fields(vec![ + ("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", weather_forecast.current.temp_c, weather_forecast.current.temp_f, weather_forecast.current.dewpoint_c.unwrap_or(0.0), weather_forecast.current.dewpoint_f.unwrap_or(0.0)), true), + ("Feels like", format!("{}°C/{}°F", weather_forecast.current.feelslike_c, weather_forecast.current.feelslike_f), true), + ("Condition", format!("{}", weather_forecast.current.condition.text), true), + ("Pressure", format!("{}mb/{}in", weather_forecast.current.pressure_mb, weather_forecast.current.pressure_in), true), + ("Precipitation", format!("{}mm/{}in", weather_forecast.current.precip_mm, weather_forecast.current.precip_in), true), + ("Humidity", format!("{}%", weather_forecast.current.humidity), true), + ("Cloud coverage", format!("{}%", weather_forecast.current.cloud), true), + ("UV index", format!("{}", weather_forecast.current.uv), true), + ("Coordinates", format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true), + ]) + .field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", weather_forecast.current.wind_mph, weather_forecast.current.wind_kph, weather_forecast.current.wind_dir, weather_forecast.current.wind_degree, weather_forecast.current.gust_mph, weather_forecast.current.gust_kph), false) + .footer(|f| { + f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string()))); + f + }).to_owned() + ); + + if let Some(f) = weather_forecast.forecast.forecastday.first() { + f.hour.iter().for_each(|f| { + pages.push(CreateEmbed::default() + .colour(card_colour) + .title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country)) + .description(format!("Forecast for {} made at {}.", f.time, weather_forecast.current.last_updated)) + .image(format!("https:{}", f.condition.icon)) + .fields(vec![ + ("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", f.temp_c, f.temp_f, f.dewpoint_c, f.dewpoint_f), true), + ("Feels like", format!("{}°C/{}°F", f.feelslike_c, f.feelslike_f), true), + ("Condition", format!("{}", f.condition.text), true), + ("Pressure", format!("{}mb/{}in", f.pressure_mb, f.pressure_in), true), + ("Precipitation", format!("{}mm/{}in", f.precip_mm, f.precip_in), true), + ("Humidity", format!("{}%", f.humidity), true), + ("Cloud coverage", format!("{}%", f.cloud), true), + ("Coordinates", format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true), + ]) + .field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", f.wind_mph, f.wind_kph, f.wind_dir, f.wind_degree, f.gust_mph, f.gust_kph), false) + .footer(|f| { + f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string()))); + f + }).to_owned()); + }); + } + my_message.edit(ctx, |m| { m.content(""); - m.embed(|e| { - - let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8); - - e.colour(card_colour); - e.title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country)); - e.description(format!("Observations recorded at {}.", weather_forecast.current.last_updated)); - e.image(format!("https:{}", weather_forecast.current.condition.icon)); - e.fields(vec![ - ("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", weather_forecast.current.temp_c, weather_forecast.current.temp_f, weather_forecast.current.dewpoint_c.unwrap_or(0.0), weather_forecast.current.dewpoint_f.unwrap_or(0.0)), true), - ("Feels like", format!("{}°C/{}°F", weather_forecast.current.feelslike_c, weather_forecast.current.feelslike_f), true), - ("Condition", format!("{}", weather_forecast.current.condition.text), true), - ("Pressure", format!("{}mb/{}in", weather_forecast.current.pressure_mb, weather_forecast.current.pressure_in), true), - ("Precipitation", format!("{}mm/{}in", weather_forecast.current.precip_mm, weather_forecast.current.precip_in), true), - ("Humidity", format!("{}%", weather_forecast.current.humidity), true), - ("Cloud coverage", format!("{}%", weather_forecast.current.cloud), true), - ("UV index", format!("{}", weather_forecast.current.uv), true), - ("Coordinates", format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true), - ]); - e.field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", weather_forecast.current.wind_mph, weather_forecast.current.wind_kph, weather_forecast.current.wind_dir, weather_forecast.current.wind_degree, weather_forecast.current.gust_mph, weather_forecast.current.gust_kph), false); - e.footer(|f| { - f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string()))); - f - }); - e - }); + m.embeds.extend(pages); m }).await?; Ok(()) } -/* -#[command] -#[aliases("wf")] -pub async fn weather_forecast(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - - let mut my_message = msg.reply_ping(&ctx, "Retrieving weather, be patient").await?; - - let weather_forecast = _get_weather(ctx, msg, args, Some(3)).await?; - - let forecast_index: usize = (chrono::NaiveDateTime::parse_from_str(&*weather_forecast.location.localtime, "%Y-%m-%d %H:%M").unwrap().hour() as usize) + 1; - - debug!("{:?}", weather_forecast); - - my_message.edit(&ctx, |m| { - m.content(""); - - for day in &weather_forecast.forecast.forecastday { - m.add_embed(|e| { - - let this_forecast = &day.hour[forecast_index]; - let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8); - - e.colour(card_colour); - e.title(format!("Weather for {} at {}, {}, {}", this_forecast.time, weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country)); - e.description(format!("Observations recorded at {}.", weather_forecast.current.last_updated)); - e.fields(vec![ - ("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", this_forecast.temp_c, this_forecast.temp_f, this_forecast.dewpoint_c, this_forecast.dewpoint_f), true), - ("Feels like", format!("{}°C/{}°F", this_forecast.feelslike_c, this_forecast.feelslike_f), true), - ("Condition", format!("{}", this_forecast.condition.text), true), - ("Pressure", format!("{}mb/{}in", this_forecast.pressure_mb, this_forecast.pressure_in), true), - ("Precipitation", format!("{}mm/{}in", this_forecast.precip_mm, this_forecast.precip_in), true), - ("Humidity", format!("{}%", this_forecast.humidity), true), - ]); - e.field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", this_forecast.wind_mph, this_forecast.wind_kph, this_forecast.wind_dir, this_forecast.wind_degree, this_forecast.gust_mph, this_forecast.gust_kph), false); - e - }); - } - - m - }).await?; - - Ok(()) -} - */ - #[poise::command(slash_command, prefix_command, aliases("wl"))] pub async fn save_weather_location(ctx: ManifoldContext<'_>, #[rest] #[description="Your default weather location"] location: String) -> ManifoldResult<()> { diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs new file mode 100644 index 0000000..5c6c2cd --- /dev/null +++ b/src/helpers/mod.rs @@ -0,0 +1,56 @@ +use crate::error::ManifoldResult; +use crate::ManifoldContext; +use poise::serenity_prelude; + +pub async fn paginate(ctx: ManifoldContext<'_>, pages: &[&str]) -> ManifoldResult<()> { + // Define some unique identifiers for the navigation buttons + let ctx_id = ctx.id(); + let prev_button_id = format!("{}prev", ctx.id()); + let next_button_id = format!("{}next", ctx.id()); + + // Send the embed with the first page as content + let mut current_page = 0; + ctx.send(|b| { + b.embed(|b| b.description(pages[current_page])) + .components(|b| { + b.create_action_row(|b| { + b.create_button(|b| b.custom_id(&prev_button_id).emoji('◀')) + .create_button(|b| b.custom_id(&next_button_id).emoji('▶')) + }) + }) + }) + .await?; + + // Loop through incoming interactions with the navigation buttons + while let Some(press) = serenity_prelude::CollectComponentInteraction::new(ctx) + // We defined our button IDs to start with `ctx_id`. If they don't, some other command's + // button was pressed + .filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string())) + // Timeout when no navigation button has been pressed for 24 hours + .timeout(std::time::Duration::from_secs(3600 * 24)) + .await + { + // Depending on which button was pressed, go to next or previous page + if press.data.custom_id == next_button_id { + current_page += 1; + if current_page >= pages.len() { + current_page = 0; + } + } else if press.data.custom_id == prev_button_id { + current_page = current_page.checked_sub(1).unwrap_or(pages.len() - 1); + } else { + // This is an unrelated button interaction + continue; + } + + // Update the message with the new page contents + press + .create_interaction_response(ctx, |b| { + b.kind(serenity_prelude::InteractionResponseType::UpdateMessage) + .interaction_response_data(|b| b.embed(|b| b.description(pages[current_page]))) + }) + .await?; + } + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 7c6e472..267b30a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ use crate::models::user::{ManifoldUserInfo, UserInfo}; use crate::models::fueltank::FuelTank; use crate::responses::Responses; -pub mod autocomplete_helpers; +pub mod helpers; pub mod config; pub mod error; pub mod events; From 90be789c6bd9f6a1fdde1dc51c99a12fbf9ff1d2 Mon Sep 17 00:00:00 2001 From: Xyon Date: Tue, 29 Aug 2023 16:32:33 +0100 Subject: [PATCH 2/6] Pagination fixes --- src/commands/weather.rs | 7 ++----- src/helpers/mod.rs | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/commands/weather.rs b/src/commands/weather.rs index 79d7899..5acc669 100644 --- a/src/commands/weather.rs +++ b/src/commands/weather.rs @@ -7,6 +7,7 @@ use crate::{ManifoldCommand, ManifoldContext}; use crate::models::weather::{Weather, WeatherForecastRequestResponse}; use crate::models::user::UserInfo; use crate::error::ManifoldResult; +use crate::helpers::paginate; #[poise::command(slash_command, prefix_command, aliases("w"))] async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to look up weather for"] location: Option) -> ManifoldResult<()> { @@ -67,11 +68,7 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo }); } - my_message.edit(ctx, |m| { - m.content(""); - m.embeds.extend(pages); - m - }).await?; + paginate(ctx, my_message, pages).await?; Ok(()) } diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 5c6c2cd..1a9f776 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -1,8 +1,9 @@ use crate::error::ManifoldResult; use crate::ManifoldContext; -use poise::serenity_prelude; +use poise::{ReplyHandle, serenity_prelude}; +use poise::serenity_prelude::CreateEmbed; -pub async fn paginate(ctx: ManifoldContext<'_>, pages: &[&str]) -> ManifoldResult<()> { +pub async fn paginate(ctx: ManifoldContext<'_>, message: ReplyHandle<'_>, pages: Vec) -> ManifoldResult<()> { // Define some unique identifiers for the navigation buttons let ctx_id = ctx.id(); let prev_button_id = format!("{}prev", ctx.id()); @@ -10,16 +11,15 @@ pub async fn paginate(ctx: ManifoldContext<'_>, pages: &[&str]) -> ManifoldResul // Send the embed with the first page as content let mut current_page = 0; - ctx.send(|b| { - b.embed(|b| b.description(pages[current_page])) - .components(|b| { - b.create_action_row(|b| { - b.create_button(|b| b.custom_id(&prev_button_id).emoji('◀')) - .create_button(|b| b.custom_id(&next_button_id).emoji('▶')) - }) + message.edit(ctx, |m| { + m.embeds = vec![pages[current_page].to_owned()]; + m.components(|b| { + b.create_action_row(|b| { + b.create_button(|b| b.custom_id(&prev_button_id).emoji('◀')) + .create_button(|b| b.custom_id(&next_button_id).emoji('▶')) }) - }) - .await?; + }) + }).await?; // Loop through incoming interactions with the navigation buttons while let Some(press) = serenity_prelude::CollectComponentInteraction::new(ctx) @@ -47,7 +47,7 @@ pub async fn paginate(ctx: ManifoldContext<'_>, pages: &[&str]) -> ManifoldResul press .create_interaction_response(ctx, |b| { b.kind(serenity_prelude::InteractionResponseType::UpdateMessage) - .interaction_response_data(|b| b.embed(|b| b.description(pages[current_page]))) + .interaction_response_data(|b| b.set_embeds(vec![pages[current_page.clone()].to_owned()])) }) .await?; } From f58193ddf758c8ff5f540924c99f7cefb48b66a6 Mon Sep 17 00:00:00 2001 From: Xyon Date: Tue, 29 Aug 2023 16:44:57 +0100 Subject: [PATCH 3/6] Discard _current_ and use forecast with current page set to now --- src/commands/weather.rs | 25 ++----------------------- src/helpers/mod.rs | 3 +-- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/src/commands/weather.rs b/src/commands/weather.rs index 5acc669..03c8269 100644 --- a/src/commands/weather.rs +++ b/src/commands/weather.rs @@ -1,3 +1,4 @@ +use chrono::{Timelike, Utc}; use poise::serenity_prelude::utils::Colour; use d20::roll_range; use poise::ReplyHandle; @@ -20,28 +21,6 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo let mut pages = Vec::::new(); let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8); - pages.push(CreateEmbed::default() - .colour(card_colour) - .title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country)) - .description(format!("Observations recorded at {}.", weather_forecast.current.last_updated)) - .image(format!("https:{}", weather_forecast.current.condition.icon)) - .fields(vec![ - ("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", weather_forecast.current.temp_c, weather_forecast.current.temp_f, weather_forecast.current.dewpoint_c.unwrap_or(0.0), weather_forecast.current.dewpoint_f.unwrap_or(0.0)), true), - ("Feels like", format!("{}°C/{}°F", weather_forecast.current.feelslike_c, weather_forecast.current.feelslike_f), true), - ("Condition", format!("{}", weather_forecast.current.condition.text), true), - ("Pressure", format!("{}mb/{}in", weather_forecast.current.pressure_mb, weather_forecast.current.pressure_in), true), - ("Precipitation", format!("{}mm/{}in", weather_forecast.current.precip_mm, weather_forecast.current.precip_in), true), - ("Humidity", format!("{}%", weather_forecast.current.humidity), true), - ("Cloud coverage", format!("{}%", weather_forecast.current.cloud), true), - ("UV index", format!("{}", weather_forecast.current.uv), true), - ("Coordinates", format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true), - ]) - .field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", weather_forecast.current.wind_mph, weather_forecast.current.wind_kph, weather_forecast.current.wind_dir, weather_forecast.current.wind_degree, weather_forecast.current.gust_mph, weather_forecast.current.gust_kph), false) - .footer(|f| { - f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string()))); - f - }).to_owned() - ); if let Some(f) = weather_forecast.forecast.forecastday.first() { f.hour.iter().for_each(|f| { @@ -68,7 +47,7 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo }); } - paginate(ctx, my_message, pages).await?; + paginate(ctx, my_message, pages, (Utc::now().hour() + 1) as usize).await?; Ok(()) } diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 1a9f776..3818bbd 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -3,14 +3,13 @@ use crate::ManifoldContext; use poise::{ReplyHandle, serenity_prelude}; use poise::serenity_prelude::CreateEmbed; -pub async fn paginate(ctx: ManifoldContext<'_>, message: ReplyHandle<'_>, pages: Vec) -> ManifoldResult<()> { +pub async fn paginate(ctx: ManifoldContext<'_>, message: ReplyHandle<'_>, pages: Vec, mut current_page: usize) -> ManifoldResult<()> { // Define some unique identifiers for the navigation buttons let ctx_id = ctx.id(); let prev_button_id = format!("{}prev", ctx.id()); let next_button_id = format!("{}next", ctx.id()); // Send the embed with the first page as content - let mut current_page = 0; message.edit(ctx, |m| { m.embeds = vec![pages[current_page].to_owned()]; m.components(|b| { From 61bd91560acc57d3c8cdf6045c858d73feb87b9e Mon Sep 17 00:00:00 2001 From: Xyon Date: Tue, 29 Aug 2023 17:05:57 +0100 Subject: [PATCH 4/6] Paginated weather forecasting --- src/commands/weather.rs | 6 ++++-- src/helpers/mod.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/commands/weather.rs b/src/commands/weather.rs index 03c8269..47bd1c3 100644 --- a/src/commands/weather.rs +++ b/src/commands/weather.rs @@ -1,4 +1,4 @@ -use chrono::{Timelike, Utc}; +use chrono::{NaiveDateTime, Timelike}; use poise::serenity_prelude::utils::Colour; use d20::roll_range; use poise::ReplyHandle; @@ -47,7 +47,9 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo }); } - paginate(ctx, my_message, pages, (Utc::now().hour() + 1) as usize).await?; + let now = NaiveDateTime::parse_from_str(weather_forecast.current.last_updated.as_str(), "%Y-%m-%d %H:%M").unwrap_or(NaiveDateTime::default()); + + paginate(ctx, my_message, pages, (now.hour() + 1) as usize).await?; Ok(()) } diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 3818bbd..e6c1c69 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -26,7 +26,7 @@ pub async fn paginate(ctx: ManifoldContext<'_>, message: ReplyHandle<'_>, pages: // button was pressed .filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string())) // Timeout when no navigation button has been pressed for 24 hours - .timeout(std::time::Duration::from_secs(3600 * 24)) + .timeout(std::time::Duration::from_secs(300)) .await { // Depending on which button was pressed, go to next or previous page From 4f9171d96a7423016613234d3be0fdaa048e875e Mon Sep 17 00:00:00 2001 From: Xyon Date: Tue, 29 Aug 2023 17:10:48 +0100 Subject: [PATCH 5/6] Always fetch tomorrow's weather, today, to avoid having no forecast close to midnight --- src/commands/weather.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/weather.rs b/src/commands/weather.rs index 47bd1c3..c9aa2bc 100644 --- a/src/commands/weather.rs +++ b/src/commands/weather.rs @@ -14,7 +14,7 @@ use crate::helpers::paginate; async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to look up weather for"] location: Option) -> ManifoldResult<()> { let my_message = ctx.say("Retrieving weather, be patient").await?; - let weather_forecast = _get_weather(ctx, &my_message, location, Some(1)).await?; + let weather_forecast = _get_weather(ctx, &my_message, location, Some(2)).await?; let responses = &ctx.data().responses; @@ -22,8 +22,8 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8); - if let Some(f) = weather_forecast.forecast.forecastday.first() { - f.hour.iter().for_each(|f| { + weather_forecast.forecast.forecastday.iter().for_each(|d| { + d.hour.iter().for_each(|f| { pages.push(CreateEmbed::default() .colour(card_colour) .title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country)) @@ -44,8 +44,8 @@ async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to lo f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string()))); f }).to_owned()); - }); - } + }) + }); let now = NaiveDateTime::parse_from_str(weather_forecast.current.last_updated.as_str(), "%Y-%m-%d %H:%M").unwrap_or(NaiveDateTime::default()); From c9f6f5ced0a3d5e99db5481ae6583150fe8f9724 Mon Sep 17 00:00:00 2001 From: Xyon Date: Wed, 30 Aug 2023 16:53:24 +0100 Subject: [PATCH 6/6] V3 - config refactoring, animal pics --- Cargo.toml | 2 +- src/commands/admin.rs | 31 ++---------- src/commands/animal.rs | 50 +++++++++++++++++++ src/commands/frog.rs | 23 --------- src/commands/mod.rs | 4 +- src/commands/weather.rs | 12 ++--- src/config.rs | 70 +++++++------------------- src/events.rs | 9 ++-- src/lib.rs | 20 +++----- src/models/animalpics.rs | 26 ++++++++++ src/models/frogtip.rs | 29 +++++++++++ src/models/fueltank.rs | 105 +++++++++++++++++++-------------------- src/models/mod.rs | 2 + src/models/weather.rs | 27 +++------- 14 files changed, 205 insertions(+), 205 deletions(-) create mode 100644 src/commands/animal.rs delete mode 100644 src/commands/frog.rs create mode 100644 src/models/animalpics.rs create mode 100644 src/models/frogtip.rs diff --git a/Cargo.toml b/Cargo.toml index de7f0d9..b3f0cfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "manifold" -version = "2.0.0" +version = "3.0.0" authors = ["Lucy Bladen "] edition = "2021" diff --git a/src/commands/admin.rs b/src/commands/admin.rs index 5c32c64..eb26025 100644 --- a/src/commands/admin.rs +++ b/src/commands/admin.rs @@ -4,32 +4,7 @@ use crate::error::ManifoldError; #[poise::command(slash_command, prefix_command, owners_only)] async fn dump_config(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { - let config = &ctx.data().bot_config; - - ctx.say(format!("Config dump; {:?}", config.config)).await?; - - Ok(()) -} - -#[poise::command(slash_command, prefix_command, owners_only)] -async fn get_environment(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { - let environment = ctx.data().bot_config.get_environment(); - - ctx.say(format!("Currently running under the {} environment", environment)).await?; - - Ok(()) -} - -#[poise::command(slash_command, prefix_command, owners_only)] -async fn get_config(ctx: ManifoldContext<'_>, #[description="Config key"] key: String) -> ManifoldResult<()> { - let config = &ctx.data().bot_config; - - let value = match config.get_value(&key) { - Ok(v) => v.clone(), - Err(_) => "not found, sorry!".to_string() - }; - - ctx.say(format!("Value for key {} was {}", &key, &value)).await?; + ctx.say(format!("Config dump; {:?}", &ctx.data().bot_config)).await?; Ok(()) } @@ -77,6 +52,6 @@ async fn save_world(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { Ok(()) } -pub fn commands() -> [poise::Command; 6] { - [dump_config(), get_environment(), get_config(), register_commands(), set_activity(), save_world()] +pub fn commands() -> [poise::Command; 4] { + [dump_config(), register_commands(), set_activity(), save_world()] } diff --git a/src/commands/animal.rs b/src/commands/animal.rs new file mode 100644 index 0000000..866eddc --- /dev/null +++ b/src/commands/animal.rs @@ -0,0 +1,50 @@ +use crate::models::frogtip::{Croak, Tip}; +use crate::error::{ManifoldError, ManifoldResult}; +use crate::{ManifoldContext, ManifoldData}; +use crate::models::animalpics::{AnimalPic, AnimalPicWrapper}; + +#[poise::command(slash_command, prefix_command, aliases("ft"))] +async fn frog_tip(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { + + debug!("Processing tip"); + + if let Some(tank) = ctx.data().bot_config.services.get("frog_tips") { + let tip: &Tip = &tank.next_or_fill::().await?; + + debug!("Tip: {:?}", tip); + + ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?; + } else { + error!("No such fuel tank frog_tips"); + ctx.send(|f| f.content("No tips could be located. Try rubbing it.".to_string()).reply(true)).await?; + } + + Ok(()) +} + +#[poise::command(slash_command, prefix_command, aliases("dog"))] +async fn dog_pic(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { + animal_pic(ctx, "dog_pics").await +} + +#[poise::command(slash_command, prefix_command, aliases("cat"))] +async fn cat_pic(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { + animal_pic(ctx, "cat_pics").await +} + +async fn animal_pic(ctx: ManifoldContext<'_>, tank_name: &str) -> ManifoldResult<()> { + if let Some(tank) = ctx.data().bot_config.services.get(tank_name) { + let pic: &AnimalPic = &tank.next_or_fill::().await?; + + ctx.send(|f| f.content(format!("{}", pic)).reply(true)).await?; + } else { + error!("No such fuel tank {}", tank_name); + ctx.send(|f| f.content("No frens here at the moment. Perhaps some bacon would help?".to_string()).reply(true)).await?; + } + + Ok(()) +} + +pub fn commands() -> [poise::Command; 3] { + [frog_tip(), dog_pic(), cat_pic()] +} diff --git a/src/commands/frog.rs b/src/commands/frog.rs deleted file mode 100644 index 8c0c7e9..0000000 --- a/src/commands/frog.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::models::fueltank::Tip; -use crate::error::{ManifoldError, ManifoldResult}; -use crate::{ManifoldContext, ManifoldData}; - -#[poise::command(slash_command, prefix_command, aliases("ft"))] -async fn frogtip(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { - - debug!("Processing tip"); - - let tank = ctx.data().frogtip_fueltank.lock().await; - - let tip: &Tip = &tank.next_or_fill().await?; - - debug!("Tip: {:?}", tip); - - ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?; - - Ok(()) -} - -pub fn commands() -> [poise::Command; 1] { - [frogtip()] -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 471701d..f284413 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,5 @@ mod admin; -mod frog; +mod animal; mod convert; mod core; mod weather; @@ -9,7 +9,7 @@ use crate::ManifoldCommand; pub fn collect_commands(injected: Vec) -> Vec { core::commands().into_iter() .chain(admin::commands()) - .chain(frog::commands()) + .chain(animal::commands()) .chain(convert::commands()) .chain(weather::commands()) .chain(injected) diff --git a/src/commands/weather.rs b/src/commands/weather.rs index c9aa2bc..7b47f17 100644 --- a/src/commands/weather.rs +++ b/src/commands/weather.rs @@ -5,16 +5,16 @@ use poise::ReplyHandle; use poise::serenity_prelude::CreateEmbed; use crate::{ManifoldCommand, ManifoldContext}; -use crate::models::weather::{Weather, WeatherForecastRequestResponse}; +use crate::models::weather::WeatherForecastRequestResponse; use crate::models::user::UserInfo; -use crate::error::ManifoldResult; +use crate::error::{ManifoldError, ManifoldResult}; use crate::helpers::paginate; #[poise::command(slash_command, prefix_command, aliases("w"))] async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to look up weather for"] location: Option) -> ManifoldResult<()> { let my_message = ctx.say("Retrieving weather, be patient").await?; - let weather_forecast = _get_weather(ctx, &my_message, location, Some(2)).await?; + let weather_forecast = _get_weather(ctx, &my_message, location).await?; let responses = &ctx.data().responses; @@ -81,7 +81,7 @@ pub async fn save_weather_location(ctx: ManifoldContext<'_>, #[rest] #[descripti Ok(()) } -pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle<'_>, location: Option, days: Option) -> ManifoldResult { +pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle<'_>, location: Option) -> ManifoldResult { let mut weather_location: String = String::default(); @@ -90,7 +90,7 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle let userinfo = &mut ctx.data().user_info.lock().await; - let weather_client = Weather::new(config.get_value(&"WeatherApiKey".to_string()).unwrap().clone(), config.get_value(&"WeatherBaseUrl".to_string()).unwrap().clone()); + let weather_client = config.services.get("weather").ok_or_else(|| ManifoldError::from("No weather client available"))?; if let Some(loc) = location { weather_location = loc; @@ -118,7 +118,7 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle debug!("Making weather request!"); - let weather_forecast: WeatherForecastRequestResponse = match weather_client.get_weather_forecast(weather_location, days).await { + let weather_forecast: WeatherForecastRequestResponse = match weather_client.get_weather_forecast(weather_location).await { Ok(w) => w, Err(e) => { ctx.say(format!("Something went wrong. Maybe there is no weather there. {:?}", e)).await?; diff --git a/src/config.rs b/src/config.rs index 38ceb2c..c36255e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,66 +1,32 @@ +use std::collections::HashMap; use std::path::PathBuf; use config::Config; -use poise::serenity_prelude::model::prelude::{ChannelId, MessageId, RoleId}; +use poise::serenity_prelude::ChannelId; use crate::ManifoldResult; +use crate::models::fueltank::FuelTank; -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize)] +pub struct Channels { + pub log: ChannelId, +} + +#[derive(Debug, Deserialize, Serialize)] pub struct ManifoldConfig { - pub environment: String, - pub path: PathBuf, - pub config: Config, + pub prefix: String, + pub channels: Channels, + pub nickname: String, + pub services: HashMap, + pub responses_file_path: PathBuf, } impl ManifoldConfig { - pub fn load_config(config_file: &String, bot_environment: &String) -> ManifoldResult { + pub fn new(config_file: &String, bot_environment: &String) -> ManifoldResult { let settings = Config::builder() - .set_override("BotEnvironment", bot_environment.clone())? - .add_source(config::File::with_name(config_file)) - .add_source(config::Environment::with_prefix("MANIFOLD")) + .add_source(config::File::with_name(&*format!("config/{}.{}", bot_environment.to_lowercase(), config_file))) + .add_source(config::Environment::with_prefix("manifold")) .build()?; - Ok(Self { - environment: bot_environment.clone(), - path: PathBuf::from(config_file), - config: settings - }) - } - - pub fn get_environment(&self) -> &String { - &self.environment - } - - pub fn get_path(&self) -> &PathBuf { - &self.path - } - - pub fn get_environment_mut(&mut self) -> &mut String { - &mut self.environment - } - - pub fn get_path_mut(&mut self) -> &mut PathBuf { - &mut self.path - } - - pub fn get_value(&self, key: &String) -> ManifoldResult { - Ok(self.config.get_string(&format!("{}{}", &self.environment, key))?) - } - - pub fn get_role(&self, role_name: &String) -> ManifoldResult { - let key = format!("{}Role", role_name); - - Ok(RoleId(self.get_value(&key)?.parse::()?)) - } - - pub fn get_channel(&self, channel_name: &String) -> ManifoldResult { - let key = format!("{}Channel", channel_name); - - Ok(ChannelId(self.get_value(&key)?.parse::()?)) - } - - pub fn get_message(&self, message_id: &String) -> ManifoldResult { - let key = format!("{}Message", message_id); - - Ok(MessageId(self.get_value(&key)?.parse::()?)) + Ok(settings.try_deserialize()?) } } diff --git a/src/events.rs b/src/events.rs index 6b30181..65f86bd 100644 --- a/src/events.rs +++ b/src/events.rs @@ -3,7 +3,6 @@ use std::sync::atomic::AtomicBool; use poise::{Event, FrameworkContext}; use poise::serenity_prelude::model::{ gateway::Ready, - id::ChannelId, }; use poise::serenity_prelude::{Context, Message}; use crate::ManifoldData; @@ -40,10 +39,8 @@ impl Handler { None => "Manifold bot connected to discord and ready to begin broadcast operations.".to_string(), }; - let bot_nickname = config.get_value(&"BotNickname".to_string()).unwrap_or("BrokenManifoldBot".to_string()); - let channel: ChannelId = config.get_channel(&"Log".to_string()).expect("Specified log channel invalid or unavailable"); for guild in &data_about_bot.guilds { - match guild.id.edit_nickname(&ctx, Some(&*bot_nickname)).await { + match guild.id.edit_nickname(&ctx, Some(&*config.nickname)).await { Ok(()) => (), Err(e) => { error!("Error setting bot nickname (lack permission?): {:?}", e); @@ -51,7 +48,7 @@ impl Handler { } } - channel.say(&ctx, greeting).await.expect("Couldn't message log channel!"); + config.channels.log.say(&ctx, greeting).await.expect("Couldn't message log channel!"); Ok(()) } @@ -78,7 +75,7 @@ impl Handler { } async fn message_edited(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, original: &Option, new_message: &Option) -> ManifoldResult<()> { - let log_channel = fctx.user_data().await.bot_config.get_channel(&"Log".to_string()).unwrap(); + let log_channel = fctx.user_data().await.bot_config.channels.log; if let Some(new) = new_message.as_ref() { log_channel.send_message(ctx, |f| { diff --git a/src/lib.rs b/src/lib.rs index 267b30a..e5891b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,6 @@ use crate::config::ManifoldConfig; use crate::error::{ManifoldError, ManifoldResult}; use crate::events::Handler; use crate::models::user::{ManifoldUserInfo, UserInfo}; -use crate::models::fueltank::FuelTank; use crate::responses::Responses; pub mod helpers; @@ -67,7 +66,6 @@ pub struct ManifoldDataInner { responses: Responses, user_info: Mutex, version_string: String, - frogtip_fueltank: Mutex, } pub type ManifoldContext<'a> = poise::Context<'a, ManifoldData, ManifoldError>; @@ -75,13 +73,11 @@ pub type ManifoldCommand = poise::Command; pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, injected_commands: Vec, caller_version_string: String) -> ManifoldResult> { let bot_environment = arguments.get_one("environment").unwrap(); - let config_file = format!("config/{}", arguments.get_one::("config-file").unwrap()); + let config_file = arguments.get_one::("config-file").unwrap(); info!("Reading configuration..."); debug!("Configuration file path: {}", &config_file); - let config = ManifoldConfig::load_config(&config_file, bot_environment).expect(&*format!("Could not read configuration file {}", &config_file)); - - let prefix = config.get_value(&"BotPrefix".to_string()).expect("Could not read bot_prefix from config."); + let bot_config = ManifoldConfig::new(&config_file, bot_environment).expect(&*format!("Could not read configuration file {}", &config_file)); let manager = ConnectionManager::::new("manifold.db"); let pool = r2d2::Pool::builder() @@ -90,7 +86,7 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje .expect("Database setup error!"); let mut responses = Responses::new(); - responses.reload(&PathBuf::from(config.get_value(&"ResponsesFilePath".to_string()).unwrap_or("txt/responses.txt".to_string()))).expect("Could not load responses file!"); + responses.reload(&PathBuf::from(&bot_config.responses_file_path)).expect("Could not load responses file!"); let token = env::var("DISCORD_TOKEN").expect( "Could not find an environment variable called DISCORD_TOKEN", @@ -103,13 +99,11 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje }), pre_command: |ctx: ManifoldContext<'_>| Box::pin(async move { info!("Received command {} from {}", ctx.command().name, ctx.author().name); - let config = &ctx.data().bot_config; - let log_channel = config.get_channel(&"Log".to_string()).unwrap(); - let _ = log_channel.say(ctx, format!("Received command {} from {}", ctx.command().name, ctx.author().name)).await; + let _ = ctx.data().bot_config.channels.log.say(ctx, format!("Received command {} from {}", ctx.command().name, ctx.author().name)).await; }), commands: commands::collect_commands(injected_commands), prefix_options: poise::PrefixFrameworkOptions { - prefix: Some(prefix), + prefix: Some(bot_config.prefix.clone()), ..Default::default() }, ..Default::default() @@ -124,13 +118,13 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje apply_migrations(&mut db.get()?); let user_info = UserInfo::load(&db).expect("Could not load user info, rejecting"); let git_info: String = built_info::GIT_VERSION.unwrap_or("unknown").to_string(); + Ok(ManifoldData(Arc::new(ManifoldDataInner { - bot_config: config, + bot_config, database: db, responses, user_info: Mutex::new(user_info), version_string: format!("{caller} (Manifold framework version {mfold_ver} built at {mfold_time} from revision {mfold_rev})", caller=caller_version_string, mfold_ver=built_info::PKG_VERSION, mfold_time=built_info::BUILT_TIME_UTC, mfold_rev=git_info), - frogtip_fueltank: Mutex::new(FuelTank::with_cache()) }))) }) }); diff --git a/src/models/animalpics.rs b/src/models/animalpics.rs new file mode 100644 index 0000000..cad49a7 --- /dev/null +++ b/src/models/animalpics.rs @@ -0,0 +1,26 @@ +use std::fmt::{Display, Formatter}; +use crate::models::fueltank::Pump; + +#[derive(Serialize, Deserialize, Debug)] +pub struct AnimalPic { + pub id: String, + pub url: String, +} + +pub type AnimalPicWrapper = Vec; + +impl Display for AnimalPic { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.url) + } +} + +impl Pump for AnimalPicWrapper { + fn pump(&mut self) -> Option { + self.pop() + } + + fn len(&self) -> usize { + self.len() + } +} \ No newline at end of file diff --git a/src/models/frogtip.rs b/src/models/frogtip.rs new file mode 100644 index 0000000..2a809a1 --- /dev/null +++ b/src/models/frogtip.rs @@ -0,0 +1,29 @@ +use std::fmt::{Display, Formatter}; +use crate::models::fueltank::Pump; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Tip { + pub number: i32, + pub tip: String, +} + +impl Display for Tip { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "#{}: {}", self.number, self.tip) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Croak { + pub tips: Vec, +} + +impl Pump for Croak { + fn pump(&mut self) -> Option { + self.tips.pop() + } + + fn len(&self) -> usize { + self.tips.len() + } +} \ No newline at end of file diff --git a/src/models/fueltank.rs b/src/models/fueltank.rs index 6c10c9b..1cb3748 100644 --- a/src/models/fueltank.rs +++ b/src/models/fueltank.rs @@ -1,78 +1,57 @@ -use std::fmt::{Display, Formatter}; +use std::fmt::Debug; use std::fs; use std::path::PathBuf; use dirs::home_dir; use std::io::{SeekFrom, Seek}; use reqwest::Client; use reqwest::header::CONTENT_TYPE; +use serde::de::DeserializeOwned; +use serde::Serialize; use crate::error::{ManifoldError, ManifoldResult}; const CACHE_VERSION: &'static str = "1"; pub trait Pump { - fn pump(self) -> Result; + fn pump(&mut self) -> Option; + fn len(&self) -> usize; } -#[derive(Copy, Clone, Debug)] -#[allow(dead_code)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct FuelTank { - mode: TankMode, + pub(crate) source_uri: String, + cache_name: Option, + pub(crate) api_key: Option, + cache_mode: TankMode, } -#[derive(Copy, Clone, Debug)] -enum TankMode { +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum TankMode { Cache, + NoCache, } impl FuelTank { - pub fn with_cache() -> Self { + pub fn new(source_uri: String, cache_name: Option, api_key: Option, cache_mode: TankMode) -> Self { Self { - mode: TankMode::Cache, + source_uri, + cache_name, + api_key, + cache_mode, } } -} -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct Tip { - pub number: i32, - pub tip: String, -} - -impl Display for Tip { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "#{}: {}", self.number, self.tip) - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Croak { - pub tips: Vec, -} - -pub async fn croak() -> ManifoldResult> { - - debug!("Getting new tips"); - let result: Croak; - - let client = Client::new(); - result = client.get("https://frog.tips/api/1/tips/") - .header(CONTENT_TYPE, "application/json") - .send().await?.json().await?; - - debug!("Result: {:?}", &result); - - Ok(result.tips) -} - -impl FuelTank { - pub async fn next_or_fill(self) -> Result { + pub async fn next_or_fill(&self) -> ManifoldResult + where T: DeserializeOwned, + U: Serialize, + U: DeserializeOwned, U: Pump + { debug!("Filling tank!"); let mut cache = { let mut path = PathBuf::new(); path.push(home_dir().ok_or(ManifoldError::from("No home dir"))?); path.push(".local"); - path.push(format!("coffeebot-frog-cache-{}.json", CACHE_VERSION)); + path.push(format!("manifold-{}-cache-{}.json", self.cache_name.clone().unwrap(), CACHE_VERSION)); debug!("Cache path: {:?}", &path); fs::OpenOptions::new() .read(true) @@ -87,29 +66,47 @@ impl FuelTank { debug!("Cache: {:?}", &cache); - let json: Result, serde_json::Error> = serde_json::from_reader(&mut cache); + let json: Result = serde_json::from_reader(&mut cache); debug!("Json: {:?}", &json); - let mut tips = match json { + let mut fuel = match json { Ok(contents) => { match contents.len() { - 0 => croak().await?, + 0 => self.emit().await?, _ => contents, } } - _ => croak().await?, + _ => self.emit().await?, }; - debug!("Got tips: {:?}", &tips); - let tip = tips.pop().ok_or(ManifoldError::from("FROG HAS NO TIPS FOR YOU. CROAK ERROR 12"))?; + debug!("Got tips: {:?}", &fuel); + let single = fuel.pump().ok_or(ManifoldError::from("CACHE ERROR"))?; - debug!("Got single tip: {:?}", &tip); + debug!("Got single tip: {:?}", &single); cache .set_len(0) .and_then(|_| cache.seek(SeekFrom::Start(0)))?; debug!("Writing cache"); - serde_json::to_writer(cache, &tips)?; + serde_json::to_writer(cache, &fuel)?; - Ok(tip) + return Ok(single) + } + + pub async fn emit(&self) -> ManifoldResult + where U: DeserializeOwned + { + debug!("Getting new tips"); + let result: U; + + let mut client = Client::new().get(&self.source_uri) + .header(CONTENT_TYPE, "application/json"); + if let Some(api_key) = &self.api_key { + client = client.header("X-API-KEY", api_key); + } + result = client.send().await?.json().await?; + + debug!("Result: {:?}", &result); + + Ok(result) } } diff --git a/src/models/mod.rs b/src/models/mod.rs index b1eabeb..e7e2e2a 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,3 +1,5 @@ +pub mod animalpics; pub mod fueltank; +pub mod frogtip; pub mod user; pub mod weather; \ No newline at end of file diff --git a/src/models/weather.rs b/src/models/weather.rs index 507f758..e78247d 100644 --- a/src/models/weather.rs +++ b/src/models/weather.rs @@ -1,11 +1,7 @@ -use crate::error::ManifoldResult; +use crate::error::{ManifoldError, ManifoldResult}; use reqwest::Client; use reqwest::header::CONTENT_TYPE; - -pub struct Weather { - base_url: String, - api_key: String, -} +use crate::models::fueltank::FuelTank; #[derive(Debug, Deserialize)] pub struct CurrentConditionInformation { @@ -145,22 +141,13 @@ pub struct WeatherForecastRequestResponse { pub forecast: ForecastDayWrapper, } +pub type Weather = FuelTank; + impl Weather { - pub fn new(api_key:String, base_url: String) -> Self { - Weather { - base_url, - api_key, - } - } - pub async fn get_weather_forecast(&self, target: String, days: Option) -> ManifoldResult { - let target_url: String; + pub async fn get_weather_forecast(&self, target: String) -> ManifoldResult { - if let Some(d) = days { - target_url = format!("{}/forecast.json?key={}&q={}&days={}", self.base_url, self.api_key, &target, d); - } else { - target_url = format!("{}/forecast.json?key={}&q={}&days=3", self.base_url, self.api_key, &target); - } + let target_url = format!("{}/forecast.json?key={}&q={}&days=2", self.source_uri, self.api_key.clone().ok_or_else(|| ManifoldError::from("No API key presented for Weather API"))?, &target); let client = Client::new(); let result = client.get(target_url) @@ -179,7 +166,7 @@ impl Weather { decoded_result.current.dewpoint_f = Some((decoded_result.current.dewpoint_c.unwrap() * 1.8) + 32.0); - debug!("{:?}", decoded_result); + debug!("{:?}", &decoded_result); Ok(decoded_result) }