Annotation For src/main.rs
Logged in as anonymous

Lines of src/main.rs from check-in 51adce1e7e that are changed by the sequence of edits moving toward check-in 667b874fdb:

                         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
                        76: 					let mut rcpt: HashSet<UserId> = HashSet::new();
                        77: 					for item in to {
                        78: 						let item = item.into_owned();
                        79: 						if core.recipients.contains_key(&item) {
                        80: 							rcpt.insert(core.recipients[&item]);
                        81: 						} else {
                        82: 							core.debug(format!("Recipient [{}] not found.", &item)).await.unwrap();
                        83: 							rcpt.insert(core.default);
                        84: 						}
                        85: 					};
                        86: 					if rcpt.is_empty() {
                        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() {
51adce1e7e 2024-05-22   94: 						reply.push(format!("<b>Subject:</b> {}", subject).into());
                        95: 					} else if let Some(thread) = mail.thread_name() {
51adce1e7e 2024-05-22   96: 						reply.push(format!("<b>Thread:</b> {}", thread).into());
                        97: 					}
                        98: 					if let Some(from) = mail.from() {
51adce1e7e 2024-05-22   99: 						reply.push(format!("<b>From:</b> {:?}", from).into());
                       100: 					}
                       101: 					if let Some(sender) = mail.sender() {
51adce1e7e 2024-05-22  102: 						reply.push(format!("<b>Sender:</b> {:?}", sender).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(body);
                       136: 
                       137: 					// and let's coillect all other attachment parts
                       138: 					let mut files_to_send = vec![];
                       139: 					/*
                       140: 					 * let's just skip html parts for now, they just duplicate text?
                       141: 					while html_num < html_parts {
                       142: 						files_to_send.push(mail.html_part(html_num).unwrap());
                       143: 						html_num += 1;
                       144: 					}
                       145: 					*/
                       146: 					while text_num < text_parts {
                       147: 						files_to_send.push(mail.text_part(text_num).unwrap());
                       148: 						text_num += 1;
                       149: 					}
                       150: 					while file_num < attachments {
                       151: 						files_to_send.push(mail.attachment(file_num).unwrap());
                       152: 						file_num += 1;
                       153: 					}
                       154: 
                       155: 					for chat in rcpt {
                       156: 						core.send(chat, reply.join("\n")).await.unwrap();
                       157: 						for chunk in &files_to_send {
                       158: 							task::sleep(Duration::from_secs(5)).await;
                       159: 							let data = chunk.contents().to_vec();
                       160: 							let obj = telegram_bot::types::InputFileUpload::with_data(data, "Attachment");
                       161: 							core.sendfile(chat, obj).await.unwrap();
                       162: 						}
                       163: 					}
                       164: 				},
                       165: 				None => { core.debug("None mail.").await.unwrap(); },
                       166: 				//send_to_sendgrid(mail, sendgrid_api_key).await;
                       167: 			};
                       168: 		});
                       169: 
                       170: 		std::fs::remove_file(file.path())?;
                       171: 	}
                       172: 	Ok(())
                       173: }
                       174: 
                       175: fn my_prudence() -> Prudence {
                       176: 	Prudence::default().with_read_timeout(Duration::from_secs(60)).with_banner_delay(Duration::from_secs(1))
                       177: }
                       178: 
                       179: pub struct TelegramTransport {
                       180: 	default: UserId,
                       181: 	tg: Api,
                       182: 	recipients: HashMap<String, UserId>,
                       183: }
                       184: 
                       185: impl TelegramTransport {
                       186: 	pub fn new(settings: &config::Config) -> TelegramTransport {
                       187: 		let api_key = settings.get_string("api_key").unwrap();
                       188: 		let tg = Api::new(api_key);
                       189: 		let default_recipient = settings.get_string("default").unwrap();
                       190: 		let recipients: HashMap<String, UserId> = settings.get_table("recipients").unwrap().into_iter().map(|(a, b)| (a, UserId::new(b.into_int().unwrap()))).collect();
                       191: 		// Barf if no default
                       192: 		let default = recipients[&default_recipient];
                       193: 
                       194: 		TelegramTransport {
                       195: 			default,
                       196: 			tg,
                       197: 			recipients,
                       198: 		}
                       199: 	}
                       200: 
                       201: 	pub async fn debug<'b, S>(&self, msg: S) -> Result<()>
                       202: 	where S: Into<Cow<'b, str>> {
                       203: 		self.tg.send(SendMessage::new(self.default, msg)
                       204: 			.parse_mode(ParseMode::Html)).await?;
                       205: 		Ok(())
                       206: 	}
                       207: 
                       208: 	pub async fn send<'b, S>(&self, to: UserId, msg: S) -> Result<()>
                       209: 	where S: Into<Cow<'b, str>> {
                       210: 		self.tg.send(SendMessage::new(to, msg)
                       211: 			.parse_mode(ParseMode::Html)).await?;
                       212: 		Ok(())
                       213: 	}
                       214: 
                       215: 	pub async fn sendfile<V>(&self, to: UserId, chunk: V) -> Result<()>
                       216: 	where V: Into<telegram_bot::InputFile> {
                       217: 		self.tg.send(telegram_bot::SendDocument::new(to, chunk)).await?;
                       218: 		Ok(())
                       219: 	}
                       220: }
                       221: 
                       222: #[async_std::main]
                       223: async fn main() {
                       224: 	let settings: config::Config = config::Config::builder()
                       225: 		.add_source(config::File::with_name("smtp2tg.toml"))
                       226: 		.build().unwrap();
                       227: 
                       228: 	let core = TelegramTransport::new(&settings);
                       229: 	let maildir: PathBuf = settings.get_string("maildir").unwrap().into();
                       230: 	let listen_on = settings.get_string("listen_on").unwrap();
                       231: 	let sink = Builder + Name::new("smtp2tg") + DebugService +
                       232: 		my_prudence() + MailDir::new(maildir.clone()).unwrap();
                       233: 
                       234: 	task::spawn(async move {
                       235: 		loop {
                       236: 			relay_mails(&maildir, &core).unwrap();
                       237: 			task::sleep(Duration::from_secs(5)).await;
                       238: 		}
                       239: 	});
                       240: 
                       241: 	match listen_on.as_str() {
                       242: 		"socket" => {
                       243: 			let sink = sink + samotop::smtp::Lmtp.with(SmtpParser);
                       244: 			samotop::server::UnixServer::on("./smtp2tg.sock")
                       245: 				.serve(sink.build()).await.unwrap();
                       246: 		},
                       247: 		_ => {
                       248: 			let sink = sink + samotop::smtp::Esmtp.with(SmtpParser);
                       249: 			samotop::server::TcpServer::on(listen_on)
                       250: 				.serve(sink.build()).await.unwrap();
                       251: 		},
                       252: 	};
                       253: }