mirror of
https://github.com/jhbruhn/ics-adapter.git
synced 2025-03-14 19:15:50 +00:00
Fix repeat rule parsing again
This commit is contained in:
parent
45630b5665
commit
84e7a04006
2 changed files with 85 additions and 67 deletions
|
@ -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.
|
||||||
|
|
151
src/main.rs
151
src/main.rs
|
@ -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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue