mailmerge.py 17 KB

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