1# Caolan McNamara caolanm@redhat.com
2# a simple email mailmerge component
3
4# manual installation for hackers, not necessary for users
5# cp mailmerge.py /usr/lib/openoffice.org2.0/program
6# cd /usr/lib/openoffice.org2.0/program
7# ./unopkg add --shared mailmerge.py
8# edit ~/.openoffice.org2/user/registry/data/org/openoffice/Office/Writer.xcu
9# and change EMailSupported to as follows...
10#  <prop oor:name="EMailSupported" oor:type="xs:boolean">
11#   <value>true</value>
12#  </prop>
13
14import unohelper
15import uno
16import re
17
18#to implement com::sun::star::mail::XMailServiceProvider
19#and
20#to implement com.sun.star.mail.XMailMessage
21
22from com.sun.star.mail import XMailServiceProvider
23from com.sun.star.mail import XMailService
24from com.sun.star.mail import XSmtpService
25from com.sun.star.mail import XConnectionListener
26from com.sun.star.mail import XAuthenticator
27from com.sun.star.mail import XMailMessage
28from com.sun.star.mail.MailServiceType import SMTP
29from com.sun.star.mail.MailServiceType import POP3
30from com.sun.star.mail.MailServiceType import IMAP
31from com.sun.star.uno import XCurrentContext
32from com.sun.star.lang import IllegalArgumentException
33from com.sun.star.lang import EventObject
34from com.sun.star.mail import SendMailMessageFailedException
35
36from email.MIMEBase import MIMEBase
37from email.Message import Message
38from email import Encoders
39from email.Header import Header
40from email.MIMEMultipart import MIMEMultipart
41from email.Utils import formatdate
42from email.Utils import parseaddr
43from socket import _GLOBAL_DEFAULT_TIMEOUT
44
45import sys, smtplib, imaplib, poplib
46
47dbg = False
48
49class PyMailSMTPService(unohelper.Base, XSmtpService):
50	def __init__( self, ctx ):
51		self.ctx = ctx
52		self.listeners = []
53		self.supportedtypes = ('Insecure', 'Ssl')
54		self.server = None
55		self.connectioncontext = None
56		self.notify = EventObject(self)
57		if dbg:
58			print >> sys.stderr, "PyMailSMPTService init"
59	def addConnectionListener(self, xListener):
60		if dbg:
61			print >> sys.stderr, "PyMailSMPTService addConnectionListener"
62		self.listeners.append(xListener)
63	def removeConnectionListener(self, xListener):
64		if dbg:
65			print >> sys.stderr, "PyMailSMPTService removeConnectionListener"
66		self.listeners.remove(xListener)
67	def getSupportedConnectionTypes(self):
68		if dbg:
69			print >> sys.stderr, "PyMailSMPTService getSupportedConnectionTypes"
70		return self.supportedtypes
71	def connect(self, xConnectionContext, xAuthenticator):
72		self.connectioncontext = xConnectionContext
73		if dbg:
74			print >> sys.stderr, "PyMailSMPTService connect"
75
76		server = xConnectionContext.getValueByName("ServerName")
77		if dbg:
78			print >> sys.stderr, "ServerName: %s" % server
79
80		port = xConnectionContext.getValueByName("Port")
81		if dbg:
82			print >> sys.stderr, "Port: %d" % port
83
84		tout = xConnectionContext.getValueByName("Timeout")
85		if dbg:
86			print >> sys.stderr, isinstance(tout,int)
87		if not isinstance(tout,int):
88			tout = _GLOBAL_DEFAULT_TIMEOUT
89		if dbg:
90			print >> sys.stderr, "Timeout: %s" % str(tout)
91
92		self.server = smtplib.SMTP(server, port,timeout=tout)
93		if dbg:
94			self.server.set_debuglevel(1)
95
96		connectiontype = xConnectionContext.getValueByName("ConnectionType")
97		if dbg:
98			print >> sys.stderr, "ConnectionType: %s" % connectiontype
99
100		if connectiontype.upper() == 'SSL':
101			self.server.ehlo()
102			self.server.starttls()
103			self.server.ehlo()
104
105		user = xAuthenticator.getUserName().encode('ascii')
106		password = xAuthenticator.getPassword().encode('ascii')
107		if user != '':
108			if dbg:
109				print >> sys.stderr, 'Logging in, username of', user
110			self.server.login(user, password)
111
112		for listener in self.listeners:
113			listener.connected(self.notify)
114	def disconnect(self):
115		if dbg:
116			print >> sys.stderr, "PyMailSMPTService disconnect"
117		if self.server:
118			self.server.quit()
119			self.server = None
120		for listener in self.listeners:
121			listener.disconnected(self.notify)
122	def isConnected(self):
123		if dbg:
124			print >> sys.stderr, "PyMailSMPTService isConnected"
125		return self.server != None
126	def getCurrentConnectionContext(self):
127		if dbg:
128			print >> sys.stderr, "PyMailSMPTService getCurrentConnectionContext"
129		return self.connectioncontext
130	def sendMailMessage(self, xMailMessage):
131		COMMASPACE = ', '
132
133		if dbg:
134			print >> sys.stderr, "PyMailSMPTService sendMailMessage"
135		recipients = xMailMessage.getRecipients()
136		sendermail = xMailMessage.SenderAddress
137		sendername = xMailMessage.SenderName
138		subject = xMailMessage.Subject
139		ccrecipients = xMailMessage.getCcRecipients()
140		bccrecipients = xMailMessage.getBccRecipients()
141		if dbg:
142			print >> sys.stderr, "PyMailSMPTService subject", subject
143			print >> sys.stderr, "PyMailSMPTService from", sendername.encode('utf-8')
144			print >> sys.stderr, "PyMailSMTPService from", sendermail
145			print >> sys.stderr, "PyMailSMPTService send to", recipients
146
147		attachments = xMailMessage.getAttachments()
148
149		textmsg = Message()
150
151		content = xMailMessage.Body
152		flavors = content.getTransferDataFlavors()
153		if dbg:
154			print >> sys.stderr, "PyMailSMPTService flavors len", len(flavors)
155
156		#Use first flavor that's sane for an email body
157		for flavor in flavors:
158			if flavor.MimeType.find('text/html') != -1 or flavor.MimeType.find('text/plain') != -1:
159				if dbg:
160					print >> sys.stderr, "PyMailSMPTService mimetype is", flavor.MimeType
161				textbody = content.getTransferData(flavor)
162				try:
163					textbody = textbody.value
164				except:
165					pass
166				textbody = textbody.encode('utf-8')
167
168				if len(textbody):
169					mimeEncoding = re.sub("charset=.*", "charset=UTF-8", flavor.MimeType)
170					if mimeEncoding.find('charset=UTF-8') == -1:
171						mimeEncoding = mimeEncoding + "; charset=UTF-8"
172					textmsg['Content-Type'] = mimeEncoding
173					textmsg['MIME-Version'] = '1.0'
174					textmsg.set_payload(textbody)
175
176				break
177
178		if (len(attachments)):
179			msg = MIMEMultipart()
180			msg.epilogue = ''
181			msg.attach(textmsg)
182		else:
183			msg = textmsg
184
185		hdr = Header(sendername, 'utf-8')
186		hdr.append('<'+sendermail+'>','us-ascii')
187		msg['Subject'] = subject
188		msg['From'] = hdr
189		msg['To'] = COMMASPACE.join(recipients)
190		if len(ccrecipients):
191			msg['Cc'] = COMMASPACE.join(ccrecipients)
192		if xMailMessage.ReplyToAddress != '':
193			msg['Reply-To'] = xMailMessage.ReplyToAddress
194
195		mailerstring = "OpenOffice.org 2.0 via Caolan's mailmerge component"
196		try:
197			ctx = uno.getComponentContext()
198			aConfigProvider = ctx.ServiceManager.createInstance("com.sun.star.configuration.ConfigurationProvider")
199			prop = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
200			prop.Name = "nodepath"
201			prop.Value = "/org.openoffice.Setup/Product"
202			aSettings = aConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
203				(prop,))
204			mailerstring = aSettings.getByName("ooName") + " " + \
205				aSettings.getByName("ooSetupVersion") + " via Caolan's mailmerge component"
206		except:
207			pass
208
209		msg['X-Mailer'] = mailerstring
210		msg['Date'] = formatdate(localtime=True)
211
212		for attachment in attachments:
213			content = attachment.Data
214			flavors = content.getTransferDataFlavors()
215			flavor = flavors[0]
216			ctype = flavor.MimeType
217			maintype, subtype = ctype.split('/', 1)
218			msgattachment = MIMEBase(maintype, subtype)
219			data = content.getTransferData(flavor)
220			msgattachment.set_payload(data)
221			Encoders.encode_base64(msgattachment)
222			fname = attachment.ReadableName
223			try:
224				fname.encode('ascii')
225			except:
226				fname = ('utf-8','',fname.encode('utf-8'))
227			msgattachment.add_header('Content-Disposition', 'attachment', \
228				filename=fname)
229			msg.attach(msgattachment)
230
231		uniquer = {}
232		for key in recipients:
233			uniquer[key] = True
234		if len(ccrecipients):
235			for key in ccrecipients:
236				uniquer[key] = True
237		if len(bccrecipients):
238			for key in bccrecipients:
239				uniquer[key] = True
240		truerecipients = uniquer.keys()
241
242		if dbg:
243			print >> sys.stderr, "PyMailSMPTService recipients are", truerecipients
244
245		self.server.sendmail(sendermail, truerecipients, msg.as_string())
246
247class PyMailIMAPService(unohelper.Base, XMailService):
248	def __init__( self, ctx ):
249		self.ctx = ctx
250		self.listeners = []
251		self.supportedtypes = ('Insecure', 'Ssl')
252		self.server = None
253		self.connectioncontext = None
254		self.notify = EventObject(self)
255		if dbg:
256			print >> sys.stderr, "PyMailIMAPService init"
257	def addConnectionListener(self, xListener):
258		if dbg:
259			print >> sys.stderr, "PyMailIMAPService addConnectionListener"
260		self.listeners.append(xListener)
261	def removeConnectionListener(self, xListener):
262		if dbg:
263			print >> sys.stderr, "PyMailIMAPService removeConnectionListener"
264		self.listeners.remove(xListener)
265	def getSupportedConnectionTypes(self):
266		if dbg:
267			print >> sys.stderr, "PyMailIMAPService getSupportedConnectionTypes"
268		return self.supportedtypes
269	def connect(self, xConnectionContext, xAuthenticator):
270		if dbg:
271			print >> sys.stderr, "PyMailIMAPService connect"
272
273		self.connectioncontext = xConnectionContext
274		server = xConnectionContext.getValueByName("ServerName")
275		if dbg:
276			print >> sys.stderr, server
277		port = xConnectionContext.getValueByName("Port")
278		if dbg:
279			print >> sys.stderr, port
280		connectiontype = xConnectionContext.getValueByName("ConnectionType")
281		if dbg:
282			print >> sys.stderr, connectiontype
283		print >> sys.stderr, "BEFORE"
284		if connectiontype.upper() == 'SSL':
285			self.server = imaplib.IMAP4_SSL(server, port)
286		else:
287			self.server = imaplib.IMAP4(server, port)
288		print >> sys.stderr, "AFTER"
289
290		user = xAuthenticator.getUserName().encode('ascii')
291		password = xAuthenticator.getPassword().encode('ascii')
292		if user != '':
293			if dbg:
294				print >> sys.stderr, 'Logging in, username of', user
295			self.server.login(user, password)
296
297		for listener in self.listeners:
298			listener.connected(self.notify)
299	def disconnect(self):
300		if dbg:
301			print >> sys.stderr, "PyMailIMAPService disconnect"
302		if self.server:
303			self.server.logout()
304			self.server = None
305		for listener in self.listeners:
306			listener.disconnected(self.notify)
307	def isConnected(self):
308		if dbg:
309			print >> sys.stderr, "PyMailIMAPService isConnected"
310		return self.server != None
311	def getCurrentConnectionContext(self):
312		if dbg:
313			print >> sys.stderr, "PyMailIMAPService getCurrentConnectionContext"
314		return self.connectioncontext
315
316class PyMailPOP3Service(unohelper.Base, XMailService):
317	def __init__( self, ctx ):
318		self.ctx = ctx
319		self.listeners = []
320		self.supportedtypes = ('Insecure', 'Ssl')
321		self.server = None
322		self.connectioncontext = None
323		self.notify = EventObject(self)
324		if dbg:
325			print >> sys.stderr, "PyMailPOP3Service init"
326	def addConnectionListener(self, xListener):
327		if dbg:
328			print >> sys.stderr, "PyMailPOP3Service addConnectionListener"
329		self.listeners.append(xListener)
330	def removeConnectionListener(self, xListener):
331		if dbg:
332			print >> sys.stderr, "PyMailPOP3Service removeConnectionListener"
333		self.listeners.remove(xListener)
334	def getSupportedConnectionTypes(self):
335		if dbg:
336			print >> sys.stderr, "PyMailPOP3Service getSupportedConnectionTypes"
337		return self.supportedtypes
338	def connect(self, xConnectionContext, xAuthenticator):
339		if dbg:
340			print >> sys.stderr, "PyMailPOP3Service connect"
341
342		self.connectioncontext = xConnectionContext
343		server = xConnectionContext.getValueByName("ServerName")
344		if dbg:
345			print >> sys.stderr, server
346		port = xConnectionContext.getValueByName("Port")
347		if dbg:
348			print >> sys.stderr, port
349		connectiontype = xConnectionContext.getValueByName("ConnectionType")
350		if dbg:
351			print >> sys.stderr, connectiontype
352		print >> sys.stderr, "BEFORE"
353		if connectiontype.upper() == 'SSL':
354			self.server = poplib.POP3_SSL(server, port)
355		else:
356			tout = xConnectionContext.getValueByName("Timeout")
357			if dbg:
358				print >> sys.stderr, isinstance(tout,int)
359			if not isinstance(tout,int):
360				tout = _GLOBAL_DEFAULT_TIMEOUT
361			if dbg:
362				print >> sys.stderr, "Timeout: %s" % str(tout)
363			self.server = poplib.POP3(server, port, timeout=tout)
364		print >> sys.stderr, "AFTER"
365
366		user = xAuthenticator.getUserName().encode('ascii')
367		password = xAuthenticator.getPassword().encode('ascii')
368		if dbg:
369			print >> sys.stderr, 'Logging in, username of', user
370		self.server.user(user)
371		self.server.pass_(password)
372
373		for listener in self.listeners:
374			listener.connected(self.notify)
375	def disconnect(self):
376		if dbg:
377			print >> sys.stderr, "PyMailPOP3Service disconnect"
378		if self.server:
379			self.server.quit()
380			self.server = None
381		for listener in self.listeners:
382			listener.disconnected(self.notify)
383	def isConnected(self):
384		if dbg:
385			print >> sys.stderr, "PyMailPOP3Service isConnected"
386		return self.server != None
387	def getCurrentConnectionContext(self):
388		if dbg:
389			print >> sys.stderr, "PyMailPOP3Service getCurrentConnectionContext"
390		return self.connectioncontext
391
392class PyMailServiceProvider(unohelper.Base, XMailServiceProvider):
393	def __init__( self, ctx ):
394		if dbg:
395			print >> sys.stderr, "PyMailServiceProvider init"
396		self.ctx = ctx
397	def create(self, aType):
398		if dbg:
399			print >> sys.stderr, "PyMailServiceProvider create with", aType
400		if aType == SMTP:
401			return PyMailSMTPService(self.ctx);
402		elif aType == POP3:
403			return PyMailPOP3Service(self.ctx);
404		elif aType == IMAP:
405			return PyMailIMAPService(self.ctx);
406		else:
407			print >> sys.stderr, "PyMailServiceProvider, unknown TYPE", aType
408
409class PyMailMessage(unohelper.Base, XMailMessage):
410	def __init__( self, ctx, sTo='', sFrom='', Subject='', Body=None, aMailAttachment=None ):
411		if dbg:
412			print >> sys.stderr, "PyMailMessage init"
413		self.ctx = ctx
414
415		self.recipients = [sTo]
416		self.ccrecipients = []
417		self.bccrecipients = []
418		self.aMailAttachments = []
419		if aMailAttachment != None:
420			self.aMailAttachments.append(aMailAttachment)
421
422		self.SenderName, self.SenderAddress = parseaddr(sFrom)
423		self.ReplyToAddress = sFrom
424		self.Subject = Subject
425		self.Body = Body
426		if dbg:
427			print >> sys.stderr, "post PyMailMessage init"
428	def addRecipient( self, recipient ):
429		if dbg:
430			print >> sys.stderr, "PyMailMessage.addRecipient", recipient
431		self.recipients.append(recipient)
432	def addCcRecipient( self, ccrecipient ):
433		if dbg:
434			print >> sys.stderr, "PyMailMessage.addCcRecipient", ccrecipient
435		self.ccrecipients.append(ccrecipient)
436	def addBccRecipient( self, bccrecipient ):
437		if dbg:
438			print >> sys.stderr, "PyMailMessage.addBccRecipient", bccrecipient
439		self.bccrecipients.append(bccrecipient)
440	def getRecipients( self ):
441		if dbg:
442			print >> sys.stderr, "PyMailMessage.getRecipients", self.recipients
443		return tuple(self.recipients)
444	def getCcRecipients( self ):
445		if dbg:
446			print >> sys.stderr, "PyMailMessage.getCcRecipients", self.ccrecipients
447		return tuple(self.ccrecipients)
448	def getBccRecipients( self ):
449		if dbg:
450			print >> sys.stderr, "PyMailMessage.getBccRecipients", self.bccrecipients
451		return tuple(self.bccrecipients)
452	def addAttachment( self, aMailAttachment ):
453		if dbg:
454			print >> sys.stderr, "PyMailMessage.addAttachment"
455		self.aMailAttachments.append(aMailAttachment)
456	def getAttachments( self ):
457		if dbg:
458			print >> sys.stderr, "PyMailMessage.getAttachments"
459		return tuple(self.aMailAttachments)
460
461
462# pythonloader looks for a static g_ImplementationHelper variable
463g_ImplementationHelper = unohelper.ImplementationHelper()
464g_ImplementationHelper.addImplementation( \
465	PyMailServiceProvider, "org.openoffice.pyuno.MailServiceProvider",
466		("com.sun.star.mail.MailServiceProvider",),)
467g_ImplementationHelper.addImplementation( \
468	PyMailMessage, "org.openoffice.pyuno.MailMessage",
469		("com.sun.star.mail.MailMessage",),)
470