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