8
8
# Created: 23/11/2008
9
9
from datetime import datetime
10
10
from email.header import decode_header
11
from htmlentitydefs import name2codepoint, codepoint2name
12
11
from optparse import OptionParser
12
from plugin_common import getHTMLText, getTypedValue
24
25
app_name = "gtk-desktop-info"
25
26
app_path = os.path.dirname(os.path.abspath(__file__))
67
68
self.loadConfigData()
69
70
def loadConfigData(self):
71
72
self.config = EmailConfig()
73
74
if self.options.config != None:
74
75
# load the config based on options passed in from the main app
75
76
configfilepath = self.options.config
77
78
# load plugin config from home directory of the user
78
79
configfilepath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/"+module_name+".config")
80
81
if os.path.exists(configfilepath):
82
83
self.logger.info("Loading config settings from \"%s\""%configfilepath)
84
85
for line in fileinput.input(os.path.expanduser(configfilepath)):
85
86
line = line.strip()
86
87
if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones
88
89
name = line.split("=")[0].strip().upper() # config setting name on the left of =
89
90
value = line.split("=")[1].split("#")[0].strip() # config value on the right of = (minus any trailing comments)
92
93
if name == "HEADERTEMPLATE":
93
self.config.HEADERTEMPLATE = self.getTypedValue(value, "string")
94
self.config.HEADERTEMPLATE = getTypedValue(value, "string")
94
95
elif name == "TEMPLATE":
95
self.config.TEMPLATE = self.getTypedValue(value, "string")
96
self.config.TEMPLATE = getTypedValue(value, "string")
96
97
elif name == "CONNECTIONTIMEOUT":
97
self.config.CONNECTIONTIMEOUT = self.getTypedValue(value, "integer")
98
self.config.CONNECTIONTIMEOUT = getTypedValue(value, "integer")
98
99
elif name == "MAILINFO":
99
self.config.MAILINFO = self.getTypedValue(value, "integer")
100
self.config.MAILINFO = getTypedValue(value, "integer")
100
101
elif name == "FOLDER":
101
self.config.FOLDER = self.getTypedValue(value, "string")
102
self.config.FOLDER = getTypedValue(value, "string")
103
104
self.logger.error("Unknown option in config file: " + name)
105
106
self.logger.info("Config data file %s not found, using defaults and setting up config file for next time" % configfilepath)
107
108
userconfigpath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/")
108
109
configsource = os.path.join(app_path, "config/"+module_name+".config")
110
111
if os.path.exists(userconfigpath) == False:
111
112
os.makedirs(userconfigpath)
115
116
except Exception, e:
116
117
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
118
def getTypedValue(self, value, expectedtype):
121
if len(value.strip(" ")) == 0:
124
elif value.lower() == "true":
125
if expectedtype == "boolean":
128
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
130
elif value.lower() == "false":
131
if expectedtype == "boolean":
134
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
136
elif self.isNumeric(value) == True:
137
if expectedtype == "integer":
140
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
145
except (TypeError, ValueError):
146
self.logger.error("Cannot convert '%s' to expected type of '%s'"%(value,expectedtype))
149
119
def getTemplateList(self,template):
151
121
templatelist = []
153
123
for template_part in template.split("{"):
154
124
if template_part != "":
155
125
for template_part in template_part.split("}"):
161
131
def getOutputData(self,servertype,servername,port,folder,ssl,username,password,connectiontimeout,mailinfo):
165
135
socket.setdefaulttimeout(connectiontimeout)
167
137
if servertype == "POP":
168
138
count = self.getPOPEmailData(servername,port,folder,ssl,username,password,mailinfo)
169
139
elif servertype == "IMAP":
183
153
output = "%s New"%count
186
156
self.emaillist.sort(reverse=True)
187
157
for emaildata in self.emaillist:
188
158
counter = counter + 1
189
159
if mailinfo >= counter:
190
output = output + "\n<br>%s. %s: \"%s\""%(counter,emaildata.sender,emaildata.subject)
160
output = output + "\n<br>%s. %s: \"%s\""%(counter,emaildata.sender,emaildata.subject)
192
162
output = str(count)
196
166
except Exception, e:
197
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
167
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
200
170
def getTemplateItemOutput(self, template_text):
202
172
# keys to template data
203
173
SERVERTYPE_KEY = "servertype"
204
174
SERVERNAME_KEY = "servername"
220
190
connectiontimeout = self.config.CONNECTIONTIMEOUT
221
191
mailinfo = self.config.MAILINFO
223
193
for option in template_text.split('--'):
224
194
if len(option) == 0 or option.isspace():
227
197
# not using split here...it can't assign both key and value in one call, this should be faster
228
198
x = option.find('=')
235
205
key = option.strip()
239
209
if key == SERVERTYPE_KEY:
240
servertype = self.getTypedValue(value, "string")
210
servertype = getTypedValue(value, "string")
241
211
elif key == SERVERNAME_KEY:
242
servername = self.getTypedValue(value, "string")
212
servername = getTypedValue(value, "string")
243
213
elif key == PORT_KEY:
244
port = self.getTypedValue(value, "integer")
214
port = getTypedValue(value, "integer")
245
215
elif key == FOLDER_KEY:
246
folder = self.getTypedValue(value, "string")
216
folder = getTypedValue(value, "string")
247
217
elif key == SSL_KEY:
249
219
elif key == USERNAME_KEY:
250
username = self.getTypedValue(value, "string")
220
username = getTypedValue(value, "string")
251
221
elif key == PASSWORD_KEY:
252
password = self.getTypedValue(value, "string")
222
password = getTypedValue(value, "string")
253
223
elif key == CONNECTION_TIMEOUT_KEY:
254
connectiontimeout = self.getTypedValue(value, "integer")
224
connectiontimeout = getTypedValue(value, "integer")
255
225
elif key == MAILINFO_KEY:
256
mailinfo = self.getTypedValue(value, "integer")
226
mailinfo = getTypedValue(value, "integer")
258
228
self.logger.error("Unknown template option: " + option)
260
230
except (TypeError, ValueError):
261
231
self.logger.error("Cannot convert option argument to number: " + option)
264
234
if servername != None:
265
235
output = self.getOutputData(servertype,servername,port,folder,ssl,username,password,connectiontimeout,mailinfo)
266
output = self.getHTMLText(output)
236
output = getHTMLText(output)
269
239
self.logger.error("Template item does not have servername defined")
278
248
# a and b are indexes in the template string
279
249
# moving from left to right the string is processed
280
250
# b is index of the opening bracket and a of the closing bracket
281
251
# everything between b and a is a template that needs to be parsed
283
253
b = template.find('[', a)
286
256
b = len(template)
289
259
# if there is something between a and b, append it straight to output
291
261
output += template[a : b]
296
266
# skip the bracket in the input string and continue from the beginning
303
273
a = template.find(']', b)
306
276
self.logger.error("Missing terminal bracket (]) for a template item")
309
279
# if there is some template text...
311
281
output += self.getTemplateItemOutput(template[b + 1 : a])
317
287
def getEmailData(self,servername,folder,username,num,lines):
320
290
self.logger.info("Processing email data to determine 'From', 'Subject' and 'Received Date'")
327
297
for line in lines:
328
298
if sender is None and line.find("From: ") >= 0:
329
299
text = line.replace("From: ","").strip("\r ")
331
301
text = self.decodeHeader(text)
332
302
except Exception, e:
334
self.logger.error("getEmailData:Unexpected error when decoding sender:" + e.__str__()+"\n"+traceback.format_exc())
304
self.logger.error("getEmailData:Unexpected error when decoding sender:" + e.__str__()+"\n"+traceback.format_exc())
335
305
sender = re.sub('<.*?@.*?>','',text).strip().lstrip('"').rstrip('"') # remove trailing email in <>
336
306
elif subject is None and line.find("Subject: ") >= 0:
337
307
text = line.replace("Subject: ","").strip("\r\" ")
339
309
subject = self.decodeHeader(text)
340
310
except Exception, e:
342
self.logger.error("getEmailData:Unexpected error when decoding subject:" + e.__str__()+"\n"+traceback.format_exc())
312
self.logger.error("getEmailData:Unexpected error when decoding subject:" + e.__str__()+"\n"+traceback.format_exc())
343
313
elif recvdate is None and line.find("Date: ") >= 0:
344
314
text = line.replace("Date: ","").strip("\r ")
347
317
recvdate = datetime.strptime(text,"%d %b %Y %H:%M:%S") # convert to proper datetime
348
318
except Exception, e:
349
319
recvdate = datetime.now()
350
self.logger.error("getEmailData:Unexpected error when converting recieve date to datetime:" + e.__str__()+"\n"+traceback.format_exc())
320
self.logger.error("getEmailData:Unexpected error when converting recieve date to datetime:" + e.__str__()+"\n"+traceback.format_exc())
351
321
elif messageid is None and line.find("Message-ID: ") >= 0:
352
322
text = line.replace("Message-ID: ","").strip("\r ")
355
325
if sender is not None and \
356
326
subject is not None and \
357
327
recvdate is not None and \
358
messageid is not None:
328
messageid is not None:
361
331
if subject is None:
364
334
emaildata = EmailData(servername, folder, username, num, sender, subject, recvdate, messageid)
368
338
except Exception, e:
369
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
339
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
372
342
def getPOPEmailData(self,servername,port,folder,ssl,username,password,mailinfo):
376
346
self.logger.info("Logging on to POP server: "+ servername)
380
350
pop = poplib.POP3_SSL(servername)
382
pop = poplib.POP3(servername)
352
pop = poplib.POP3(servername)
385
355
pop = poplib.POP3_SSL(servername, port)
387
pop = poplib.POP3(servername, port)
357
pop = poplib.POP3(servername, port)
389
359
pop.user(username)
390
360
pop.pass_(password)
392
362
self.logger.info("Getting message count from POP server: "+ servername)
394
364
count = len(pop.list()[1])
396
366
if count > 0 and mailinfo > 0:
398
368
self.logger.info("Extracting message data from POP server \"%s\""%servername)
400
370
self.emaillist = []
402
372
for num in range(count):
404
374
if self.POP_FETCH_OPTION == "TOP":
405
375
lines = pop.top(num+1,1)[1]
407
377
lines = pop.retr(num+1,1)[1] #more robust but sets message as seen!
409
379
emaildata = self.getEmailData(servername,folder,username,num,lines)
411
381
if emaildata is not None:
412
382
self.emaillist.append(emaildata)
414
384
self.logger.info("Logging off from POP server: "+ servername)
420
390
except Exception, e:
421
391
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
424
394
def getIMAPEmailData(self,servername,port,folder,ssl,username,password,mailinfo):
428
398
self.logger.info("Logging on to IMAP server: "+ servername)
432
402
imap = imaplib.IMAP4_SSL(servername)
437
407
imap = imaplib.IMAP4_SSL(servername, port)
439
409
imap = imaplib.IMAP4(servername, port)
441
411
imap.login(username, password)
443
413
self.logger.info("Searching for new mail on IMAP server \"%s\" in folder \"%s\""%(servername,folder))
445
415
imap.select(folder)
446
416
typ, data = imap.search(None, self.IMAP_SEARCH_OPTION)
447
417
for item in data:
453
423
count = (len(nums))
457
427
if count > 0 and mailinfo > 0:
459
429
self.logger.info("Extracting message data for IMAP server: "+ servername)
461
431
self.emaillist = []
464
434
typ, message = imap.fetch(num, '(BODY.PEEK[HEADER])')
465
435
lines = message[0][1].split("\n") # grab the content we want and split out lines
467
437
emaildata = self.getEmailData(servername,folder,username,num,lines)
469
439
if emaildata is not None:
470
440
self.emaillist.append(emaildata)
472
442
self.logger.info("Logging of from IMAP server: "+ servername)
480
450
except Exception, e:
481
451
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
484
454
def getOutput(self):
486
456
if self.options.noheader == True:
525
495
template = inputfile.read()
527
497
inputfile.close()
529
499
output = headertemplate
530
500
output = output + self.getOutputFromTemplate(template)
532
502
return output.encode("utf-8")
534
504
def decodeHeader(self,header_text):
536
506
text,encoding = decode_header(header_text)[0]
545
def getHTMLText(self,text):
548
for char in text: #html:
550
htmlentities.append(char)
552
htmlentities.append('&%s;' % codepoint2name[ord(char)])
553
html = "".join(htmlentities)
555
html = html.replace("\n","<br>\n") # switch out new line for html breaks
560
def getCleanText(self,html):
563
text = text.replace("\n","") # remove new lines from html
564
text = text.replace("'","'") # workaround for shitty xml codes not compliant with html
565
text = text.replace("<br>","\n") # switch out html breaks for new line
566
text = re.sub('<(.|\n)+?>','',text) # remove any html tags
567
text = re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: chr(name2codepoint[m.group(1)]), text)
572
def isNumeric(self, string):
574
dummy = float(string)
579
515
def getHTML(options):
580
516
output = Output(options)
581
517
html = output.getOutput()
585
521
# to enable testing in isolation
586
522
if __name__ == "__main__":
588
524
parser = OptionParser()
589
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
525
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
590
526
parser.add_option("--headertemplate", dest="headertemplate", type="string", metavar="FILE", help=u"Override the header template for the plugin, default or config based template ignored.")
591
527
parser.add_option("--template", dest="template", type="string", metavar="FILE", help=u"Override the template for the plugin, default or config based template ignored.")
592
528
parser.add_option("--verbose", dest="verbose", default=False, action="store_true", help=u"Outputs verbose info to the terminal")
593
529
parser.add_option("--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.")
594
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
530
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
596
532
(options, args) = parser.parse_args()
598
534
output = Output(options)
599
535
html = output.getOutput()