foxbot/src/handlers/error_reply.rs

142 lines
3.8 KiB
Rust

use super::Status::*;
use crate::needs_field;
use async_trait::async_trait;
use telegram::*;
pub struct ErrorReplyHandler {
client: reqwest::Client,
}
impl ErrorReplyHandler {
pub fn new() -> Self {
Self {
client: reqwest::Client::new(),
}
}
}
#[async_trait]
impl super::Handler for ErrorReplyHandler {
fn name(&self) -> &'static str {
"error_reply"
}
async fn handle(
&self,
handler: &crate::MessageHandler,
update: &Update,
_command: Option<&Command>,
) -> Result<super::Status, failure::Error> {
let message = needs_field!(update, message);
let text = needs_field!(message, text);
let reply_message = needs_field!(message, reply_to_message);
let reply_message_from = needs_field!(reply_message, from);
let reply_message_text = needs_field!(reply_message, text);
let entities = needs_field!(reply_message, entities);
// Only want to look at messages that are replies to this bot
if reply_message_from.id != handler.bot_user.id {
return Ok(Ignored);
}
let code = match get_code_block(&entities, &reply_message_text) {
Some(code) => code,
_ => return Ok(Ignored),
};
let dsn = match &handler.config.sentry_dsn {
Some(dsn) => dsn,
_ => return Ok(Completed),
};
let auth = format!("DSN {}", dsn);
let data = SentryFeedback {
comments: text.to_string(),
event_id: code,
// This field is required, but Telegram doesn't give us emails...
email: "telegram-user@example.com".to_string(),
name: message
.from
.as_ref()
.map(|from| from.username.clone().unwrap_or_else(|| from.id.to_string())),
};
self.client
.post(&format!(
"https://sentry.io/api/0/projects/{}/{}/user-feedback/",
handler.config.sentry_organization_slug.as_ref().unwrap(),
handler.config.sentry_project_slug.as_ref().unwrap()
))
.json(&data)
.header(reqwest::header::AUTHORIZATION, auth)
.send()
.await?;
handler
.send_generic_reply(&message, "error-feedback")
.await?;
Ok(Completed)
}
}
#[derive(serde::Serialize)]
struct SentryFeedback {
comments: String,
event_id: String,
name: Option<String>,
email: String,
}
fn get_code_block(entities: &[MessageEntity], text: &str) -> Option<String> {
// Find any code blocks, ignore if there's more than one
let code_blocks = entities
.iter()
.filter(|entity| entity.entity_type == MessageEntityType::Code)
.collect::<Vec<_>>();
if code_blocks.len() != 1 {
return None;
}
// Make sure the code block is the correct length
let entity = code_blocks[0];
if entity.length != 36 {
return None;
}
// Iterate the text of the message this is replying to in order to
// get the event ID
let code = text
.chars()
.skip(entity.offset as usize)
.take(entity.length as usize)
.filter(|c| *c != '-')
.collect();
Some(code)
}
#[cfg(test)]
mod tests {
#[test]
fn test_get_code_block() {
let entities = vec![telegram::MessageEntity {
entity_type: telegram::MessageEntityType::Code,
offset: 0,
length: 36,
url: None,
user: None,
}];
let text = "e52569fa-99a0-44fc-ae9d-2477177b550b";
assert_eq!(
Some("e52569fa99a044fcae9d2477177b550b".to_string()),
super::get_code_block(&entities, &text)
);
let entities = vec![];
assert_eq!(None, super::get_code_block(&entities, text));
}
}