imip-agent

Annotated imiptools/mail.py

886:8a3994e54ea4
2015-10-20 Paul Boddie Permit the selection of a same-day ending while still allowing time adjustments.
paul@83 1
#!/usr/bin/env python
paul@83 2
paul@146 3
"""
paul@146 4
Mail preparation support.
paul@146 5
paul@146 6
Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
paul@146 7
paul@146 8
This program is free software; you can redistribute it and/or modify it under
paul@146 9
the terms of the GNU General Public License as published by the Free Software
paul@146 10
Foundation; either version 3 of the License, or (at your option) any later
paul@146 11
version.
paul@146 12
paul@146 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@146 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@146 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@146 16
details.
paul@146 17
paul@146 18
You should have received a copy of the GNU General Public License along with
paul@146 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@146 20
"""
paul@146 21
paul@128 22
from imiptools.config import MESSAGE_SENDER, OUTGOING_PREFIX
paul@83 23
from email.mime.message import MIMEMessage
paul@83 24
from email.mime.multipart import MIMEMultipart
paul@83 25
from email.mime.text import MIMEText
paul@83 26
from smtplib import LMTP, SMTP
paul@83 27
paul@83 28
MESSAGE_SUBJECT = "Calendar system message"
paul@83 29
paul@83 30
MESSAGE_TEXT = """\
paul@139 31
This is a message from the calendar system.
paul@83 32
"""
paul@83 33
paul@87 34
PREAMBLE_TEXT = """\
paul@87 35
This message contains several different parts, one of which will contain
paul@87 36
calendar information that will only be understood by a suitable program.
paul@87 37
"""
paul@87 38
paul@83 39
class Messenger:
paul@83 40
paul@83 41
    "Sending of outgoing messages."
paul@83 42
paul@666 43
    def __init__(self, lmtp_socket=None, local_smtp=False, sender=None, subject=None, body_text=None, preamble_text=None):
paul@607 44
paul@607 45
        """
paul@666 46
        Deliver to a local mail system using LMTP if 'lmtp_socket' is provided
paul@666 47
        or if 'local_smtp' is given as a true value.
paul@607 48
        """
paul@607 49
paul@607 50
        self.lmtp_socket = lmtp_socket
paul@666 51
        self.local_smtp = local_smtp
paul@83 52
        self.sender = sender or MESSAGE_SENDER
paul@83 53
        self.subject = subject or MESSAGE_SUBJECT
paul@83 54
        self.body_text = body_text or MESSAGE_TEXT
paul@87 55
        self.preamble_text = preamble_text or PREAMBLE_TEXT
paul@83 56
paul@607 57
    def local_delivery(self):
paul@607 58
paul@607 59
        "Return whether local delivery is performed using this messenger."
paul@607 60
paul@666 61
        return self.lmtp_socket is not None or self.local_smtp
paul@607 62
paul@666 63
    def sendmail(self, recipients, data, sender=None, outgoing_bcc=None):
paul@83 64
paul@83 65
        """
paul@83 66
        Send a mail to the given 'recipients' consisting of the given 'data',
paul@128 67
        using the given 'sender' identity if indicated, indicating an
paul@607 68
        'outgoing_bcc' identity if indicated.
paul@445 69
paul@445 70
        The 'outgoing_bcc' argument is required when sending on behalf of a user
paul@445 71
        from the calendar@domain address, since this will not be detected as a
paul@445 72
        valid participant and handled using the outgoing transport.
paul@83 73
        """
paul@83 74
paul@607 75
        if self.lmtp_socket:
paul@607 76
            smtp = LMTP(self.lmtp_socket)
paul@83 77
        else:
paul@83 78
            smtp = SMTP("localhost")
paul@83 79
paul@128 80
        if outgoing_bcc:
paul@128 81
            recipients = list(recipients) + ["%s+%s" % (OUTGOING_PREFIX, outgoing_bcc)]
paul@666 82
        elif self.local_smtp:
paul@666 83
            recipients = [self.make_local(recipient) for recipient in recipients]
paul@128 84
paul@86 85
        smtp.sendmail(sender or self.sender, recipients, data)
paul@83 86
        smtp.quit()
paul@83 87
paul@666 88
    def make_local(self, recipient):
paul@666 89
paul@666 90
        """
paul@666 91
        Make the 'recipient' an address for local delivery. For this to function
paul@666 92
        correctly, a virtual alias or equivalent must be defined for addresses
paul@666 93
        of the following form:
paul@666 94
paul@666 95
        local+NAME@DOMAIN
paul@666 96
paul@666 97
        Such aliases should direct delivery to the local recipient.
paul@666 98
        """
paul@666 99
paul@666 100
        parts = recipient.split("+", 1)
paul@666 101
        return "%s+%s" % ("local", parts[-1])
paul@666 102
paul@178 103
    def make_outgoing_message(self, parts, recipients, sender=None, outgoing_bcc=None):
paul@83 104
paul@86 105
        """
paul@86 106
        Make a message from the given 'parts' for the given 'recipients', using
paul@128 107
        the given 'sender' identity if indicated, indicating an 'outgoing_bcc'
paul@128 108
        identity if indicated.
paul@86 109
        """
paul@83 110
paul@178 111
        message = self._make_summary_for_parts(parts)
paul@83 112
paul@86 113
        message["From"] = sender or self.sender
paul@83 114
        for recipient in recipients:
paul@83 115
            message["To"] = recipient
paul@128 116
        if outgoing_bcc:
paul@128 117
            message["Bcc"] = "%s+%s" % (OUTGOING_PREFIX, outgoing_bcc)
paul@83 118
        message["Subject"] = self.subject
paul@83 119
paul@83 120
        return message
paul@83 121
paul@178 122
    def make_summary_message(self, msg, parts):
paul@178 123
paul@178 124
        """
paul@178 125
        Return a simple summary using details from 'msg' and the given 'parts'.
paul@178 126
        """
paul@178 127
paul@178 128
        message = self._make_summary_for_parts(parts)
paul@178 129
        self._copy_headers(message, msg)
paul@178 130
        return message
paul@178 131
paul@83 132
    def wrap_message(self, msg, parts):
paul@83 133
paul@83 134
        "Wrap 'msg' and provide the given 'parts' as the primary content."
paul@83 135
paul@178 136
        message = self._make_container_for_parts(parts)
paul@178 137
        payload = message.get_payload()
paul@178 138
        payload.append(MIMEMessage(msg))
paul@178 139
        self._copy_headers(message, msg)
paul@178 140
        return message
paul@178 141
paul@178 142
    def _make_summary_for_parts(self, parts):
paul@178 143
paul@178 144
        "Return a simple summary for the given 'parts'."
paul@178 145
paul@178 146
        if len(parts) == 1:
paul@178 147
            return parts[0]
paul@178 148
        else:
paul@178 149
            return self._make_container_for_parts(parts)
paul@178 150
paul@178 151
    def _make_container_for_parts(self, parts):
paul@178 152
paul@178 153
        "Return a container for the given 'parts'."
paul@178 154
paul@83 155
        message = MIMEMultipart("mixed", _subparts=parts)
paul@87 156
        message.preamble = self.preamble_text
paul@178 157
        return message
paul@83 158
paul@178 159
    def _copy_headers(self, message, msg):
paul@83 160
        message["From"] = msg["From"]
paul@83 161
        message["To"] = msg["To"]
paul@83 162
        message["Subject"] = msg["Subject"]
paul@83 163
paul@83 164
# vim: tabstop=4 expandtab shiftwidth=4