monboob 6.46 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ft=python et softtabstop=4 cinoptions=4 shiftwidth=4 ts=4 ai

"""
Copyright(C) 2010  Romain Bignon

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

"""

23 24 25 26
from email.mime.text import MIMEText
from smtplib import SMTP
from email.Header import Header
from email.Utils import parseaddr, formataddr
27
from email import message_from_file
28
import time
29
import re
30
import sys
31

32
from weboob.capabilities.messages import ICapMessages, ICapMessagesReply
33
from weboob.tools.application import ConsoleApplication
34
from weboob.tools.misc import html2text
35

36
class Monboob(ConsoleApplication):
Romain Bignon's avatar
Romain Bignon committed
37
    APPNAME = 'monboob'
Romain Bignon's avatar
Romain Bignon committed
38 39 40
    CONFIG = {'interval':  15,
              'domain':    'weboob.example.org',
              'recipient': 'weboob@example.org',
41 42
              'smtp':      'localhost',
              'html':      False}
Romain Bignon's avatar
Romain Bignon committed
43

44
    def main(self, argv):
45
        self.load_config()
46
        self.weboob.load_backends(ICapMessages, storage=self.create_storage())
47

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
        return self.process_command(*argv[1:])

    @ConsoleApplication.command("pipe with a mail to post message")
    def command_post(self):
        msg = message_from_file(sys.stdin)
        reply_to = msg.get('In-Reply-To')
        if not reply_to:
            print >>sys.stderr, 'This is not a reply (no Reply-To field)'
            return 1

        m = re.match('<(.*)@(.*)>', reply_to)
        if m:
            reply_to = m.group(1)
        title = msg.get('Subject')

        content = u''
        for part in msg.walk():
            if part.get_content_type() == 'text/plain':
                s = part.get_payload(decode=True)
                charsets = part.get_charsets() + msg.get_charsets()
                for charset in charsets:
                    try:
                        content += unicode(s, charset)
                    except:
                        continue
                    else:
                        break

        # remove signature
        content = content.split(u'\n-- \n')[0]

        bname, id = reply_to.split('.', 1)
80 81 82 83 84 85 86 87 88
        try:
            backend = self.weboob.backends[bname]
        except KeyError:
            print >>sys.stderr, 'Backend %s not found' % bname
            return 1

        if not backend.has_caps(ICapMessagesReply):
            print >>sys.stderr, 'The backend %s does not implement ICapMessagesReply' % bname
            return 1
89 90 91 92 93 94

        thread_id, msg_id = id.rsplit('.', 1)
        backend.post_reply(thread_id, msg_id, title, content)

    @ConsoleApplication.command("run daemon")
    def command_run(self):
Romain Bignon's avatar
Romain Bignon committed
95
        self.weboob.repeat(self.config.get('interval'), self.process)
96 97 98
        self.weboob.loop()

    def process(self):
99 100
        for name, backend in self.weboob.iter_backends():
            for message in backend.iter_new_messages():
101
                self.send_email(backend, message)
102

103
    def send_email(self, backend, mail):
104 105
        domain = self.config.get('domain')
        recipient = self.config.get('recipient')
106 107

        reply_id = ''
108
        if mail.get_reply_id():
109
            reply_id = u'<%s.%s@%s>' % (backend.name, mail.get_full_reply_id(), domain)
Romain Bignon's avatar
Romain Bignon committed
110
        subject = mail.get_title()
111
        sender = u'%s <%s@%s>' % (mail.get_from(), backend.name, domain)
112

113 114
        # assume that get_date() returns an UTC datetime
        date = time.strftime('%a, %d %b %Y %H:%M:%S +0000', mail.get_date().timetuple())
115 116
        msg_id = u'<%s.%s@%s>' % (backend.name, mail.get_full_id(), domain)

117
        if self.config.get('html') and mail.is_html:
118 119 120
            body = mail.get_content()
            content_type = 'html'
        else:
121 122 123 124
            if mail.is_html:
                body = html2text(mail.get_content())
            else:
                body = mail.get_content()
125
            content_type = 'plain'
126

127
        if mail.get_signature():
128
            if self.config.get('html') and mail.is_html:
129 130 131
                body += u'<p>-- <br />%s</p>' % mail.get_signature()
            else:
                body += u'\n\n-- \n'
132 133 134 135
                if mail.is_html:
                    body += html2text(mail.get_signature())
                else:
                    body += mail.get_signature()
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

        # Header class is smart enough to try US-ASCII, then the charset we
        # provide, then fall back to UTF-8.
        header_charset = 'ISO-8859-1'

        # We must choose the body charset manually
        for body_charset in 'US-ASCII', 'ISO-8859-1', 'UTF-8':
            try:
                body.encode(body_charset)
            except UnicodeError:
                pass
            else:
                break

        # Split real name (which is optional) and email address parts
        sender_name, sender_addr = parseaddr(sender)
        recipient_name, recipient_addr = parseaddr(recipient)

        # We must always pass Unicode strings to Header, otherwise it will
        # use RFC 2047 encoding even on plain ASCII strings.
        sender_name = str(Header(unicode(sender_name), header_charset))
        recipient_name = str(Header(unicode(recipient_name), header_charset))

        # Make sure email addresses do not contain non-ASCII characters
        sender_addr = sender_addr.encode('ascii')
        recipient_addr = recipient_addr.encode('ascii')

        # Create the message ('plain' stands for Content-Type: text/plain)
164
        msg = MIMEText(body.encode(body_charset), content_type, body_charset)
165 166 167 168 169 170 171 172 173
        msg['From'] = formataddr((sender_name, sender_addr))
        msg['To'] = formataddr((recipient_name, recipient_addr))
        msg['Subject'] = Header(unicode(subject), header_charset)
        msg['Message-Id'] = msg_id
        msg['Date'] = date
        if reply_id:
            msg['In-Reply-To'] = reply_id

        # Send the message via SMTP to localhost:25
174
        smtp = SMTP(self.config.get('smtp'))
Romain Bignon's avatar
Romain Bignon committed
175
        print 'Send mail from <%s> to <%s>' % (sender, recipient)
176 177 178 179 180
        smtp.sendmail(sender, recipient, msg.as_string())
        smtp.quit()

        return msg['Message-Id']

181
if __name__ == '__main__':
182
    Monboob.run()