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