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 _decode_header(self, s):
214 from email.Header import decode_header
216 return ''.join(map(lambda x:x[0].decode(x[1] or 'ascii', 'replace'), s))
218 def msg_new(self, cr, uid, msg, model):
219 message = self.msg_body_get(msg)
220 res_model = self.pool.get(model)
221 res_id = res_model.msg_new(cr, uid, msg)
223 attachments = message['attachment']
225 for attach in attachments or []:
228 'datas':binascii.b2a_base64(str(attachments[attach])),
229 'datas_fname': str(attach),
230 'description': 'Mail attachment',
234 self.pool.get('ir.attachment').create(cr, uid, data_attach)
238 def msg_body_get(self, msg):
240 message['body'] = '';
241 message['attachment'] = {};
242 attachment = message['attachment'];
246 for part in msg.walk():
247 if part.get_content_maintype() == 'multipart':
250 if part.get_content_maintype()=='text':
251 buf = part.get_payload(decode=True)
253 txt = buf.decode(part.get_charsets()[0] or 'ascii', 'replace')
254 txt = re.sub("<(\w)>", replace, txt)
255 txt = re.sub("<\/(\w)>", replace, txt)
256 if txt and part.get_content_subtype() == 'plain':
257 message['body'] += txt
258 elif txt and part.get_content_subtype() == 'html':
259 message['body'] += tools.html2plaintext(txt)
261 filename = part.get_filename();
263 attachment[filename] = part.get_payload(decode=True);
265 elif part.get_content_maintype()=='application' or part.get_content_maintype()=='image' or part.get_content_maintype()=='text':
266 filename = part.get_filename();
268 attachment[filename] = part.get_payload(decode=True);
270 filename = 'attach_file'+str(counter);
272 attachment[filename] = part.get_payload(decode=True);
275 message['attachment'] = attachment
280 def msg_update(self, cr, uid, msg, res_id, res_model, user_email):
281 if user_email and self.emails_get(user_email)==self.emails_get(self._decode_header(msg['From'])):
282 return self.msg_user(cr, uid, msg, res_id, res_model)
284 return self.msg_partner(cr, uid, msg, res_id, res_model)
286 def msg_act_get(self, msg):
287 body = self.msg_body_get(msg)
289 # handle email body commands (ex: Set-State: Draft)
292 for line in body['body'].split('\n'):
293 res = tools.command_re.match(line)
295 actions[res.group(1).lower()] = res.group(2).lower()
297 body_data += line+'\n'
298 return actions, body_data
300 def msg_user(self, cr, uid, msg, res_id, res_model):
301 actions, body_data = self.msg_act_get(msg)
303 if 'user' in actions:
304 uids = self.pool.get('res.users').name_search(cr, uid, actions['user'])
306 data['user_id'] = uids[0][0]
308 res_model = self.pool.get(res_model)
309 return res_model.msg_update(cr, uid, res_id, msg, data=data, default_act='pending')
311 def msg_send(self, msg, reply_to, emails, priority=None, res_id=False):
315 msg_subject = msg['Subject']
317 msg_body = self.msg_body_get(msg)
320 msg_attachment = map(lambda x: (x[0], x[1]), msg_body['attachment'].items())
321 return tools.email_send(reply_to, msg_to, msg_subject , msg_body['body'], email_cc=msg_cc,
322 reply_to=reply_to, attach=msg_attachment, openobject_id=res_id, priority=priority)
325 def msg_partner(self, cr, uid, msg, res_id, res_model):
326 res_model = self.pool.get(res_model)
327 return res_model.msg_update(cr, uid, res_id, msg, data={}, default_act='open')
331 def msg_parse(self, cr, uid, mailgateway_id, msg):
332 mailgateway = self.browse(cr, uid, mailgateway_id)
333 res_model = mailgateway.object_id.model
334 res_str = tools.reference_re.search(msg.get('References', ''))
336 res_str = res_str.group(1)
338 res_str = tools.res_re.search(msg.get('Subject', ''))
340 res_str = res_str.group(1)
342 def msg_test(res_str):
343 emails = ('', '', '', '')
345 return (False, emails)
346 res_str = int(res_str)
347 if hasattr(self.pool.get(res_model), 'emails_get'):
348 emails = self.pool.get(res_model).emails_get(cr, uid, [res_str])[0]
349 return (res_str, emails)
351 (res_id, emails) = msg_test(res_str)
352 user_email, from_email, cc_email, priority = emails
354 self.msg_update(cr, uid, msg, res_id, res_model, user_email)
357 res_id = self.msg_new(cr, uid, msg, res_model)
358 (res_id, emails) = msg_test(res_id)
359 user_email, from_email, cc_email, priority = emails
360 subject = self._decode_header(msg['subject'])
361 if msg.get('Subject', ''):
363 msg['Subject'] = '[%s] %s' %(str(res_id), subject)
365 em = [user_email, from_email] + (cc_email or '').split(',')
366 emails = map(self.emails_get, filter(None, em))
368 mm = [self._decode_header(msg['From']), self._decode_header(msg['To'])]+self._decode_header(msg.get('Cc','')).split(',')
369 msg_mails = map(self.emails_get, filter(None, mm))
371 emails = filter(lambda m: m and m not in msg_mails, emails)
373 self.msg_send(msg, mailgateway.reply_to, emails, priority, res_id)
374 if hasattr(self.pool.get(res_model), 'msg_send'):
375 emails = self.pool.get(res_model).msg_send(cr, uid, res_id)
377 if mailgateway.email_default:
378 a = self._decode_header(msg['Subject'])
380 msg['Subject'] = '[OpenERP-Error] ' + a
381 self.msg_send(msg, mailgateway.reply_to, mailgateway.email_default.split(','), res_id)
387 class mail_gateway_history(osv.osv):
388 _name = "mail.gateway.history"
389 _description = "Mail Gateway History"
391 'name': fields.char('Message Id', size=64, help="Message Id in Email Server."),
392 'res_id': fields.integer("Resource ID"),
393 'gateway_id': fields.many2one('mail.gateway',"Mail Gateway", required=True),
394 'model_id':fields.related('gateway_id', 'object_id', type='many2one', relation='ir.model', string='Model'),
395 'note': fields.text('Notes'),
396 'create_date': fields.datetime('Created Date'),
399 mail_gateway_history()