🎉 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