monboob 6.84 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
from email.mime.text import MIMEText
from smtplib import SMTP
Romain Bignon's avatar
Romain Bignon committed
25
from email.Header import Header, decode_header
26
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'
38 39
    VERSION = '1.0'
    COPYRIGHT = 'Copyright(C) 2010 Romain Bignon'
Romain Bignon's avatar
Romain Bignon committed
40 41 42
    CONFIG = {'interval':  15,
              'domain':    'weboob.example.org',
              'recipient': 'weboob@example.org',
43
              'smtp':      'localhost',
44
              'html':      0}
Romain Bignon's avatar
Romain Bignon committed
45

46
    def main(self, argv):
47
        self.load_config()
48
        self.load_backends(ICapMessages, storage=self.create_storage())
49

50 51 52 53 54 55 56 57 58 59 60 61 62 63
        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')
Romain Bignon's avatar
Romain Bignon committed
64 65 66 67 68 69 70 71
        if title:
            new_title = u''
            for part in decode_header(title):
                if part[1]:
                    new_title += unicode(part[0], part[1])
                else:
                    new_title += unicode(part[0])
            title = new_title
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

        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)
90 91 92 93 94 95 96 97 98
        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
99 100

        thread_id, msg_id = id.rsplit('.', 1)
101 102
        for m in backend.post_reply(thread_id, msg_id, title, content):
            self.send_email(backend, m)
103 104 105

    @ConsoleApplication.command("run daemon")
    def command_run(self):
106
        self.weboob.repeat(int(self.config.get('interval')), self.process)
107 108 109
        self.weboob.loop()

    def process(self):
110 111
        for backend, message in self.weboob.do('iter_new_messages'):
            self.send_email(backend, message)
112

113
    def send_email(self, backend, mail):
114 115
        domain = self.config.get('domain')
        recipient = self.config.get('recipient')
116 117

        reply_id = ''
118
        if mail.get_reply_id():
119
            reply_id = u'<%s.%s@%s>' % (backend.name, mail.get_full_reply_id(), domain)
Romain Bignon's avatar
Romain Bignon committed
120
        subject = mail.get_title()
121
        sender = u'"%s" <%s@%s>' % (mail.get_from().replace('"', '""'), backend.name, domain)
122

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

127
        if int(self.config.get('html')) and mail.is_html:
128 129 130
            body = mail.get_content()
            content_type = 'html'
        else:
131 132 133 134
            if mail.is_html:
                body = html2text(mail.get_content())
            else:
                body = mail.get_content()
135
            content_type = 'plain'
136

137
        if mail.get_signature():
138
            if int(self.config.get('html')) and mail.is_html:
139 140 141
                body += u'<p>-- <br />%s</p>' % mail.get_signature()
            else:
                body += u'\n\n-- \n'
142 143 144 145
                if mail.is_html:
                    body += html2text(mail.get_signature())
                else:
                    body += mail.get_signature()
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173

        # 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)
174
        msg = MIMEText(body.encode(body_charset), content_type, body_charset)
175 176 177 178 179 180 181 182 183
        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
184
        smtp = SMTP(self.config.get('smtp'))
Romain Bignon's avatar
Romain Bignon committed
185
        print 'Send mail from <%s> to <%s>' % (sender, recipient)
186 187 188 189 190
        smtp.sendmail(sender, recipient, msg.as_string())
        smtp.quit()

        return msg['Message-Id']

191
if __name__ == '__main__':
192
    Monboob.run()