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
|
//! Simple SMTP-to-Telegram gateway. Can parse email and send them as telegram
//! messages to specified chats, generally you specify which email address is
//! available in configuration, everything else is sent to default address.
use anyhow::Result;
use async_std::{
fs::metadata,
io::Error,
task,
};
use just_getopt::{
OptFlags,
OptSpecs,
OptValueType,
};
use lazy_static::lazy_static;
use mailin_embedded::{
Response,
response::*,
};
use regex::Regex;
use teloxide::{
Bot,
prelude::{
Requester,
RequesterExt,
},
types::{
ChatId,
InputMedia,
Message,
ParseMode::MarkdownV2,
},
};
use thiserror::Error;
use std::{
borrow::Cow,
collections::{
HashMap,
HashSet,
|
>
>
>
>
>
>
>
>
>
>
>
>
>
<
<
<
<
<
<
<
<
<
<
<
<
<
|
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
|
//! Simple SMTP-to-Telegram gateway. Can parse email and send them as telegram
//! messages to specified chats, generally you specify which email address is
//! available in configuration, everything else is sent to default address.
use anyhow::Result;
use async_std::{
fs::metadata,
io::Error,
task,
};
use frankenstein::{
client_reqwest::Bot,
input_file::InputFile,
input_media::{InputMedia, InputMediaDocument},
methods::{SendDocumentParams, SendMessageParams},
response::MethodResponse,
types::{
ChatId,
Message,
},
AsyncTelegramApi,
ParseMode::MarkdownV2,
};
use just_getopt::{
OptFlags,
OptSpecs,
OptValueType,
};
use lazy_static::lazy_static;
use mailin_embedded::{
Response,
response::*,
};
use regex::Regex;
use thiserror::Error;
use std::{
borrow::Cow,
collections::{
HashMap,
HashSet,
|
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
|
#[error("No headers found")]
NoHeaders,
#[error("No recipient addresses")]
NoRecipient,
#[error("Failed to extract text from message")]
NoText,
#[error(transparent)]
RequestError(#[from] teloxide::RequestError),
}
/// `SomeHeaders` object to store data through SMTP session
#[derive(Clone, Debug)]
struct SomeHeaders {
from: String,
to: Vec<String>,
}
/// `TelegramTransport` Central object with TG api and configuration
#[derive(Clone)]
struct TelegramTransport {
data: Vec<u8>,
headers: Option<SomeHeaders>,
recipients: HashMap<String, ChatId>,
relay: bool,
tg: teloxide::adaptors::DefaultParseMode<teloxide::adaptors::Throttle<Bot>>,
fields: HashSet<String>,
}
lazy_static! {
static ref RE_SPECIAL: Regex = Regex::new(r"([\-_*\[\]()~`>#+|{}\.!])").unwrap();
}
|
|
|
|
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
|
#[error("No headers found")]
NoHeaders,
#[error("No recipient addresses")]
NoRecipient,
#[error("Failed to extract text from message")]
NoText,
#[error(transparent)]
RequestError(#[from] frankenstein::Error),
}
/// `SomeHeaders` object to store data through SMTP session
#[derive(Clone, Debug)]
struct SomeHeaders {
from: String,
to: Vec<String>,
}
/// `TelegramTransport` Central object with TG api and configuration
#[derive(Clone)]
struct TelegramTransport {
data: Vec<u8>,
headers: Option<SomeHeaders>,
recipients: HashMap<String, ChatId>,
relay: bool,
tg: Bot,
fields: HashSet<String>,
}
lazy_static! {
static ref RE_SPECIAL: Regex = Regex::new(r"([\-_*\[\]()~`>#+|{}\.!])").unwrap();
}
|
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
assert_eq!(res, "\\-\\_\\*\\[\\]\\(\\)\\~\\`\\>\\#\\+\\|\\{\\}\\.\\!");
}
}
impl TelegramTransport {
/// Initialize API and read configuration
fn new(settings: config::Config) -> TelegramTransport {
let tg = Bot::new(settings.get_string("api_key")
.expect("[smtp2tg.toml] missing \"api_key\" parameter.\n"))
.throttle(teloxide::adaptors::throttle::Limits::default())
.parse_mode(MarkdownV2);
let recipients: HashMap<String, ChatId> = settings.get_table("recipients")
.expect("[smtp2tg.toml] missing table \"recipients\".\n")
.into_iter().map(|(a, b)| (a, ChatId (b.into_int()
.expect("[smtp2tg.toml] \"recipient\" table values should be integers.\n")
))).collect();
if !recipients.contains_key("_") {
eprintln!("[smtp2tg.toml] \"recipient\" table misses \"default_recipient\".\n");
panic!("no default recipient");
}
let fields = HashSet::<String>::from_iter(settings.get_array("fields")
.expect("[smtp2tg.toml] \"fields\" should be an array")
.iter().map(|x| x.clone().into_string().expect("should be strings")));
|
|
|
<
<
|
|
|
|
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
assert_eq!(res, "\\-\\_\\*\\[\\]\\(\\)\\~\\`\\>\\#\\+\\|\\{\\}\\.\\!");
}
}
impl TelegramTransport {
/// Initialize API and read configuration
fn new(settings: config::Config) -> TelegramTransport {
let tg = Bot::new(&settings.get_string("api_key")
.expect("[smtp2tg.toml] missing \"api_key\" parameter.\n"));
let recipients: HashMap<String, ChatId> = settings.get_table("recipients")
.expect("[smtp2tg.toml] missing table \"recipients\".\n")
.into_iter().map(|(a, b)| (a, b.into_int()
.expect("[smtp2tg.toml] \"recipient\" table values should be integers.\n").into()
)).collect();
if !recipients.contains_key("_") {
eprintln!("[smtp2tg.toml] \"recipient\" table misses \"default_recipient\".\n");
panic!("no default recipient");
}
let fields = HashSet::<String>::from_iter(settings.get_array("fields")
.expect("[smtp2tg.toml] \"fields\" should be an array")
.iter().map(|x| x.clone().into_string().expect("should be strings")));
|
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
relay,
tg,
fields,
}
}
/// Send message to default user, used for debug/log/info purposes
async fn debug (&self, msg: &str) -> Result<Message, MyError> {
Ok(self.tg.send_message(*self.recipients.get("_").ok_or(MyError::NoDefault)?, encode(msg)).await?)
}
/// Send message to specified user
async fn send <S> (&self, to: &ChatId, msg: S) -> Result<Message, MyError>
where S: Into<String> {
Ok(self.tg.send_message(*to, msg).await?)
}
/// Attempt to deliver one message
async fn relay_mail (&self) -> Result<(), MyError> {
if let Some(headers) = &self.headers {
let mail = mail_parser::MessageParser::new().parse(&self.data)
.ok_or(MyError::BadMail)?;
|
|
>
|
>
>
>
|
>
>
>
>
|
|
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
relay,
tg,
fields,
}
}
/// Send message to default user, used for debug/log/info purposes
async fn debug (&self, msg: &str) -> Result<MethodResponse<Message>, MyError> {
let msg = SendMessageParams::builder()
.chat_id(self.recipients.get("_").ok_or(MyError::NoDefault)?.clone())
.text(encode(msg))
.build();
Ok(self.tg.send_message(&msg).await?)
}
/// Send message to specified user
async fn send <S> (&self, to: &ChatId, msg: S) -> Result<MethodResponse<Message>, MyError>
where S: Into<String> {
let msg = SendMessageParams::builder()
.chat_id(to.clone())
.text(msg)
.build();
Ok(self.tg.send_message(&msg).await?)
}
/// Attempt to deliver one message
async fn relay_mail (&self) -> Result<(), MyError> {
if let Some(headers) = &self.headers {
let mail = mail_parser::MessageParser::new().parse(&self.data)
.ok_or(MyError::BadMail)?;
|
286
287
288
289
290
291
292
293
294
295
296
297
298
299
|
fname
} else {
"Attachment.txt".into()
};
let item = teloxide::types::InputMediaDocument::new(
teloxide::types::InputFile::memory(data.to_vec())
.file_name(filename));
let item = if first_one {
first_one = false;
item.caption(&msg)
} else {
item
};
files.push(InputMedia::Document(item));
|
>
>
>
|
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
fname
} else {
"Attachment.txt".into()
};
let item = teloxide::types::InputMediaDocument::new(
teloxide::types::InputFile::memory(data.to_vec())
.file_name(filename));
let item = InputMediaDocument::builder()
.media(data)
.build();
let item = if first_one {
first_one = false;
item.caption(&msg)
} else {
item
};
files.push(InputMedia::Document(item));
|