Error handling

This commit is contained in:
FliegendeWurst 2020-05-04 17:36:19 +02:00
parent a4d8fd6948
commit 9d615fbd66

View File

@ -10,11 +10,8 @@ use telegram_bot::types::{EditMessageText, InlineKeyboardButton, InlineKeyboardM
use thiserror::Error; use thiserror::Error;
use tokio::task; use tokio::task;
use url::Url; use url::Url;
use warp::http::StatusCode;
use warp::{Filter, Rejection, Reply};
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::Infallible;
use std::env; use std::env;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@ -25,6 +22,17 @@ static API: Lazy<Arc<Api>> = Lazy::new(|| {
Arc::new(Api::new(telegram_token)) Arc::new(Api::new(telegram_token))
}); });
static TRILIUM_TOKEN: Lazy<String> = Lazy::new(|| {
println!("Loading passwords..");
let trilium_user = env::var("TRILIUM_USER").expect("TRILIUM_USER not set");
let trilium_password = env::var("TRILIUM_PASSWORD").expect("TRILIUM_PASSWORD not set");
let client = reqwest::blocking::Client::new();
let resp: HashMap<String, String> = client.post("http://localhost:9001/api/login/token")
.json(&hashmap!{ "username" => &trilium_user, "password" => &trilium_password })
.send().unwrap().json().unwrap();
resp["token"].clone()
});
static OWNER: Lazy<UserId> = Lazy::new(|| { static OWNER: Lazy<UserId> = Lazy::new(|| {
println!("Loading configuration.."); println!("Loading configuration..");
UserId::new(env::var("TELEGRAM_USER_ID").expect("TELEGRAM_USER_ID not set").parse().expect("TELEGRAM_USER_ID not numeric")) UserId::new(env::var("TELEGRAM_USER_ID").expect("TELEGRAM_USER_ID not set").parse().expect("TELEGRAM_USER_ID not numeric"))
@ -36,28 +44,16 @@ static CLIENT: Lazy<Client> = Lazy::new(Client::new);
async fn main() -> Result<(), Error> { async fn main() -> Result<(), Error> {
&*OWNER; &*OWNER;
println!("Loading passwords..");
let trilium_user = env::var("TRILIUM_USER").expect("TRILIUM_USER not set");
let trilium_password = env::var("TRILIUM_PASSWORD").expect("TRILIUM_PASSWORD not set");
&*API; &*API;
println!("Initializing Trilium API.."); println!("Initializing Trilium API..");
// Trilium login: // Trilium login:
// curl 'http://localhost:9001/api/login/token' -H 'User-Agent: Mozilla/5.0 ..' -H 'Accept: application/json' -H 'Accept-Language: en' --compressed -H 'Content-Type: application/json' -H 'Origin: moz-extension://13bc3fd7-5cb0-4d48-b368-76e389fd7c5f' -H 'DNT: 1' -H 'Connection: keep-alive' --data '{"username":"username","password":"insert_password_here"}' // curl 'http://localhost:9001/api/login/token' -H 'User-Agent: Mozilla/5.0 ..' -H 'Accept: application/json' -H 'Accept-Language: en' --compressed -H 'Content-Type: application/json' -H 'Origin: moz-extension://13bc3fd7-5cb0-4d48-b368-76e389fd7c5f' -H 'DNT: 1' -H 'Connection: keep-alive' --data '{"username":"username","password":"insert_password_here"}'
// -> {"token":"icB3xohFDpkVt7YFpbTflUYC8pucmryVGpb1DFpd6ns="} // -> {"token":"icB3xohFDpkVt7YFpbTflUYC8pucmryVGpb1DFpd6ns="}
let resp: HashMap<String, String> = CLIENT.post("http://localhost:9001/api/login/token") println!("Acquired token: {}", *TRILIUM_TOKEN);
.json(&hashmap!{ "username" => &trilium_user, "password" => &trilium_password })
.send().await?.json().await?;
let trilium_token = &resp["token"];
println!("Acquired token: {}", trilium_token);
println!("Init done!"); println!("Init done!");
task::spawn(async move {
start_server().await;
});
task::spawn(async move { task::spawn(async move {
start_polling().await; start_polling().await;
}); });
@ -70,35 +66,43 @@ async fn main() -> Result<(), Error> {
// Fetch new updates via long poll method // Fetch new updates via long poll method
let mut stream = API.stream(); let mut stream = API.stream();
while let Some(update) = stream.next().await { while let Some(update) = stream.next().await {
// If the received update contains a new message...
if update.is_err() { if update.is_err() {
println!("Error: {:?}", update.err().unwrap()); println!("Telegram error: {:?}", update.err().unwrap());
continue; continue;
} }
let update = update.unwrap(); let update = update.unwrap();
if let Err(e) = process_one(update, &mut reminder_msg, &mut reminder_text, &mut reminder_start, &mut reminder_time).await {
println!("Error: {}", e);
}
}
Ok(())
}
async fn process_one(update: Update, reminder_msg: &mut MessageId, reminder_text: &mut String, reminder_start: &mut DateTime<Local>, reminder_time: &mut chrono::Duration) -> Result<(), Error> {
if let UpdateKind::Message(message) = update.kind { if let UpdateKind::Message(message) = update.kind {
let now = Local::now(); let now = Local::now();
println!("[{}-{:02}-{:02} {:02}:{:02}] Receiving msg {:?}", now.year(), now.month(), now.day(), now.hour(), now.minute(), message); println!("[{}-{:02}-{:02} {:02}:{:02}] Receiving msg {:?}", now.year(), now.month(), now.day(), now.hour(), now.minute(), message);
if message.from.id != *OWNER { if message.from.id != *OWNER {
// don't handle message // don't handle message
continue; return Ok(());
} }
if let MessageKind::Text { ref data, .. } = message.kind { if let MessageKind::Text { ref data, .. } = message.kind {
if data == "/remindme" { if data == "/remindme" {
let mut msg = SendMessage::new(*OWNER, "in 0m: new reminder"); let mut msg = SendMessage::new(*OWNER, "in 0m: new reminder");
msg.reply_markup(get_keyboard()); msg.reply_markup(get_keyboard());
reminder_msg = API.send(msg).await?.to_message_id(); *reminder_msg = API.send(msg).await?.to_message_id();
reminder_text = "new reminder".to_owned(); *reminder_text = "new reminder".to_owned();
reminder_time = chrono::Duration::minutes(0); *reminder_time = chrono::Duration::minutes(0);
reminder_start = Local::now(); *reminder_start = Local::now();
continue; return Ok(());
} else if !reminder_text.is_empty() { } else if !reminder_text.is_empty() {
reminder_text = data.to_owned(); *reminder_text = data.to_owned();
let mut edit = EditMessageText::new(*OWNER, reminder_msg, format!("in {}: {}", format_time(reminder_time), reminder_text)); let mut edit = EditMessageText::new(*OWNER, *reminder_msg, format!("in {}: {}", format_time(*reminder_time), reminder_text));
edit.reply_markup(get_keyboard()); edit.reply_markup(get_keyboard());
API.send(edit).await?; API.send(edit).await?;
continue; return Ok(());
} }
let is_url = false; //Url::parse(&data).is_ok(); // TODO: read this data from the Telegram json data (utf16 idxes..) let is_url = false; //Url::parse(&data).is_ok(); // TODO: read this data from the Telegram json data (utf16 idxes..)
let formatted_text = if is_url { let formatted_text = if is_url {
@ -107,7 +111,7 @@ async fn main() -> Result<(), Error> {
format!("<ul><li>{}</li></ul>", data) format!("<ul><li>{}</li></ul>", data)
}; };
let title = format!("{} found at {:02}:{:02}", if is_url { "URL" } else { "content" }, now.hour(), now.minute()); let title = format!("{} found at {:02}:{:02}", if is_url { "URL" } else { "content" }, now.hour(), now.minute());
create_text_note(&CLIENT, trilium_token, create_text_note(&CLIENT, &*TRILIUM_TOKEN,
&title, &title,
&formatted_text &formatted_text
).await?; ).await?;
@ -122,44 +126,43 @@ async fn main() -> Result<(), Error> {
} 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() {
"10m_cb" => { "10m_cb" => {
reminder_time = reminder_time.checked_add(&chrono::Duration::minutes(10)).unwrap(); *reminder_time = reminder_time.checked_add(&chrono::Duration::minutes(10)).unwrap();
let mut edit = EditMessageText::new(*OWNER, reminder_msg, format!("in {}: {}", format_time(reminder_time), reminder_text)); let mut edit = EditMessageText::new(*OWNER, *reminder_msg, format!("in {}: {}", format_time(*reminder_time), reminder_text));
edit.reply_markup(get_keyboard()); edit.reply_markup(get_keyboard());
API.send(edit).await?; API.send(edit).await?;
}, },
"1h_cb" => { "1h_cb" => {
reminder_time = reminder_time.checked_add(&chrono::Duration::hours(1)).unwrap(); *reminder_time = reminder_time.checked_add(&chrono::Duration::hours(1)).unwrap();
let mut edit = EditMessageText::new(*OWNER, reminder_msg, format!("in {}: {}", format_time(reminder_time), reminder_text)); let mut edit = EditMessageText::new(*OWNER, *reminder_msg, format!("in {}: {}", format_time(*reminder_time), reminder_text));
edit.reply_markup(get_keyboard()); edit.reply_markup(get_keyboard());
API.send(edit).await?; API.send(edit).await?;
}, },
"1d_cb" => { "1d_cb" => {
reminder_time = reminder_time.checked_add(&chrono::Duration::days(1)).unwrap(); *reminder_time = reminder_time.checked_add(&chrono::Duration::days(1)).unwrap();
let mut edit = EditMessageText::new(*OWNER, reminder_msg, format!("in {}: {}", format_time(reminder_time), reminder_text)); let mut edit = EditMessageText::new(*OWNER, *reminder_msg, format!("in {}: {}", format_time(*reminder_time), reminder_text));
edit.reply_markup(get_keyboard()); edit.reply_markup(get_keyboard());
API.send(edit).await?; API.send(edit).await?;
}, },
"1w_cb" => { "1w_cb" => {
reminder_time = reminder_time.checked_add(&chrono::Duration::days(7)).unwrap(); *reminder_time = reminder_time.checked_add(&chrono::Duration::days(7)).unwrap();
let mut edit = EditMessageText::new(*OWNER, reminder_msg, format!("in {}: {}", format_time(reminder_time), reminder_text)); let mut edit = EditMessageText::new(*OWNER, *reminder_msg, format!("in {}: {}", format_time(*reminder_time), reminder_text));
edit.reply_markup(get_keyboard()); edit.reply_markup(get_keyboard());
API.send(edit).await?; API.send(edit).await?;
}, },
"save_cb" => { "save_cb" => {
let remind_time = reminder_start + reminder_time; let remind_time = *reminder_start + *reminder_time;
CLIENT.get("http://localhost:9001/custom/new_reminder").form(&json!({ CLIENT.get("http://localhost:9001/custom/new_reminder").form(&json!({
"time": remind_time.to_rfc3339(), "time": remind_time.to_rfc3339(),
"task": reminder_text "task": *reminder_text
})).send().await?.text().await?; })).send().await?.text().await?;
API.send(SendMessage::new(*OWNER, "Reminder saved :-)")).await?; API.send(SendMessage::new(*OWNER, "Reminder saved :-)")).await?;
reminder_text = String::new(); *reminder_text = String::new();
}, },
_ => {} _ => {}
} }
} else { } else {
println!("{:?}", update.kind); println!("{:?}", update.kind);
} }
}
Ok(()) Ok(())
} }
@ -270,36 +273,9 @@ fn format_time(diff: chrono::Duration) -> String {
} }
} }
async fn start_server() { async fn notify_owner_impl(time_left: &str, task: Task) -> Result<(), Error> {
let heartbeat = warp::get()
.and(warp::path("heartbeat"))
.map(|| {
println!("Trilium heartbeat received");
"OK"
});
let task_alert = warp::post()
.and(warp::path("task_alert"))
.and(warp::path::param::<String>())
.and(warp::body::json())
.and_then(notify_owner)
.recover(handle_rejection);
let routes = heartbeat.or(task_alert);
warp::serve(routes)
.run(([127, 0, 0, 1], 19517))
.await;
}
async fn notify_owner(note_id: String, task: Task) -> Result<impl warp::Reply, Rejection> {
match notify_owner_impl("..", task).await {
Err(e) => Err(warp::reject::custom(e)),
x => x.map_err(|_| unreachable!())
}
}
async fn notify_owner_impl(time_left: &str, task: Task) -> Result<impl warp::Reply, Error> {
API.send(SendMessage::new(*OWNER, format!("{}: {}", time_left, task.title))).await?; API.send(SendMessage::new(*OWNER, format!("{}: {}", time_left, task.title))).await?;
Ok("OK") Ok(())
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -348,11 +324,3 @@ pub enum Error {
#[error("telegram error: {0}")] #[error("telegram error: {0}")]
Telegram(#[from] telegram_bot::Error), Telegram(#[from] telegram_bot::Error),
} }
impl warp::reject::Reject for Error {}
// This function receives a `Rejection` and tries to return a custom
// value, otherwise simply passes the rejection along.
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
Ok(warp::reply::with_status(format!("{:?}", err), StatusCode::INTERNAL_SERVER_ERROR))
}