Each election should only have one set of results; use UNIQUE to enforce.
[conservancy:voting.git] / bin / mail-instructions.py
1 #!/usr/bin/python
2
3 # Script to send instructions to all voters.
4 #
5 # How to use this script
6 # ======================
7 #
8 # You probably want to first update the subject of the e-mail that will be
9 # sent. The second line of the instructions.txt will be the subject.
10 #
11 # So let's suppose that the instructions are in instructions.txt and that you
12 # made a list of voters in maildata.txt (probably using create-tmp-tokens.pl).
13 # The format of this file should be:
14 # name;email;token
15 #
16 # You should use this script like this:
17 # $ ./mail-instructions.pl maildata.txt instructions.txt
18 #
19 # This script needs a MTA to send the e-mails. If you don't have one or if
20 # you're not sure that your ISP allows you to directly send mails, it's
21 # probably better and safer to run the script from a gnome.org server.
22 # Please test this script with your own email address by creating a 
23 # maildata.txt with a single entry like
24 # foo;your@address;bar
25 #
26 # You may want to look at your mail server logs (and maybe keep them) to
27 # know if the mail was delivered. There are usually 10-15 errors. In case of
28 # such errors, you can try to look for the new e-mail addresses of the voters
29 # to ask them if they want to update their registered e-mail address and
30 # receive the instructions.
31
32 #    This program is free software: you can redistribute it and/or modify
33 #    it under the terms of the GNU General Public License as published by
34 #    the Free Software Foundation, either version 3 of the License, or
35 #    (at your option) any later version.
36 #
37 #    This program is distributed in the hope that it will be useful,
38 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
39 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
40 #    GNU General Public License for more details.
41 #
42 #    You should have received a copy of the GNU General Public License
43 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
44
45 __author__ = "Tobias Mueller"
46 __license__ = "GPLv3+"
47 __email__ = "tobiasmue@gnome.org"
48
49 import smtplib
50 import sys
51 import string
52 import re
53 try:
54     from email.mime.text import MIMEText
55     from email.mime.nonmultipart import MIMENonMultipart
56     from email.charset import Charset
57 except ImportError:
58     from email.MIMEText import MIMEText
59     from email.Charset import Charset
60     from email.MIMENonMultipart import MIMENonMultipart
61
62 re_template_fixes = [
63     (re.compile(r'^(\s*Dear )<member>', re.MULTILINE), '\\1$member'),
64     (re.compile(r'^(\s*E-mail:)', re.MULTILINE), '\\1 $email'),
65     (re.compile(r'^(\s*Vote token:)', re.MULTILINE), '\\1 $token')
66 ]
67
68 class MTText(MIMEText):
69     def __init__(self, _text, _subtype='plain', _charset='utf-8'):
70         if not isinstance(_charset, Charset):
71                 _charset = Charset(_charset)
72         if isinstance(_text,unicode):
73             _text = _text.encode(_charset.input_charset)
74         MIMENonMultipart.__init__(self, 'text', _subtype,
75                                        **{'charset': _charset.input_charset})
76         self.set_payload(_text, _charset)
77
78 def email_it(recipients_file, instructions_file):
79     instructions = file(instructions_file, "r").read().decode('utf-8').splitlines()
80
81     from_header = instructions.pop(0)
82     subject_header = instructions.pop(0)
83
84     instructions = "\n".join(instructions)
85     for re_fix in re_template_fixes:
86        instructions  = re_fix[0].sub(re_fix[1], instructions)
87     template = string.Template(instructions)
88
89     f = file(recipients_file, "r")
90     recipient_lines = f.read().decode('utf-8').splitlines()
91
92     sent = 0
93     errors = 0
94     s = None
95
96     for line in recipient_lines:
97         l = line.strip()
98         if l.startswith("#") or l == "":
99             continue
100
101         l = l.split(";", 2)
102         if len(l) <> 3:
103             print "ERROR in recipients file, invalid line:"
104             print line,
105             continue
106
107         member_name, member_email, token = l
108
109         payload = template.substitute(member=member_name, email=member_email, token=token)
110         msg = MTText(payload)
111         msg['To'] = member_email
112         msg['From'] = from_header
113         msg['Subject'] = subject_header
114         msgstr = msg.as_string()
115
116         if s is None:
117             s = smtplib.SMTP()
118             s.connect('localhost')
119
120         try:
121             s.sendmail(from_header, [member_email,], msgstr)
122         except smtplib.SMTPException:
123             print "Error: Could not send to %s (%s)!" % (member_email, member_name)
124             errors += 1
125         else:
126             sent += 1
127
128     if s:
129         s.quit()
130
131     f.close()
132
133     print "Mailed %s instructions; %s could not be mailed." % (sent, errors)
134
135
136 if __name__ == '__main__':
137     if len(sys.argv) != 3:
138         print "Usage: mail-instructions.py <recipient list> <instructions template>"
139         sys.exit(1)
140
141     email_it(sys.argv[1], sys.argv[2])