# -*- coding: utf-8 -*- import os import jinja2 import sys import json import inspect import locale import smtplib from email.header import Header from email.message import Message from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.Utils import formatdate try: import misaka def markdown(text): return misaka.html(text, misaka.EXT_TABLES) except ImportError: from markdown import markdown from locale_util import setlocale if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') from gestion import secrets_new as secrets default_language = 'fr' template_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'template') + '/' html_template = template_path + 'html' html_mutilang_template = template_path + 'html_multilang' text_mutilang_template = template_path + 'text_multilang' templateLoader = jinja2.FileSystemLoader( searchpath=["/", template_path] ) templateEnv = jinja2.Environment( loader=templateLoader ) def format_date(d): # Beware: l'encodage utilisé ici dépend de la locale choisie plus bas if 'fr_FR' in locale.getlocale(): return d.strftime('%A %d %B %Y').decode('utf-8') else: return d.strftime('%A, %B %d %Y').decode('utf-8') def given_name(adh): if 'club' in adh['objectClass']: return u'Club %s' % unicode(adh['nom'][0]) return unicode(adh['prenom'][0]) + " " + unicode(adh['nom'][0]) templateEnv.filters['date'] = format_date templateEnv.filters['name'] = given_name # file extension to rendering function map markup = { 'md' : markdown, 'html' : lambda x:x, } ### For an example: ### print generate('bienvenue', {'From':'respbats@crans.org', 'To':'admin@genua.fr', 'lang_info':'English version below'}).as_string() ### or from a shell : python -c "import mail; print mail.generate('bienvenue', {'From':'respbats@crans.org', 'To':'admin@genua.fr', 'lang_info':'English version below'})" def submessage(playload, type, charset='utf-8'): """Renvois un sous message à mettre dans un message multipart""" submsg=MIMEText('', type, charset) del(submsg['Content-Transfer-Encoding']) submsg['Content-Transfer-Encoding']='8bit' submsg.set_payload(playload) return submsg def get_lang(mail, part, lang, lang_fallback): for l in [lang, lang_fallback]: for ext in markup.keys(): if os.path.isfile(template_path + mail + '/' + part + '/' + l + '.' + ext): return l, ext, template_path + mail + '/' + part + '/' + l + '.' + ext if os.path.isfile(template_path + mail + '/' + part + '/' + l): return l, None, template_path + mail + '/' + part + '/' + l raise ValueError("Language %s nor %s found" % (lang, lang_fallback)) def gen_local_body(fname, params, lang): """Génère le texte localisé d'un body""" locales = { 'fr': 'fr_FR.UTF-8', 'en': 'en_US.UTF-8', } with setlocale(locales.get(lang, 'C')): return templateEnv.get_template(fname).render(params) def body(mail, lang1, lang2, mk, params, charset): ret = [] file1 = template_path + mail + '/body/' + lang1 file2 = template_path + mail + '/body/' + lang2 if mk: file1 = file1 + '.' + mk file2 = file2 + '.' + mk if lang1 == lang2 or not os.path.isfile(file2): # No alt language txt = gen_local_body(file1, params, lang1) if mk != "html": ret.append(submessage(txt.encode(charset), 'plain', charset)) if mk: # compute the html version html = templateEnv.get_template(html_template).render({'body': markup[mk](txt)}) ret.append(submessage(html.encode(charset), 'html', charset)) else: txt1 = gen_local_body(file1, params, lang1) txt2 = gen_local_body(file2, params, lang2) if mk != "html": params_txt=dict(params) params_txt.update({'body1': txt1, 'body2':txt2}) txt = templateEnv.get_template(text_mutilang_template).render(params_txt) ret.append(submessage(txt.encode(charset), 'plain', charset)) if mk: # compute the html version params_html=dict(params) params_html.update({'lang1':lang1, 'lang2':lang2, 'body1': markup[mk](txt1), 'body2': markup[mk](txt2)}) html = templateEnv.get_template(html_mutilang_template).render(params_html) ret.append(submessage(html.encode(charset), 'html', charset)) return ret def generate(mail, params, lang=default_language, lang_fallback=default_language, lang_alt='en', charset='utf-8'): """Génère un message multipart""" if 'mailer' not in params: # Retrouve le nom du fichire ayant fait l'appel à la fonction frm = inspect.stack()[1] mod = inspect.getmodule(frm[0]) if mod: params['mailer'] = os.path.abspath(mod.__file__) else: params['mailer'] = 'unknown file (%s)' % os.getlogin() msg = MIMEMultipart('alternative') if os.path.isdir(template_path + mail): for filename in [dir for dir in os.listdir(template_path + mail) if os.path.isdir(template_path + mail + '/' + dir)]: lang_tmp, mk, file = get_lang(mail, filename, lang, lang_fallback) if filename == 'body': for part in body(mail, lang_tmp, lang_alt, mk, params, charset): msg.attach(part) else: txt = templateEnv.get_template(file).render(params) if filename in ['From', 'To', 'Cc', 'Bcc']: msg[filename] = format_sender(txt, charset) else: msg[filename]=Header(txt.encode(charset), charset) msg['Date']=formatdate(localtime=True) return msg def validation_url(view_name, data='', debug=False): """Enregistre une nouvelle url pour le module "validation" de l'intranet.""" import requests if debug: CA = False ROOT = 'https://intranet-dev.crans.org' else: CA = '/etc/ssl/certs/cacert.org.pem' ROOT = 'https://intranet2.crans.org' url = ROOT + '/validation/register/%s/' % view_name payload = { 'data': json.dumps(data), 'shared_secret': secrets.get('validation'), } req = requests.post(url, payload, verify=CA) if req.status_code != 200: raise Exception('Bad code status %d' % req.status_code) return ROOT + req.text.encode('utf-8') def format_sender(sender, header_charset='utf-8'): """ Check and format sender for header. """ from email.Header import Header from email.Utils import parseaddr, formataddr # Split real name (which is optional) and email address parts sender_name, sender_addr = parseaddr(sender) # We must always pass Unicode strings to Header, otherwise it will # use RFC 2047 encoding even on plain ASCII strings. sender_name = str(Header(unicode(sender_name), header_charset)) # Make sure email addresses do not contain non-ASCII characters sender_addr = sender_addr.encode('ascii') return formataddr((sender_name, sender_addr)) class ServerConnection(object): """Connexion au serveur smtp""" _conn = None def __enter__(self): self._conn = smtplib.SMTP('smtp.adm.crans.org') return self def sendmail(self, From, to, mail): if os.getenv('DBG_MAIL', False): deb = os.getenv('DBG_MAIL') if '@' in deb: to = [deb] else: print mail return self._conn.sendmail(From, to, mail) def __exit__(self, type, value, traceback): self._conn.quit()