mirror of
				https://github.com/jhbruhn/ics-adapter.git
				synced 2025-10-31 11:26:04 +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