1 # Caolan McNamara caolanm@redhat.com
2 # a simple email mailmerge component
4 # manual installation for hackers, not necessary for users
5 # cp mailmerge.py /usr/lib/libreoffice/program
6 # cd /usr/lib/libreoffice/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">
14 from __future__
import print_function
21 #to implement com::sun::star::mail::XMailServiceProvider
23 #to implement com.sun.star.mail.XMailMessage
25 from com
.sun
.star
.mail
import XMailServiceProvider
26 from com
.sun
.star
.mail
import XMailService
27 from com
.sun
.star
.mail
import XSmtpService
28 from com
.sun
.star
.mail
import XConnectionListener
29 from com
.sun
.star
.mail
import XAuthenticator
30 from com
.sun
.star
.mail
import XMailMessage
31 from com
.sun
.star
.mail
.MailServiceType
import SMTP
32 from com
.sun
.star
.mail
.MailServiceType
import POP3
33 from com
.sun
.star
.mail
.MailServiceType
import IMAP
34 from com
.sun
.star
.uno
import XCurrentContext
35 from com
.sun
.star
.lang
import IllegalArgumentException
36 from com
.sun
.star
.lang
import EventObject
37 from com
.sun
.star
.lang
import XServiceInfo
38 from com
.sun
.star
.mail
import SendMailMessageFailedException
40 from email
.mime
.base
import MIMEBase
41 from email
.message
import Message
42 from email
.charset
import Charset
43 from email
.charset
import QP
44 from email
.encoders
import encode_base64
45 from email
.header
import Header
46 from email
.mime
.multipart
import MIMEMultipart
47 from email
.utils
import formatdate
48 from email
.utils
import parseaddr
49 from socket
import _GLOBAL_DEFAULT_TIMEOUT
51 import sys
, smtplib
, imaplib
, poplib
54 # pythonloader looks for a static g_ImplementationHelper variable
55 g_ImplementationHelper
= unohelper
.ImplementationHelper()
56 g_providerImplName
= "org.openoffice.pyuno.MailServiceProvider"
57 g_messageImplName
= "org.openoffice.pyuno.MailMessage"
59 #no stderr under windows, output to pymailmerge.log
61 if dbg
and os
.name
== 'nt':
62 dbgout
= open('pymailmerge.log', 'w', 0)
66 class PyMailSMTPService(unohelper
.Base
, XSmtpService
):
67 def __init__( self
, ctx
):
70 self
.supportedtypes
= ('Insecure', 'Ssl')
72 self
.connectioncontext
= None
73 self
.notify
= EventObject(self
)
75 print("PyMailSMTPService init", file=dbgout
)
76 print("python version is: " + sys
.version
, file=dbgout
)
77 def addConnectionListener(self
, xListener
):
79 print("PyMailSMTPService addConnectionListener", file=dbgout
)
80 self
.listeners
.append(xListener
)
81 def removeConnectionListener(self
, xListener
):
83 print("PyMailSMTPService removeConnectionListener", file=dbgout
)
84 self
.listeners
.remove(xListener
)
85 def getSupportedConnectionTypes(self
):
87 print("PyMailSMTPService getSupportedConnectionTypes", file=dbgout
)
88 return self
.supportedtypes
89 def connect(self
, xConnectionContext
, xAuthenticator
):
90 self
.connectioncontext
= xConnectionContext
92 print("PyMailSMTPService connect", file=dbgout
)
93 server
= xConnectionContext
.getValueByName("ServerName")
95 print("ServerName: " + server
, file=dbgout
)
96 port
= int(xConnectionContext
.getValueByName("Port"))
98 print("Port: " + str(port
), file=dbgout
)
99 tout
= xConnectionContext
.getValueByName("Timeout")
101 print(isinstance(tout
,int), file=dbgout
)
102 if not isinstance(tout
,int):
103 tout
= _GLOBAL_DEFAULT_TIMEOUT
105 print("Timeout: " + str(tout
), file=dbgout
)
106 self
.server
= smtplib
.SMTP(server
, port
,timeout
=tout
)
108 #stderr not available for us under windows, but
109 #set_debuglevel outputs there, and so throw
110 #an exception under windows on debugging mode
112 if dbg
and os
.name
!= 'nt':
113 self
.server
.set_debuglevel(1)
115 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
117 print("ConnectionType: " + connectiontype
, file=dbgout
)
118 if connectiontype
.upper() == 'SSL':
120 self
.server
.starttls()
123 user
= xAuthenticator
.getUserName()
124 password
= xAuthenticator
.getPassword()
126 if sys
.version
< '3': # fdo#59249 i#105669 Python 2 needs "ascii"
127 user
= user
.encode('ascii')
128 password
= password
.encode('ascii')
130 print("Logging in, username of: " + user
, file=dbgout
)
131 self
.server
.login(user
, password
)
133 for listener
in self
.listeners
:
134 listener
.connected(self
.notify
)
135 def disconnect(self
):
137 print("PyMailSMTPService disconnect", file=dbgout
)
141 for listener
in self
.listeners
:
142 listener
.disconnected(self
.notify
)
143 def isConnected(self
):
145 print("PyMailSMTPService isConnected", file=dbgout
)
146 return self
.server
!= None
147 def getCurrentConnectionContext(self
):
149 print("PyMailSMTPService getCurrentConnectionContext", file=dbgout
)
150 return self
.connectioncontext
151 def sendMailMessage(self
, xMailMessage
):
155 print("PyMailSMTPService sendMailMessage", file=dbgout
)
156 recipients
= xMailMessage
.getRecipients()
157 sendermail
= xMailMessage
.SenderAddress
158 sendername
= xMailMessage
.SenderName
159 subject
= xMailMessage
.Subject
160 ccrecipients
= xMailMessage
.getCcRecipients()
161 bccrecipients
= xMailMessage
.getBccRecipients()
163 print("PyMailSMTPService subject: " + subject
, file=dbgout
)
164 print("PyMailSMTPService from: " + sendername
, file=dbgout
)
165 print("PyMailSMTPService from: " + sendermail
, file=dbgout
)
166 print("PyMailSMTPService send to: %s" % (recipients
,), file=dbgout
)
168 attachments
= xMailMessage
.getAttachments()
172 content
= xMailMessage
.Body
173 flavors
= content
.getTransferDataFlavors()
175 print("PyMailSMTPService flavors len: %d" % (len(flavors
),), file=dbgout
)
177 #Use first flavor that's sane for an email body
178 for flavor
in flavors
:
179 if flavor
.MimeType
.find('text/html') != -1 or flavor
.MimeType
.find('text/plain') != -1:
181 print("PyMailSMTPService mimetype is: " + flavor
.MimeType
, file=dbgout
)
182 textbody
= content
.getTransferData(flavor
)
185 mimeEncoding
= re
.sub("charset=.*", "charset=UTF-8", flavor
.MimeType
)
186 if mimeEncoding
.find('charset=UTF-8') == -1:
187 mimeEncoding
= mimeEncoding
+ "; charset=UTF-8"
188 textmsg
['Content-Type'] = mimeEncoding
189 textmsg
['MIME-Version'] = '1.0'
192 #it's a string, get it as utf-8 bytes
193 textbody
= textbody
.encode('utf-8')
195 #it's a bytesequence, get raw bytes
196 textbody
= textbody
.value
197 if sys
.version
>= '3':
198 if sys
.version_info
.minor
< 3 or (sys
.version_info
.minor
== 3 and sys
.version_info
.micro
<= 1):
199 #http://stackoverflow.com/questions/9403265/how-do-i-use-python-3-2-email-module-to-send-unicode-messages-encoded-in-utf-8-w
200 #see http://bugs.python.org/16564, etc. basically it now *seems* to be all ok
201 #in python 3.3.2 onwards, but a little busted in 3.3.0
203 textbody
= textbody
.decode('iso8859-1')
205 textbody
= textbody
.decode('utf-8')
208 textmsg
.set_payload(textbody
, c
)
210 textmsg
.set_payload(textbody
)
214 if (len(attachments
)):
215 msg
= MIMEMultipart()
221 hdr
= Header(sendername
, 'utf-8')
222 hdr
.append('<'+sendermail
+'>','us-ascii')
223 msg
['Subject'] = subject
225 msg
['To'] = COMMASPACE
.join(recipients
)
226 if len(ccrecipients
):
227 msg
['Cc'] = COMMASPACE
.join(ccrecipients
)
228 if xMailMessage
.ReplyToAddress
!= '':
229 msg
['Reply-To'] = xMailMessage
.ReplyToAddress
231 mailerstring
= "LibreOffice via Caolan's mailmerge component"
233 ctx
= uno
.getComponentContext()
234 aConfigProvider
= ctx
.ServiceManager
.createInstance("com.sun.star.configuration.ConfigurationProvider")
235 prop
= uno
.createUnoStruct('com.sun.star.beans.PropertyValue')
236 prop
.Name
= "nodepath"
237 prop
.Value
= "/org.openoffice.Setup/Product"
238 aSettings
= aConfigProvider
.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
240 mailerstring
= aSettings
.getByName("ooName") + " " + \
241 aSettings
.getByName("ooSetupVersion") + " via Caolan's mailmerge component"
245 msg
['X-Mailer'] = mailerstring
246 msg
['Date'] = formatdate(localtime
=True)
248 for attachment
in attachments
:
249 content
= attachment
.Data
250 flavors
= content
.getTransferDataFlavors()
252 ctype
= flavor
.MimeType
253 maintype
, subtype
= ctype
.split('/', 1)
254 msgattachment
= MIMEBase(maintype
, subtype
)
255 data
= content
.getTransferData(flavor
)
256 msgattachment
.set_payload(data
.value
)
257 encode_base64(msgattachment
)
258 fname
= attachment
.ReadableName
260 msgattachment
.add_header('Content-Disposition', 'attachment', \
263 msgattachment
.add_header('Content-Disposition', 'attachment', \
264 filename
=('utf-8','',fname
))
266 print(("PyMailSMTPService attachmentheader: ", str(msgattachment
)), file=dbgout
)
268 msg
.attach(msgattachment
)
271 for key
in recipients
:
273 if len(ccrecipients
):
274 for key
in ccrecipients
:
276 if len(bccrecipients
):
277 for key
in bccrecipients
:
279 truerecipients
= uniquer
.keys()
282 print(("PyMailSMTPService recipients are: ", truerecipients
), file=dbgout
)
284 self
.server
.sendmail(sendermail
, truerecipients
, msg
.as_string())
286 class PyMailIMAPService(unohelper
.Base
, XMailService
):
287 def __init__( self
, ctx
):
290 self
.supportedtypes
= ('Insecure', 'Ssl')
292 self
.connectioncontext
= None
293 self
.notify
= EventObject(self
)
295 print("PyMailIMAPService init", file=dbgout
)
296 def addConnectionListener(self
, xListener
):
298 print("PyMailIMAPService addConnectionListener", file=dbgout
)
299 self
.listeners
.append(xListener
)
300 def removeConnectionListener(self
, xListener
):
302 print("PyMailIMAPService removeConnectionListener", file=dbgout
)
303 self
.listeners
.remove(xListener
)
304 def getSupportedConnectionTypes(self
):
306 print("PyMailIMAPService getSupportedConnectionTypes", file=dbgout
)
307 return self
.supportedtypes
308 def connect(self
, xConnectionContext
, xAuthenticator
):
310 print("PyMailIMAPService connect", file=dbgout
)
312 self
.connectioncontext
= xConnectionContext
313 server
= xConnectionContext
.getValueByName("ServerName")
315 print(server
, file=dbgout
)
316 port
= int(xConnectionContext
.getValueByName("Port"))
318 print(port
, file=dbgout
)
319 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
321 print(connectiontype
, file=dbgout
)
322 print("BEFORE", file=dbgout
)
323 if connectiontype
.upper() == 'SSL':
324 self
.server
= imaplib
.IMAP4_SSL(server
, port
)
326 self
.server
= imaplib
.IMAP4(server
, port
)
327 print("AFTER", file=dbgout
)
329 user
= xAuthenticator
.getUserName()
330 password
= xAuthenticator
.getPassword()
332 if sys
.version
< '3': # fdo#59249 i#105669 Python 2 needs "ascii"
333 user
= user
.encode('ascii')
334 password
= password
.encode('ascii')
336 print("Logging in, username of: " + user
, file=dbgout
)
337 self
.server
.login(user
, password
)
339 for listener
in self
.listeners
:
340 listener
.connected(self
.notify
)
341 def disconnect(self
):
343 print("PyMailIMAPService disconnect", file=dbgout
)
347 for listener
in self
.listeners
:
348 listener
.disconnected(self
.notify
)
349 def isConnected(self
):
351 print("PyMailIMAPService isConnected", file=dbgout
)
352 return self
.server
!= None
353 def getCurrentConnectionContext(self
):
355 print("PyMailIMAPService getCurrentConnectionContext", file=dbgout
)
356 return self
.connectioncontext
358 class PyMailPOP3Service(unohelper
.Base
, XMailService
):
359 def __init__( self
, ctx
):
362 self
.supportedtypes
= ('Insecure', 'Ssl')
364 self
.connectioncontext
= None
365 self
.notify
= EventObject(self
)
367 print("PyMailPOP3Service init", file=dbgout
)
368 def addConnectionListener(self
, xListener
):
370 print("PyMailPOP3Service addConnectionListener", file=dbgout
)
371 self
.listeners
.append(xListener
)
372 def removeConnectionListener(self
, xListener
):
374 print("PyMailPOP3Service removeConnectionListener", file=dbgout
)
375 self
.listeners
.remove(xListener
)
376 def getSupportedConnectionTypes(self
):
378 print("PyMailPOP3Service getSupportedConnectionTypes", file=dbgout
)
379 return self
.supportedtypes
380 def connect(self
, xConnectionContext
, xAuthenticator
):
382 print("PyMailPOP3Service connect", file=dbgout
)
384 self
.connectioncontext
= xConnectionContext
385 server
= xConnectionContext
.getValueByName("ServerName")
387 print(server
, file=dbgout
)
388 port
= int(xConnectionContext
.getValueByName("Port"))
390 print(port
, file=dbgout
)
391 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
393 print(connectiontype
, file=dbgout
)
394 print("BEFORE", file=dbgout
)
395 if connectiontype
.upper() == 'SSL':
396 self
.server
= poplib
.POP3_SSL(server
, port
)
398 tout
= xConnectionContext
.getValueByName("Timeout")
400 print(isinstance(tout
,int), file=dbgout
)
401 if not isinstance(tout
,int):
402 tout
= _GLOBAL_DEFAULT_TIMEOUT
404 print("Timeout: " + str(tout
), file=dbgout
)
405 self
.server
= poplib
.POP3(server
, port
, timeout
=tout
)
406 print("AFTER", file=dbgout
)
408 user
= xAuthenticator
.getUserName()
409 password
= xAuthenticator
.getPassword()
410 if sys
.version
< '3': # fdo#59249 i#105669 Python 2 needs "ascii"
411 user
= user
.encode('ascii')
412 password
= password
.encode('ascii')
414 print("Logging in, username of: " + user
, file=dbgout
)
415 self
.server
.user(user
)
416 self
.server
.pass_(password
)
418 for listener
in self
.listeners
:
419 listener
.connected(self
.notify
)
420 def disconnect(self
):
422 print("PyMailPOP3Service disconnect", file=dbgout
)
426 for listener
in self
.listeners
:
427 listener
.disconnected(self
.notify
)
428 def isConnected(self
):
430 print("PyMailPOP3Service isConnected", file=dbgout
)
431 return self
.server
!= None
432 def getCurrentConnectionContext(self
):
434 print("PyMailPOP3Service getCurrentConnectionContext", file=dbgout
)
435 return self
.connectioncontext
437 class PyMailServiceProvider(unohelper
.Base
, XMailServiceProvider
, XServiceInfo
):
438 def __init__( self
, ctx
):
440 print("PyMailServiceProvider init", file=dbgout
)
442 def create(self
, aType
):
444 print("PyMailServiceProvider create with", aType
, file=dbgout
)
446 return PyMailSMTPService(self
.ctx
);
448 return PyMailPOP3Service(self
.ctx
);
450 return PyMailIMAPService(self
.ctx
);
452 print("PyMailServiceProvider, unknown TYPE " + aType
, file=dbgout
)
454 def getImplementationName(self
):
455 return g_providerImplName
457 def supportsService(self
, ServiceName
):
458 return g_ImplementationHelper
.supportsService(g_providerImplName
, ServiceName
)
460 def getSupportedServiceNames(self
):
461 return g_ImplementationHelper
.getSupportedServiceNames(g_providerImplName
)
463 class PyMailMessage(unohelper
.Base
, XMailMessage
):
464 def __init__( self
, ctx
, sTo
='', sFrom
='', Subject
='', Body
=None, aMailAttachment
=None ):
466 print("PyMailMessage init", file=dbgout
)
469 self
.recipients
= [sTo
]
470 self
.ccrecipients
= []
471 self
.bccrecipients
= []
472 self
.aMailAttachments
= []
473 if aMailAttachment
!= None:
474 self
.aMailAttachments
.append(aMailAttachment
)
476 self
.SenderName
, self
.SenderAddress
= parseaddr(sFrom
)
477 self
.ReplyToAddress
= sFrom
478 self
.Subject
= Subject
481 print("post PyMailMessage init", file=dbgout
)
482 def addRecipient( self
, recipient
):
484 print("PyMailMessage.addRecipient: " + recipient
, file=dbgout
)
485 self
.recipients
.append(recipient
)
486 def addCcRecipient( self
, ccrecipient
):
488 print("PyMailMessage.addCcRecipient: " + ccrecipient
, file=dbgout
)
489 self
.ccrecipients
.append(ccrecipient
)
490 def addBccRecipient( self
, bccrecipient
):
492 print("PyMailMessage.addBccRecipient: " + bccrecipient
, file=dbgout
)
493 self
.bccrecipients
.append(bccrecipient
)
494 def getRecipients( self
):
496 print("PyMailMessage.getRecipients: " + str(self
.recipients
), file=dbgout
)
497 return tuple(self
.recipients
)
498 def getCcRecipients( self
):
500 print("PyMailMessage.getCcRecipients: " + str(self
.ccrecipients
), file=dbgout
)
501 return tuple(self
.ccrecipients
)
502 def getBccRecipients( self
):
504 print("PyMailMessage.getBccRecipients: " + str(self
.bccrecipients
), file=dbgout
)
505 return tuple(self
.bccrecipients
)
506 def addAttachment( self
, aMailAttachment
):
508 print("PyMailMessage.addAttachment", file=dbgout
)
509 self
.aMailAttachments
.append(aMailAttachment
)
510 def getAttachments( self
):
512 print("PyMailMessage.getAttachments", file=dbgout
)
513 return tuple(self
.aMailAttachments
)
515 def getImplementationName(self
):
516 return g_messageImplName
518 def supportsService(self
, ServiceName
):
519 return g_ImplementationHelper
.supportsService(g_messageImplName
, ServiceName
)
521 def getSupportedServiceNames(self
):
522 return g_ImplementationHelper
.getSupportedServiceNames(g_messageImplName
)
524 g_ImplementationHelper
.addImplementation( \
525 PyMailServiceProvider
, g_providerImplName
,
526 ("com.sun.star.mail.MailServiceProvider",),)
527 g_ImplementationHelper
.addImplementation( \
528 PyMailMessage
, g_messageImplName
,
529 ("com.sun.star.mail.MailMessage",),)