Added weekend food truck information

You can now see the list of food trucks that are coming to RIT on the weekends and their hours. This data is scraped directly from the RIT Events website which means that accessing it isn't the best, but it works. The code behind it is really bad right now, but it works as expected currently and will be improved soon™️
This commit is contained in:
2025-10-06 00:37:59 -04:00
parent dba5511ed5
commit dec8788276
7 changed files with 287 additions and 4 deletions

View File

@@ -6,6 +6,7 @@
//
import Foundation
import SwiftSoup
func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus {
// This can probably be done a little cleaner but it's okay for now. If the location is open but the close date is within the next
@@ -257,6 +258,8 @@ func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> Dining
}
extension DiningLocation {
// Updates the open status of a location and of its visiting chefs, so that the labels in the UI update automatically as
// time progresses and locations open/close/etc.
mutating func updateOpenStatus() {
var openStatus: OpenStatus = .closed
if let diningTimes = diningTimes, !diningTimes.isEmpty {
@@ -272,5 +275,114 @@ extension DiningLocation {
} else {
self.open = .closed
}
if let visitingChefs = visitingChefs, !visitingChefs.isEmpty {
let now = Date()
for i in visitingChefs.indices {
self.visitingChefs![i].status = switch parseOpenStatus(
openTime: visitingChefs[i].openTime,
closeTime: visitingChefs[i].closeTime) {
case .open:
.hereNow
case .closed:
if now < visitingChefs[i].openTime {
.arrivingLater
} else {
.gone
}
case .openingSoon:
.arrivingSoon
case .closingSoon:
.leavingSoon
}
}
}
}
}
// This code is actually miserable and might break sometimes. Sorry. Parse the HTML of the RIT food trucks web page and build
// a list of food trucks that are going to be there the next time they're there. This is not a good way to get this data but it's
// unfortunately the best way that I think I could make it happen. Sorry again for both my later self and anyone else who tries to
// work on this code.
func parseWeekendFoodTrucks(htmlString: String) -> [FoodTruckEvent] {
do {
let doc = try SwiftSoup.parse(htmlString)
var events: [FoodTruckEvent] = []
let now = Date()
let calendar = Calendar.current
let paragraphs = try doc.select("p:has(strong)")
for p in paragraphs {
let text = try p.text()
let parts = text.components(separatedBy: .whitespaces).joined(separator: " ")
let dateRegex = /(?:(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\s+[A-Za-z]+\s+\d+)/
let date = parts.firstMatch(of: dateRegex).map { String($0.0) } ?? ""
if date.isEmpty { continue }
let timeRegex = /(\d{1,2}(:\d{2})?\s*[-]\s*\d{1,2}(:\d{2})?\s*p\.m\.)/
let time = parts.firstMatch(of: timeRegex).map { String($0.0) } ?? ""
let locationRegex = /A-Z Lot/
let location = parts.firstMatch(of: locationRegex).map { String($0.0) } ?? ""
let year = Calendar.current.component(.year, from: Date())
let fullDateString = "\(date) \(year)"
let formatter = DateFormatter()
formatter.dateFormat = "EEEE, MMMM d yyyy"
formatter.locale = Locale(identifier: "en_US_POSIX")
let dateParsed = formatter.date(from: fullDateString) ?? now
let timeStrings = time.split(separator: "-", maxSplits: 1)
print("raw open range: \(timeStrings)")
var openTime = Date()
var closeTime = Date()
if let openString = timeStrings.first?.trimmingCharacters(in: .whitespaces) {
// If the time is NOT in the morning, add 12 hours.
let openHour = if openString.contains("a.m") {
Int(openString.filter("0123456789".contains))!
} else {
Int(openString)! + 12
}
let openTimeComponents = DateComponents(hour: openHour, minute: 0, second: 0)
openTime = calendar.date(
bySettingHour: openTimeComponents.hour!,
minute: openTimeComponents.minute!,
second: openTimeComponents.second!,
of: now)!
}
if let closeString = timeStrings.last?.filter(":0123456789".contains) {
// I've chosen to assume that no visiting chef will ever close in the morning. This could bad choice but I have
// yet to see any evidence of a visiting chef leaving before noon so far.
let closeStringComponents = closeString.split(separator: ":", maxSplits: 1)
let closeTimeComponents = DateComponents(
hour: Int(closeStringComponents[0])! + 12,
minute: closeStringComponents.count > 1 ? Int(closeStringComponents[1]) : 0,
second: 0)
closeTime = calendar.date(
bySettingHour: closeTimeComponents.hour!,
minute: closeTimeComponents.minute!,
second: closeTimeComponents.second!,
of: now)!
}
if let ul = try p.nextElementSibling(), ul.tagName() == "ul" {
let trucks = try ul.select("li").array().map { try $0.text() }
events.append(FoodTruckEvent(
date: dateParsed,
openTime: openTime,
closeTime: closeTime,
location: location,
trucks: trucks
))
print(events)
}
}
return events
} catch {
print(error)
return []
}
}

View File

@@ -0,0 +1,10 @@
//
// PushScheduler.swift
// RIT Dining
//
// Created by Campbell on 10/3/25.
//
import Foundation

View File

@@ -96,3 +96,22 @@ func getOccupancyPercentage(mdoId: Int) async -> Result<Double, Error> {
return .failure(error)
}
}
func getFoodTruckPage() async -> Result<String, Error> {
let urlString = "https://www.rit.edu/events/weekend-food-trucks"
guard let url = URL(string: urlString) else {
return .failure(URLError(.badURL))
}
do {
let contents = try String(contentsOf: url)
let scheduleRegex = /<div class=\".*?field--name-field-event-description.*?\">([\s\S]*?)<\/div>/
if let match = contents.firstMatch(of: scheduleRegex) {
return .success(String(match.0))
}
return .success(contents)
} catch {
return .failure(error)
}
}