🎉 first commit
This commit is contained in:
commit
7767674ae7
20 changed files with 671 additions and 0 deletions
24
README.md
Normal file
24
README.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Script de massmail
|
||||||
|
|
||||||
|
Ce script est un fork (a peine modifié) du script de massmail utilsé au crans
|
||||||
|
qui se base la même architecture a base de re2o. Ce depo est fait pour être
|
||||||
|
pull et exectué sur `re2o-server.adm.auro.re`
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
Tout est expliqué dans l'aide. Pour optenir de l'aide :
|
||||||
|
|
||||||
|
```
|
||||||
|
./mail_all.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exemples
|
||||||
|
|
||||||
|
Envoyer uniquement sur des mails de test pour vous si tout fonctionne bien
|
||||||
|
|
||||||
|
```
|
||||||
|
./mail_all.py -s "Aurore <no-reply@auro.re>" -f list_test_mails.txt -t Innondations_Fev_2021 -p
|
||||||
|
```
|
||||||
|
|
||||||
|
On oublie pas de tester avec `p` avant tout envoie de mail, ne surtout pas
|
||||||
|
faire de `--doit` avant d'avoir fait une simulation !
|
41
cprint.py
Normal file
41
cprint.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#!/bin/python3
|
||||||
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
|
|
||||||
|
def coul(txt, col=None):
|
||||||
|
"""
|
||||||
|
Retourne la chaine donnée encadrée des séquences qui
|
||||||
|
vont bien pour obtenir la couleur souhaitée
|
||||||
|
Les couleur sont celles de codecol
|
||||||
|
Il est possible de changer la couleur de fond grace aux couleur f_<couleur>
|
||||||
|
"""
|
||||||
|
if not col:
|
||||||
|
return txt
|
||||||
|
|
||||||
|
codecol = {'rouge': 31,
|
||||||
|
'vert': 32,
|
||||||
|
'jaune': 33,
|
||||||
|
'bleu': 34,
|
||||||
|
'violet': 35,
|
||||||
|
'cyan': 36,
|
||||||
|
'gris': 30,
|
||||||
|
'gras': 50}
|
||||||
|
codecol_dialog = {'rouge': 1,
|
||||||
|
'vert': 2,
|
||||||
|
'jaune': 3,
|
||||||
|
'bleu': 4,
|
||||||
|
'violet': 5,
|
||||||
|
'cyan': 6,
|
||||||
|
'gris': 0,
|
||||||
|
'gras': 'b'}
|
||||||
|
try:
|
||||||
|
if col[:2] == 'f_':
|
||||||
|
add = 10
|
||||||
|
col = col[2:]
|
||||||
|
else:
|
||||||
|
add = 0
|
||||||
|
txt = "\033[1;%sm%s\033[1;0m" % (codecol[col] + add, txt)
|
||||||
|
finally:
|
||||||
|
return txt
|
||||||
|
|
||||||
|
def cprint(txt, col='blanc'):
|
||||||
|
print(coul(txt, col))
|
27
locale_util.py
Executable file
27
locale_util.py
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
|
# Source:
|
||||||
|
# http://stackoverflow.com/questions/18593661/how-do-i-strftime-a-date-object-in-a-different-locale
|
||||||
|
import locale
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
LOCALE_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def setlocale(name):
|
||||||
|
with LOCALE_LOCK:
|
||||||
|
saved = locale.setlocale(locale.LC_ALL)
|
||||||
|
try:
|
||||||
|
current_val = locale.setlocale(locale.LC_ALL, name)
|
||||||
|
except:
|
||||||
|
current_val = saved
|
||||||
|
print("Warning: Failed setting locale %r" % name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield current_val
|
||||||
|
finally:
|
||||||
|
locale.setlocale(locale.LC_ALL, saved)
|
||||||
|
|
254
mail.py
Executable file
254
mail.py
Executable file
|
@ -0,0 +1,254 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import jinja2
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import inspect
|
||||||
|
import locale
|
||||||
|
import smtplib
|
||||||
|
import traceback
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
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, parseaddr, formataddr
|
||||||
|
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')
|
||||||
|
|
||||||
|
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):
|
||||||
|
""" Renvoie une jolie représentation (unicode) d'un datetime"""
|
||||||
|
# L'encoding dépend de ce qu'on a choisi plus bas
|
||||||
|
lang, encoding = locale.getlocale()
|
||||||
|
if not encoding:
|
||||||
|
encoding = 'ascii'
|
||||||
|
if lang == 'fr_FR':
|
||||||
|
return d.strftime('%A %d %B %Y').decode(encoding)
|
||||||
|
else:
|
||||||
|
return d.strftime('%A, %B %d %Y').decode(encoding)
|
||||||
|
|
||||||
|
def given_name(adh):
|
||||||
|
"""Renvoie le joli nom d'un adhérent"""
|
||||||
|
if adh.is_class_club:
|
||||||
|
return "Club {}".format(adh.surname)
|
||||||
|
return adh.name + " " + adh.surname
|
||||||
|
|
||||||
|
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_bytes())
|
||||||
|
### 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(payload, 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['Content-Disposition'] = 'inline'
|
||||||
|
submsg.set_payload(payload)
|
||||||
|
return submsg
|
||||||
|
|
||||||
|
def get_lang(mail, part, lang, lang_fallback):
|
||||||
|
"""Récupère le chemin vers le fichier à utiliser, en fonction de la
|
||||||
|
langue souhaitée"""
|
||||||
|
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):
|
||||||
|
"""Génère le texte du mail, en deux langues, avec une extension `mk` donnée
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
# Il y a vraiment des gens qui lisent ce champ ?
|
||||||
|
params['mailer'] = "Un MA de Aurore a personnalisé ce message à la main pour toi"
|
||||||
|
|
||||||
|
msg = MIMEMultipart('mixed')
|
||||||
|
inline_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):
|
||||||
|
inline_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)
|
||||||
|
msg.attach(inline_msg)
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def format_sender(sender, header_charset='utf-8'):
|
||||||
|
"""
|
||||||
|
Check and format sender for header.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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(str(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))
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def bugreport():
|
||||||
|
"""Context manager: Si erreur, renvoie un bugreport avec un traceback à
|
||||||
|
roots@."""
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except Exception as exc:
|
||||||
|
From = 'root@auro.re'
|
||||||
|
to = From
|
||||||
|
tb = sys.exc_info()[2].tb_next
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'from': From,
|
||||||
|
'to': to,
|
||||||
|
'exc': exc,
|
||||||
|
'lineno': tb.tb_frame.f_lineno,
|
||||||
|
'filename': os.path.basename(tb.tb_frame.f_code.co_filename),
|
||||||
|
'traceback': traceback.format_exc(),
|
||||||
|
}
|
||||||
|
mail = generate('bugreport', data)
|
||||||
|
with ServerConnection() as conn:
|
||||||
|
conn.sendmail(From, [to], mail.as_string())
|
||||||
|
raise
|
||||||
|
|
||||||
|
class ServerConnection(object):
|
||||||
|
"""Connexion au serveur smtp"""
|
||||||
|
_conn = None
|
||||||
|
def __enter__(self):
|
||||||
|
if os.getenv('DBG_MAIL') != 'print':
|
||||||
|
self._conn = smtplib.SMTP('localhost')
|
||||||
|
return self
|
||||||
|
|
||||||
|
def check_sender(self, mail):
|
||||||
|
"""Vérifie l'expéditeur, pour éviter certaines erreurs"""
|
||||||
|
if mail.split('@')[0].lower() in ['ca.aurore', 'tech.aurore', 'communication.aurore', 'events.aurore']:
|
||||||
|
raise Exception(u"Merci d'utiliser une autre adresse mail d'expédition, celle-ci est une ML publique sur laquelle des bounces seraient malvenus.")
|
||||||
|
|
||||||
|
def sendmail(self, From, to, mail):
|
||||||
|
"""Envoie un mail"""
|
||||||
|
self.check_sender(From)
|
||||||
|
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 send_template(self, tpl_name, data):
|
||||||
|
"""Envoie un mail à partir d'un template.
|
||||||
|
`data` est un dictionnaire contenant entre
|
||||||
|
"""
|
||||||
|
From = data.get('from', '')
|
||||||
|
adh = data.get('adh', data.get('proprio', ''))
|
||||||
|
to = data.get('to', None) or (adh.get_mail() if adh else None)
|
||||||
|
if to is None:
|
||||||
|
print("Pas de mail valide pour %r. Skipping..." % (adh, ))
|
||||||
|
return
|
||||||
|
# TODO: get lang toussa
|
||||||
|
body = generate(tpl_name, data).as_string()
|
||||||
|
self.sendmail(From, to, body)
|
||||||
|
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
if os.getenv('DBG_MAIL') != 'print':
|
||||||
|
self._conn.quit()
|
||||||
|
|
||||||
|
# TODO: intégrer ceci dans le ServerConnection
|
||||||
|
def postconf(i):
|
||||||
|
"Fixe la fréquence d'envoi maximale par client (en msg/min)"
|
||||||
|
os.system("/usr/sbin/postconf -e smtpd_client_message_rate_limit=%s" % i)
|
||||||
|
os.system("/etc/init.d/postfix reload")
|
||||||
|
|
||||||
|
# opt = commands.getoutput("/usr/sbin/postconf smtpd_client_message_rate_limit")
|
197
mail_all.py
Executable file
197
mail_all.py
Executable file
|
@ -0,0 +1,197 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Script générique pour envoyer des mails en masse ~~au crans~~ à Aurore après un
|
||||||
|
(petit) fork
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import django
|
||||||
|
import argparse
|
||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from cprint import cprint
|
||||||
|
|
||||||
|
try:
|
||||||
|
import mail
|
||||||
|
except ImportError:
|
||||||
|
cprint("Vérifie bien d'avoir tous les paquets (par exemple python3-jinja2)", "rouge")
|
||||||
|
|
||||||
|
|
||||||
|
def mail_sender(template, From, recipients, PREV=True, SEND=False, cc=None, bcc=None):
|
||||||
|
"""
|
||||||
|
``template`` template du mail à envoyer.
|
||||||
|
``From`` Pour remplir le champ From du mail.
|
||||||
|
``recipients`` Liste des addresses mails recipiendaires.
|
||||||
|
``PREV`` Booléen specifiant s'il faut faire un dry-run avec prévisualisation.
|
||||||
|
Default = True.
|
||||||
|
``SEND`` Booléen spécifiant s'il faut effectivement envoyer le mail.
|
||||||
|
Default = False.
|
||||||
|
``cc`` Liste des addresses mails en copie.
|
||||||
|
``bcc`` Liste des addresses mails en copie cachée.
|
||||||
|
"""
|
||||||
|
echecs = []
|
||||||
|
if PREV:
|
||||||
|
print("Envoi simulé")
|
||||||
|
try:
|
||||||
|
print("{} destinataires (Ctrl + C pour annuler l'envoi)".format(len(recipients)))
|
||||||
|
input("Envoyer ? (Ret pour envoyer)\n")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
cprint("\nEnvoi annulé.", "rouge")
|
||||||
|
sys.exit(1)
|
||||||
|
with mail.ServerConnection() as conn_smtp:
|
||||||
|
for line in recipients:
|
||||||
|
extra_params = {}
|
||||||
|
if len(line.split(';')) == 3:
|
||||||
|
# Format Prénom Nom Email
|
||||||
|
Prenom, Nom, To = line.split(':')
|
||||||
|
elif len(line.split(';')) == 2:
|
||||||
|
# Format Prénom Email
|
||||||
|
Nom, Prenom, To = '', line.split(';')
|
||||||
|
elif len(line.split(';')) == 1:
|
||||||
|
# Juste un email
|
||||||
|
Prenom, Nom, To = '', '', line
|
||||||
|
else:
|
||||||
|
# Format Prénom Nom Email ExtraJson
|
||||||
|
Prenom, Nom, To, extra = line.split(';')
|
||||||
|
extra_params = json.loads(extra)
|
||||||
|
print("Envoi du mail à {}".format(To))
|
||||||
|
try:
|
||||||
|
params = {'To' : To,
|
||||||
|
'From' : From,
|
||||||
|
'lang_info' : 'English version below',
|
||||||
|
'Prenom' : Prenom,
|
||||||
|
'Nom': Nom,
|
||||||
|
}
|
||||||
|
|
||||||
|
params.update(extra_params)
|
||||||
|
|
||||||
|
mailtxt = mail.generate(
|
||||||
|
template,
|
||||||
|
params).as_bytes()
|
||||||
|
except KeyError: # Il faut corriger le fichier source pour que ça marche
|
||||||
|
cprint("Félicitation tu viens de tomber sur un bug python (cf {})".format(
|
||||||
|
"https://bugs.python.org/issue27321"),
|
||||||
|
"rouge")
|
||||||
|
raise
|
||||||
|
if PREV:
|
||||||
|
print(mailtxt)
|
||||||
|
try:
|
||||||
|
if SEND:
|
||||||
|
conn_smtp.sendmail(From, (To,), mailtxt)
|
||||||
|
print(" Envoyé !")
|
||||||
|
else:
|
||||||
|
print(" Simulé !")
|
||||||
|
except:
|
||||||
|
print(sys.exc_info()[:2])
|
||||||
|
cprint("Erreur lors de l'envoi à {} ".format(To), "rouge")
|
||||||
|
echecs.append(To)
|
||||||
|
if not SEND:
|
||||||
|
cprint("\n\
|
||||||
|
/!\ Avant d'envoyer réellement ce mail all, as-tu vérifié que:\n\
|
||||||
|
- Les lignes sont bien formatées à 75-80 caractères ?\n\
|
||||||
|
- Le texte a été lu et relu ?\n\
|
||||||
|
- Il existe une version en anglais ?\n\
|
||||||
|
- Les destinataires sont bien les bons ?\n\
|
||||||
|
- Il y a bien une signature ?\n",
|
||||||
|
'rouge'
|
||||||
|
)
|
||||||
|
|
||||||
|
if echecs:
|
||||||
|
print("\nIl y a eu des erreurs pour les addresses suivantes :")
|
||||||
|
for echec in echecs:
|
||||||
|
print(" - {}\n".format(echec))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RE2O = '/var/www/re2o'
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
if not RE2O in sys.path:
|
||||||
|
sys.path.append(RE2O)
|
||||||
|
try:
|
||||||
|
import re2o
|
||||||
|
except ImportError:
|
||||||
|
print("Nécessite une instance re2o")
|
||||||
|
sys.exit(42)
|
||||||
|
|
||||||
|
# Setup l'environnement django
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 're2o.settings')
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Mail all générique. Prend un template en argument.",
|
||||||
|
add_help=True
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-f", "--recipientfile",
|
||||||
|
help="Un fichier contenant un destinataire par ligne. Les formats acceptés sont : `Prenom:Nom:Email`, `Prenom:Email`, ou tout simplement `Email`. Override tous les filtres. Dans le premier cas, on peut aussi rajouter des paramètres supplémentaires sous format json.",
|
||||||
|
action="store"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t", "--template",
|
||||||
|
help="Un template de mail. Fournir le chemin du dossier principal du mail, relatif à "
|
||||||
|
+ mail.template_path,
|
||||||
|
action="store"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s", "--sender",
|
||||||
|
help="Spécifier un expéditeur particulier. Par défaut no-reply@auro.re",
|
||||||
|
action="store",
|
||||||
|
default="no-reply@auro.re"
|
||||||
|
)
|
||||||
|
|
||||||
|
filtres = parser.add_argument_group('Filtres')
|
||||||
|
filtres.add_argument(
|
||||||
|
"-a", "--allaccess",
|
||||||
|
help="Envoie un mail à toutes les personnes bénéficiant d'une connexion valide.",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
filtres.add_argument(
|
||||||
|
"-A", "--alladh",
|
||||||
|
help="Envoie un mail à tous les adhérents.",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
|
||||||
|
exclusive = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
exclusive.add_argument(
|
||||||
|
"--doit",
|
||||||
|
help="Lance effectivement le mail",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
exclusive.add_argument(
|
||||||
|
"-p", "--prev",
|
||||||
|
help="Prévisualise le mail à envoyer",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.recipientfile:
|
||||||
|
with open(args.recipientfile, 'r') as recipientfile:
|
||||||
|
recipients = recipientfile.readlines()
|
||||||
|
else:
|
||||||
|
if args.allaccess:
|
||||||
|
print("{} du mail à toutes les personnes bénéficiant d'une connexion valide (Ctrl + C pour annuler l'envoi)".format("Simulation" if args.prev else "Envoi"))
|
||||||
|
input("Envoyer ? (Ret pour envoyer)\n")
|
||||||
|
users = re2o.utils.all_has_access()
|
||||||
|
elif args.alladh:
|
||||||
|
print("{} du mail à tous les adhérents (Ctrl + C pour annuler l'envoi)".format("Simulation" if args.prev else "Envoi"))
|
||||||
|
input("Envoyer ? (Ret pour envoyer)\n")
|
||||||
|
users = re2o.utils.all_adherent()
|
||||||
|
else:
|
||||||
|
print("Spécifier au moins un destinataire")
|
||||||
|
sys.exit(2)
|
||||||
|
recipients = ['{}:{}:{}'.format(user.name,
|
||||||
|
user.surname,
|
||||||
|
user.get_mail)
|
||||||
|
for user in users]
|
||||||
|
|
||||||
|
mail_sender(args.template, args.sender, recipients, args.prev, args.doit)
|
||||||
|
sys.exit(0)
|
1
template/Coupure_Fibre_Mar_2021/From/fr
Executable file
1
template/Coupure_Fibre_Mar_2021/From/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
"L'équipe d'Aurore" <no-reply@auro.re>
|
1
template/Coupure_Fibre_Mar_2021/Subject/fr
Executable file
1
template/Coupure_Fibre_Mar_2021/Subject/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
Fin de la coupure d'internet globale -- End of the global internet outage
|
1
template/Coupure_Fibre_Mar_2021/To/fr
Executable file
1
template/Coupure_Fibre_Mar_2021/To/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{{To}}
|
1
template/Coupure_Fibre_Mar_2021/X-Mailer/fr
Executable file
1
template/Coupure_Fibre_Mar_2021/X-Mailer/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{{ mailer }}
|
28
template/Coupure_Fibre_Mar_2021/body/en
Normal file
28
template/Coupure_Fibre_Mar_2021/body/en
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
Dear members,
|
||||||
|
|
||||||
|
As you probably noticed, Aurore's Internet connection went down from March 2nd
|
||||||
|
2:41 PM to March 3rd 3:49 PM.
|
||||||
|
|
||||||
|
This service interruption was due to an incident in a nearby construction site,
|
||||||
|
where an excavator severed our optical fiber. The break in the optical fiber
|
||||||
|
caused the technicians to pull a new one as a replacement.
|
||||||
|
|
||||||
|
Nonetheless, we would like to apologise for the inconvinence and thank you for
|
||||||
|
your patience.
|
||||||
|
|
||||||
|
We would also like to thank the members who reached us via
|
||||||
|
support.aurore@lists.crans.org and Matrix to inform us of the incident, as they
|
||||||
|
helped our technical team to confirm our diagnosis.
|
||||||
|
|
||||||
|
Since 3:50 PM, we have not noticed any issue on our network. All services are
|
||||||
|
working as intended. We do not declare any permanent loss in term of data or
|
||||||
|
quality of service, in particular, our bandwidth does not seem to be affected
|
||||||
|
by the new fiber.
|
||||||
|
|
||||||
|
If you are still experiencing any trouble, please contact us at once at our
|
||||||
|
support email address (support.aurore@lists.crans.org).
|
||||||
|
|
||||||
|
To conclude, "With a great excavator comes great responsibility".
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
Aurore's team
|
29
template/Coupure_Fibre_Mar_2021/body/fr
Normal file
29
template/Coupure_Fibre_Mar_2021/body/fr
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
Comme vous avez pu le constater, Aurore a subi une interruption de service
|
||||||
|
entre le mardi 2 mars à 14h41 et le mercredi 3 mars à 15h49.
|
||||||
|
|
||||||
|
Cet incident était dû à une erreur sur un chantier du plateau du Moulon, où une
|
||||||
|
fibre optique nous reliant au reste du réseau Internet a été coupée. La rupture
|
||||||
|
de la fibre optique a forcé les techniciens à tirer une nouvelle fibre en
|
||||||
|
remplacement.
|
||||||
|
|
||||||
|
Nous voulons nous excuser pour le désagrément lié à cette interruption de
|
||||||
|
service, et vous remercier pour votre patience.
|
||||||
|
|
||||||
|
Nous tenons aussi à remercier les adhérents qui nous ont contacté via
|
||||||
|
support.aurore@lists.crans.org et Matrix afin de signaler l'incident, ce qui a
|
||||||
|
permis de conforter le diagnostic effectué par nos équipes.
|
||||||
|
|
||||||
|
Depuis 15h50, nous ne constatons plus de défaillance sur le réseau. Tous les
|
||||||
|
services sont revenus en node nominal. Nous ne déplorons aucune perte de
|
||||||
|
données ou de qualité de service, en particulier le débit est revenu à la
|
||||||
|
normale.
|
||||||
|
|
||||||
|
Si vous subissez encore des dysfonctionnements, vous pouvez contacter notre
|
||||||
|
support (support.aurore@lists.crans.org).
|
||||||
|
|
||||||
|
En conclusion, « une grande pelleteuse implique de grandes responsabilités ».
|
||||||
|
|
||||||
|
Cordialement,
|
||||||
|
L'équipe d'Aurore
|
1
template/Innondation_Fev_2021/From/fr
Executable file
1
template/Innondation_Fev_2021/From/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
"L'équipe technique d'Aurore" <no-reply@auro.re>
|
1
template/Innondation_Fev_2021/Subject/fr
Executable file
1
template/Innondation_Fev_2021/Subject/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
Problème de connexion -- Internet trouble
|
1
template/Innondation_Fev_2021/To/fr
Executable file
1
template/Innondation_Fev_2021/To/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{{To}}
|
1
template/Innondation_Fev_2021/X-Mailer/fr
Executable file
1
template/Innondation_Fev_2021/X-Mailer/fr
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{{ mailer }}
|
11
template/Innondation_Fev_2021/body/en
Normal file
11
template/Innondation_Fev_2021/body/en
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Hello everyone,
|
||||||
|
|
||||||
|
Due to flooding in the core network of Aurore today, our internet connection
|
||||||
|
has been experiencing some troubles, with many cuts. A temporary solution has
|
||||||
|
been etablished, in order to maintain a decent quality of service.
|
||||||
|
|
||||||
|
We apologize for any inconvenience caused
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
The technical members of Aurore
|
13
template/Innondation_Fev_2021/body/fr
Normal file
13
template/Innondation_Fev_2021/body/fr
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Bonsoir à tous,
|
||||||
|
|
||||||
|
Aujourd'hui, une inondation est survenue dans le coeur de réseau d'Aurore.
|
||||||
|
Suite à cela, nous rencontrons des problèmes d'alimentation électrique
|
||||||
|
entraînant une baisse de qualité de nos services, ainsi que de nombreuses
|
||||||
|
coupures. Le temps que nous soyons en mesure de régler le problème, une
|
||||||
|
solution temporaire a été mise en place.
|
||||||
|
|
||||||
|
Nous nous excusons pour les désagréments occasionnés.
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
L'équipe technique d'Aurore
|
15
template/html
Executable file
15
template/html
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||||
|
<meta name="generator" content="pandoc" />
|
||||||
|
<title></title>
|
||||||
|
<style type="text/css">code{white-space: pre;}body{max-width: 55em;text-align:justify;}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block body %}
|
||||||
|
{{body}}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
13
template/html_multilang
Executable file
13
template/html_multilang
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "html" %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<p>{{lang_info}}</p>
|
||||||
|
|
||||||
|
<div lang="{{lang1}}">
|
||||||
|
{{body1}}
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div lang="{{lang2}}">
|
||||||
|
{{body2}}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
11
template/text_multilang
Executable file
11
template/text_multilang
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
{{lang_info}}
|
||||||
|
|
||||||
|
|
||||||
|
{{body1}}
|
||||||
|
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
{{body2}}
|
||||||
|
|
Loading…
Reference in a new issue