File: message.py

package info (click to toggle)
python-exchangelib 5.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 12,084 kB
  • sloc: python: 25,351; sh: 6; makefile: 5
file content (201 lines) | stat: -rw-r--r-- 9,050 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import logging

from ..fields import Base64Field, BooleanField, CharField, EWSElementField, MailboxField, MailboxListField, TextField
from ..properties import ReferenceItemId, ReminderMessageData
from ..util import require_account, require_id
from ..version import EXCHANGE_2013, EXCHANGE_2013_SP1
from .base import AUTO_RESOLVE, SEND_AND_SAVE_COPY, SEND_ONLY, SEND_TO_NONE, BaseReplyItem
from .item import Item

log = logging.getLogger(__name__)


class Message(Item):
    """MSDN:
    https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/message-ex15websvcsotherref
    """

    ELEMENT_NAME = "Message"

    sender = MailboxField(field_uri="message:Sender", is_read_only=True, is_read_only_after_send=True)
    to_recipients = MailboxListField(
        field_uri="message:ToRecipients", is_read_only_after_send=True, is_searchable=False
    )
    cc_recipients = MailboxListField(
        field_uri="message:CcRecipients", is_read_only_after_send=True, is_searchable=False
    )
    bcc_recipients = MailboxListField(
        field_uri="message:BccRecipients", is_read_only_after_send=True, is_searchable=False
    )
    is_read_receipt_requested = BooleanField(
        field_uri="message:IsReadReceiptRequested", is_required=True, default=False, is_read_only_after_send=True
    )
    is_delivery_receipt_requested = BooleanField(
        field_uri="message:IsDeliveryReceiptRequested", is_required=True, default=False, is_read_only_after_send=True
    )
    conversation_index = Base64Field(field_uri="message:ConversationIndex", is_read_only=True)
    conversation_topic = CharField(field_uri="message:ConversationTopic", is_read_only=True)
    # Rename 'From' to 'author'. We can't use field name 'from' since it's a Python keyword.
    author = MailboxField(field_uri="message:From", is_read_only_after_send=True)
    message_id = TextField(field_uri="message:InternetMessageId", is_read_only_after_send=True)
    is_read = BooleanField(field_uri="message:IsRead", is_required=True, default=False)
    is_response_requested = BooleanField(field_uri="message:IsResponseRequested", default=False, is_required=True)
    references = TextField(field_uri="message:References")
    reply_to = MailboxListField(field_uri="message:ReplyTo", is_read_only_after_send=True, is_searchable=False)
    received_by = MailboxField(field_uri="message:ReceivedBy", is_read_only=True)
    received_representing = MailboxField(field_uri="message:ReceivedRepresenting", is_read_only=True)
    reminder_message_data = EWSElementField(
        field_uri="message:ReminderMessageData",
        value_cls=ReminderMessageData,
        supported_from=EXCHANGE_2013_SP1,
        is_read_only=True,
    )

    @require_account
    def send(
        self,
        save_copy=True,
        copy_to_folder=None,
        conflict_resolution=AUTO_RESOLVE,
        send_meeting_invitations=SEND_TO_NONE,
    ):
        from ..services import SendItem

        # Only sends a message. The message can either be an existing draft stored in EWS or a new message that does
        # not yet exist in EWS.
        if copy_to_folder and not save_copy:
            raise AttributeError("'save_copy' must be True when 'copy_to_folder' is set")
        if save_copy and not copy_to_folder:
            copy_to_folder = self.account.sent  # 'Sent' is default EWS behaviour
        if self.id:
            SendItem(account=self.account).get(items=[self], saved_item_folder=copy_to_folder)
            # The item will be deleted from the original folder
            self._id = None
            self.folder = copy_to_folder
            return None

        # New message
        if copy_to_folder:
            # This would better be done via send_and_save() but let's just support it here
            self.folder = copy_to_folder
            return self.send_and_save(
                conflict_resolution=conflict_resolution, send_meeting_invitations=send_meeting_invitations
            )

        if self.account.version.build < EXCHANGE_2013 and self.attachments:
            # At least some versions prior to Exchange 2013 can't send attachments immediately. You need to first save,
            # then attach, then send. This is done in send_and_save(). send() will delete the item again.
            self.send_and_save(
                conflict_resolution=conflict_resolution, send_meeting_invitations=send_meeting_invitations
            )
            return None

        self._create(message_disposition=SEND_ONLY, send_meeting_invitations=send_meeting_invitations)
        return None

    def send_and_save(
        self, update_fields=None, conflict_resolution=AUTO_RESOLVE, send_meeting_invitations=SEND_TO_NONE
    ):
        # Sends Message and saves a copy in the parent folder. Does not return an ItemId.
        if self.id:
            return self._update(
                update_fieldnames=update_fields,
                message_disposition=SEND_AND_SAVE_COPY,
                conflict_resolution=conflict_resolution,
                send_meeting_invitations=send_meeting_invitations,
            )
        if self.account.version.build < EXCHANGE_2013 and self.attachments:
            # At least some versions prior to Exchange 2013 can't send-and-save attachments immediately. You need
            # to first save, then attach, then send. This is done in save().
            self.save(
                update_fields=update_fields,
                conflict_resolution=conflict_resolution,
                send_meeting_invitations=send_meeting_invitations,
            )
            return self.send(
                save_copy=False,
                conflict_resolution=conflict_resolution,
                send_meeting_invitations=send_meeting_invitations,
            )
        return self._create(message_disposition=SEND_AND_SAVE_COPY, send_meeting_invitations=send_meeting_invitations)

    @require_id
    def create_reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None, author=None):
        if not to_recipients:
            if not self.author:
                raise ValueError("'to_recipients' must be set when message has no 'author'")
            to_recipients = [self.author]
        return ReplyToItem(
            account=self.account,
            reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
            subject=subject,
            new_body=body,
            to_recipients=to_recipients,
            cc_recipients=cc_recipients,
            bcc_recipients=bcc_recipients,
            author=author,
        )

    def reply(self, subject, body, to_recipients=None, cc_recipients=None, bcc_recipients=None, author=None):
        return self.create_reply(subject, body, to_recipients, cc_recipients, bcc_recipients, author).send()

    @require_id
    def create_reply_all(self, subject, body, author=None):
        me = MailboxField().clean(self.account.primary_smtp_address.lower())
        to_recipients = set(self.to_recipients or [])
        to_recipients.discard(me)
        cc_recipients = set(self.cc_recipients or [])
        cc_recipients.discard(me)
        bcc_recipients = set(self.bcc_recipients or [])
        bcc_recipients.discard(me)
        if self.author:
            to_recipients.add(self.author)
        return ReplyAllToItem(
            account=self.account,
            reference_item_id=ReferenceItemId(id=self.id, changekey=self.changekey),
            subject=subject,
            new_body=body,
            to_recipients=list(to_recipients),
            cc_recipients=list(cc_recipients),
            bcc_recipients=list(bcc_recipients),
            author=author,
        )

    def reply_all(self, subject, body, author=None):
        return self.create_reply_all(subject, body, author).send()

    def mark_as_junk(self, is_junk=True, move_item=True):
        """Mark or un-marks items as junk email.

        :param is_junk: If True, the sender will be added from the blocked sender list. Otherwise, the sender will be
        removed.
        :param move_item: If true, the item will be moved to the junk folder.
        :return:
        """
        from ..services import MarkAsJunk

        res = MarkAsJunk(account=self.account).get(
            items=[self], is_junk=is_junk, move_item=move_item, expect_result=None
        )
        if res is None:
            return
        self.folder = self.account.junk if is_junk else self.account.inbox
        self.id, self.changekey = res


class ReplyToItem(BaseReplyItem):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/replytoitem"""

    ELEMENT_NAME = "ReplyToItem"


class ReplyAllToItem(BaseReplyItem):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/replyalltoitem"""

    ELEMENT_NAME = "ReplyAllToItem"


class ForwardItem(BaseReplyItem):
    """MSDN: https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/forwarditem"""

    ELEMENT_NAME = "ForwardItem"