/
imapprowl
executable file
·193 lines (159 loc) · 5.85 KB
/
imapprowl
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
188
189
190
191
192
#!/usr/bin/env python
import imaplib2
import os
import re
import signal
import sys
from application import log
from application.configuration import ConfigFile, ConfigSection, ConfigSetting
from application.configuration.datatypes import NetworkAddress
from application.process import process, ProcessError
from optparse import OptionParser
from threading import Event, Thread
from prowlpy import Prowl, ProwlAuthError, ProwlError
__version__ = '0.0.1'
# Configuration
class GeneralConfig(ConfigSection):
__section__ = 'general'
api_key = ''
class IMAPServer(NetworkAddress):
default_port = 143
class AccountConfig(ConfigSection):
name = ''
username = ''
password = ''
server = ConfigSetting(IMAPServer, value=None)
skip_mailing_lists = True
class AccountCfgObj(object):
def __init__(self, **kw):
self.__dict__.update(kw)
class GeneralCfgObj(object):
def __init__(self, **kw):
self.__dict__.update(kw)
def load_config(cfg_file):
config_file = ConfigFile(cfg_file)
GeneralConfig.read(config_file)
general_config = dict(GeneralConfig)
accounts_config = []
for section in (section for section in config_file.parser.sections() if section != 'general'):
AccountConfig.read(config_file, section)
accounts_config.append(AccountCfgObj(**dict(AccountConfig)))
AccountConfig.reset()
return GeneralCfgObj(**dict(general_config)), accounts_config
# IMAP IDLE worker
class Idler(object):
def __init__(self, account):
self.account = account
self.thread = Thread(target=self._idle)
self.last_emails = set()
try:
self.imap = imaplib2.IMAP4_SSL(account.server[0], account.server[1])
self.imap.login(account.username, account.password)
self.imap.select()
except imaplib2.error, e:
log.fatal(str(e))
sys.exit(1)
self.event = Event()
self.sync = False
def start(self):
self.thread.start()
def stop(self):
self.event.set()
self.thread.join()
try:
self.imap.close()
self.imap.logout()
except imaplib2.error:
pass
def handle_idle(self, args):
if not self.event.isSet():
self.sync = True
self.event.set()
def _list_emails(self):
emails = self.imap.uid('search', None, 'ALL')[1]
return set(emails[0].split())
def _idle(self):
while True:
if self.event.isSet():
break
self.sync = False
# Do the actual idle call. This returns immediately,
# since it's asynchronous.
self.last_emails = self._list_emails()
try:
self.imap.idle(callback=self.handle_idle)
except imaplib2.error, e:
pass
# This waits until the event is set. The event is
# set by the callback, when the server 'answers'
# the idle call and the callback function gets
# called.
self.event.wait()
if self.sync:
self.event.clear()
self.process_event()
def process_event(self):
new_emails = self._list_emails()
unseen_emails = new_emails.difference(self.last_emails)
if unseen_emails:
for email in unseen_emails:
try:
typ, data = self.imap.uid('fetch', email, '(BODY.PEEK[HEADER])')
headers_data = data[0][1]
headers_data = re.sub('\r\n\t', '', headers_data)
headers = {}
for h in headers_data.split('\r\n'):
if h:
header, sep, value = h.partition(':')
headers[header.lower()] = value.strip()
if self.account.skip_mailing_lists and any(header.startswith('list-') for header in headers):
log.msg('Skipping email coming from mailing list')
return
except IndexError:
pass
except imaplib2.error:
pass
else:
log.msg('Email received, sending notification...')
prowl.post(self.account.name or 'IMAP Notifier', 'New Email', headers.get('subject', "You've got new email!"))
prowl = None
def quit(signal, frame):
log.msg("Quitting...")
if __name__ == '__main__':
name = 'imap-notifier'
description = 'An IMAP notifier for Prowl'
version = __version__
parser = OptionParser(version="%%prog %s" % version)
parser.add_option("--no-fork", action="store_false", dest="fork", default=1, help="run the process in the foreground (for debugging)")
options, args = parser.parse_args()
process.runtime_directory = '/tmp'
process.signals.add_handler(signal.SIGINT, quit)
process.signals.add_handler(signal.SIGTERM, quit)
process.signals.add_handler(signal.SIGUSR1, quit)
if options.fork:
try:
pidfile = process.runtime_file('%s.pid' % name)
process.daemonize(pidfile)
except ProcessError, e:
log.fatal(str(e))
sys.exit(1)
log.start_syslog(name)
log.msg('IMAP notifier started!')
imap_idler_threads = []
if not os.path.isfile('config.ini'):
log.fatal("Config file missing")
sys.exit(1)
general_cfg, accounts_cfg = load_config('config.ini')
try:
prowl = Prowl(general_cfg.api_key)
except (ProwlAuthError, ProwlError), e:
log.fatal("Couldn't start Prowl: %s" % str(e))
sys.exit(1)
for account in accounts_cfg:
idler = Idler(account)
idler.start()
imap_idler_threads.append(idler)
signal.pause()
for idler in imap_idler_threads:
idler.stop()
log.msg('IMAP notifier ended.')