|
@@ -1,3 +1,5 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
1
|
3
|
import sys
|
2
|
4
|
import time
|
3
|
5
|
import imaplib
|
|
@@ -6,7 +8,7 @@ import json
|
6
|
8
|
from typing import Union
|
7
|
9
|
from email.message import Message
|
8
|
10
|
from email.header import Header, decode_header, make_header
|
9
|
|
-from email.utils import parseaddr,parsedate_tz,mktime_tz
|
|
11
|
+from email.utils import parseaddr, parsedate_tz, mktime_tz
|
10
|
12
|
from email import message_from_bytes
|
11
|
13
|
|
12
|
14
|
import requests
|
|
@@ -14,75 +16,80 @@ import requests
|
14
|
16
|
from tracim.controllers.events import VALID_TOKEN_VALUE
|
15
|
17
|
|
16
|
18
|
|
17
|
|
-TRACIM_SPECIAL_KEY_HEADER="X-Tracim-Key"
|
|
19
|
+TRACIM_SPECIAL_KEY_HEADER = "X-Tracim-Key"
|
|
20
|
+
|
18
|
21
|
|
19
|
|
-def str_header(header:Header):
|
|
22
|
+def str_header(header: Header) -> str:
|
20
|
23
|
return str(make_header(decode_header(header)))
|
21
|
24
|
|
22
|
|
-def decode_mail(msg:Message)-> dict:
|
|
25
|
+
|
|
26
|
+def decode_mail(msg: Message)-> dict:
|
23
|
27
|
"""
|
24
|
28
|
Get useful header and body content and decode from Message
|
25
|
29
|
:param msg:
|
26
|
30
|
:return:
|
27
|
31
|
"""
|
28
|
|
- mailData = {}
|
|
32
|
+ mail_data = {}
|
29
|
33
|
|
30
|
34
|
try:
|
31
|
|
- mailData['subject'] = str_header(msg['subject'])
|
32
|
|
- mailData['msg_id'] = str_header(msg['Message-ID'])
|
33
|
|
- mailData['from'] = parseaddr(msg['From'])[1]
|
|
35
|
+ mail_data['subject'] = str_header(msg['subject'])
|
|
36
|
+ mail_data['msg_id'] = str_header(msg['Message-ID'])
|
|
37
|
+ mail_data['from'] = parseaddr(msg['From'])[1]
|
34
|
38
|
# Reply key
|
35
|
|
- mailData['to'] = parseaddr(msg['To'])[1]
|
|
39
|
+ mail_data['to'] = parseaddr(msg['To'])[1]
|
36
|
40
|
|
37
|
|
- mailData['references'] = parseaddr(msg['References'])[1]
|
|
41
|
+ mail_data['references'] = parseaddr(msg['References'])[1]
|
38
|
42
|
if TRACIM_SPECIAL_KEY_HEADER in msg:
|
39
|
|
- mailData[TRACIM_SPECIAL_KEY_HEADER] = str_header(msg[TRACIM_SPECIAL_KEY_HEADER])
|
|
43
|
+ mail_data[TRACIM_SPECIAL_KEY_HEADER] = str_header(msg[TRACIM_SPECIAL_KEY_HEADER]) # nopep8
|
40
|
44
|
# date
|
41
|
45
|
date_h = str_header(msg['Date'])
|
42
|
46
|
date_tuple = parsedate_tz(date_h)
|
43
|
47
|
|
44
|
|
- mailData['date'] = datetime.datetime.fromtimestamp(mktime_tz(date_tuple))
|
|
48
|
+ mail_data['date'] = datetime.datetime.fromtimestamp(
|
|
49
|
+ mktime_tz(date_tuple)
|
|
50
|
+ )
|
45
|
51
|
|
46
|
|
- except Exception as e:
|
|
52
|
+ except Exception:
|
47
|
53
|
# TODO: exception -> mail not correctly formatted
|
48
|
|
- return None
|
49
|
|
- #email.utils.mktime_tz(date_tuple))
|
50
|
|
- #print( "Local Date:", local_date.strftime("%a, %d %b %Y %H:%M:%S"))
|
51
|
|
- ## TODO : msg.get_body look like the best way to get body but it's a new feature now (08112017).
|
|
54
|
+ return {}
|
|
55
|
+ # TODO : msg.get_body look like the best way to get body
|
|
56
|
+ # but it's a new feature now (08112017).
|
52
|
57
|
for part in msg.walk():
|
53
|
58
|
if not part.get_content_type() == "text/plain":
|
54
|
59
|
continue
|
55
|
60
|
else:
|
56
|
61
|
# TODO: check if decoding is working correctly
|
57
|
62
|
charset = part.get_content_charset('iso-8859-1')
|
58
|
|
- mailData['body']= part.get_payload(decode=True).decode(charset)
|
|
63
|
+ mail_data['body'] = part.get_payload(decode=True).decode(charset)
|
59
|
64
|
break
|
60
|
|
- return mailData
|
|
65
|
+ return mail_data
|
|
66
|
+
|
61
|
67
|
|
62
|
|
-def get_tracim_content_key(mailData:dict) -> Union[str,None]:
|
|
68
|
+def get_tracim_content_key(mail_data: dict) -> Union[str, None]:
|
63
|
69
|
|
64
|
|
- """ Link mailData dict to tracim content
|
|
70
|
+ """ Link mail_data dict to tracim content
|
65
|
71
|
First try checking special header, them check 'to' header
|
66
|
72
|
and finally check first(oldest) mail-id of 'references' header
|
67
|
73
|
"""
|
68
|
74
|
key = None
|
69
|
|
- if TRACIM_SPECIAL_KEY_HEADER in mailData:
|
70
|
|
- key = mailData[TRACIM_SPECIAL_KEY_HEADER]
|
71
|
|
- if key is None and 'to' in mailData:
|
72
|
|
- key = find_key_from_mail_adress(mailData['to'])
|
73
|
|
- if key is None and 'references' in mailData:
|
74
|
|
- mail_adress = mailData['references']
|
|
75
|
+ if TRACIM_SPECIAL_KEY_HEADER in mail_data:
|
|
76
|
+ key = mail_data[TRACIM_SPECIAL_KEY_HEADER]
|
|
77
|
+ if key is None and 'to' in mail_data:
|
|
78
|
+ key = find_key_from_mail_adress(mail_data['to'])
|
|
79
|
+ if key is None and 'references' in mail_data:
|
|
80
|
+ mail_adress = mail_data['references']
|
75
|
81
|
key = find_key_from_mail_adress(mail_adress)
|
76
|
82
|
return key
|
77
|
83
|
|
78
|
|
-def find_key_from_mail_adress(mail_address:str) -> Union[str,None]:
|
|
84
|
+
|
|
85
|
+def find_key_from_mail_adress(mail_address: str) -> Union[str, None]:
|
79
|
86
|
""" Parse mail_adress-like string
|
80
|
87
|
to retrieve key.
|
81
|
88
|
|
82
|
89
|
:param mail_address: user+key@something like string
|
83
|
90
|
:return: key
|
84
|
91
|
"""
|
85
|
|
- username= mail_address.split('@')[0]
|
|
92
|
+ username = mail_address.split('@')[0]
|
86
|
93
|
username_data = username.split('+')
|
87
|
94
|
if len(username_data) == 2:
|
88
|
95
|
key = username_data[1]
|
|
@@ -106,8 +113,7 @@ class MailFetcher(object):
|
106
|
113
|
|
107
|
114
|
self._is_active = True
|
108
|
115
|
|
109
|
|
-
|
110
|
|
- def run(self):
|
|
116
|
+ def run(self) -> None:
|
111
|
117
|
while self._is_active:
|
112
|
118
|
time.sleep(self.delay)
|
113
|
119
|
self._connect()
|
|
@@ -115,29 +121,29 @@ class MailFetcher(object):
|
115
|
121
|
self._notify_tracim()
|
116
|
122
|
self._disconnect()
|
117
|
123
|
|
118
|
|
- def stop(self):
|
|
124
|
+ def stop(self) -> None:
|
119
|
125
|
self._is_active = False
|
120
|
126
|
|
121
|
|
- def _connect(self):
|
122
|
|
- ## verify if connected ?
|
|
127
|
+ def _connect(self) -> None:
|
|
128
|
+ # verify if connected ?
|
123
|
129
|
if self._connection:
|
124
|
130
|
self._disconnect()
|
125
|
131
|
# TODO: Support unencrypted connection ?
|
126
|
132
|
# TODO: Support keyfile,certfile ?
|
127
|
|
- self._connection = imaplib.IMAP4_SSL(self.host,self.port)
|
|
133
|
+ self._connection = imaplib.IMAP4_SSL(self.host, self.port)
|
128
|
134
|
try:
|
129
|
|
- rv, data = self._connection.login(self.user,self.password)
|
|
135
|
+ self._connection.login(self.user, self.password)
|
130
|
136
|
except Exception as e:
|
131
|
137
|
log = 'IMAP login error: {}'
|
132
|
138
|
logger.debug(self, log.format(e.__str__()))
|
133
|
139
|
|
134
|
|
- def _disconnect(self):
|
|
140
|
+ def _disconnect(self) -> None:
|
135
|
141
|
if self._connection:
|
136
|
142
|
self._connection.close()
|
137
|
143
|
self._connection.logout()
|
138
|
144
|
self._connection = None
|
139
|
145
|
|
140
|
|
- def _fetch(self):
|
|
146
|
+ def _fetch(self) -> None:
|
141
|
147
|
"""
|
142
|
148
|
Get news message from mailbox
|
143
|
149
|
"""
|
|
@@ -155,29 +161,28 @@ class MailFetcher(object):
|
155
|
161
|
if rv == 'OK':
|
156
|
162
|
msg = message_from_bytes(data[0][1])
|
157
|
163
|
self._mails.append(msg)
|
158
|
|
- ret = True
|
159
|
164
|
else:
|
160
|
165
|
# TODO : Check best debug value
|
161
|
166
|
log = 'IMAP : Unable to get mail : {}'
|
162
|
|
- logger.debug(self,log.format(str(rv)))
|
|
167
|
+ logger.debug(self, log.format(str(rv)))
|
163
|
168
|
else:
|
164
|
|
- #TODO : Distinct error from empty mailbox ?
|
|
169
|
+ # TODO : Distinct error from empty mailbox ?
|
165
|
170
|
pass
|
166
|
|
- else :
|
|
171
|
+ else:
|
167
|
172
|
# TODO : Check best debug value
|
168
|
173
|
log = 'IMAP : Unable to open mailbox : {}'
|
169
|
|
- logger.debug(self,log.format(str(rv)))
|
|
174
|
+ logger.debug(self, log.format(str(rv)))
|
170
|
175
|
|
171
|
|
- def _notify_tracim(self):
|
|
176
|
+ def _notify_tracim(self) -> None:
|
172
|
177
|
while self._mails:
|
173
|
178
|
mail = self._mails.pop()
|
174
|
179
|
decoded_mail = decode_mail(mail)
|
175
|
|
- msg = {"token" : VALID_TOKEN_VALUE,
|
176
|
|
- "user_mail" : decoded_mail['from'],
|
177
|
|
- "content_id" : get_tracim_content_key(decoded_mail),
|
|
180
|
+ msg = {"token": VALID_TOKEN_VALUE,
|
|
181
|
+ "user_mail": decoded_mail['from'],
|
|
182
|
+ "content_id": get_tracim_content_key(decoded_mail),
|
178
|
183
|
"payload": {
|
179
|
|
- "content": decoded_mail['body']
|
|
184
|
+ "content": decoded_mail['body'],
|
180
|
185
|
}}
|
181
|
186
|
|
182
|
|
- requests.post(self.endpoint,json=msg)
|
|
187
|
+ requests.post(self.endpoint, json=msg)
|
183
|
188
|
pass
|