"""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 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 = {} # # debug if on local computer self.debug = self.is_dev() # self.re_nama = re.compile(r"([^<]+)<([^@]+)@([^>]+)>\s*") self.re_nama = 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)) 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') return msg def prepare(self, maix, empro='repis', in_what='empros', dont_send=False, base='base', only=None): """send the email via file, stores the file in base""" mail_fufi = self.conf['folders']['mail'] + '/' + base + '.mail' if os.path.isfile(mail_fufi) and not dont_send: 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.debug and empro == 'repis') or self.is_dev(): 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.") return None head_doc = self.sheets.via_system(sheet_fufi, 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)) 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) 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) mail_file = open(mail_fufi, 'w') mail_file.write(msg.as_string()) mail_file.close() if dont_send is True: if self.is_dev(): print(f"emailer wrote {mail_fufi}, not sent") return None # # for the admirable p.o'brien@... #if "'" in mail_fufi: # mail_command = f'cat "{mail_fufi}" | /usr/sbin/exim4 -t' #else: out = self.send_file(mail_fufi) 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: os.remove(mail_fufi) out = mail_command + " yields " + str(error) print(out, file=sys.stderr) return out def send(self, mail_fufi, msg, dont_send=False): filing.prepare(mail_fufi) 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) # 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: # os.remove(mail_fufi) # out = mail_command + " yields " + str(error) # print(out, file=sys.stderr) # #if empro != 'repis': # # print(f"emailer wrote {mail_fufi}") # return out 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}" ### check the configuration if self.has_conf('sheets', target): fufi = self.conf['sheets'][target] if not os.path.isfile(fufi): print(f"emailer does not see {fufi}", file=sys.stderr) return None # print(f"I (1) use {fufi}") return fufi # # 2nd: use style directory. This approach does not work. # # it leads to files not found in the style sheet, even # # though the return file can be identical with the first # # appoarch #if self.has_conf('folders', 'style'): # style_fudi = self.conf['folders']['style'] # fufi = style_fudi + '/' + empro + '_' + part + '.xslt.xml' # if os.path.isfile(fufi): # print(f"I (2) 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): return fufi # # try empros what_fudi = self.conf['folders']['empros'] fufi = f"{what_fudi}/{target}.xslt.xml" if not os.path.isfile(fufi): print(f"emailer does not see {fufi}", file=sys.stderr) return None return fufi ### should be in repis, but that leads to a circular import def get_latest(self, repcode): 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 ## fixme: we should try to send the latest issues regardless ## of whether it was sent. # print('base is ' + base) #maix = filing.parse_lax(latest_repis_fufi) #out = self.prepare(maix, empro='repis', in_what='empros', # dont_send=False, # base='base', only=None) # # should be as easy as # out = self.repis.send(emad, surks_docu.getroot(), only=repcodes) # # but we can't use repis, it goes circular #print("out is " + out) 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