Drone improvements, reply for Sentry feedback.

This commit is contained in:
Syfaro 2020-03-08 19:02:50 -05:00
parent 9e25205405
commit 1404d09436
9 changed files with 181 additions and 15 deletions

View File

@ -14,7 +14,9 @@ steps:
- apt-get update -y
- apt-get install pkg-config libssl-dev ca-certificates python3 python3-pip nodejs -y
- pip3 install cfscrape
- rustup component add clippy
- cargo test
- cargo clippy
- name: sentry-release
image: getsentry/sentry-cli
@ -35,6 +37,8 @@ steps:
image: plugins/docker
settings:
auto_tag: true
build_args_from_env:
- DRONE_COMMIT_SHA
password:
from_secret: docker_password
registry: registry.huefox.com

View File

@ -1,6 +1,8 @@
FROM rustlang/rust:nightly-slim AS builder
WORKDIR /src
RUN apt-get update -y && apt-get install pkg-config libssl-dev python3 python3-dev -y
ARG DRONE_COMMIT_SHA
ENV RELEASE $DRONE_COMMIT_SHA
COPY . .
RUN cargo install --root / --path .

View File

@ -62,5 +62,6 @@ alternate-distance = · { $link } (distance of { $distance })
alternate-multiple-photo = I can only find alternates for a single photo, sorry.
# Error Messages
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 }
error-generic = Oh no, something went wrong! Please send a message to my creator, { -creatorName }, saying what happened.
error-uuid = Oh no, something went wrong! Please reply to this message saying what happened. You may also send a message to my creator, { -creatorName }, with this ID if you continue having issues: { $uuid }
error-feedback = Thank you for the feedback, hopefully we can get this issue resolved soon.

1
quaint Submodule

@ -0,0 +1 @@
Subproject commit dc628de3342203b71cdedd4ff3c8a86eb7acefa4

View File

@ -1,4 +1,5 @@
use super::Status::*;
use crate::needs_field;
use async_trait::async_trait;
use telegram::*;
@ -16,15 +17,13 @@ impl super::Handler for ChosenInlineHandler {
update: &Update,
_command: Option<&Command>,
) -> Result<super::Status, failure::Error> {
Ok(if let Some(chosen_result) = &update.chosen_inline_result {
let point = influxdb::Query::write_query(influxdb::Timestamp::Now, "chosen")
.add_field("user_id", chosen_result.from.id);
let chosen_result = needs_field!(update, chosen_inline_result);
let _ = handler.influx.query(&point).await;
let point = influxdb::Query::write_query(influxdb::Timestamp::Now, "chosen")
.add_field("user_id", chosen_result.from.id);
Completed
} else {
Ignored
})
let _ = handler.influx.query(&point).await;
Ok(Completed)
}
}

View File

@ -51,6 +51,7 @@ impl super::Handler for CommandHandler {
"/mirror" => self.handle_mirror(&handler, message).await,
"/source" => self.handle_source(&handler, message).await,
"/alts" => self.handle_alts(&handler, message).await,
"/error" => Err(failure::format_err!("a test error message")),
_ => {
tracing::info!("unknown command: {}", command.name);
Ok(())

141
src/handlers/error_reply.rs Normal file
View File

@ -0,0 +1,141 @@
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));
}
}

View File

@ -3,6 +3,7 @@ use async_trait::async_trait;
mod channel_photo;
mod chosen_inline_handler;
mod commands;
mod error_reply;
mod group_add;
mod inline_handler;
mod photo;
@ -11,6 +12,7 @@ mod text;
pub use channel_photo::ChannelPhotoHandler;
pub use chosen_inline_handler::ChosenInlineHandler;
pub use commands::CommandHandler;
pub use error_reply::ErrorReplyHandler;
pub use group_add::GroupAddHandler;
pub use inline_handler::InlineHandler;
pub use photo::PhotoHandler;

View File

@ -67,7 +67,9 @@ pub struct Config {
// Logging
jaeger_collector: Option<String>,
sentry_dsn: Option<String>,
pub sentry_dsn: Option<String>,
pub sentry_organization_slug: Option<String>,
pub sentry_project_slug: Option<String>,
// Telegram config
telegram_apitoken: String,
@ -236,6 +238,7 @@ async fn main() {
Box::new(handlers::PhotoHandler),
Box::new(handlers::CommandHandler),
Box::new(handlers::TextHandler),
Box::new(handlers::ErrorReplyHandler::new()),
];
let handler = Arc::new(MessageHandler {
@ -256,7 +259,11 @@ async fn main() {
let _guard = if let Some(dsn) = config.sentry_dsn {
sentry::integrations::panic::register_panic_handler();
Some(sentry::init(dsn))
Some(sentry::init(sentry::ClientOptions {
dsn: Some(dsn.parse().unwrap()),
release: option_env!("RELEASE").map(std::borrow::Cow::from),
..Default::default()
}))
} else {
None
};
@ -432,7 +439,6 @@ pub struct MessageHandler {
}
impl MessageHandler {
#[tracing::instrument(skip(self, callback))]
async fn get_fluent_bundle<C, R>(&self, requested: Option<&str>, callback: C) -> R
where
C: FnOnce(&fluent::FluentBundle<fluent::FluentResource>) -> R,
@ -643,14 +649,23 @@ impl MessageHandler {
Ok(status) if status == handlers::Status::Completed => break,
Err(e) => {
log::error!("Error handling update: {:#?}", e);
let mut tags = vec![("handler", handler.name().to_string())];
if let Some(user) = user {
tags.push(("user_id", user.id.to_string()));
}
if let Some(command) = command {
tags.push(("command", command.name));
}
utils::with_user_scope(user, Some(tags), || {
if let Some(msg) = &update.message {
self.report_error(&msg, Some(tags), || capture_error(&e))
.await;
} else {
capture_error(&e);
});
}
break;
}
_ => (),
}