-
Notifications
You must be signed in to change notification settings - Fork 2
/
autoresponder.py
executable file
·187 lines (157 loc) · 6.18 KB
/
autoresponder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
import imaplib
import smtplib
import email
import time
import sys
import yaml
import argparse
import logging
from email.mime.text import MIMEText
# pragma mark - Server Connections
def makeIMAPServer(server, port, ssl, username, password):
imapServer = (imaplib.IMAP4_SSL(server, port) if ssl else
imaplib.IMAP(server, port))
imapServer.login(username, password)
imapServer.select('INBOX')
return imapServer
def makeSMTPServer(server, port, ssl, username, password):
smtpServer = (smtplib.SMTP_SSL(server, port) if ssl else
smtplib.SMTP(server, port))
smtpServer.login(username, password)
return smtpServer
def makeIMAPServerWithConfig(imapConfig):
logging.info("Connecting to %s:%i as %s" % (imapConfig["server"],
imapConfig["port"], imapConfig["username"]))
imapServer = makeIMAPServer(imapConfig["server"],
imapConfig["port"], imapConfig["ssl"], imapConfig["username"],
imapConfig["password"])
return imapServer
def makeSMTPServerWithConfig(smtpConfig):
logging.info("Connecting to %s:%i as %s" % (smtpConfig["server"],
smtpConfig["port"], smtpConfig["username"]))
smtpServer = makeSMTPServer(smtpConfig["server"],
smtpConfig["port"], smtpConfig["ssl"], smtpConfig["username"],
smtpConfig["password"])
return smtpServer
# pragma mark - IMAP Utils
def hasNewMail(imapServer):
try:
status, data = imapServer.status('INBOX', '(UNSEEN)')
except imaplib.IMAP4.error:
logging.exception("Unable to check for new emails")
logging.debug("%s %s" % (status, data))
if status != "OK":
return False
unreadcount = int(data[0].split()[2].strip(').,]'))
logging.info("There are %i unseen emails" % unreadcount)
return unreadcount > 0
def UIDsForNewEmail(imapServer):
try:
status, data = imapServer.uid('search', None, '(UNSEEN)')
except imaplib.IMAP4.error:
logging.exception("Was unable to retrieve UIDs for new emails")
logging.debug("%s %s" % (status, data))
if status != "OK":
return []
uids = data[0].split()
return uids
def emailForUID(uid, imapServer):
logging.info("Fetching email with uid: %s", uid)
status, data = imapServer.uid('fetch', uid, '(RFC822)')
logging.debug("%s" % status)
raw_email = data[0][1]
emailObj = email.message_from_string(raw_email)
logging.info("From: %s. Subject: %s" % (emailObj['From'],
emailObj['Subject']))
return emailObj
def moveMessage(uid, folder, imapServer):
logging.info("Copying message to folder %s" % folder)
status, _ = imapServer.uid('copy', uid, folder)
if status == "OK":
logging.info("Adding deleted flag to message")
imapServer('store', uid, '+FLAGS', '(\Deleted)')
imapServer.expunge()
else:
logging.warning("Unable to apply copy message %s", uid)
#pragma mark -
def replyWithOriginalEmail(emailObj, fromAddress, body):
logging.info("Creating reply")
replyObj = MIMEText(body, _charset="utf-8")
# TODO (Terin Stock): Do I need to ensure these are clean,
# or does 'email' handle that?
replyObj['To'] = emailObj['From']
if emailObj['Reply-To'] != "None":
replyObj['To'] = emailObj['Reply-To']
replyObj['From'] = fromAddress
replyObj['Date'] = email.utils.formatdate(time.time(), True)
# According to jwz, RFC1036 allows us to truncate references
# (http://www.jwz.org/doc/threading.html)
replyObj['References'] = emailObj['Message-ID']
replyObj['In-Reply-To'] = emailObj['Message-ID']
replyObj['Subject'] = emailObj['Subject']
return replyObj
def parseArgs():
parser = argparse.ArgumentParser(description='Autorespond to new emails.')
parser.add_argument('config', type=file, help="YAML configuration file")
parser.add_argument('--log', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR',
'CRITICAL'], type=(lambda string: string.upper()),
help="Set the log level.")
return parser.parse_args()
def setupLogging(loglevel):
if loglevel:
numeric_level = getattr(logging, loglevel, None)
logging.basicConfig(level=numeric_level)
def main():
args = parseArgs()
setupLogging(args.log)
logging.debug(args)
try:
config = yaml.load(args.config)
except yaml.YAMLError:
logging.exception("Unable to parse the configuration file %s"
% args.config)
return 1
logging.debug(config)
args.config.close()
for server in config:
try:
imapServer = makeIMAPServerWithConfig(server['imap'])
except imaplib.IMAP4.error:
logging.exception("There was an error logging into the IMAP"
"server")
imapServer.close()
imapServer.logout()
continue # next server configuration
logging.debug(imapServer.capabilities)
if hasNewMail(imapServer):
try:
smtpServer = makeSMTPServerWithConfig(server['smtp'])
except smtplib.SMTPException:
logging.exception("There was an error logging into the SMTP"
" server")
smtpServer.quit()
continue # next server configuration
uids = UIDsForNewEmail(imapServer)
for uid in uids:
try:
emailObj = emailForUID(uid, imapServer)
except imaplib.IMAP4.error:
logging.exception("Was unable to fetch email from"
"server")
continue # next UID
replyObj = replyWithOriginalEmail(emailObj,
server['smtp']['from'],
server['body'])
smtpServer.sendmail(server['smtp']['from'],
replyObj['To'],
replyObj.as_string())
logging.info("Email sent")
moveMessage(uid, server["imap"]["move_folder"], imapServer)
smtpServer.quit()
imapServer.close()
imapServer.logout()
logging.info("We're all done here")
if __name__ == "__main__":
sys.exit(main())