Initial commit
This commit is contained in:
commit
b00cf3dfea
6 changed files with 2219 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
docker-compose.yml
|
2064
Cargo.lock
generated
Normal file
2064
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "rooster"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { version = "0.11.13", default-features = false, features = ["rustls-tls"] }
|
||||||
|
icalendar = { version = "0.13.3", features = ["parser"] }
|
||||||
|
rocket = "0.5.0-rc.2"
|
||||||
|
chrono = "0.4.23"
|
||||||
|
url = "2.3.1"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
FROM rust:1 AS builder
|
||||||
|
WORKDIR /usr/src/rooster
|
||||||
|
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
RUN cargo install --path .
|
||||||
|
|
||||||
|
FROM debian:buster-slim
|
||||||
|
|
||||||
|
ENV ROCKET_ADDRESS=0.0.0.0
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
COPY --from=builder /usr/local/cargo/bin/rooster /usr/local/bin/rooster
|
||||||
|
|
||||||
|
CMD ["rooster"]
|
8
docker-compose.dist.yml
Normal file
8
docker-compose.dist.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
BASE_URL: http://localhost:8000
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
115
src/main.rs
Normal file
115
src/main.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use icalendar::{Calendar, Component, Event};
|
||||||
|
use rocket::response::content::RawHtml;
|
||||||
|
use rocket::{get, launch, routes};
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> RawHtml<&'static str> {
|
||||||
|
RawHtml(
|
||||||
|
r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<form action="/url">
|
||||||
|
<label for="url">iCal URL:</label>
|
||||||
|
<input type="text" name="rooster_url" id="url" style="width: 500px" placeholder="https://rooster.utwente.nl/ical?...">
|
||||||
|
<input type="submit">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/url?<rooster_url>")]
|
||||||
|
async fn generate_url(rooster_url: &str) -> String {
|
||||||
|
let mut url = Url::parse(&std::env::var("BASE_URL").unwrap()).unwrap();
|
||||||
|
|
||||||
|
url.set_path("ical");
|
||||||
|
|
||||||
|
url.query_pairs_mut()
|
||||||
|
.clear()
|
||||||
|
.append_pair("url", rooster_url);
|
||||||
|
|
||||||
|
url.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Source {
|
||||||
|
Twente,
|
||||||
|
Delft,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/ical?<url>")]
|
||||||
|
async fn ics(url: &str) -> String {
|
||||||
|
let source = if url.starts_with("https://rooster.utwente.nl/ical") {
|
||||||
|
Source::Twente
|
||||||
|
} else if url.starts_with("https://mytimetable.tudelft.nl/ical") {
|
||||||
|
Source::Delft
|
||||||
|
} else {
|
||||||
|
return "Must be a rooster.utwente.nl URL".to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = reqwest::get(url).await.unwrap().text().await.unwrap();
|
||||||
|
|
||||||
|
let calendar = body.parse::<Calendar>().unwrap();
|
||||||
|
let mut new_calendar = Calendar::new();
|
||||||
|
|
||||||
|
for component in calendar.iter() {
|
||||||
|
let event = match component.as_event() {
|
||||||
|
Some(event) => event,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let summary = event.get_summary().unwrap();
|
||||||
|
let subject = match source {
|
||||||
|
Source::Twente => {
|
||||||
|
let (subject, _) = summary.rsplit_once(' ').unwrap();
|
||||||
|
subject
|
||||||
|
}
|
||||||
|
|
||||||
|
Source::Delft => {
|
||||||
|
let (_, subject) = summary.split_once(" - ").unwrap();
|
||||||
|
subject
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let description = event.get_description().unwrap();
|
||||||
|
let (kind, _) = description.split_once("\\n").unwrap();
|
||||||
|
let (_, kind) = kind.split_once(": ").unwrap();
|
||||||
|
|
||||||
|
let start = event.get_start().unwrap();
|
||||||
|
let end = event.get_end().unwrap();
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
url.hash(&mut hasher);
|
||||||
|
format!("{start:?}").hash(&mut hasher);
|
||||||
|
format!("{end:?}").hash(&mut hasher);
|
||||||
|
summary.hash(&mut hasher);
|
||||||
|
description.hash(&mut hasher);
|
||||||
|
let mut new_event = Event::new();
|
||||||
|
|
||||||
|
new_event
|
||||||
|
.summary(&format!("{kind} {subject}"))
|
||||||
|
.description(description)
|
||||||
|
.starts(start)
|
||||||
|
.ends(end);
|
||||||
|
if let Some(location) = event.get_location() {
|
||||||
|
new_event.location(location);
|
||||||
|
location.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = hasher.finish();
|
||||||
|
new_event.uid(&format!("{:x}", hash));
|
||||||
|
|
||||||
|
new_calendar.push(new_event.done());
|
||||||
|
}
|
||||||
|
|
||||||
|
new_calendar.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
rocket::build().mount("/", routes![index, generate_url, ics])
|
||||||
|
}
|
Loading…
Reference in a new issue