mirror of
https://github.com/jhbruhn/ics-adapter.git
synced 2025-03-15 03:25:49 +00:00
Update image with new functionality for OEPL days and all day events
This commit is contained in:
parent
0de6b8391f
commit
2a2821c5b2
4 changed files with 78 additions and 24 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -96,6 +96,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"time",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
@ -296,7 +297,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -476,10 +477,12 @@ name = "ics-adapter"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
"icalendar",
|
"icalendar",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"urlencoding",
|
||||||
"warp",
|
"warp",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -622,7 +625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1198,6 +1201,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1389,6 +1403,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
@ -1457,6 +1477,12 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
|
|
@ -12,3 +12,5 @@ reqwest = { version = "0.11", features = ["native-tls-vendored"] }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
icalendar = { version = "0.15", features = ["chrono-tz"] }
|
icalendar = { version = "0.15", features = ["chrono-tz"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
urlencoding = "2"
|
||||||
|
chrono = "0"
|
||||||
|
|
14
README.md
14
README.md
|
@ -16,6 +16,20 @@ services:
|
||||||
|
|
||||||
As you can see, you only have to forward port 3000 to wherever you like, in this case port 3000.
|
As you can see, you only have to forward port 3000 to wherever you like, in this case port 3000.
|
||||||
The URL you have to enter into the OEPL Google Calendar config then looks like this:
|
The URL you have to enter into the OEPL Google Calendar config then looks like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://<docker-host>:3000/calendar/<ics-url>/entries
|
||||||
|
```
|
||||||
|
|
||||||
|
It is important that you urlencode the `<ics-url>` with a tool like this: https://www.urlencoder.org/
|
||||||
|
|
||||||
|
With an actual .ics url, it looks like this:
|
||||||
|
```
|
||||||
|
http://192.168.178.42:3000/calendar/https%3A%2F%2Fnextcloud.net%2Fremote.php%2Fdav%2Fpublic-calendars%2Fasdlkijf/entries
|
||||||
|
```
|
||||||
|
|
||||||
|
Previous versions also supported these URL formats, but these are not compatible with modern OEPL features:
|
||||||
|
|
||||||
```
|
```
|
||||||
http://<docker-host>:3000/get?url=<ics-url>
|
http://<docker-host>:3000/get?url=<ics-url>
|
||||||
```
|
```
|
||||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -19,11 +19,15 @@ fn now_timestamp_secs() -> i64 {
|
||||||
|
|
||||||
async fn handler(params: HashMap<String, String>) -> Result<impl Reply, Rejection> {
|
async fn handler(params: HashMap<String, String>) -> Result<impl Reply, Rejection> {
|
||||||
match params.get("url") {
|
match params.get("url") {
|
||||||
Some(url) => Ok(warp::reply::json(&convert(&url).await.map_err(|_| warp::reject::reject())?.entries)),
|
Some(url) => Ok(warp::reply::json(&convert(&url, params.get("days")).await.map_err(|_| warp::reject::reject())?.entries)),
|
||||||
None => Err(warp::reject::reject())
|
None => Err(warp::reject::reject())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn new_handler(url: String, params: HashMap<String, String>) -> Result<impl Reply, Rejection> {
|
||||||
|
Ok(warp::reply::json(&convert(&url, params.get("days")).await.map_err(|_| warp::reject::reject())?.entries))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct CustomCalendar {
|
struct CustomCalendar {
|
||||||
entries: Vec<CustomCalendarEntry>,
|
entries: Vec<CustomCalendarEntry>,
|
||||||
|
@ -36,9 +40,11 @@ struct CustomCalendarEntry {
|
||||||
end: i64,
|
end: i64,
|
||||||
location: String,
|
location: String,
|
||||||
description: String,
|
description: String,
|
||||||
|
isallday: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn convert(url: &str) -> Result<CustomCalendar> {
|
async fn convert(url: &str, days: Option<&String>) -> Result<CustomCalendar> {
|
||||||
|
let url = urlencoding::decode(url)?.into_owned();
|
||||||
let ics_text = reqwest::get(url)
|
let ics_text = reqwest::get(url)
|
||||||
.await?
|
.await?
|
||||||
.text()
|
.text()
|
||||||
|
@ -49,7 +55,7 @@ async fn convert(url: &str) -> Result<CustomCalendar> {
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
let filter_start = now_timestamp_secs();
|
let filter_start = now_timestamp_secs();
|
||||||
let filter_end = now_timestamp_secs() + (24 * 60 * 60);
|
let filter_end = now_timestamp_secs() + (24 * 60 * 60) * days.unwrap_or(&String::from("1")).parse().unwrap_or(1) as i64;
|
||||||
|
|
||||||
for event in calendar.components {
|
for event in calendar.components {
|
||||||
if let Some(event) = event.as_event() {
|
if let Some(event) = event.as_event() {
|
||||||
|
@ -57,10 +63,6 @@ async fn convert(url: &str) -> Result<CustomCalendar> {
|
||||||
println!("No start!");
|
println!("No start!");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(end) = event.get_end() else {
|
|
||||||
println!("No end!");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let start = match convert_time(start) {
|
let start = match convert_time(start) {
|
||||||
Ok(t) => { t },
|
Ok(t) => { t },
|
||||||
|
@ -70,23 +72,30 @@ async fn convert(url: &str) -> Result<CustomCalendar> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let end = match convert_time(end) {
|
let end = match event.get_end() {
|
||||||
|
Some(end) => {
|
||||||
|
match convert_time(end) {
|
||||||
Ok(t) => { t },
|
Ok(t) => { t },
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Invalid end timestamp: {:?}", e);
|
println!("Invalid end timestamp: {:?}", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => start + chrono::Duration::days(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
if start < filter_start || start > filter_end {
|
if start.timestamp() < filter_start || start.timestamp() > filter_end {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
entries.push(CustomCalendarEntry {
|
entries.push(CustomCalendarEntry {
|
||||||
title: event.get_summary().unwrap_or("").to_string(),
|
title: event.get_summary().unwrap_or("").to_string(),
|
||||||
description: event.get_description().unwrap_or("").to_string(),
|
description: event.get_description().unwrap_or("").to_string(),
|
||||||
location: event.get_location().unwrap_or("").to_string(),
|
location: event.get_location().unwrap_or("").to_string(),
|
||||||
start,
|
start: start.timestamp(),
|
||||||
end
|
end: end.timestamp(),
|
||||||
|
isallday: start.time() == chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap() && end - start == chrono::Duration::days(1) // the event has a length of 24 hours and
|
||||||
|
// starts at 00:00
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +103,7 @@ async fn convert(url: &str) -> Result<CustomCalendar> {
|
||||||
Ok(CustomCalendar{entries})
|
Ok(CustomCalendar{entries})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_time(dt: icalendar::DatePerhapsTime) -> Result<i64> {
|
fn convert_time(dt: icalendar::DatePerhapsTime) -> Result<chrono::DateTime<chrono::Utc>> {
|
||||||
Ok(match dt {
|
Ok(match dt {
|
||||||
icalendar::DatePerhapsTime::DateTime(cdt) => {
|
icalendar::DatePerhapsTime::DateTime(cdt) => {
|
||||||
let cdt = match cdt {
|
let cdt = match cdt {
|
||||||
|
@ -106,20 +115,23 @@ fn convert_time(dt: icalendar::DatePerhapsTime) -> Result<i64> {
|
||||||
},
|
},
|
||||||
_ => cdt,
|
_ => cdt,
|
||||||
};
|
};
|
||||||
cdt.try_into_utc().ok_or(anyhow::Error::msg("failed to convert to utc"))?.timestamp()
|
cdt.try_into_utc().ok_or(anyhow::Error::msg("failed to convert to utc"))?
|
||||||
},
|
},
|
||||||
icalendar::DatePerhapsTime::Date(nd) => nd.and_hms_opt(0, 0, 0).unwrap().timestamp(),
|
icalendar::DatePerhapsTime::Date(nd) => nd.and_hms_opt(0, 0, 0).unwrap().and_utc(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let converter = warp::get()
|
let converter = warp::path("get")
|
||||||
.and(warp::path("get"))
|
|
||||||
.and(warp::query::<HashMap<String, String>>())
|
.and(warp::query::<HashMap<String, String>>())
|
||||||
.and_then(handler);
|
.and_then(handler);
|
||||||
|
|
||||||
warp::serve(converter)
|
let new_converter = warp::path!("calendar" / String / "entries")
|
||||||
|
.and(warp::query::<HashMap<String, String>>())
|
||||||
|
.and_then(new_handler);
|
||||||
|
|
||||||
|
warp::serve(warp::get().and(converter.or(new_converter)))
|
||||||
.run(([0, 0, 0, 0], 3000))
|
.run(([0, 0, 0, 0], 3000))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue