monboob 6.77 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 101 102 103 104

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

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

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

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

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

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

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

136
        if mail.get_signature():
137
            if int(self.config.get('html')) and mail.is_html:
138 139 140
                body += u'<p>-- <br />%s</p>' % mail.get_signature()
            else:
                body += u'\n\n-- \n'
141 142 143 144
                if mail.is_html:
                    body += html2text(mail.get_signature())
                else:
                    body += mail.get_signature()
145 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

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

        return msg['Message-Id']

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