mirror of
https://github.com/FliegendeWurst/telegram_notes_bot.git
synced 2024-11-22 10:54:57 +00:00
Print debug info for files and attempt to parse iCalendar files
This commit is contained in:
parent
da90479eda
commit
ec00812887
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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)",
|
||||||
|
@ -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
116
src/ical_parsing.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
43
src/main.rs
43
src/main.rs
@ -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())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user