Compare commits

..

14 Commits

8 changed files with 73 additions and 108 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Editors
*~
*.sw[po]
\#*\#
.\#*
# Python
*.py[cod]
__pycache__

View File

@ -21,11 +21,13 @@ following packages:
1. Clone repo 1. Clone repo
$ git clone https://forge.april.org/adminsys/spamotron $ git clone https://forge.april.org/adminsys/spamotron
$ chmod a+x spamotron/mailing.py $ cd spamotron
$ chmod a+x mailing.py
2. Try it 2. Try it
$ ./spamotron/mailing.py --help $ ./mailing.py --help
$ ./mailing.py -t test-recipients-data -b test-emailling --dry-run --verbose
## License ## License

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# (C) Olivier Berger 2001-2002 # (C) Olivier Berger 2001-2002
# (C) François Poulain 2019 # (C) François Poulain 2019-2020
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -22,9 +22,8 @@
# This program is used to mail a message to several recipients, thus # This program is used to mail a message to several recipients, thus
# providing customization for each recipient. # providing customization for each recipient.
import argparse, csv, sys, re import argparse, csv, sys
import smtplib, time, mimetypes import smtplib, time, mimetypes
from email.header import Header
from email.message import EmailMessage from email.message import EmailMessage
from jinja2 import Environment, FileSystemLoader, select_autoescape from jinja2 import Environment, FileSystemLoader, select_autoescape
@ -67,7 +66,9 @@ def read_recipients_datas(args):
with open(args.tofile) as csv_file: with open(args.tofile) as csv_file:
csv_soup = csv.reader(csv_file) csv_soup = csv.reader(csv_file)
first = [(s.split(":", 1)) for s in csv_soup.__next__()] first = [(s.split(":", 1)) for s in csv_soup.__next__()]
header = [(h[0].strip(), h[1].strip() if len(h) == 2 else None) for h in first] header = [
(h[0].strip().lower(), h[1].strip() if len(h) == 2 else None) for h in first
]
datas = [[s.strip() for s in d] for d in csv_soup] datas = [[s.strip() for s in d] for d in csv_soup]
full_datas = [] full_datas = []
for data in datas: for data in datas:
@ -79,7 +80,6 @@ def read_recipients_datas(args):
except Exception: except Exception:
print("Ligne mal renseignée:", data, file=sys.stderr) print("Ligne mal renseignée:", data, file=sys.stderr)
full_datas.append(full_data) full_datas.append(full_data)
print(datas)
return full_datas return full_datas
@ -98,17 +98,17 @@ def read_templates(args):
return templates return templates
def read_join(args): def read_attachments(args):
r = [] r = []
for attachement in args.attachement: for attachement in args.attachement or []:
try: try:
with open(args.attachement, "rb") as fp: with open(attachement, "rb") as fp:
ctype, encoding = mimetypes.guess_type(args.attachement) ctype, encoding = mimetypes.guess_type(attachement)
if ctype is None or encoding is not None: if ctype is None or encoding is not None:
ctype = "application/octet-stream" ctype = "application/octet-stream"
maintype, subtype = ctype.split("/", 1) maintype, subtype = ctype.split("/", 1)
metadata = { metadata = {
"filename": args.attachement, "filename": attachement,
"maintype": maintype, "maintype": maintype,
"subtype": subtype, "subtype": subtype,
} }
@ -119,88 +119,23 @@ def read_join(args):
return r return r
def replace_values(bodytemplate, values): def send_message(templates, data, attachments, args):
body = bodytemplate dests = [data["to"]]
for i in range(len(values)):
i = i + 1
pattern = "${{%02d}}" % i
body = body.replace(pattern, values[i - 1])
return body
# patterns for header of the mail
FROMPATTERN = re.compile(r"^From: *(.*)")
SUBJECTPATTERN = re.compile(r"^Subject: *(.*)")
CCPATTERN = re.compile(r"^Cc: *(.*)")
BCCPATTERN = re.compile(r"^Bcc: *(.*)")
REPLYTOPATTERN = re.compile(r"^Reply-To: *(.*)")
def qencode_subject(message):
subjectmatches = re.search(r"^Subject: *(.*)$", message, re.MULTILINE)
if subjectmatches == None:
return message
subjectstr = subjectmatches.group(1)
h = Header(subjectstr, "utf-8")
qsubjectstr = h.encode()
return message.replace(subjectstr, qsubjectstr, 1)
def send_message(message, to, attachments, args):
lines = message.split("\n")
# identify headers in the template
fromvalue = None
subjectvalue = None
ccvalue = None
bccvalue = None
replytovalue = None
for index, line in enumerate(lines):
if not line:
body = "\n".join(lines[index + 1 :])
break
frommatches = FROMPATTERN.match(line)
subjectmatches = SUBJECTPATTERN.match(line)
ccmatches = CCPATTERN.match(line)
bccmatches = BCCPATTERN.match(line)
replytomatches = REPLYTOPATTERN.match(line)
if frommatches:
fromvalue = frommatches.group(1)
if subjectmatches:
subjectvalue = subjectmatches.group(1)
if ccmatches:
ccvalue = ccmatches.group(1)
if bccmatches:
bccvalue = bccmatches.group(1)
if replytomatches:
replytovalue = replytomatches.group(1)
# lists all addresses to which the mail will be sent
dests = [to]
if ccvalue:
dests.append(ccvalue)
if bccvalue:
dests.append(bccvalue)
msg = EmailMessage() msg = EmailMessage()
msg["Subject"] = subjectvalue msg["Subject"] = templates[0].render(**data).strip()
msg["From"] = fromvalue msg["From"] = data["from"]
msg["To"] = to msg["To"] = data["to"]
if replytovalue: if "reply-to" in data:
msg["Reply-To"] = replytovalue msg["Reply-To"] = data["reply-to"]
if ccvalue: if "cc" in data:
msg["Cc"] = ccvalue msg["Cc"] = data["cc"]
msg.set_content(body) dests.append(data["cc"])
if "bcc" in data:
dests.append(data["bcc"])
msg.set_content(templates[1].render(**data))
if attachments: if attachments:
for attachement in attachments: for attachment in attachments:
msg.add_attachment(attachment[0], **attachment[1]) msg.add_attachment(attachment[0], **attachment[1])
if args.verbose: if args.verbose:
@ -209,7 +144,7 @@ def send_message(message, to, attachments, args):
else: else:
print( print(
"Sending : %s, from %s to %s, dests : %s" "Sending : %s, from %s to %s, dests : %s"
% (subjectvalue, fromvalue, dests[0], repr(dests)) % (msg["Subject"], data["from"], dests[0], repr(dests))
) )
if not args.dry_run: if not args.dry_run:
@ -227,32 +162,20 @@ if __name__ == "__main__":
# read the recipients file # read the recipients file
datas = read_recipients_datas(args) datas = read_recipients_datas(args)
# read the template of the mail # read the templates of the mail
templates = read_templates(args) templates = read_templates(args)
print(templates)
exit(0)
# optionnaly read the attachment of the mail # optionnaly read the attachment of the mail
attachment = None attachments = read_attachments(args)
if args.attachement:
attachments = read_join(args)
# counter to be able to pause each 10 mails send # counter to be able to pause each 10 mails send
counter = 0 counter = 0
# send mail to each recipient # send mail to each recipient
for set in sets: for data in datas:
values = set
# the recipient is always in first column
recipient = values[0]
# replace substitution variables in the template for each recipient
body = replace_values(bodytemplate, values)
# send message # send message
send_message(body, recipient, attachments, args) send_message(templates, data, attachments, args)
# pause every 10 mails for 5 secs so that the MTA doesn't explode # pause every 10 mails for 5 secs so that the MTA doesn't explode
counter = counter + 1 counter = counter + 1

7
test-emailling.footer Normal file
View File

@ -0,0 +1,7 @@
--
Vous disposez d'un droit d'accès, de modification, de rectification et de
suppression des données vous concernant (loi « Informatique et Libertés » du 6
janvier 1978). Pour toute demande, merci de vous adressez à contact@april.org.
Si vous souhaitez ne plus recevoir nos communiqués de presse merci de vous
adressez à contact@april.org.

15
test-emailling.html Normal file
View File

@ -0,0 +1,15 @@
<html>
<header>
<title>
Vive le libre en fête
</title>
</header>
<body>
<p>
Chère {{ nom }},
</p><p>
Venez découvrir le logiciel libre autour du printemps. Déjà 42 événements pour
le Libre en Fête à venir.
</p>
</body>
</html>

1
test-emailling.subject Normal file
View File

@ -0,0 +1 @@
Vivle le libre en fête

4
test-emailling.txt Normal file
View File

@ -0,0 +1,4 @@
Chère {{ nom }},
Venez découvrir le logiciel libre autour du printemps. Déjà 42 événements pour
le Libre en Fête à venir.

4
test-recipients-data Normal file
View File

@ -0,0 +1,4 @@
to,nom,from:president@april.org,cc:contact@april.org,reply-to:contact@april.org
noc@april.org,network operating center
secretaire@april.org,secrétaire,president@paril.org,
prez@april.org,présidente,prez@elysee.fr