From d1245ab365bc20fd7b81b6352719006d1fb7698f Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 12 May 2023 15:51:46 +0200 Subject: [PATCH] feat: command to list available dates --- affluences-api/src/lib.rs | 6 ++-- bot/src/commands.rs | 61 +++++++++++++++++++++++++++++++++++++++ bot/src/main.rs | 5 +++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/affluences-api/src/lib.rs b/affluences-api/src/lib.rs index 57d103f..395dacc 100644 --- a/affluences-api/src/lib.rs +++ b/affluences-api/src/lib.rs @@ -17,17 +17,17 @@ impl AffluencesClient { } } - pub async fn available(&mut self, site_id: uuid::Uuid, date: NaiveDate, resource_type: u32) -> reqwest::Result> { + pub async fn available(&self, site_id: uuid::Uuid, date: NaiveDate, resource_type: u32) -> reqwest::Result> { let url = format!("https://reservation.affluences.com/api/resources/{}/available", site_id); self.client.get(url).query(&[("date", date.format("%Y-%m-%d").to_string()), ("type", resource_type.to_string())]).send().await?.json::>().await } - pub async fn site_data(&mut self, slug: &str) -> reqwest::Result { + pub async fn site_data(&self, slug: &str) -> reqwest::Result { let url = format!("https://api.affluences.com/app/v3/sites/{}", slug); Ok(self.client.get(url).send().await?.json::>().await?.data) } - pub async fn make_reservation(&mut self, resource_id: u32, reservation: &Reservation) -> reqwest::Result { + pub async fn make_reservation(&self, resource_id: u32, reservation: &Reservation) -> reqwest::Result { let url = format!("https://reservation.affluences.com/api/reserve/{}", resource_id); self.client.post(url).json(reservation).send().await?.json::().await } diff --git a/bot/src/commands.rs b/bot/src/commands.rs index 376da63..1dad50b 100644 --- a/bot/src/commands.rs +++ b/bot/src/commands.rs @@ -1,4 +1,11 @@ use crate::{Context, Error}; +use chrono::{NaiveDate, Duration}; +use uuid::{uuid, Uuid}; +use affluences_api::Hour; +use poise::serenity_prelude as serenity; + +const STERRE_BIB_ID: Uuid = uuid!("4737e57a-ee05-4f7b-901a-7bb541eeb297"); +const TIME_FORMAT: &'static str = "%H:%M"; /// Show this help menu #[poise::command(prefix_command, track_edits, slash_command)] @@ -75,3 +82,57 @@ pub async fn getvotes( Ok(()) } + +/// List available timeslots for day +#[poise::command(prefix_command, slash_command)] +pub async fn available( + ctx: Context<'_>, + date: NaiveDate, +) -> Result<(), Error> { + let client = &ctx.data().client; + let resources = client.available(STERRE_BIB_ID, date, 1).await?; + let mut fields: Vec<(String, String, bool)> = Default::default(); + + for resource in &resources { + if resource.hours.len() == 0 { + fields.push((resource.resource_name.clone(), "Nothing available.".to_string(), false)); + + continue + } + + let mut lines: Vec = Default::default(); + + let mut start_hour_opt: Option<&Hour> = None; + let mut duration = Duration::seconds(0); + + for hour in &resource.hours { + if let Some(start_hour) = start_hour_opt { + if hour.state == 1 { + duration = duration + Duration::minutes(hour.granularity.into()); + } else { + let end_hour = start_hour.hour + duration; + lines.push(format!("{} - {} ({:02}:{:02})", start_hour.hour.format(TIME_FORMAT), end_hour.format(TIME_FORMAT), duration.num_hours(), duration.num_minutes() % 60)); + start_hour_opt = None; + } + } else if hour.state == 1 { + start_hour_opt = Some(hour); + duration = Duration::minutes(hour.granularity.into()); + } + } + + // Print final entry if present + if let Some(start_hour) = start_hour_opt { + let end_hour = start_hour.hour + duration; + lines.push(format!("{} - {} ({:02}:{:02})", start_hour.hour.format(TIME_FORMAT), end_hour.format(TIME_FORMAT), duration.num_hours(), duration.num_minutes() % 60)); + } + + fields.push((resource.resource_name.clone(), lines.join("\n"), false)); + } + + ctx.send(|f| + f.embed(|e| + e.description(format!("Available booking dates for {}.", date)) + .fields(fields))).await?; + + Ok(()) +} diff --git a/bot/src/main.rs b/bot/src/main.rs index e510176..f7d482a 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,5 +1,6 @@ mod commands; +use affluences_api::AffluencesClient; use poise::serenity_prelude as serenity; use std::{collections::HashMap, env::var, sync::Mutex, time::Duration}; @@ -10,6 +11,7 @@ type Context<'a> = poise::Context<'a, Data, Error>; // Custom user data passed to all command functions pub struct Data { votes: Mutex>, + client: AffluencesClient, } async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { @@ -34,7 +36,7 @@ async fn main() { // FrameworkOptions contains all of poise's configuration option in one struct // Every option can be omitted to use its default value let options = poise::FrameworkOptions { - commands: vec![commands::help(), commands::vote(), commands::getvotes()], + commands: vec![commands::help(), commands::vote(), commands::getvotes(), commands::available()], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("~".into()), edit_tracker: Some(poise::EditTracker::for_timespan(Duration::from_secs(3600))), @@ -90,6 +92,7 @@ async fn main() { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { votes: Mutex::new(HashMap::new()), + client: AffluencesClient::new(), }) }) })