1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::error::Error;
use std::fmt::Debug;
use chrono::{Local, SecondsFormat, TimeZone};
use serde::{Serialize, Deserialize};
use super::MessageDestination;
use crate::{http_util, Message};
use crate::destination::message_condition_config::MessageNotifyConditionConfigEntry;
use crate::message::formatted_detail::{FormattedMessageComponent, FormattedString, Style};
use crate::message::MessageDetail;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TelegramDestination {
bot_token: String,
chat_id: String,
#[serde(default = "Vec::new")]
notify: Vec<MessageNotifyConditionConfigEntry<bool>>,
}
#[derive(Serialize, Debug)]
struct TelegramMessage {
chat_id: String,
text: String,
disable_notification: bool,
#[serde(skip_serializing_if = "Option::is_none")]
parse_mode: Option<ParseMode>,
}
#[derive(Serialize, Debug)]
enum ParseMode {
HTML,
}
impl TelegramMessage {
pub fn new(chat_id: String, message: String, notify: bool, parse_mode: Option<ParseMode>) -> Self {
Self {
chat_id,
text: message,
disable_notification: !notify,
parse_mode,
}
}
}
impl TelegramDestination {
fn to_tg_message(&self, message: &Message) -> TelegramMessage {
let html_formatting = message.get_message_detail().has_formatting();
let mut content = String::new();
content.push_str(&format!("{:?}", message.get_level()));
if let Some(title) = message.get_title() {
if html_formatting {
content.push_str(&format!(": <b>{}</b>", title));
} else {
content.push_str(&format!(": {}", title));
}
}
content.push('\n');
match message.get_message_detail() {
MessageDetail::Raw(raw) => content.push_str(raw),
MessageDetail::Formatted(formatted) => {
for component in formatted.components() {
match component {
FormattedMessageComponent::Section(title, section_content) => {
content.push_str(&format!("<b><u>{}</u></b>\n", title));
for part in section_content {
content.push_str(&to_tg_format(part))
}
}
FormattedMessageComponent::Text(parts) => {
for part in parts {
content.push_str(&to_tg_format(part))
}
}
}
}
}
}
content.push('\n');
content.push_str("-----\n");
let timestamp = Local::timestamp_millis(&Local, message.get_unix_timestamp_millis());
let timestamp_string = timestamp.to_rfc3339_opts(SecondsFormat::Millis, true);
if html_formatting {
content.push_str(&format!("<pre>{}</pre>", timestamp_string));
} else {
content.push_str(×tamp_string);
}
content.push('\n');
content.push_str(&format!("@ {}", message.get_author()));
let parse_mode = if html_formatting { Some(ParseMode::HTML) } else { None };
let notify = self.notify.iter()
.filter(|n| n.matches(&message))
.map(|n| n.get_notify())
.any(|b| *b);
TelegramMessage::new(self.chat_id.clone(), content, notify, parse_mode)
}
}
impl MessageDestination for TelegramDestination {
fn send(&self, message: &Message) -> Result<(), Box<dyn Error>> {
let message = self.to_tg_message(message);
let url = format!("https://api.telegram.org/bot{}/sendMessage", self.bot_token);
http_util::post_as_json_to(&url, &message)
}
}
fn to_tg_format(formatted_string: &FormattedString) -> String {
let mut result = String::from(formatted_string.get_string());
for style in formatted_string.get_styles() {
result = apply_style(&result, style);
}
result
}
fn apply_style(s: &str, style: &Style) -> String {
match style {
Style::Bold => format!("*{}*", s),
Style::Italics => format!("_{}_", s),
Style::Monospace => format!("`{}`", s),
}
}