Lines of
src/main.rs
from check-in 61238a3618
that are changed by the sequence of edits moving toward
check-in 51adce1e7e:
1: use anyhow::Result;
2: use async_std::task;
3: use samotop::{
4: mail::{
5: Builder,
6: DebugService,
7: MailDir,
8: Name
9: },
61238a3618 2024-05-22 10: smtp::Prudence,
11: };
12: use telegram_bot::{
13: Api,
14: ParseMode,
15: SendMessage,
16: UserId,
17: };
18:
19: use std::{
20: borrow::Cow,
21: collections::{
22: HashMap,
23: HashSet,
24: },
25: io::Read,
26: path::{
27: Path,
28: PathBuf
29: },
30: time::Duration,
31: vec::Vec,
32: };
33:
34: fn address_into_iter<'a>(addr: &'a mail_parser::Address<'a, >) -> impl Iterator<Item = Cow<'a, str>> {
35: addr.clone().into_list().into_iter().map(|a| a.address.unwrap())
36: }
37:
38: fn relay_mails(maildir: &Path, core: &TelegramTransport) -> Result<()> {
39: let new_dir = maildir.join("new");
40:
41: std::fs::create_dir_all(&new_dir)?;
42:
43: let files = std::fs::read_dir(new_dir)?;
44: for file in files {
45: let file = file?;
46: let mut buf = Vec::new();
47: std::fs::File::open(file.path())?.read_to_end(&mut buf)?;
48:
49: task::block_on(async move {
50: match mail_parser::MessageParser::default().parse(&buf[..]) {
51: Some(mail) => {
52: let mail = mail.clone();
53:
54: // Fetching address lists from fields we know
55: let mut to = HashSet::new();
56: if let Some(addr) = mail.to() {
57: let _ = address_into_iter(addr).map(|x| to.insert(x));
58: };
59: if let Some(addr) = mail.header("X-Samotop-To") {
60: match addr {
61: mail_parser::HeaderValue::Address(addr) => {
62: let _ = address_into_iter(addr).map(|x| to.insert(x));
63: },
64: mail_parser::HeaderValue::Text(text) => {
65: to.insert(text.clone());
66: },
67: _ => {}
68: }
69: };
70:
71: // Adding all known addresses to recipient list, for anyone else adding default
72: // Also if list is empty also adding default
73: let mut rcpt: HashSet<UserId> = HashSet::new();
74: for item in to {
75: let item = item.into_owned();
76: if core.recipients.contains_key(&item) {
77: rcpt.insert(core.recipients[&item]);
78: } else {
79: core.debug(format!("Recipient [{}] not found.", &item)).await.unwrap();
80: rcpt.insert(core.default);
81: }
82: };
83: if rcpt.is_empty() {
84: rcpt.insert(core.default);
85: core.debug("No recipient or envelope address.").await.unwrap();
86: };
87:
88: // prepating message header
89: let mut reply: Vec<Cow<str>> = vec![];
90: if let Some(subject) = mail.subject() {
91: reply.push(format!("<b>Subject:</b> {}", subject).into());
92: } else if let Some(thread) = mail.thread_name() {
93: reply.push(format!("<b>Thread:</b> {}", thread).into());
94: }
95: if let Some(from) = mail.from() {
96: reply.push(format!("<b>From:</b> {:?}", from).into());
97: }
98: if let Some(sender) = mail.sender() {
99: reply.push(format!("<b>Sender:</b> {:?}", sender).into());
100: }
101: reply.push("".into());
102: let header_size = reply.join("\n").len() + 1;
103:
104: let html_parts = mail.html_body_count();
105: let text_parts = mail.text_body_count();
106: let attachments = mail.attachment_count();
107: if html_parts != text_parts {
108: core.debug(format!("Hm, we have {} HTML parts and {} text parts.", html_parts, text_parts)).await.unwrap();
109: }
110: //let mut html_num = 0;
111: let mut text_num = 0;
112: let mut file_num = 0;
113: // let's display first html or text part as body
114: let mut body = "".into();
115: /*
116: * actually I don't wanna parse that html stuff
117: if html_parts > 0 {
118: let text = mail.body_html(0).unwrap();
119: if text.len() < 4096 - header_size {
120: body = text;
121: html_num = 1;
122: }
123: };
124: */
125: if body == "" && text_parts > 0 {
126: let text = mail.body_text(0).unwrap();
127: if text.len() < 4096 - header_size {
128: body = text;
129: text_num = 1;
130: }
131: };
132: reply.push(body);
133:
134: // and let's coillect all other attachment parts
135: let mut files_to_send = vec![];
136: /*
137: * let's just skip html parts for now, they just duplicate text?
138: while html_num < html_parts {
139: files_to_send.push(mail.html_part(html_num).unwrap());
140: html_num += 1;
141: }
142: */
143: while text_num < text_parts {
144: files_to_send.push(mail.text_part(text_num).unwrap());
145: text_num += 1;
146: }
147: while file_num < attachments {
148: files_to_send.push(mail.attachment(file_num).unwrap());
149: file_num += 1;
150: }
151:
152: for chat in rcpt {
153: core.send(chat, reply.join("\n")).await.unwrap();
154: for chunk in &files_to_send {
155: task::sleep(Duration::from_secs(5)).await;
156: let data = chunk.contents().to_vec();
157: let obj = telegram_bot::types::InputFileUpload::with_data(data, "Attachment");
158: core.sendfile(chat, obj).await.unwrap();
159: }
160: }
161: },
162: None => { core.debug("None mail.").await.unwrap(); },
163: //send_to_sendgrid(mail, sendgrid_api_key).await;
164: };
165: });
166:
167: std::fs::remove_file(file.path())?;
168: }
169: Ok(())
170: }
171:
172: fn my_prudence() -> Prudence {
173: Prudence::default().with_read_timeout(Duration::from_secs(60)).with_banner_delay(Duration::from_secs(1))
174: }
175:
176: pub struct TelegramTransport {
177: default: UserId,
178: tg: Api,
179: recipients: HashMap<String, UserId>,
180: }
181:
182: impl TelegramTransport {
183: pub fn new(settings: &config::Config) -> TelegramTransport {
184: let api_key = settings.get_string("api_key").unwrap();
185: let tg = Api::new(api_key);
186: let default_recipient = settings.get_string("default").unwrap();
187: let recipients: HashMap<String, UserId> = settings.get_table("recipients").unwrap().into_iter().map(|(a, b)| (a, UserId::new(b.into_int().unwrap()))).collect();
188: // Barf if no default
189: let default = recipients[&default_recipient];
190:
191: TelegramTransport {
192: default,
193: tg,
194: recipients,
195: }
196: }
197:
198: pub async fn debug<'b, S>(&self, msg: S) -> Result<()>
199: where S: Into<Cow<'b, str>> {
200: self.tg.send(SendMessage::new(self.default, msg)
201: .parse_mode(ParseMode::Html)).await?;
202: Ok(())
203: }
204:
205: pub async fn send<'b, S>(&self, to: UserId, msg: S) -> Result<()>
206: where S: Into<Cow<'b, str>> {
207: self.tg.send(SendMessage::new(to, msg)
208: .parse_mode(ParseMode::Html)).await?;
209: Ok(())
210: }
211:
212: pub async fn sendfile<V>(&self, to: UserId, chunk: V) -> Result<()>
213: where V: Into<telegram_bot::InputFile> {
214: self.tg.send(telegram_bot::SendDocument::new(to, chunk)).await?;
215: Ok(())
216: }
217: }
218:
219: #[async_std::main]
220: async fn main() {
221: let settings: config::Config = config::Config::builder()
222: .add_source(config::File::with_name("smtp2tg.toml"))
223: .build().unwrap();
224:
225: let core = TelegramTransport::new(&settings);
226: let maildir: PathBuf = settings.get_string("maildir").unwrap().into();
227: let listen_on = settings.get_string("listen_on").unwrap();
228: let sink = Builder + Name::new("smtp2tg") + DebugService +
61238a3618 2024-05-22 229: samotop::smtp::Esmtp.with(samotop::smtp::SmtpParser) + my_prudence() +
61238a3618 2024-05-22 230: //TelegramTransport::new(&settings);
61238a3618 2024-05-22 231: MailDir::new(maildir.clone()).unwrap();
232:
233: task::spawn(async move {
234: loop {
235: relay_mails(&maildir, &core).unwrap();
236: task::sleep(Duration::from_secs(5)).await;
237: }
238: });
239:
240: match listen_on.as_str() {
61238a3618 2024-05-22 241: "socket" => samotop::server::UnixServer::on("./smtp2tg.sock")
61238a3618 2024-05-22 242: .serve(sink.build()).await.unwrap(),
61238a3618 2024-05-22 243: _ => samotop::server::TcpServer::on(listen_on)
61238a3618 2024-05-22 244: .serve(sink.build()).await.unwrap(),
245: };
246: }