mailmerge.py 17 KB

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