Print debug info for files and attempt to parse iCalendar files

This commit is contained in:
FliegendeWurst 2020-05-22 18:52:58 +02:00
parent da90479eda
commit ec00812887
4 changed files with 160 additions and 2 deletions

1
Cargo.lock generated
View File

@ -1129,6 +1129,7 @@ dependencies = [
"futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ical 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "ical 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -2,6 +2,7 @@
name = "telegram_notes_bot" name = "telegram_notes_bot"
version = "0.1.0" version = "0.1.0"
authors = ["FliegendeWurst <2012gdwu@web.de>"] authors = ["FliegendeWurst <2012gdwu@web.de>"]
license = "GPL-3.0+"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
@ -18,3 +19,4 @@ once_cell = "1.3.1"
thiserror = "1.0.15" thiserror = "1.0.15"
serde_json = "1.0.51" serde_json = "1.0.51"
ical = "0.6.0" ical = "0.6.0"
mime = "0.3.16"

116
src/ical_parsing.rs Normal file
View File

@ -0,0 +1,116 @@
use chrono::{Duration, NaiveDateTime, NaiveDate};
use ical::parser::ical::component::IcalEvent;
use ical::parser::ical::IcalParser;
use thiserror::Error;
#[derive(Debug)]
pub struct Calendar {
pub name: String,
pub events: Vec<Event>,
}
#[derive(Debug)]
pub struct Event {
pub uid: String,
pub summary: String,
pub description: String,
pub start: NaiveDateTime,
pub end: NaiveDateTime,
pub duration: Option<Duration>,
pub location: String,
}
pub fn parse_calendar(data: &str) -> Result<Calendar, Error> {
let cal = IcalParser::new(data.as_bytes()).next().ok_or(Error::Nothing)??;
let mut name = None;
let mut events = Vec::new();
for prop in cal.properties {
match prop.name.as_ref() {
"NAME" => name = Some(prop.value.unwrap_or_default()),
_ => {}
}
}
for event in cal.events {
events.push(process_event(event)?);
}
let name = name.unwrap_or_default();
Ok(Calendar {
name, events
})
}
fn process_event(event: IcalEvent) -> Result<Event, Error> {
let mut uid = None;
let mut summary = None;
let mut description = None;
let mut start = None;
let mut end = None;
let mut duration = None;
let mut location = None;
for prop in event.properties {
let value = prop.value.unwrap_or_default();
match prop.name.as_ref() {
"UID" => uid = Some(value),
"SUMMARY" => summary = Some(value),
"LOCATION" => location = Some(value),
"DESCRIPTION" => description = Some(value),
"STATUS" => { /* TODO: status */ },
"DTSTART" => start = Some(process_dt(&value)?),
"DTEND" => end = Some(process_dt(&value)?),
"DURATION" => duration = Some(process_duration(&value)?),
"RRULE" => { /* TODO: periodic */ },
_ => (),
};
}
// TODO: don't put defaults here
let start = start.ok_or(Error::Data("no dtstart"))?;
let end = end.ok_or(Error::Data("no dtend"))?;
Ok(Event {
uid: uid.unwrap_or_default(),
summary: summary.unwrap_or_default(),
description: description.unwrap_or_default(),
start: start,
end: end,
duration,
location: location.unwrap_or_default(),
})
}
fn process_dt(value: &str) -> Result<NaiveDateTime, Error> {
// 20200626T140000
if value.len() != 15 {
return Err(Error::Data("invalid dt length"));
}
// TODO: error handling
let year = value[0..4].parse()?;
let month = value[4..6].parse()?;
let day = value[6..8].parse()?;
let hour = value[9..11].parse()?;
let minute = value[11..13].parse()?;
let second = value[13..15].parse()?;
Ok(NaiveDate::from_ymd(year, month, day).and_hms(hour, minute, second))
}
fn process_duration(_value: &str) -> Result<Duration, Error> {
// TODO
return Err(Error::Data("duration parsing not implemented"));
}
#[derive(Error, Debug)]
pub enum Error {
#[error("parsing error: {0}")]
Ical(ical::parser::ParserError),
#[error("data error: {0}")]
Data(&'static str),
#[error("parse error: {0}")]
IntegerParsing(#[from] std::num::ParseIntError),
#[error("no calendar found")]
Nothing
}
impl From<ical::parser::ParserError> for Error {
fn from(x: ical::parser::ParserError) -> Self {
Error::Ical(x)
}
}

View File

@ -1,6 +1,7 @@
use chrono::prelude::*; use chrono::prelude::*;
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use maplit::hashmap; use maplit::hashmap;
use mime::Mime;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use reqwest::Client; use reqwest::Client;
use serde_derive::Deserialize; use serde_derive::Deserialize;
@ -16,10 +17,15 @@ use std::env;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
mod ical_parsing;
static TELEGRAM_BOT_TOKEN: Lazy<String> = Lazy::new(|| {
env::var("TELEGRAM_BOT_TOKEN").expect("TELEGRAM_BOT_TOKEN not set")
});
static API: Lazy<Arc<Api>> = Lazy::new(|| { static API: Lazy<Arc<Api>> = Lazy::new(|| {
let telegram_token = env::var("TELEGRAM_BOT_TOKEN").expect("TELEGRAM_BOT_TOKEN not set");
println!("Initializing Telegram API.."); println!("Initializing Telegram API..");
Arc::new(Api::new(telegram_token)) Arc::new(Api::new(&*TELEGRAM_BOT_TOKEN))
}); });
static TRILIUM_TOKEN: Lazy<String> = Lazy::new(|| { static TRILIUM_TOKEN: Lazy<String> = Lazy::new(|| {
@ -122,6 +128,24 @@ async fn process_one(update: Update, reminder_msg: &mut MessageId, reminder_text
} else { } else {
API.send(message.text_reply("Text saved :-)")).await?; API.send(message.text_reply("Text saved :-)")).await?;
} }
} else if let MessageKind::Document { ref data, ref caption, .. } = message.kind {
let document = data;
send_message(format!("Document {:?} {:?} {:?} {:?}", caption, document.file_id, document.file_name, document.mime_type)).await?;
let get_file = GetFile::new(&document);
let file = API.send(get_file).await?;
let url = file.get_url(&TELEGRAM_BOT_TOKEN).ok_or_else(|| error("url is none"))?;
let data = CLIENT.get(&url).send().await?.bytes().await?;
let mime: Mime = document.mime_type.as_ref().ok_or_else(|| error("no mime type"))?.parse()?;
match (mime.type_(), mime.subtype()) {
(mime::TEXT, x) if x == "calendar" => {
let text = String::from_utf8_lossy(&data);
let text = text.replace("\n<", "<"); // newlines in HTML values
send_message(&text).await?;
let calendar = ical_parsing::parse_calendar(&text)?;
send_message(format!("{:?}", calendar)).await?;
},
_ => {}
}
} }
} else if let UpdateKind::CallbackQuery(cb) = update.kind { } else if let UpdateKind::CallbackQuery(cb) = update.kind {
match &*cb.data.unwrap_or_default() { match &*cb.data.unwrap_or_default() {
@ -278,6 +302,11 @@ async fn notify_owner_impl(time_left: &str, task: Task) -> Result<(), Error> {
Ok(()) Ok(())
} }
async fn send_message<S: Into<String>>(msg: S) -> Result<(), Error> {
API.send(SendMessage::new(*OWNER, msg.into())).await?;
Ok(())
}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
struct Task { struct Task {
@ -323,4 +352,14 @@ pub enum Error {
Network(#[from] reqwest::Error), Network(#[from] reqwest::Error),
#[error("telegram error: {0}")] #[error("telegram error: {0}")]
Telegram(#[from] telegram_bot::Error), Telegram(#[from] telegram_bot::Error),
#[error("mime parsing error: {0}")]
Mime(#[from] mime::FromStrError),
#[error("ical parsing error: {0}")]
Ical(#[from] ical_parsing::Error),
#[error("internal error: {0}")]
CustomMessage(String),
}
fn error<S: Into<String>>(msg: S) -> Error {
Error::CustomMessage(msg.into())
} }