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
.mail
import SendMailMessageFailedException
39 from email
.mime
.base
import MIMEBase
40 from email
.message
import Message
41 from email
.charset
import Charset
42 from email
.charset
import QP
43 from email
.encoders
import encode_base64
44 from email
.header
import Header
45 from email
.mime
.multipart
import MIMEMultipart
46 from email
.utils
import formatdate
47 from email
.utils
import parseaddr
48 from socket
import _GLOBAL_DEFAULT_TIMEOUT
50 import sys
, smtplib
, imaplib
, poplib
53 #no stderr under windows, output to pymailmerge.log
55 if dbg
and os
.name
== 'nt':
56 dbgout
= open('pymailmerge.log', 'w', 0)
60 class PyMailSMTPService(unohelper
.Base
, XSmtpService
):
61 def __init__( self
, ctx
):
64 self
.supportedtypes
= ('Insecure', 'Ssl')
66 self
.connectioncontext
= None
67 self
.notify
= EventObject(self
)
69 print("PyMailSMTPService init", file=dbgout
)
70 def addConnectionListener(self
, xListener
):
72 print("PyMailSMTPService addConnectionListener", file=dbgout
)
73 self
.listeners
.append(xListener
)
74 def removeConnectionListener(self
, xListener
):
76 print("PyMailSMTPService removeConnectionListener", file=dbgout
)
77 self
.listeners
.remove(xListener
)
78 def getSupportedConnectionTypes(self
):
80 print("PyMailSMTPService getSupportedConnectionTypes", file=dbgout
)
81 return self
.supportedtypes
82 def connect(self
, xConnectionContext
, xAuthenticator
):
83 self
.connectioncontext
= xConnectionContext
85 print("PyMailSMTPService connect", file=dbgout
)
86 server
= xConnectionContext
.getValueByName("ServerName")
88 print("ServerName: " + server
, file=dbgout
)
89 port
= int(xConnectionContext
.getValueByName("Port"))
91 print("Port: " + str(port
), file=dbgout
)
92 tout
= xConnectionContext
.getValueByName("Timeout")
94 print(isinstance(tout
,int), file=dbgout
)
95 if not isinstance(tout
,int):
96 tout
= _GLOBAL_DEFAULT_TIMEOUT
98 print("Timeout: " + str(tout
), file=dbgout
)
99 self
.server
= smtplib
.SMTP(server
, port
,timeout
=tout
)
101 #stderr not available for us under windows, but
102 #set_debuglevel outputs there, and so throw
103 #an exception under windows on debugging mode
105 if dbg
and os
.name
!= 'nt':
106 self
.server
.set_debuglevel(1)
108 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
110 print("ConnectionType: " + connectiontype
, file=dbgout
)
111 if connectiontype
.upper() == 'SSL':
113 self
.server
.starttls()
116 user
= xAuthenticator
.getUserName()
117 password
= xAuthenticator
.getPassword()
119 if sys
.version
< '3': # fdo#59249 i#105669 Python 2 needs "ascii"
120 user
= user
.encode('ascii')
121 password
= password
.encode('ascii')
123 print("Logging in, username of: " + user
, file=dbgout
)
124 self
.server
.login(user
, password
)
126 for listener
in self
.listeners
:
127 listener
.connected(self
.notify
)
128 def disconnect(self
):
130 print("PyMailSMTPService disconnect", file=dbgout
)
134 for listener
in self
.listeners
:
135 listener
.disconnected(self
.notify
)
136 def isConnected(self
):
138 print("PyMailSMTPService isConnected", file=dbgout
)
139 return self
.server
!= None
140 def getCurrentConnectionContext(self
):
142 print("PyMailSMTPService getCurrentConnectionContext", file=dbgout
)
143 return self
.connectioncontext
144 def sendMailMessage(self
, xMailMessage
):
148 print("PyMailSMTPService sendMailMessage", file=dbgout
)
149 recipients
= xMailMessage
.getRecipients()
150 sendermail
= xMailMessage
.SenderAddress
151 sendername
= xMailMessage
.SenderName
152 subject
= xMailMessage
.Subject
153 ccrecipients
= xMailMessage
.getCcRecipients()
154 bccrecipients
= xMailMessage
.getBccRecipients()
156 print("PyMailSMTPService subject: " + subject
, file=dbgout
)
157 print("PyMailSMTPService from: " + sendername
, file=dbgout
)
158 print("PyMailSMTPService from: " + sendermail
, file=dbgout
)
159 print("PyMailSMTPService send to: %s" % (recipients
,), file=dbgout
)
161 attachments
= xMailMessage
.getAttachments()
165 content
= xMailMessage
.Body
166 flavors
= content
.getTransferDataFlavors()
168 print("PyMailSMTPService flavors len: %d" % (len(flavors
),), file=dbgout
)
170 #Use first flavor that's sane for an email body
171 for flavor
in flavors
:
172 if flavor
.MimeType
.find('text/html') != -1 or flavor
.MimeType
.find('text/plain') != -1:
174 print("PyMailSMTPService mimetype is: " + flavor
.MimeType
, file=dbgout
)
175 textbody
= content
.getTransferData(flavor
)
178 mimeEncoding
= re
.sub("charset=.*", "charset=UTF-8", flavor
.MimeType
)
179 if mimeEncoding
.find('charset=UTF-8') == -1:
180 mimeEncoding
= mimeEncoding
+ "; charset=UTF-8"
181 textmsg
['Content-Type'] = mimeEncoding
182 textmsg
['MIME-Version'] = '1.0'
185 #it's a string, get it as utf-8 bytes
186 textbody
= textbody
.encode('utf-8')
188 #it's a bytesequence, get raw bytes
189 textbody
= textbody
.value
190 if sys
.version
>= '3':
191 if sys
.version_info
.minor
< 3 or (sys
.version_info
.minor
== 3 and sys
.version_info
.micro
<= 1):
192 #http://stackoverflow.com/questions/9403265/how-do-i-use-python-3-2-email-module-to-send-unicode-messages-encoded-in-utf-8-w
193 #see http://bugs.python.org/16564, etc. basically it now *seems* to be all ok
194 #in python 3.3.2 onwards, but a little busted in 3.3.0
196 textbody
= textbody
.decode('iso8859-1')
198 textbody
= textbody
.decode('utf-8')
201 textmsg
.set_payload(textbody
, c
)
203 textmsg
.set_payload(textbody
)
207 if (len(attachments
)):
208 msg
= MIMEMultipart()
214 hdr
= Header(sendername
, 'utf-8')
215 hdr
.append('<'+sendermail
+'>','us-ascii')
216 msg
['Subject'] = subject
218 msg
['To'] = COMMASPACE
.join(recipients
)
219 if len(ccrecipients
):
220 msg
['Cc'] = COMMASPACE
.join(ccrecipients
)
221 if xMailMessage
.ReplyToAddress
!= '':
222 msg
['Reply-To'] = xMailMessage
.ReplyToAddress
224 mailerstring
= "LibreOffice via Caolan's mailmerge component"
226 ctx
= uno
.getComponentContext()
227 aConfigProvider
= ctx
.ServiceManager
.createInstance("com.sun.star.configuration.ConfigurationProvider")
228 prop
= uno
.createUnoStruct('com.sun.star.beans.PropertyValue')
229 prop
.Name
= "nodepath"
230 prop
.Value
= "/org.openoffice.Setup/Product"
231 aSettings
= aConfigProvider
.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
233 mailerstring
= aSettings
.getByName("ooName") + " " + \
234 aSettings
.getByName("ooSetupVersion") + " via Caolan's mailmerge component"
238 msg
['X-Mailer'] = mailerstring
239 msg
['Date'] = formatdate(localtime
=True)
241 for attachment
in attachments
:
242 content
= attachment
.Data
243 flavors
= content
.getTransferDataFlavors()
245 ctype
= flavor
.MimeType
246 maintype
, subtype
= ctype
.split('/', 1)
247 msgattachment
= MIMEBase(maintype
, subtype
)
248 data
= content
.getTransferData(flavor
)
249 msgattachment
.set_payload(data
.value
)
250 encode_base64(msgattachment
)
251 fname
= attachment
.ReadableName
253 msgattachment
.add_header('Content-Disposition', 'attachment', \
256 msgattachment
.add_header('Content-Disposition', 'attachment', \
257 filename
=('utf-8','',fname
))
259 print(("PyMailSMTPService attachmentheader: ", str(msgattachment
)), file=dbgout
)
261 msg
.attach(msgattachment
)
264 for key
in recipients
:
266 if len(ccrecipients
):
267 for key
in ccrecipients
:
269 if len(bccrecipients
):
270 for key
in bccrecipients
:
272 truerecipients
= uniquer
.keys()
275 print(("PyMailSMTPService recipients are: ", truerecipients
), file=dbgout
)
277 self
.server
.sendmail(sendermail
, truerecipients
, msg
.as_string())
279 class PyMailIMAPService(unohelper
.Base
, XMailService
):
280 def __init__( self
, ctx
):
283 self
.supportedtypes
= ('Insecure', 'Ssl')
285 self
.connectioncontext
= None
286 self
.notify
= EventObject(self
)
288 print("PyMailIMAPService init", file=dbgout
)
289 def addConnectionListener(self
, xListener
):
291 print("PyMailIMAPService addConnectionListener", file=dbgout
)
292 self
.listeners
.append(xListener
)
293 def removeConnectionListener(self
, xListener
):
295 print("PyMailIMAPService removeConnectionListener", file=dbgout
)
296 self
.listeners
.remove(xListener
)
297 def getSupportedConnectionTypes(self
):
299 print("PyMailIMAPService getSupportedConnectionTypes", file=dbgout
)
300 return self
.supportedtypes
301 def connect(self
, xConnectionContext
, xAuthenticator
):
303 print("PyMailIMAPService connect", file=dbgout
)
305 self
.connectioncontext
= xConnectionContext
306 server
= xConnectionContext
.getValueByName("ServerName")
308 print(server
, file=dbgout
)
309 port
= int(xConnectionContext
.getValueByName("Port"))
311 print(port
, file=dbgout
)
312 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
314 print(connectiontype
, file=dbgout
)
315 print("BEFORE", file=dbgout
)
316 if connectiontype
.upper() == 'SSL':
317 self
.server
= imaplib
.IMAP4_SSL(server
, port
)
319 self
.server
= imaplib
.IMAP4(server
, port
)
320 print("AFTER", file=dbgout
)
322 user
= xAuthenticator
.getUserName()
323 password
= xAuthenticator
.getPassword()
325 if sys
.version
< '3': # fdo#59249 i#105669 Python 2 needs "ascii"
326 user
= user
.encode('ascii')
327 password
= password
.encode('ascii')
329 print("Logging in, username of: " + user
, file=dbgout
)
330 self
.server
.login(user
, password
)
332 for listener
in self
.listeners
:
333 listener
.connected(self
.notify
)
334 def disconnect(self
):
336 print("PyMailIMAPService disconnect", file=dbgout
)
340 for listener
in self
.listeners
:
341 listener
.disconnected(self
.notify
)
342 def isConnected(self
):
344 print("PyMailIMAPService isConnected", file=dbgout
)
345 return self
.server
!= None
346 def getCurrentConnectionContext(self
):
348 print("PyMailIMAPService getCurrentConnectionContext", file=dbgout
)
349 return self
.connectioncontext
351 class PyMailPOP3Service(unohelper
.Base
, XMailService
):
352 def __init__( self
, ctx
):
355 self
.supportedtypes
= ('Insecure', 'Ssl')
357 self
.connectioncontext
= None
358 self
.notify
= EventObject(self
)
360 print("PyMailPOP3Service init", file=dbgout
)
361 def addConnectionListener(self
, xListener
):
363 print("PyMailPOP3Service addConnectionListener", file=dbgout
)
364 self
.listeners
.append(xListener
)
365 def removeConnectionListener(self
, xListener
):
367 print("PyMailPOP3Service removeConnectionListener", file=dbgout
)
368 self
.listeners
.remove(xListener
)
369 def getSupportedConnectionTypes(self
):
371 print("PyMailPOP3Service getSupportedConnectionTypes", file=dbgout
)
372 return self
.supportedtypes
373 def connect(self
, xConnectionContext
, xAuthenticator
):
375 print("PyMailPOP3Service connect", file=dbgout
)
377 self
.connectioncontext
= xConnectionContext
378 server
= xConnectionContext
.getValueByName("ServerName")
380 print(server
, file=dbgout
)
381 port
= int(xConnectionContext
.getValueByName("Port"))
383 print(port
, file=dbgout
)
384 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
386 print(connectiontype
, file=dbgout
)
387 print("BEFORE", file=dbgout
)
388 if connectiontype
.upper() == 'SSL':
389 self
.server
= poplib
.POP3_SSL(server
, port
)
391 tout
= xConnectionContext
.getValueByName("Timeout")
393 print(isinstance(tout
,int), file=dbgout
)
394 if not isinstance(tout
,int):
395 tout
= _GLOBAL_DEFAULT_TIMEOUT
397 print("Timeout: " + str(tout
), file=dbgout
)
398 self
.server
= poplib
.POP3(server
, port
, timeout
=tout
)
399 print("AFTER", file=dbgout
)
401 user
= xAuthenticator
.getUserName()
402 password
= xAuthenticator
.getPassword()
403 if sys
.version
< '3': # fdo#59249 i#105669 Python 2 needs "ascii"
404 user
= user
.encode('ascii')
405 password
= password
.encode('ascii')
407 print("Logging in, username of: " + user
, file=dbgout
)
408 self
.server
.user(user
)
409 self
.server
.pass_(password
)
411 for listener
in self
.listeners
:
412 listener
.connected(self
.notify
)
413 def disconnect(self
):
415 print("PyMailPOP3Service disconnect", file=dbgout
)
419 for listener
in self
.listeners
:
420 listener
.disconnected(self
.notify
)
421 def isConnected(self
):
423 print("PyMailPOP3Service isConnected", file=dbgout
)
424 return self
.server
!= None
425 def getCurrentConnectionContext(self
):
427 print("PyMailPOP3Service getCurrentConnectionContext", file=dbgout
)
428 return self
.connectioncontext
430 class PyMailServiceProvider(unohelper
.Base
, XMailServiceProvider
):
431 def __init__( self
, ctx
):
433 print("PyMailServiceProvider init", file=dbgout
)
435 def create(self
, aType
):
437 print("PyMailServiceProvider create with", aType
, file=dbgout
)
439 return PyMailSMTPService(self
.ctx
);
441 return PyMailPOP3Service(self
.ctx
);
443 return PyMailIMAPService(self
.ctx
);
445 print("PyMailServiceProvider, unknown TYPE " + aType
, file=dbgout
)
447 class PyMailMessage(unohelper
.Base
, XMailMessage
):
448 def __init__( self
, ctx
, sTo
='', sFrom
='', Subject
='', Body
=None, aMailAttachment
=None ):
450 print("PyMailMessage init", file=dbgout
)
453 self
.recipients
= [sTo
]
454 self
.ccrecipients
= []
455 self
.bccrecipients
= []
456 self
.aMailAttachments
= []
457 if aMailAttachment
!= None:
458 self
.aMailAttachments
.append(aMailAttachment
)
460 self
.SenderName
, self
.SenderAddress
= parseaddr(sFrom
)
461 self
.ReplyToAddress
= sFrom
462 self
.Subject
= Subject
465 print("post PyMailMessage init", file=dbgout
)
466 def addRecipient( self
, recipient
):
468 print("PyMailMessage.addRecipient: " + recipient
, file=dbgout
)
469 self
.recipients
.append(recipient
)
470 def addCcRecipient( self
, ccrecipient
):
472 print("PyMailMessage.addCcRecipient: " + ccrecipient
, file=dbgout
)
473 self
.ccrecipients
.append(ccrecipient
)
474 def addBccRecipient( self
, bccrecipient
):
476 print("PyMailMessage.addBccRecipient: " + bccrecipient
, file=dbgout
)
477 self
.bccrecipients
.append(bccrecipient
)
478 def getRecipients( self
):
480 print("PyMailMessage.getRecipients: " + str(self
.recipients
), file=dbgout
)
481 return tuple(self
.recipients
)
482 def getCcRecipients( self
):
484 print("PyMailMessage.getCcRecipients: " + str(self
.ccrecipients
), file=dbgout
)
485 return tuple(self
.ccrecipients
)
486 def getBccRecipients( self
):
488 print("PyMailMessage.getBccRecipients: " + str(self
.bccrecipients
), file=dbgout
)
489 return tuple(self
.bccrecipients
)
490 def addAttachment( self
, aMailAttachment
):
492 print("PyMailMessage.addAttachment", file=dbgout
)
493 self
.aMailAttachments
.append(aMailAttachment
)
494 def getAttachments( self
):
496 print("PyMailMessage.getAttachments", file=dbgout
)
497 return tuple(self
.aMailAttachments
)
499 # pythonloader looks for a static g_ImplementationHelper variable
500 g_ImplementationHelper
= unohelper
.ImplementationHelper()
501 g_ImplementationHelper
.addImplementation( \
502 PyMailServiceProvider
, "org.openoffice.pyuno.MailServiceProvider",
503 ("com.sun.star.mail.MailServiceProvider",),)
504 g_ImplementationHelper
.addImplementation( \
505 PyMailMessage
, "org.openoffice.pyuno.MailMessage",
506 ("com.sun.star.mail.MailMessage",),)