refactor: consolidate types, remove dead code, and fix minor bugs

Replace DayOfWeek with chrono::Weekday via extension traits, unify
RateLimitConfig into the config module, and remove the unused time
command, BannerState, and ClassDetails stub. Fix open_only query
parameter to respect false values and correct 12-hour time display.
This commit is contained in:
2026-01-28 16:31:11 -06:00
parent 37942378ae
commit 992263205c
27 changed files with 236 additions and 378 deletions
+16 -21
View File
@@ -1,8 +1,8 @@
//! Google Calendar command implementation.
use crate::banner::{Course, DayOfWeek, MeetingScheduleInfo};
use crate::banner::{Course, MeetingScheduleInfo};
use crate::bot::{Context, Error, utils};
use chrono::NaiveDate;
use chrono::{NaiveDate, Weekday};
use std::collections::HashMap;
use tracing::info;
use url::Url;
@@ -39,25 +39,18 @@ pub async fn gcal(
1.. => {
// Sort meeting times by start time of their TimeRange
let mut sorted_meeting_times = meeting_times.to_vec();
sorted_meeting_times.sort_unstable_by(|a, b| {
// Primary sort: by start time
match (&a.time_range, &b.time_range) {
(Some(a_time), Some(b_time)) => a_time.start.cmp(&b_time.start),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => a.days.bits().cmp(&b.days.bits()),
}
});
MeetingScheduleInfo::sort_by_start_time(&mut sorted_meeting_times);
let links = sorted_meeting_times
.iter()
.map(|m| {
let link = generate_gcal_url(&course, m)?;
let days = m.days_string().unwrap_or_else(|| "TBA".to_string());
let detail = match &m.time_range {
Some(range) => {
format!("{} {}", m.days_string().unwrap(), range.format_12hr())
format!("{days} {}", range.format_12hr())
}
None => m.days_string().unwrap(),
None => days,
};
Ok(LinkDetail { link, detail })
})
@@ -105,7 +98,9 @@ fn generate_gcal_url(
"CRN: {}\nInstructor: {}\nDays: {}",
course.course_reference_number,
instructor_name,
meeting_time.days_string().unwrap()
meeting_time
.days_string()
.unwrap_or_else(|| "TBA".to_string())
);
// The event location
@@ -133,13 +128,13 @@ fn generate_rrule(meeting_time: &MeetingScheduleInfo, end_date: NaiveDate) -> St
let by_day = days_of_week
.iter()
.map(|day| match day {
DayOfWeek::Monday => "MO",
DayOfWeek::Tuesday => "TU",
DayOfWeek::Wednesday => "WE",
DayOfWeek::Thursday => "TH",
DayOfWeek::Friday => "FR",
DayOfWeek::Saturday => "SA",
DayOfWeek::Sunday => "SU",
Weekday::Mon => "MO",
Weekday::Tue => "TU",
Weekday::Wed => "WE",
Weekday::Thu => "TH",
Weekday::Fri => "FR",
Weekday::Sat => "SA",
Weekday::Sun => "SU",
})
.collect::<Vec<&str>>()
.join(",");
+12 -26
View File
@@ -1,6 +1,6 @@
//! ICS command implementation for generating calendar files.
use crate::banner::{Course, MeetingScheduleInfo};
use crate::banner::{Course, MeetingDays, MeetingScheduleInfo, WeekdayExt};
use crate::bot::{Context, Error, utils};
use chrono::{Datelike, NaiveDate, Utc};
use serenity::all::CreateAttachment;
@@ -61,7 +61,14 @@ impl Holiday {
}
}
/// University holidays that should be excluded from class schedules
/// University holidays excluded from class schedules.
///
/// WARNING: These dates are specific to the UTSA 2024-2025 academic calendar and must be
/// updated each academic year. Many of these holidays fall on different dates annually
/// (e.g., Labor Day is the first Monday of September, Thanksgiving is the fourth Thursday
/// of November). Ideally these would be loaded from a configuration file or computed
/// dynamically from federal/university calendar rules.
// TODO: Load holiday dates from configuration or compute dynamically per academic year.
const UNIVERSITY_HOLIDAYS: &[(&str, Holiday)] = &[
("Labor Day", Holiday::Single { month: 9, day: 1 }),
(
@@ -132,12 +139,7 @@ pub async fn ics(
// Sort meeting times by start time
let mut sorted_meeting_times = meeting_times.to_vec();
sorted_meeting_times.sort_unstable_by(|a, b| match (&a.time_range, &b.time_range) {
(Some(a_time), Some(b_time)) => a_time.start.cmp(&b_time.start),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => a.days.bits().cmp(&b.days.bits()),
});
MeetingScheduleInfo::sort_by_start_time(&mut sorted_meeting_times);
// Generate ICS content
let (ics_content, excluded_holidays) =
@@ -352,26 +354,10 @@ fn generate_event_content(
Ok((event_content, Vec::new()))
}
/// Convert chrono::Weekday to the custom DayOfWeek enum
fn chrono_weekday_to_day_of_week(weekday: chrono::Weekday) -> crate::banner::meetings::DayOfWeek {
use crate::banner::meetings::DayOfWeek;
match weekday {
chrono::Weekday::Mon => DayOfWeek::Monday,
chrono::Weekday::Tue => DayOfWeek::Tuesday,
chrono::Weekday::Wed => DayOfWeek::Wednesday,
chrono::Weekday::Thu => DayOfWeek::Thursday,
chrono::Weekday::Fri => DayOfWeek::Friday,
chrono::Weekday::Sat => DayOfWeek::Saturday,
chrono::Weekday::Sun => DayOfWeek::Sunday,
}
}
/// Check if a class meets on a specific date based on its meeting days
fn class_meets_on_date(meeting_time: &MeetingScheduleInfo, date: NaiveDate) -> bool {
let weekday = chrono_weekday_to_day_of_week(date.weekday());
let meeting_days = meeting_time.days_of_week();
meeting_days.contains(&weekday)
let day: MeetingDays = date.weekday().into();
meeting_time.days.contains(day)
}
/// Get holiday dates that fall within the course date range and would conflict with class meetings
-2
View File
@@ -4,10 +4,8 @@ pub mod gcal;
pub mod ics;
pub mod search;
pub mod terms;
pub mod time;
pub use gcal::gcal;
pub use ics::ics;
pub use search::search;
pub use terms::terms;
pub use time::time;
+6 -4
View File
@@ -4,8 +4,12 @@ use crate::banner::{SearchQuery, Term};
use crate::bot::{Context, Error};
use anyhow::anyhow;
use regex::Regex;
use std::sync::LazyLock;
use tracing::info;
static RANGE_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(\d{1,4})-(\d{1,4})?").unwrap());
static WILDCARD_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(\d+)(x+)").unwrap());
/// Search for courses with various filters
#[poise::command(slash_command, prefix_command)]
pub async fn search(
@@ -82,8 +86,7 @@ fn parse_course_code(input: &str) -> Result<(i32, i32), Error> {
// Handle range format (e.g, "3000-3999")
if input.contains('-') {
let re = Regex::new(r"(\d{1,4})-(\d{1,4})?").unwrap();
if let Some(captures) = re.captures(input) {
if let Some(captures) = RANGE_RE.captures(input) {
let low: i32 = captures[1].parse()?;
let high = if captures.get(2).is_some() {
captures[2].parse()?
@@ -110,8 +113,7 @@ fn parse_course_code(input: &str) -> Result<(i32, i32), Error> {
return Err(anyhow!("Wildcard format must be exactly 4 characters"));
}
let re = Regex::new(r"(\d+)(x+)").unwrap();
if let Some(captures) = re.captures(input) {
if let Some(captures) = WILDCARD_RE.captures(input) {
let prefix: i32 = captures[1].parse()?;
let x_count = captures[2].len();
-25
View File
@@ -1,25 +0,0 @@
//! Time command implementation for course meeting times.
use crate::bot::{Context, Error, utils};
use tracing::info;
/// Get meeting times for a specific course
#[poise::command(slash_command, prefix_command)]
pub async fn time(
ctx: Context<'_>,
#[description = "Course Reference Number (CRN)"] crn: i32,
) -> Result<(), Error> {
ctx.defer().await?;
let course = utils::get_course_by_crn(&ctx, crn).await?;
// TODO: Implement actual meeting time retrieval and display
ctx.say(format!(
"Meeting time display for '{}' is not yet implemented.",
course.display_title()
))
.await?;
info!(crn = %crn, "time command completed");
Ok(())
}
-1
View File
@@ -14,7 +14,6 @@ pub fn get_commands() -> Vec<poise::Command<Data, Error>> {
vec![
commands::search(),
commands::terms(),
commands::time(),
commands::ics(),
commands::gcal(),
]