From bce2144ff6bfc8a917a9e87bba91fff3e1036f2e Mon Sep 17 00:00:00 2001 From: Syfaro Date: Wed, 8 Jan 2020 18:04:32 -0600 Subject: [PATCH] Significant improvement in error handling. --- Cargo.lock | 1 + Cargo.toml | 1 + README.md | 1 + langs/en-US/foxbot.ftl | 3 +- src/main.rs | 295 ++++++++++++++++++++++------------------- telegram/src/lib.rs | 11 ++ 6 files changed, 175 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaf7633..23907d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,6 +535,7 @@ dependencies = [ "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "unic-langid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 124d9cc..a27ae32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ hyper = "0.13.1" log = "0.4.8" pretty_env_logger = "0.3.1" sentry = "0.17.0" +uuid = "0.7.4" failure = "0.1.6" influxdb = { git = "https://github.com/Empty2k12/influxdb-rust.git", rev = "1b0cae12d8b1b0e2bd29129e6757a9df8a8faf50" } diff --git a/README.md b/README.md index f9e3b92..e0be9ba 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,4 @@ Env Name | Description `INFLUX_USER` | InfluxDB username `INFLUX_PASS` | InfluxDB password `USE_PROXY` | If should use images.weserv.nl to proxy images +`SENTRY_DSN` | Sentry DSN to report errors diff --git a/langs/en-US/foxbot.ftl b/langs/en-US/foxbot.ftl index 3c124b6..4171acc 100644 --- a/langs/en-US/foxbot.ftl +++ b/langs/en-US/foxbot.ftl @@ -45,4 +45,5 @@ mirror-missing = source-no-photo = Sorry, I can't seem to find the photo here. # Error Messages -error-generic = Something went wrong, please try again later. +error-generic = Oh no, something went wrong! Please send a message to my creator { -creatorName } if you continue having issues. +error-uuid = Oh no, something went wrong! Please send a message to my creator { -creatorName } with this ID if you continue having issues: { $uuid } diff --git a/src/main.rs b/src/main.rs index 05c0b62..18dd971 100644 --- a/src/main.rs +++ b/src/main.rs @@ -333,12 +333,11 @@ fn get_message( } } -fn with_user_scope( - from: Option<&User>, - tags: Option>, - callback: C, -) where - C: FnOnce(), +type SentryTags<'a> = Option>; + +fn with_user_scope(from: Option<&User>, tags: SentryTags, callback: C) -> R +where + C: FnOnce() -> R, { sentry::with_scope( |scope| { @@ -357,7 +356,7 @@ fn with_user_scope( } }, callback, - ); + ) } impl MessageHandler { @@ -414,12 +413,58 @@ impl MessageHandler { .expect("Unable to add resource"); } + bundle.set_use_isolating(false); + self.best_lang.insert(requested.to_string(), bundle); self.best_lang .get(requested) .expect("Value just inserted is missing") } + async fn report_error( + &mut self, + message: &Message, + tags: Option>, + callback: C, + ) where + C: FnOnce() -> uuid::Uuid, + { + let u = with_user_scope(message.from.as_ref(), tags, callback); + + let lang_code = message + .from + .clone() + .map(|from| from.language_code) + .flatten(); + let bundle = self.get_fluent_bundle(lang_code.as_deref()); + + let msg = if u.is_nil() { + get_message(&bundle, "error-generic", None) + } else { + let f = format!("`{}`", u.to_string()); + + let mut args = fluent::FluentArgs::new(); + args.insert("uuid", fluent::FluentValue::from(f)); + + get_message(&bundle, "error-uuid", Some(args)) + }; + + let send_message = SendMessage { + chat_id: message.chat_id(), + text: msg.unwrap(), + parse_mode: Some(ParseMode::Markdown), + reply_to_message_id: Some(message.message_id), + ..Default::default() + }; + + if let Err(e) = self.bot.make_request(&send_message).await { + log::error!("Unable to send error message to user: {:?}", e); + with_user_scope(message.from.as_ref(), None, || { + capture_fail(&e); + }); + } + } + async fn handle_inline(&mut self, inline: InlineQuery) { let now = std::time::Instant::now(); @@ -441,19 +486,14 @@ impl MessageHandler { let images = match site.get_images(inline.from.id, link_str).await { Ok(images) => images, Err(e) => { + log::warn!("Unable to get image: {:?}", e); with_user_scope( Some(&inline.from), - Some( - [("site", site.name().to_string())] - .iter() - .cloned() - .collect(), - ), + Some(vec![("site", site.name().to_string())]), || { capture_fail(&e); }, ); - log::warn!("Unable to get image: {:?}", e); continue 'link; } }; @@ -517,10 +557,8 @@ impl MessageHandler { point = point.add_field("duration", now.elapsed().as_millis() as i64); if let Err(e) = self.influx.query(&point).await { - with_user_scope(Some(&inline.from), None, || { - let uuid = capture_fail(&e); - log::error!("[{}] Unable to log inline to InfluxDB: {:?}", uuid, e); - }); + log::error!("Unable to log inline to InfluxDB: {:?}", e); + with_user_scope(Some(&inline.from), None, || capture_fail(&e)); }; } @@ -587,18 +625,11 @@ impl MessageHandler { }; if let Err(e) = self.bot.make_request(&send_message).await { + log::error!("Unable to send help message: {:?}", e); with_user_scope( message.from.as_ref(), - Some( - [("command", command_text.to_string())] - .iter() - .cloned() - .collect(), - ), - || { - let uuid = capture_fail(&e); - log::error!("[{}] Unable to send help message: {:?}", uuid, e); - }, + Some(vec![("command", command_text.to_string())]), + || capture_fail(&e), ); } } @@ -613,10 +644,10 @@ impl MessageHandler { .add_field("duration", now.elapsed().as_millis() as i64); if let Err(e) = self.influx.query(&point).await { + log::error!("Unable to send command to InfluxDB: {:?}", e); with_user_scope(from.as_ref(), None, || { capture_fail(&e); }); - log::error!("Unable to send command to InfluxDB: {:?}", e); } } @@ -651,10 +682,10 @@ impl MessageHandler { }) .for_each(|_| async { if let Err(e) = bot.make_request(&chat_action).await { + log::warn!("Unable to send chat action: {:?}", e); with_user_scope(user.as_ref(), None, || { capture_fail(&e); }); - log::warn!("Unable to send chat action: {:?}", e); } }) .await; @@ -697,11 +728,13 @@ impl MessageHandler { let file = match self.bot.make_request(&get_file).await { Ok(file) => file, Err(e) => { - self.send_generic_reply(&message, "error-generic").await; - with_user_scope(message.from.as_ref(), None, || { - capture_fail(&e); - }); log::error!("Unable to get file: {:?}", e); + self.report_error( + &message, + Some(vec![("command", "source".to_string())]), + || capture_fail(&e), + ) + .await; return; } }; @@ -709,11 +742,13 @@ impl MessageHandler { let bytes = match self.bot.download_file(file.file_path.unwrap()).await { Ok(bytes) => bytes, Err(e) => { - self.send_generic_reply(&message, "error-generic").await; - with_user_scope(message.from.as_ref(), None, || { - capture_fail(&e); - }); log::error!("Unable to download file: {:?}", e); + self.report_error( + &message, + Some(vec![("command", "source".to_string())]), + || capture_fail(&e), + ) + .await; return; } }; @@ -721,11 +756,13 @@ impl MessageHandler { let matches = match self.fapi.image_search(bytes, false).await { Ok(matches) => matches, Err(e) => { - self.send_generic_reply(&message, "error-generic").await; - with_user_scope(message.from.as_ref(), None, || { - capture_fail(&e); - }); log::error!("Unable to find matches: {:?}", e); + self.report_error( + &message, + Some(vec![("command", "source".to_string())]), + || capture_fail(&e), + ) + .await; return; } }; @@ -888,9 +925,13 @@ impl MessageHandler { if let Err(e) = self.bot.make_request(&photo).await { log::error!("Unable to make request: {:?}", e); - with_user_scope(message.from.as_ref(), None, || { - capture_fail(&e); - }); + self.report_error( + &message, + Some(vec![("command", "mirror".to_string())]), + || capture_fail(&e), + ) + .await; + return; } } else { for chunk in results.chunks(10) { @@ -918,9 +959,13 @@ impl MessageHandler { if let Err(e) = self.bot.make_request(&media_group).await { log::error!("Unable to make request: {:?}", e); - with_user_scope(message.from.as_ref(), None, || { - capture_fail(&e); - }); + self.report_error( + &message, + Some(vec![("command", "mirror".to_string())]), + || capture_fail(&e), + ) + .await; + return; } } @@ -953,8 +998,8 @@ impl MessageHandler { async fn handle_text(&mut self, message: Message) { let now = std::time::Instant::now(); - let text = message.text.unwrap(); // only here because this existed - let from = message.from.unwrap(); + let text = message.text.clone().unwrap(); // only here because this existed + let from = message.from.clone().unwrap(); if text.trim().parse::().is_err() { log::trace!("Got text that wasn't OOB, ignoring"); @@ -978,18 +1023,12 @@ impl MessageHandler { Err(e) => { log::warn!("User was unable to verify OOB: {:?}", e); - let bundle = self.get_fluent_bundle(from.language_code.as_deref()); - - let message = SendMessage { - chat_id: from.id.into(), - text: get_message(&bundle, "error-generic", None).unwrap(), - reply_to_message_id: Some(message.message_id), - ..Default::default() - }; - - if let Err(e) = self.bot.make_request(&message).await { - log::warn!("Unable to send message: {:?}", e); - } + self.report_error( + &message, + Some(vec![("command", "twitter_auth".to_string())]), + || capture_fail(&e), + ) + .await; return; } Ok(token) => token, @@ -1010,28 +1049,17 @@ impl MessageHandler { ) { log::warn!("Unable to save user credentials: {:?}", e); - with_user_scope(Some(&from), None, || { - sentry::integrations::failure::capture_error(&format_err!( - "Unable to save to Twitter database: {}", - e - )); - }); - - let bundle = self.get_fluent_bundle(from.language_code.as_deref()); - - let message = SendMessage { - chat_id: from.id.into(), - text: get_message(&bundle, "error-generic", None).unwrap(), - reply_to_message_id: Some(message.message_id), - ..Default::default() - }; - - if let Err(e) = self.bot.make_request(&message).await { - log::warn!("Unable to send message: {:?}", e); - with_user_scope(Some(&from), None, || { - capture_fail(&e); - }); - } + self.report_error( + &message, + Some(vec![("command", "twitter_auth".to_string())]), + || { + sentry::integrations::failure::capture_error(&format_err!( + "Unable to save to Twitter database: {}", + e + )) + }, + ) + .await; return; } @@ -1106,25 +1134,17 @@ impl MessageHandler { Err(e) => { log::warn!("Unable to get request token: {:?}", e); - with_user_scope(Some(&user), None, || { - capture_fail(&e); - }); - - let bundle = self.get_fluent_bundle(user.language_code.as_deref()); - - let message = SendMessage { - chat_id: user.id.into(), - text: get_message(&bundle, "error-generic", None).unwrap(), - reply_to_message_id: Some(message.message_id), - ..Default::default() - }; - - if let Err(e) = self.bot.make_request(&message).await { - log::warn!("Unable to send message: {:?}", e); - with_user_scope(Some(&user), None, || { - capture_fail(&e); - }); - } + self.report_error( + &message, + Some(vec![("command", "twitter".to_string())]), + || { + sentry::integrations::failure::capture_error(&format_err!( + "Unable to get request token: {}", + e + )) + }, + ) + .await; return; } }; @@ -1135,28 +1155,17 @@ impl MessageHandler { ) { log::warn!("Unable to save authenticate: {:?}", e); - with_user_scope(Some(&user), None, || { - sentry::integrations::failure::capture_error(&format_err!( - "Unable to save to Twitter database: {}", - e - )); - }); - - let bundle = self.get_fluent_bundle(user.language_code.as_deref()); - - let message = SendMessage { - chat_id: user.id.into(), - text: get_message(&bundle, "error-generic", None).unwrap(), - reply_to_message_id: Some(message.message_id), - ..Default::default() - }; - - if let Err(e) = self.bot.make_request(&message).await { - log::warn!("Unable to send message: {:?}", e); - with_user_scope(Some(&user), None, || { - capture_fail(&e); - }); - } + self.report_error( + &message, + Some(vec![("command", "twitter".to_string())]), + || { + sentry::integrations::failure::capture_error(&format_err!( + "Unable to save to Twitter database: {}", + e + )) + }, + ) + .await; return; } @@ -1325,10 +1334,13 @@ impl MessageHandler { let file = match self.bot.make_request(&get_file).await { Ok(file) => file, Err(e) => { - with_user_scope(message.from.as_ref(), None, || { - capture_fail(&e); - }); - self.send_generic_reply(&message, "error-generic").await; + log::error!("Unable to get file: {:?}", e); + self.report_error( + &message, + Some(vec![("command", "photo".to_string())]), + || capture_fail(&e), + ) + .await; return; } }; @@ -1336,16 +1348,30 @@ impl MessageHandler { let photo = match self.bot.download_file(file.file_path.unwrap()).await { Ok(photo) => photo, Err(e) => { - with_user_scope(message.from.as_ref(), None, || { - capture_fail(&e); - }); - self.send_generic_reply(&message, "error-generic").await; + log::error!("Unable to download file: {:?}", e); + self.report_error( + &message, + Some(vec![("command", "photo".to_string())]), + || capture_fail(&e), + ) + .await; return; } }; let matches = match self.fapi.image_search(photo, false).await { Ok(matches) if !matches.is_empty() => matches, + Err(e) => { + log::error!("Unable to reverse search image file: {:?}", e); + completed.store(true, std::sync::atomic::Ordering::SeqCst); + self.report_error( + &message, + Some(vec![("command", "photo".to_string())]), + || capture_fail(&e), + ) + .await; + return; + } _ => { let bundle = self.get_fluent_bundle(message.from.clone().unwrap().language_code.as_deref()); @@ -1376,7 +1402,6 @@ impl MessageHandler { with_user_scope(message.from.as_ref(), None, || { capture_fail(&e); }); - self.send_generic_reply(&message, "error-generic").await; } return; @@ -1416,7 +1441,6 @@ impl MessageHandler { with_user_scope(message.from.as_ref(), None, || { capture_fail(&e); }); - self.send_generic_reply(&message, "error-generic").await; } let point = influxdb::Query::write_query(influxdb::Timestamp::Now, "source") @@ -1429,7 +1453,6 @@ impl MessageHandler { with_user_scope(message.from.as_ref(), None, || { capture_fail(&e); }); - self.send_generic_reply(&message, "error-generic").await; } } } diff --git a/telegram/src/lib.rs b/telegram/src/lib.rs index 356281e..b704ae9 100644 --- a/telegram/src/lib.rs +++ b/telegram/src/lib.rs @@ -375,6 +375,14 @@ pub enum ReplyMarkup { ForceReply(ForceReply), } +#[derive(Serialize, Debug)] +#[serde(rename_all = "PascalCase")] +pub enum ParseMode { + HTML, + Markdown, + MarkdownV2, +} + /// SendMessage sends a message. #[derive(Serialize, Default, Debug)] pub struct SendMessage { @@ -382,6 +390,9 @@ pub struct SendMessage { pub chat_id: ChatID, /// The text of the message. pub text: String, + /// The mode used to parse the provided text. + #[serde(skip_serializing_if = "Option::is_none")] + pub parse_mode: Option, /// The ID of the [Message] this Message is in reply to. #[serde(skip_serializing_if = "Option::is_none")] pub reply_to_message_id: Option,