Index: src/main.rs ================================================================== --- src/main.rs +++ src/main.rs @@ -1,6 +1,9 @@ -use anyhow::Result; +use anyhow::{ + anyhow, + Result, +}; use async_std::task; use samotop::{ mail::{ Builder, DebugService, @@ -41,11 +44,11 @@ fn address_into_iter<'a>(addr: &'a mail_parser::Address<'a, >) -> impl Iterator> { addr.clone().into_list().into_iter().map(|a| a.address.unwrap()) } -fn relay_mails(maildir: &Path, core: &TelegramTransport) -> Result<()> { +async fn relay_mails(maildir: &Path, core: &TelegramTransport) -> Result<()> { let new_dir = maildir.join("new"); std::fs::create_dir_all(&new_dir)?; let files = std::fs::read_dir(new_dir)?; @@ -52,130 +55,129 @@ for file in files { let file = file?; let mut buf = Vec::new(); std::fs::File::open(file.path())?.read_to_end(&mut buf)?; - task::block_on(async move { - match mail_parser::MessageParser::default().parse(&buf[..]) { - Some(mail) => { - let mail = mail.clone(); - - // Fetching address lists from fields we know - let mut to = HashSet::new(); - if let Some(addr) = mail.to() { - let _ = address_into_iter(addr).map(|x| to.insert(x)); - }; - if let Some(addr) = mail.header("X-Samotop-To") { - match addr { - mail_parser::HeaderValue::Address(addr) => { - let _ = address_into_iter(addr).map(|x| to.insert(x)); - }, - mail_parser::HeaderValue::Text(text) => { - to.insert(text.clone()); - }, - _ => {} - } - }; - - // Adding all known addresses to recipient list, for anyone else adding default - // Also if list is empty also adding default - let mut rcpt: HashSet<&UserId> = HashSet::new(); - for item in to { - let item = item.into_owned(); - match core.recipients.get(&item) { - Some(addr) => rcpt.insert(addr), - None => { - core.debug(format!("Recipient [{}] not found.", &item)).await.unwrap(); - rcpt.insert(core.recipients.get("_").unwrap()) - } - }; - }; - if rcpt.is_empty() { - core.debug("No recipient or envelope address.").await.unwrap(); - rcpt.insert(core.recipients.get("_").unwrap()); - }; - - // prepating message header - let mut reply: Vec> = vec![]; - if let Some(subject) = mail.subject() { - reply.push(format!("**Subject:** `{}`", subject).into()); - } else if let Some(thread) = mail.thread_name() { - reply.push(format!("**Thread:** `{}`", thread).into()); - } - if let Some(from) = mail.from() { - reply.push(format!("**From:** `{:?}`", address_into_iter(from).collect::>().join(", ")).into()); - } - if let Some(sender) = mail.sender() { - reply.push(format!("**Sender:** `{:?}`", address_into_iter(sender).collect::>().join(", ")).into()); - } - reply.push("".into()); - let header_size = reply.join("\n").len() + 1; - - let html_parts = mail.html_body_count(); - let text_parts = mail.text_body_count(); - let attachments = mail.attachment_count(); - if html_parts != text_parts { - core.debug(format!("Hm, we have {} HTML parts and {} text parts.", html_parts, text_parts)).await.unwrap(); - } - //let mut html_num = 0; - let mut text_num = 0; - let mut file_num = 0; - // let's display first html or text part as body - let mut body = "".into(); - /* - * actually I don't wanna parse that html stuff - if html_parts > 0 { - let text = mail.body_html(0).unwrap(); - if text.len() < 4096 - header_size { - body = text; - html_num = 1; - } - }; - */ - if body == "" && text_parts > 0 { - let text = mail.body_text(0).unwrap(); - if text.len() < 4096 - header_size { - body = text; - text_num = 1; - } - }; - reply.push("```".into()); - for line in body.lines() { - reply.push(line.into()); - } - reply.push("```".into()); - - // and let's collect all other attachment parts - let mut files_to_send = vec![]; - /* - * let's just skip html parts for now, they just duplicate text? - while html_num < html_parts { - files_to_send.push(mail.html_part(html_num).unwrap()); - html_num += 1; - } - */ - while text_num < text_parts { - files_to_send.push(mail.text_part(text_num).unwrap()); - text_num += 1; - } - while file_num < attachments { - files_to_send.push(mail.attachment(file_num).unwrap()); - file_num += 1; - } - - for chat in rcpt { - let base_post = core.send(chat, reply.join("\n")).await.unwrap(); - for chunk in &files_to_send { - let data = chunk.contents().to_vec(); - let obj = telegram_bot::types::InputFileUpload::with_data(data, "Attachment"); - core.sendfile(chat, obj, Some(&base_post)).await.unwrap(); - } - } - }, - None => { core.debug("None mail.").await.unwrap(); }, - }; - }); + let mail = mail_parser::MessageParser::default().parse(&buf[..]) + .ok_or(anyhow!("Failed to parse mail `{:?}`.", file))?.clone(); + + // Fetching address lists from fields we know + let mut to = HashSet::new(); + if let Some(addr) = mail.to() { + let _ = address_into_iter(addr).map(|x| to.insert(x)); + }; + if let Some(addr) = mail.header("X-Samotop-To") { + match addr { + mail_parser::HeaderValue::Address(addr) => { + let _ = address_into_iter(addr).map(|x| to.insert(x)); + }, + mail_parser::HeaderValue::Text(text) => { + to.insert(text.clone()); + }, + _ => {} + } + }; + + // Adding all known addresses to recipient list, for anyone else adding default + // Also if list is empty also adding default + let mut rcpt: HashSet<&UserId> = HashSet::new(); + for item in to { + let item = item.into_owned(); + match core.recipients.get(&item) { + Some(addr) => rcpt.insert(addr), + None => { + core.debug(format!("Recipient [{}] not found.", &item)).await?; + rcpt.insert(core.recipients.get("_") + .ok_or(anyhow!("Missing default address in recipient table."))?) + } + }; + }; + if rcpt.is_empty() { + core.debug("No recipient or envelope address.").await?; + rcpt.insert(core.recipients.get("_") + .ok_or(anyhow!("Missing default address in recipient table."))?); + }; + + // prepating message header + let mut reply: Vec> = vec![]; + if let Some(subject) = mail.subject() { + reply.push(format!("**Subject:** `{}`", subject).into()); + } else if let Some(thread) = mail.thread_name() { + reply.push(format!("**Thread:** `{}`", thread).into()); + } + if let Some(from) = mail.from() { + reply.push(format!("**From:** `{:?}`", address_into_iter(from).collect::>().join(", ")).into()); + } + if let Some(sender) = mail.sender() { + reply.push(format!("**Sender:** `{:?}`", address_into_iter(sender).collect::>().join(", ")).into()); + } + reply.push("".into()); + let header_size = reply.join("\n").len() + 1; + + let html_parts = mail.html_body_count(); + let text_parts = mail.text_body_count(); + let attachments = mail.attachment_count(); + if html_parts != text_parts { + core.debug(format!("Hm, we have {} HTML parts and {} text parts.", html_parts, text_parts)).await?; + } + //let mut html_num = 0; + let mut text_num = 0; + let mut file_num = 0; + // let's display first html or text part as body + let mut body = "".into(); + /* + * actually I don't wanna parse that html stuff + if html_parts > 0 { + let text = mail.body_html(0).unwrap(); + if text.len() < 4096 - header_size { + body = text; + html_num = 1; + } + }; + */ + if body == "" && text_parts > 0 { + let text = mail.body_text(0) + .ok_or(anyhow!("Failed to extract text from message."))?; + if text.len() < 4096 - header_size { + body = text; + text_num = 1; + } + }; + reply.push("```".into()); + for line in body.lines() { + reply.push(line.into()); + } + reply.push("```".into()); + + // and let's collect all other attachment parts + let mut files_to_send = vec![]; + /* + * let's just skip html parts for now, they just duplicate text? + while html_num < html_parts { + files_to_send.push(mail.html_part(html_num).unwrap()); + html_num += 1; + } + */ + while text_num < text_parts { + files_to_send.push(mail.text_part(text_num) + .ok_or(anyhow!("Failed to get text part from message"))?); + text_num += 1; + } + while file_num < attachments { + files_to_send.push(mail.attachment(file_num) + .ok_or(anyhow!("Failed to get file part from message"))?); + file_num += 1; + } + + for chat in rcpt { + let base_post = core.send(chat, reply.join("\n")).await?; + for chunk in &files_to_send { + let data = chunk.contents().to_vec(); + let obj = telegram_bot::types::InputFileUpload::with_data(data, "Attachment"); + core.sendfile(chat, obj, Some(&base_post)).await?; + } + } std::fs::remove_file(file.path())?; } Ok(()) } @@ -256,11 +258,19 @@ env_logger::init(); task::spawn(async move { loop { - relay_mails(&maildir, &core).unwrap(); + // relay mails + if let Err(err) = relay_mails(&maildir, &core).await { + // in case that fails - inform default recipient + if let Err(err) = core.debug(format!("Sending emails failed:\n{:?}", err)).await { + // in case that also fails - write some logs and bail + eprintln!("Failed to contact Telegram:\n{:?}", err); + task::sleep(Duration::from_secs(5 * 60)).await; + }; + }; task::sleep(Duration::from_secs(5)).await; } }); match listen_on.as_str() {