1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ###########################################################################################
23 import email, mimetypes
24 from email.Header import decode_header
25 from email.MIMEText import MIMEText
31 from tools.translate import _
34 from osv import fields,osv,orm
35 from osv.orm import except_orm
38 from poplib import POP3, POP3_SSL
39 from imaplib import IMAP4, IMAP4_SSL
41 class mail_gateway_server(osv.osv):
42 _name = "mail.gateway.server"
43 _description = "Email Gateway Server"
45 'name': fields.char('Server Address',size=64,required=True ,help="IMAP/POP Address Of Email gateway Server"),
46 'login': fields.char('User',size=64,required=True,help="User Login Id of Email gateway"),
47 'password': fields.char('Password',size=64,required=True,help="User Password Of Email gateway"),
48 'server_type': fields.selection([("pop","POP"),("imap","Imap")],"Type of Server", required=True, help="Type of Email gateway Server"),
49 'port': fields.integer("Port" , help="Port Of Email gateway Server. If port is omitted, the standard POP3 port (110) is used for POP EMail Server and the standard IMAP4 port (143) is used for IMAP Sever."),
50 'ssl': fields.boolean('SSL',help ="Use Secure Authentication"),
51 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the email gateway server without removing it."),
54 'server_type':lambda * a:'pop',
55 'active':lambda * a:True,
58 def check_duplicate(self, cr, uid, ids):
59 vals = self.read(cr, uid, ids, ['name', 'login'])[0]
60 cr.execute("select count(id) from mail_gateway_server \
61 where name='%s' and login='%s'" % \
62 (vals['name'], vals['login']))
70 (check_duplicate, 'Warning! Can\'t have duplicate server configuration!', ['name', 'login'])
73 def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False):
75 if server_type == 'pop':
76 port = ssl and 995 or 110
77 elif server_type == 'imap':
78 port = ssl and 993 or 143
79 return {'value':{'port':port}}
84 class mail_gateway(osv.osv):
85 _name = "mail.gateway"
86 _description = "Email Gateway"
89 'name': fields.char('Name',size=64,help="Name of Mail Gateway."),
90 'server_id': fields.many2one('mail.gateway.server',"Gateway Server", required=True),
91 'object_id': fields.many2one('ir.model',"Model", required=True),
92 'reply_to': fields.char('TO', size=64, help="Email address used in reply to/from of outgoing messages"),
93 'email_default': fields.char('Default eMail',size=64,help="Default eMail in case of any trouble."),
94 'mail_history': fields.one2many("mail.gateway.history","gateway_id","History", readonly=True)
97 'reply_to': lambda * a:tools.config.get('email_from',False)
100 def _fetch_mails(self, cr, uid, ids=False, context={}):
102 Function called by the scheduler to fetch mails
104 cr.execute('select * from mail_gateway gateway \
105 inner join mail_gateway_server server \
106 on server.id = gateway.server_id where server.active = True')
107 ids2 = map(lambda x: x[0], cr.fetchall() or [])
108 return self.fetch_mails(cr, uid, ids=ids2, context=context)
110 def parse_mail(self, cr, uid, gateway_id, email_message, context={}):
111 msg_id, res_id, note = (False, False, False)
112 mail_history_obj = self.pool.get('mail.gateway.history')
113 mailgateway = self.browse(cr, uid, gateway_id, context=context)
115 msg_txt = email.message_from_string(email_message)
116 msg_id = msg_txt['Message-ID']
117 res_id = self.msg_parse(cr, uid, gateway_id, msg_txt)
120 note = "Error in Parsing Mail: %s " %(str(e))
121 netsvc.Logger().notifyChannel('Emailgate: Parsing mail:%s' % (mailgateway and (mailgateway.name or
122 '%s (%s)'%(mailgateway.server_id.login, mailgateway.server_id.name))) or ''
123 , netsvc.LOG_ERROR, traceback.format_exc())
125 mail_history_obj.create(cr, uid, {'name': msg_id, 'res_id': res_id, 'gateway_id': mailgateway.id, 'note': note})
128 def fetch_mails(self, cr, uid, ids=[], context={}):
130 mailgate_server = False
132 for mailgateway in self.browse(cr, uid, ids):
134 mailgate_server = mailgateway.server_id
135 if not mailgate_server.active:
137 mailgate_name = mailgateway.name or "%s (%s)" % (mailgate_server.login, mailgate_server.name)
138 res_model = mailgateway.object_id.name
139 log_messages.append("Mail Server : %s" % mailgate_name)
140 log_messages.append("="*40)
142 if mailgate_server.server_type == 'pop':
143 if mailgate_server.ssl:
144 pop_server = POP3_SSL(mailgate_server.name or 'localhost', mailgate_server.port or 995)
146 pop_server = POP3(mailgate_server.name or 'localhost', mailgate_server.port or 110)
147 pop_server.user(mailgate_server.login)
148 pop_server.pass_(mailgate_server.password)
150 (numMsgs, totalSize) = pop_server.stat()
151 for i in range(1, numMsgs + 1):
152 (header, msges, octets) = pop_server.retr(i)
153 res_id, note = self.parse_mail(cr, uid, mailgateway.id, '\n'.join(msges))
156 log = _('Object Successfully Created : %d of %s'% (res_id, res_model))
159 log_messages.append(log)
160 new_messages.append(i)
163 elif mailgate_server.server_type == 'imap':
164 if mailgate_server.ssl:
165 imap_server = IMAP4_SSL(mailgate_server.name or 'localhost', mailgate_server.port or 993)
167 imap_server = IMAP4(mailgate_server.name or 'localhost', mailgate_server.port or 143)
168 imap_server.login(mailgate_server.login, mailgate_server.password)
170 typ, data = imap_server.search(None, '(UNSEEN)')
171 for num in data[0].split():
172 typ, data = imap_server.fetch(num, '(RFC822)')
173 res_id, note = self.parse_mail(cr, uid, mailgateway.id, data[0][1])
176 log = _('Object Successfully Created/Modified: %d of %s'% (res_id, res_model))
179 log_messages.append(log)
180 new_messages.append(num)
186 log_messages.append("Error in Fetching Mail: %s " %(str(e)))
187 netsvc.Logger().notifyChannel('Emailgate: Fetching mail:[%d]%s' %
188 (mailgate_server and mailgate_server.id or 0, mailgate_server and mailgate_server.name or ''),
189 netsvc.LOG_ERROR, traceback.format_exc())
191 log_messages.append("-"*25)
192 log_messages.append("Total Read Mail: %d\n\n" %(len(new_messages)))
195 def emails_get(self, email_from):
196 res = tools.email_re.search(email_from)
197 return res and res.group(1)
199 def partner_get(self, cr, uid, email):
200 mail = self.emails_get(email)
201 adr_ids = self.pool.get('res.partner.address').search(cr, uid, [('email', '=', mail)])
204 adr = self.pool.get('res.partner.address').read(cr, uid, adr_ids, ['partner_id'])
208 'partner_address_id': adr[0]['id'],
209 'partner_id': adr[0].get('partner_id',False) and adr[0]['partner_id'][0] or False
213 def _to_decode(self, s, charsets):
214 for charset in charsets:
217 return s.decode(charset)
221 return s.decode('ascii')
225 def _decode_header(self, s):
226 from email.Header import decode_header
228 return ''.join(map(lambda x:self._to_decode(x[0], x[1]), s))
230 def msg_new(self, cr, uid, msg, model):
231 message = self.msg_body_get(msg)
232 res_model = self.pool.get(model)
233 res_id = res_model.msg_new(cr, uid, msg)
235 attachments = message['attachment']
237 for attach in attachments or []:
240 'datas':binascii.b2a_base64(str(attachments[attach])),
241 'datas_fname': str(attach),
242 'description': 'Mail attachment',
246 self.pool.get('ir.attachment').create(cr, uid, data_attach)
250 def msg_body_get(self, msg):
252 message['body'] = '';
253 message['attachment'] = {};
254 attachment = message['attachment'];
258 for part in msg.walk():
259 if part.get_content_maintype() == 'multipart':
262 if part.get_content_maintype()=='text':
263 buf = part.get_payload(decode=True)
265 txt = self._to_decode(buf, part.get_charsets)
266 txt = re.sub("<(\w)>", replace, txt)
267 txt = re.sub("<\/(\w)>", replace, txt)
268 if txt and part.get_content_subtype() == 'plain':
269 message['body'] += txt
270 elif txt and part.get_content_subtype() == 'html':
271 message['body'] += tools.html2plaintext(txt)
273 filename = part.get_filename();
275 attachment[filename] = part.get_payload(decode=True);
277 elif part.get_content_maintype()=='application' or part.get_content_maintype()=='image' or part.get_content_maintype()=='text':
278 filename = part.get_filename();
280 attachment[filename] = part.get_payload(decode=True);
282 filename = 'attach_file'+str(counter);
284 attachment[filename] = part.get_payload(decode=True);
287 message['attachment'] = attachment
292 def msg_update(self, cr, uid, msg, res_id, res_model, user_email):
293 if user_email and self.emails_get(user_email)==self.emails_get(self._decode_header(msg['From'])):
294 return self.msg_user(cr, uid, msg, res_id, res_model)
296 return self.msg_partner(cr, uid, msg, res_id, res_model)
298 def msg_act_get(self, msg):
299 body = self.msg_body_get(msg)
301 # handle email body commands (ex: Set-State: Draft)
304 for line in body['body'].split('\n'):
305 res = tools.command_re.match(line)
307 actions[res.group(1).lower()] = res.group(2).lower()
309 body_data += line+'\n'
310 return actions, body_data
312 def msg_user(self, cr, uid, msg, res_id, res_model):
313 actions, body_data = self.msg_act_get(msg)
315 if 'user' in actions:
316 uids = self.pool.get('res.users').name_search(cr, uid, actions['user'])
318 data['user_id'] = uids[0][0]
320 res_model = self.pool.get(res_model)
321 return res_model.msg_update(cr, uid, res_id, msg, data=data, default_act='pending')
323 def msg_send(self, msg, reply_to, emails, priority=None, res_id=False):
327 msg_subject = msg['Subject']
329 msg_body = self.msg_body_get(msg)
332 msg_attachment = map(lambda x: (x[0], x[1]), msg_body['attachment'].items())
333 return tools.email_send(reply_to, msg_to, msg_subject , msg_body['body'], email_cc=msg_cc,
334 reply_to=reply_to, attach=msg_attachment, openobject_id=res_id, priority=priority)
337 def msg_partner(self, cr, uid, msg, res_id, res_model):
338 res_model = self.pool.get(res_model)
339 return res_model.msg_update(cr, uid, res_id, msg, data={}, default_act='open')
343 def msg_parse(self, cr, uid, mailgateway_id, msg):
344 mailgateway = self.browse(cr, uid, mailgateway_id)
345 res_model = mailgateway.object_id.model
346 res_str = tools.reference_re.search(msg.get('References', ''))
348 res_str = res_str.group(1)
350 res_str = tools.res_re.search(msg.get('Subject', ''))
352 res_str = res_str.group(1)
354 def msg_test(res_str):
355 emails = ('', '', '', '')
357 return (False, emails)
358 res_str = int(res_str)
359 if hasattr(self.pool.get(res_model), 'emails_get'):
360 emails = self.pool.get(res_model).emails_get(cr, uid, [res_str])[0]
361 return (res_str, emails)
363 (res_id, emails) = msg_test(res_str)
364 user_email, from_email, cc_email, priority = emails
366 self.msg_update(cr, uid, msg, res_id, res_model, user_email)
369 res_id = self.msg_new(cr, uid, msg, res_model)
370 (res_id, emails) = msg_test(res_id)
371 user_email, from_email, cc_email, priority = emails
372 subject = self._decode_header(msg['subject'])
373 if msg.get('Subject', ''):
375 msg['Subject'] = '[%s] %s' %(str(res_id), subject)
377 em = [user_email or '', from_email] + (cc_email or '').split(',')
378 emails = map(self.emails_get, filter(None, em))
379 mm = [self._decode_header(msg['From']), self._decode_header(msg['To'])]+self._decode_header(msg.get('Cc','')).split(',')
380 msg_mails = map(self.emails_get, filter(None, mm))
381 emails = filter(lambda m: m and m not in msg_mails, emails)
383 self.msg_send(msg, mailgateway.reply_to, emails, priority, res_id)
384 if hasattr(self.pool.get(res_model), 'msg_send'):
385 emails = self.pool.get(res_model).msg_send(cr, uid, res_id)
387 if mailgateway.email_default:
388 a = self._decode_header(msg['Subject'])
390 msg['Subject'] = '[OpenERP-Error] ' + a
391 self.msg_send(msg, mailgateway.reply_to, mailgateway.email_default.split(','), res_id)
397 class mail_gateway_history(osv.osv):
398 _name = "mail.gateway.history"
399 _description = "Mail Gateway History"
401 'name': fields.char('Message Id', size=64, help="Message Id in Email Server."),
402 'res_id': fields.integer("Resource ID"),
403 'gateway_id': fields.many2one('mail.gateway',"Mail Gateway", required=True),
404 'model_id':fields.related('gateway_id', 'object_id', type='many2one', relation='ir.model', string='Model'),
405 'note': fields.text('Notes'),
406 'create_date': fields.datetime('Created Date'),
409 mail_gateway_history()