"""function to send email""" import os import sys from email.mime.text import MIMEText from email.header import Header from email.mime.multipart import MIMEMultipart from email.utils import formataddr import glob import re import subprocess import filing from nitpo import Nitpo ## only for remail from infile import Infile ## only for remail, can not be imported, leads to circular # from profile import Profile from sheets import Sheets from inces import Inces ## to get the error in self.transform import lxml.etree as etree import docing class Emailer(Nitpo): def __init__(self, do_verbose=False): super().__init__() self.sheets = Sheets() self.inces = Inces() self.repis_fufi = None self.do_verbose = do_verbose self.infile = Infile() # # can be set in the headers self.params = {} # self.re_nama = re.compile(r"([^<]+)<([^@]+)@([^>]+)>\s*") self.re_nama = re.compile(r"([^<]+)<([^>]+)>\s*") self.re_is_blank = re.compile(r"^\s*$") def web(self, fufi, maix, empro, preps): """from the web, using prepared sheets""" head = empro + '_head' if head not in preps['sheets']: raise Exception(f"no {head} prepared") if not fufi.endswith('.xml'): raise Exception(f"{fufi} does not end in .xml") base_fufi = fufi[:-4] mail_fufi = base_fufi + '.mail' html_fufi = base_fufi + '.html' text_fufi = base_fufi + '.text' empro_head = empro + '_head' if os.path.isfile(mail_fufi): # # don't send the mail again, unless we are testing if not self.is_dev(): return None head_sheet = preps['sheets'][empro_head] msg = self.headers(head_sheet(maix)) if msg is None: return None empro_text = empro + '_text' empro_html = empro + '_html' if((empro_html not in preps['sheets']) and (empro_text not in preps['sheets'])): raise Exception(f"I need either {empro_html} or {empro_text}") if empro_html in preps['sheets']: sheet = preps['sheets'][empro_html] # # before inces # string = str(sheet(maix)) xhtml = sheet(maix) html = self.inces.trans_doc(xhtml, preps['locs']) string = str(html) if self.is_dev(): filing.srite(html_fufi, string) else: part = MIMEText(string, "html", _charset='utf-8') msg.attach(part) else: if self.is_dev(): raise Exception('no html') if empro_text in preps['sheets']: sheet = preps['sheets'][empro_text] string = str(sheet(maix)) if self.is_dev(): filing.srite(text_fufi, string) else: part = MIMEText(string, "plain", _charset='utf-8') msg.attach(part) self.send(mail_fufi, msg, dont_send=self.is_dev()) def headers(self, doc): msg = MIMEMultipart("alternative") xp = '/n:headers/n:header' header_eles = doc.xpath(xp, namespaces={'n': self.const['ns']}) for header_ele in header_eles: if 'name' not in header_ele.attrib: show_head_ele = docing.show(header_ele) print(f"email: no name in {show_head_ele}", file=sys.stderr) continue name = header_ele.attrib['name'] if 'value' not in header_ele.attrib: show_head_ele = docing.show(header_ele) print(f"email: no value in {show_head_ele}", file=sys.stderr) continue value = header_ele.attrib['value'] # # if all uppercase names, it's a parameter if name.upper() == name: self.params[name] = value continue if self.is_it_ascii(value): msg[name] = value continue if header_ele.get('type') == 'address': out = str(self.make_address(value)) if out is not None: msg[name] = out continue msg[name] = Header(value, 'utf-8') if 'To' not in msg or self.re_is_blank.match(msg['to']): print_header = str(msg) print(f"emailer: no destination in {print_header}", file=sys.stderr) return None return msg def prepare(self, maix, empro='repis', in_what='empros', dont_send=False, base='base', only=None, do_repeat=False): """send the email via file, stores the file in base""" mail_fufi = self.conf['folders']['mail'] + '/' + base + '.mail' if os.path.isfile(mail_fufi): ## skip repeats if not dont_send and not do_repeat: if self.do_verbose: print(f"emailer sees {mail_fufi}, skip.") return None # # save xml, for debugging maix_fufi = self.conf['folders']['mail'] + '/' + base + '.xml' html_fufi = self.conf['folders']['mail'] + '/' + base + '.html' text_fufi = self.conf['folders']['mail'] + '/' + base + '.text' filing.prepare(maix_fufi) if (self.is_dev() and empro == 'repis') or self.is_dev(): print(f"I write {maix_fufi}") maix_file = open(maix_fufi, 'w') string = docing.show(maix) maix_file.write(string) maix_file.close() sheet_fufi = self.get_empro_part(empro, 'head', in_what=in_what) if sheet_fufi is None: print("emailer does not see the empro head.", file=sys.stderr) return None empro_head = empro + '_head' head_doc = self.sheets.get_result(empro_head, maix) msg = self.headers(head_doc) if only != 'html': sheet_fufi = self.get_empro_part(empro, 'text', in_what=in_what) if sheet_fufi is not None: empro_text = empro + '_text' # string = str(self.sheets.get_result(empro_text, maix)) string = str(self.transform(empro_text, maix)) if self.is_dev(): filing.srite(text_fufi, string) #if dont_send: # print(string) part = MIMEText(string, "plain", _charset='utf-8') msg.attach(part) if only != 'text': sheet_fufi = self.get_empro_part(empro, 'html', in_what=in_what) if sheet_fufi is not None: locs = self.inces.read_locs() empro_html = empro + '_html' # html = str(self.sheets.get_result(empro_html, maix)) # xhtml = self.sheets.get_result(empro_html, maix) xhtml = self.transform(empro_html, maix) html = self.inces.trans_doc(xhtml, locs) string = docing.show(html) if self.is_dev(): filing.srite(html_fufi, string) part = MIMEText(string, "html", _charset='utf-8') msg.attach(part) mail_fufi = self.conf['folders']['mail'] + '/' + base + '.mail' filing.prepare(mail_fufi) if self.do_verbose: print(f"emailer writes {mail_fufi}") mail_file = open(mail_fufi, 'w') mail_file.write(msg.as_string()) mail_file.close() ## if this file is empty, don't send it, but report an error if os.path.isfile(mail_fufi) and os.stat(mail_fufi).st_size == 0: print(f"emailer: {mail_fufi} is empty, I remove it", file=sys.stderr) os.remove(mail_fufi) return None if not os.path.isfile(mail_fufi): print(f"emailer: I don't see {mail_fufi}", file=sys.stderr) return None if dont_send is True: if self.is_dev(): print(f"emailer wrote {mail_fufi}, not sent") if self.do_verbose: print(f"emailer sees dont_send as {dont_send}, returns.") return None if not dont_send: if self.do_verbose: print(f"emailer sends {mail_fufi}") out = self.send_file(mail_fufi) else: if self.do_verbose: print(f"emailer sees dont_send as {dont_send}") if out is None: return None if empro != 'repis': if self.is_dev(): print(f"emailer wrote {mail_fufi}") return out def send_file(self, mail_fufi): mail_command = f"cat {mail_fufi} | /usr/sbin/exim4 -t" envelope_address = self.get_envelope_address() if envelope_address is not None: mail_command += f" -f {envelope_address}" # # don't send on the test machine if self.is_dev(): print(f"emailer wrote {mail_fufi}") return None try: out = subprocess.run(mail_command, shell=True, check=True) except subprocess.CalledProcessError as error: out = mail_command + " yields " + str(error) print(out, file=sys.stderr) if self.is_on_terminal(): sys.exit(1) if self.do_verbose: print(f"emailer removes {mail_fufi}") os.remove(mail_fufi) return out def send(self, mail_fufi, msg, dont_send=False): mail_file = open(mail_fufi, 'w') mail_file.write(msg.as_string()) mail_file.close() if dont_send is True: print(f"emailer wrote {mail_fufi}, not sent") return None self.send_file(mail_fufi) def get_envelope_address(self): if 'ENVELOPE_ADDRESS' in self.params: return self.params['ENVELOPE_ADDRESS'] if self.has_conf('addresses', 'envelope'): return self.conf['addresses']['envelope'] return None def is_it_ascii(self, string): try: string.encode('ascii') except UnicodeEncodeError: return False return True def make_address(self, nama): out = self.re_nama.match(nama) if(out is None): print(f"emailer can't parse '{nama}'") return None name = out.group(1).strip() emad = out.group(2).strip() # # https://stackoverflow.com/questions/10551933/python-email-module-form-header-from-with-some-unicode-name-email address = formataddr((str(Header(name, 'utf-8')), emad)) return address def has_it_empro(self, empro, in_what='empros'): """is there is empro available""" if self.get_empro_part(empro, 'head', in_what=in_what) is None: return False if self.get_empro_part(empro, 'text', in_what=in_what) is not None: return True if self.get_empro_part(empro, 'html', in_what=in_what) is not None: return True return True def get_empro_part(self, empro, part, in_what='empros'): """empro part either by sheets conf or seen""" target = f"{empro}_{part}" #if self.do_verbose: # print(f"emailer: trying to find empro with {target}") ### check the configuration if self.has_conf('sheets', target): fufi = self.conf['sheets'][target] if not os.path.isfile(fufi): if self.do_verbose: print(f"emailer does not see {fufi}", file=sys.stderr) return None # print(f"I (1) use {fufi}") return fufi if not self.has_conf('folders', in_what): print(f"emailer: not [folders][{in_what}] configured.", file=sys.stderr) what_fudi = self.conf['folders'][in_what] fufi = f"{what_fudi}/{target}.xslt.xml" if os.path.isfile(fufi): if self.do_verbose: print(f"emailer uses {fufi}") return fufi # # try empros what_fudi = self.conf['folders']['empros'] fufi = f"{what_fudi}/{target}.xslt.xml" if self.do_verbose: print(f"emailer uses {fufi}") if not os.path.isfile(fufi): if self.do_verbose: print(f"emailer does not see {fufi}", file=sys.stderr) return None return fufi def get_latest(self, repcode): """should be in repis, but that leads to a circular import""" if not self.has_conf('folders', 'repis'): print("repis needs Ф[folders][repis] to find the latest issue", file=sys.stderr) return None repis_fudi = self.conf['folders']['repis'] glob_string = repis_fudi + '/????-??-??' issue_fudis = sorted(glob.glob(glob_string)) issue_fudis.reverse() for issue_fudi in issue_fudis: glob_string = issue_fudi + '/' + repcode + '*' found = glob.glob(glob_string) if len(found) > 0: return found[0] return None def send_latest(self, repcode, emad): latest_repis_fufi = self.get_latest(repcode) if latest_repis_fufi is None: return None base = self.infile.get_base(latest_repis_fufi) base = base + '/' + self.relfi_from_emad(emad)[:-4] mail_base_fufi = self.conf['folders']['mail'] + '/' + base mail_fufi = mail_base_fufi + '.mail' # print(mail_fufi) if os.path.isfile(mail_fufi): self.send_file(mail_fufi) return True return False def relfi_from_emad(self, emad): """This is in profile, but profile can't be imported, so I copy""" emad = emad.lower() if '@' not in emad: print(f"profile: {emad} is not an emad.") return None parts = emad.split('@') host = parts[1] user = parts[0] parts = host.split('.') parts.reverse() folder = '' for part in parts: folder += part + '/' # for p.o'brien --> p.o’brien if "'" in user: user = user.replace("'", "’") relfi = folder + user + '.xml' return relfi def transform(self, shena, maix): out = None try: out = self.sheets.get_result(shena, maix) except etree.XSLTApplyError: # print("emailer: I need to use xsltproc", file=sys.stderr) out = self.sheets.via_system(shena, maix) return out