Fix repeat rule parsing again

This commit is contained in:
Jan-Henrik 2024-01-17 21:29:55 +01:00
parent 45630b5665
commit 84e7a04006
2 changed files with 85 additions and 67 deletions

View file

@ -14,7 +14,6 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
TZ: Europe/Berlin # your timezone! TZ: Europe/Berlin # your timezone!
RULE_REPEATS: 100 # optional, the amount of repeats that should be done for repeating rules. Set to higher value if you are missing events of old repeating events
``` ```
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.

View file

@ -1,28 +1,37 @@
use warp::{
Filter,
Reply,
Rejection,
};
use std::collections::HashMap;
use anyhow::Result; use anyhow::Result;
use icalendar::{Component, Calendar, EventLike}; use icalendar::{Calendar, Component, EventLike};
use serde::Serialize; use serde::Serialize;
use std::collections::HashMap;
use warp::{Filter, Rejection, Reply};
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], params.get("days")).await.map_err(|_| warp::reject::reject())?.entries)), Some(url) => Ok(warp::reply::json(
None => Err(warp::reject::reject()) &convert(&[url], params.get("days"))
.await
.map_err(|_| warp::reject::reject())?
.entries,
)),
None => Err(warp::reject::reject()),
} }
} }
async fn new_handler(url: String, params: HashMap<String, String>) -> Result<impl Reply, Rejection> { async fn new_handler(
url: String,
params: HashMap<String, String>,
) -> Result<impl Reply, Rejection> {
let urls: Vec<&str> = url.split(";").collect(); let urls: Vec<&str> = url.split(";").collect();
Ok(warp::reply::json(&convert(&urls, params.get("days")).await.map_err(|_| warp::reject::reject())?.entries)) Ok(warp::reply::json(
&convert(&urls, params.get("days"))
.await
.map_err(|_| warp::reject::reject())?
.entries,
))
} }
#[derive(Serialize)] #[derive(Serialize)]
struct CustomCalendar { struct CustomCalendar {
entries: Vec<CustomCalendarEntry>, entries: Vec<CustomCalendarEntry>,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -41,16 +50,19 @@ async fn convert(urls: &[&str], days: Option<&String>) -> Result<CustomCalendar>
let mut calendar_index = 0; let mut calendar_index = 0;
for url in urls { for url in urls {
let url = urlencoding::decode(url)?.into_owned(); let url = urlencoding::decode(url)?.into_owned();
let ics_text = reqwest::get(url) let ics_text = reqwest::get(url).await?.text().await?;
.await?
.text()
.await?;
let calendar = ics_text.parse::<Calendar>().map_err(|e| anyhow::Error::msg(e))?;
let calendar = ics_text
let filter_start = chrono::Utc::now().date_naive().and_hms_opt(0, 0, 0).unwrap().and_utc(); .parse::<Calendar>()
let filter_end = filter_start + chrono::Duration::days(days.unwrap_or(&String::from("1")).parse().unwrap_or(1)); .map_err(|e| anyhow::Error::msg(e))?;
let filter_start = chrono::Utc::now()
.date_naive()
.and_hms_opt(0, 0, 0)
.unwrap()
.and_utc();
let filter_end =
filter_start + chrono::Duration::days(days.and_then(|x| x.parse().ok()).unwrap_or(1));
for event in calendar.components { for event in calendar.components {
if let Some(event) = event.as_event() { if let Some(event) = event.as_event() {
@ -60,7 +72,7 @@ async fn convert(urls: &[&str], days: Option<&String>) -> Result<CustomCalendar>
}; };
let start = match convert_time(start) { let start = match convert_time(start) {
Ok(t) => { t }, Ok(t) => t,
Err(e) => { Err(e) => {
println!("Invalid start timestamp: {:?}", e); println!("Invalid start timestamp: {:?}", e);
continue; continue;
@ -68,13 +80,11 @@ async fn convert(urls: &[&str], days: Option<&String>) -> Result<CustomCalendar>
}; };
let end = match event.get_end() { let end = match event.get_end() {
Some(end) => { Some(end) => match convert_time(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), None => start + chrono::Duration::days(1),
@ -85,36 +95,41 @@ async fn convert(urls: &[&str], days: Option<&String>) -> Result<CustomCalendar>
let start_dates = if let Some(rrule) = event.properties().get("RRULE") { let start_dates = if let Some(rrule) = event.properties().get("RRULE") {
//let vector = Vec::new(); //let vector = Vec::new();
let rrule_str = rrule.value(); let rrule_str = rrule.value();
let string = format!("DTSTART:{}\n{}", event.properties().get("DTSTART").unwrap().value(), rrule_str); let string = format!(
"DTSTART:{}\n{}",
event.properties().get("DTSTART").unwrap().value(),
rrule_str
);
let rrule: rrule::RRuleSet = string.parse()?; let rrule: rrule::RRuleSet = string.parse()?;
let repeats = std::env::var("RULE_REPEATS").ok().and_then(|x| x.parse().ok()).unwrap_or(100); let date_set = rrule
let date_set = rrule.all(repeats).dates; .into_iter()
date_set.iter().map(|x| x.with_timezone(&chrono::Utc)).collect() .skip_while(|x| x < &filter_start)
.take_while(|x| x <= &filter_end)
.map(|x| x.with_timezone(&chrono::Utc))
.collect();
date_set
} else { } else {
vec!(start) vec![start]
}; };
let mut start = None; for start in start_dates
for start_date in start_dates { .iter()
if start_date >= filter_start && start_date <= filter_end { .skip_while(|x| x < &&filter_start)
start = Some(start_date); .take_while(|x| x <= &&filter_end)
break; {
} let end = *start + length;
}
if let Some(start) = start { entries.push(CustomCalendarEntry {
let end = start + length; title: event.get_summary().unwrap_or("").to_string(),
description: event.get_description().unwrap_or("").to_string(),
entries.push(CustomCalendarEntry { location: event.get_location().unwrap_or("").to_string(),
title: event.get_summary().unwrap_or("").to_string(), start: start.timestamp(),
description: event.get_description().unwrap_or("").to_string(), end: end.timestamp(),
location: event.get_location().unwrap_or("").to_string(), calendar: calendar_index,
start: start.timestamp(), isallday: start.time() == chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap()
end: end.timestamp(), && end - *start == chrono::Duration::days(1), // the event has a length of 24 hours and
calendar: calendar_index, // starts at 00:00
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
});
} }
} }
} }
@ -124,26 +139,30 @@ async fn convert(urls: &[&str], days: Option<&String>) -> Result<CustomCalendar>
entries.sort_by(|a, b| a.start.cmp(&b.start)); entries.sort_by(|a, b| a.start.cmp(&b.start));
Ok(CustomCalendar{entries}) Ok(CustomCalendar { entries })
} }
fn convert_time(dt: icalendar::DatePerhapsTime) -> Result<chrono::DateTime<chrono::Utc>> { 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 {
icalendar::CalendarDateTime::WithTimezone{date_time, tzid} => { icalendar::CalendarDateTime::WithTimezone { date_time, tzid } => {
icalendar::CalendarDateTime::WithTimezone{date_time, tzid: String::from(match tzid.as_str() { icalendar::CalendarDateTime::WithTimezone {
"Turkey Standard Time" => "Europe/Istanbul", date_time,
"India Standard Time" => "Asia/Kolkata", tzid: String::from(match tzid.as_str() {
"Pacific Standard Time" => "America/Los Angeles", "Turkey Standard Time" => "Europe/Istanbul",
"W. Europe Standard Time" => "Europe/Berlin", "India Standard Time" => "Asia/Kolkata",
_ => &tzid "Pacific Standard Time" => "America/Los Angeles",
})} "W. Europe Standard Time" => "Europe/Berlin",
}, _ => &tzid,
}),
}
}
_ => cdt, _ => cdt,
}; };
cdt.try_into_utc().ok_or(anyhow::Error::msg("failed to convert to utc"))? 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().and_utc(), icalendar::DatePerhapsTime::Date(nd) => nd.and_hms_opt(0, 0, 0).unwrap().and_utc(),
}) })
} }