extract-msg 0.48.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- extract_msg/__init__.py +71 -0
- extract_msg/__main__.py +115 -0
- extract_msg/_rtf/__init__.py +21 -0
- extract_msg/_rtf/create_doc.py +29 -0
- extract_msg/_rtf/inject_rtf.py +174 -0
- extract_msg/_rtf/token.py +35 -0
- extract_msg/_rtf/tokenize_rtf.py +253 -0
- extract_msg/attachments/__init__.py +144 -0
- extract_msg/attachments/attachment.py +188 -0
- extract_msg/attachments/attachment_base.py +730 -0
- extract_msg/attachments/broken_att.py +42 -0
- extract_msg/attachments/custom_att.py +173 -0
- extract_msg/attachments/custom_att_handler/__init__.py +80 -0
- extract_msg/attachments/custom_att_handler/custom_handler.py +134 -0
- extract_msg/attachments/custom_att_handler/lnk_obj_att.py +109 -0
- extract_msg/attachments/custom_att_handler/outlook_image_dib.py +141 -0
- extract_msg/attachments/emb_msg_att.py +134 -0
- extract_msg/attachments/signed_att.py +234 -0
- extract_msg/attachments/unsupported_att.py +40 -0
- extract_msg/attachments/web_att.py +74 -0
- extract_msg/constants/__init__.py +294 -0
- extract_msg/constants/ps.py +46 -0
- extract_msg/constants/re.py +42 -0
- extract_msg/constants/st.py +110 -0
- extract_msg/data/logging-config/logging-nt.json +64 -0
- extract_msg/data/logging-config/logging-posix.json +64 -0
- extract_msg/encoding/__init__.py +277 -0
- extract_msg/encoding/_dt/__init__.py +4 -0
- extract_msg/encoding/_dt/_mac_ce.py +8 -0
- extract_msg/encoding/_dt/_mac_cyrillic.py +8 -0
- extract_msg/encoding/_dt/_mac_greek.py +8 -0
- extract_msg/encoding/_dt/_mac_iceland.py +8 -0
- extract_msg/encoding/_dt/_mac_turkish.py +8 -0
- extract_msg/encoding/_dt/_win874_dec.py +8 -0
- extract_msg/encoding/_dt/_win950_dec.py +6 -0
- extract_msg/encoding/utils.py +329 -0
- extract_msg/enums.py +2067 -0
- extract_msg/exceptions.py +170 -0
- extract_msg/msg_classes/__init__.py +49 -0
- extract_msg/msg_classes/appointment.py +195 -0
- extract_msg/msg_classes/calendar.py +92 -0
- extract_msg/msg_classes/calendar_base.py +496 -0
- extract_msg/msg_classes/contact.py +1335 -0
- extract_msg/msg_classes/journal.py +131 -0
- extract_msg/msg_classes/meeting_cancellation.py +108 -0
- extract_msg/msg_classes/meeting_exception.py +55 -0
- extract_msg/msg_classes/meeting_forward.py +115 -0
- extract_msg/msg_classes/meeting_related.py +65 -0
- extract_msg/msg_classes/meeting_request.py +189 -0
- extract_msg/msg_classes/meeting_response.py +74 -0
- extract_msg/msg_classes/message.py +12 -0
- extract_msg/msg_classes/message_base.py +1382 -0
- extract_msg/msg_classes/message_signed.py +17 -0
- extract_msg/msg_classes/message_signed_base.py +168 -0
- extract_msg/msg_classes/msg.py +1172 -0
- extract_msg/msg_classes/post.py +53 -0
- extract_msg/msg_classes/sticky_note.py +67 -0
- extract_msg/msg_classes/task.py +363 -0
- extract_msg/msg_classes/task_request.py +86 -0
- extract_msg/null_date.py +34 -0
- extract_msg/ole_writer.py +1086 -0
- extract_msg/open_msg.py +204 -0
- extract_msg/properties/__init__.py +23 -0
- extract_msg/properties/named.py +481 -0
- extract_msg/properties/prop.py +506 -0
- extract_msg/properties/properties_store.py +476 -0
- extract_msg/py.typed +0 -0
- extract_msg/recipient.py +362 -0
- extract_msg/structures/__init__.py +34 -0
- extract_msg/structures/_helpers.py +345 -0
- extract_msg/structures/business_card.py +422 -0
- extract_msg/structures/cfoas.py +108 -0
- extract_msg/structures/contact_link_entry.py +51 -0
- extract_msg/structures/dev_mode_a.py +621 -0
- extract_msg/structures/dv_target_device.py +168 -0
- extract_msg/structures/entry_id.py +697 -0
- extract_msg/structures/misc_id.py +222 -0
- extract_msg/structures/mon_stream.py +55 -0
- extract_msg/structures/odt.py +74 -0
- extract_msg/structures/ole_pres.py +302 -0
- extract_msg/structures/ole_stream_struct.py +117 -0
- extract_msg/structures/recurrence_pattern.py +199 -0
- extract_msg/structures/report_tag.py +127 -0
- extract_msg/structures/system_time.py +158 -0
- extract_msg/structures/time_zone_definition.py +111 -0
- extract_msg/structures/time_zone_struct.py +102 -0
- extract_msg/structures/toc_entry.py +137 -0
- extract_msg/structures/tz_rule.py +148 -0
- extract_msg/utils.py +1270 -0
- extract_msg-0.48.2.dist-info/LICENSE.txt +674 -0
- extract_msg-0.48.2.dist-info/METADATA +321 -0
- extract_msg-0.48.2.dist-info/RECORD +95 -0
- extract_msg-0.48.2.dist-info/WHEEL +5 -0
- extract_msg-0.48.2.dist-info/entry_points.txt +2 -0
- extract_msg-0.48.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'Post',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from .. import constants
|
|
12
|
+
from .message_base import MessageBase
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Post(MessageBase):
|
|
16
|
+
"""
|
|
17
|
+
Class for parsing Post messages.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def getJson(self) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Returns the JSON representation of the Post.
|
|
23
|
+
"""
|
|
24
|
+
return json.dumps({
|
|
25
|
+
'from': self.sender,
|
|
26
|
+
'subject': self.subject,
|
|
27
|
+
'date': self.date.__format__(self.datetimeFormat) if self.date else None,
|
|
28
|
+
'conversation': self.conversation,
|
|
29
|
+
'body': self.body,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
@functools.cached_property
|
|
33
|
+
def conversation(self) -> Optional[str]:
|
|
34
|
+
"""
|
|
35
|
+
The name of the conversation being posted to.
|
|
36
|
+
"""
|
|
37
|
+
return self.getStringStream('__substg1.0_0070')
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
|
|
41
|
+
return {
|
|
42
|
+
'-main details-': {
|
|
43
|
+
'From': self.sender,
|
|
44
|
+
'Posted At': self.date.__format__(self.datetimeFormat) if self.date else None,
|
|
45
|
+
'Conversation': self.conversation,
|
|
46
|
+
},
|
|
47
|
+
'-subject-': {
|
|
48
|
+
'Subject': self.subject,
|
|
49
|
+
},
|
|
50
|
+
'-importance-': {
|
|
51
|
+
'Importance': self.importanceString,
|
|
52
|
+
},
|
|
53
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from .. import constants
|
|
7
|
+
from ..enums import NoteColor
|
|
8
|
+
from .message_base import MessageBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Note: Sticky note is basically just text and a background color, so we don't
|
|
12
|
+
# really do much when saving it.
|
|
13
|
+
class StickyNote(MessageBase):
|
|
14
|
+
"""
|
|
15
|
+
A sticky note.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def getJson(self) -> str:
|
|
19
|
+
return json.dumps({
|
|
20
|
+
'subject': self.subject,
|
|
21
|
+
'date': self.date.__format__(self.datetimeFormat) if self.date else None,
|
|
22
|
+
'body': self.body,
|
|
23
|
+
'height': self.noteHeight,
|
|
24
|
+
'width': self.noteWidth,
|
|
25
|
+
'color': None if self.noteColor is None else self.noteColor.name.lower(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
@functools.cached_property
|
|
33
|
+
def noteColor(self) -> Optional[NoteColor]:
|
|
34
|
+
"""
|
|
35
|
+
The color of the sticky note.
|
|
36
|
+
"""
|
|
37
|
+
return self.getNamedAs('8B00', constants.ps.PSETID_NOTE, NoteColor)
|
|
38
|
+
|
|
39
|
+
@functools.cached_property
|
|
40
|
+
def noteHeight(self) -> Optional[int]:
|
|
41
|
+
"""
|
|
42
|
+
The height of the note window, in pixels.
|
|
43
|
+
"""
|
|
44
|
+
return self.getNamedProp('8B03', constants.ps.PSETID_NOTE)
|
|
45
|
+
|
|
46
|
+
@functools.cached_property
|
|
47
|
+
def noteWidth(self) -> Optional[int]:
|
|
48
|
+
"""
|
|
49
|
+
The width of the note window, in pixels.
|
|
50
|
+
"""
|
|
51
|
+
return self.getNamedProp('8B02', constants.ps.PSETID_NOTE)
|
|
52
|
+
|
|
53
|
+
@functools.cached_property
|
|
54
|
+
def noteX(self) -> Optional[int]:
|
|
55
|
+
"""
|
|
56
|
+
The distance, in pixels, from the left edge of the screen that a user
|
|
57
|
+
interface displays the note.
|
|
58
|
+
"""
|
|
59
|
+
return self.getNamedProp('8B02', constants.ps.PSETID_NOTE)
|
|
60
|
+
|
|
61
|
+
@functools.cached_property
|
|
62
|
+
def noteY(self) -> Optional[int]:
|
|
63
|
+
"""
|
|
64
|
+
The distance, in pixels, from the top edge of the screen that a user
|
|
65
|
+
interafce displays the note.
|
|
66
|
+
"""
|
|
67
|
+
return self.getNamedProp('8B02', constants.ps.PSETID_NOTE)
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'Task',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import functools
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from .. import constants
|
|
14
|
+
from ..enums import (
|
|
15
|
+
TaskAcceptance, TaskHistory, TaskMode, TaskMultipleRecipients,
|
|
16
|
+
TaskOwnership, TaskState, TaskStatus
|
|
17
|
+
)
|
|
18
|
+
from .message_base import MessageBase
|
|
19
|
+
from ..structures.recurrence_pattern import RecurrencePattern
|
|
20
|
+
from ..utils import unsignedToSignedInt
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
logger.addHandler(logging.NullHandler())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Task(MessageBase):
|
|
28
|
+
"""
|
|
29
|
+
Class used for parsing task files.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def getJson(self) -> str:
|
|
33
|
+
status = {
|
|
34
|
+
TaskStatus.NOT_STARTED: 'Not Started',
|
|
35
|
+
TaskStatus.IN_PROGRESS: 'In Progress',
|
|
36
|
+
TaskStatus.COMPLETE: 'Completed',
|
|
37
|
+
TaskStatus.WAITING_ON_OTHER: 'Waiting on someone else',
|
|
38
|
+
TaskStatus.DEFERRED: 'Deferred',
|
|
39
|
+
None: None,
|
|
40
|
+
}[self.taskStatus]
|
|
41
|
+
|
|
42
|
+
return json.dumps({
|
|
43
|
+
'subject': self.subject,
|
|
44
|
+
'status': status,
|
|
45
|
+
'percentComplete': f'{self.percentComplete*100:.0f}%',
|
|
46
|
+
'dateCompleted': self.taskDateCompleted.__format__(self.dateFormat) if self.taskDateCompleted else None,
|
|
47
|
+
'totalWork': f'{self.taskEstimatedEffort or 0} minutes',
|
|
48
|
+
'actualWork': f'{self.taskActualEffort or 0} minutes',
|
|
49
|
+
'owner': self.taskOwner,
|
|
50
|
+
'importance': self.importanceString,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
|
|
55
|
+
status = {
|
|
56
|
+
TaskStatus.NOT_STARTED: 'Not Started',
|
|
57
|
+
TaskStatus.IN_PROGRESS: 'In Progress',
|
|
58
|
+
TaskStatus.COMPLETE: 'Completed',
|
|
59
|
+
TaskStatus.WAITING_ON_OTHER: 'Waiting on someone else',
|
|
60
|
+
TaskStatus.DEFERRED: 'Deferred',
|
|
61
|
+
None: None,
|
|
62
|
+
}[self.taskStatus]
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
'-basic info-': {
|
|
66
|
+
'Subject': self.subject,
|
|
67
|
+
},
|
|
68
|
+
'-status-': {
|
|
69
|
+
'Status': status,
|
|
70
|
+
'Percent Complete': f'{self.percentComplete*100:.0f}%',
|
|
71
|
+
'Date Completed': self.taskDateCompleted.__format__(self.dateFormat) if self.taskDateCompleted else None,
|
|
72
|
+
},
|
|
73
|
+
'-work-': {
|
|
74
|
+
'Total Work': f'{self.taskEstimatedEffort or 0} minutes',
|
|
75
|
+
'Actual Work': f'{self.taskActualEffort or 0} minutes',
|
|
76
|
+
},
|
|
77
|
+
'-owner-': {
|
|
78
|
+
'Owner': self.taskOwner,
|
|
79
|
+
},
|
|
80
|
+
'-importance-': {
|
|
81
|
+
'Importance': self.importanceString,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@functools.cached_property
|
|
86
|
+
def percentComplete(self) -> float:
|
|
87
|
+
"""
|
|
88
|
+
Indicates whether a time-flagged Message object is complete.
|
|
89
|
+
|
|
90
|
+
:returns: A percentage in decimal form. 1.0 indicates it is complete.
|
|
91
|
+
"""
|
|
92
|
+
return self.getNamedProp('8102', constants.ps.PSETID_TASK, 0.0)
|
|
93
|
+
|
|
94
|
+
@functools.cached_property
|
|
95
|
+
def taskAcceptanceState(self) -> Optional[TaskAcceptance]:
|
|
96
|
+
"""
|
|
97
|
+
Indicates the acceptance state of the task.
|
|
98
|
+
"""
|
|
99
|
+
return self.getNamedAs('812A', constants.ps.PSETID_TASK, TaskAcceptance)
|
|
100
|
+
|
|
101
|
+
@functools.cached_property
|
|
102
|
+
def taskAccepted(self) -> bool:
|
|
103
|
+
"""
|
|
104
|
+
Indicates whether a task assignee has replied to a tesk request for this
|
|
105
|
+
task object.
|
|
106
|
+
|
|
107
|
+
Does not indicate if it was accepted or rejected.
|
|
108
|
+
"""
|
|
109
|
+
return bool(self.getNamedProp('8108', constants.ps.PSETID_TASK))
|
|
110
|
+
|
|
111
|
+
@functools.cached_property
|
|
112
|
+
def taskActualEffort(self) -> Optional[int]:
|
|
113
|
+
"""
|
|
114
|
+
Indicates the number of minutes that the user actually spent working on
|
|
115
|
+
a task.
|
|
116
|
+
"""
|
|
117
|
+
return self.getNamedProp('8110', constants.ps.PSETID_TASK)
|
|
118
|
+
|
|
119
|
+
@functools.cached_property
|
|
120
|
+
def taskAssigner(self) -> Optional[str]:
|
|
121
|
+
"""
|
|
122
|
+
Specifies the name of the user that last assigned the task.
|
|
123
|
+
"""
|
|
124
|
+
return self.getNamedProp('8121', constants.ps.PSETID_TASK)
|
|
125
|
+
|
|
126
|
+
@functools.cached_property
|
|
127
|
+
def taskAssigners(self) -> Optional[bytes]:
|
|
128
|
+
"""
|
|
129
|
+
A stack of entries, each representing a task assigner.
|
|
130
|
+
|
|
131
|
+
The most recent task assigner (that is, the top) appears at the bottom.
|
|
132
|
+
|
|
133
|
+
The documentation on this is weird, so I don't know how to parse it.
|
|
134
|
+
"""
|
|
135
|
+
return self.getNamedProp('8117', constants.ps.PSETID_TASK)
|
|
136
|
+
|
|
137
|
+
@functools.cached_property
|
|
138
|
+
def taskComplete(self) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Indicates if the task is complete.
|
|
141
|
+
"""
|
|
142
|
+
return bool(self.getNamedProp('811C', constants.ps.PSETID_TASK))
|
|
143
|
+
|
|
144
|
+
@functools.cached_property
|
|
145
|
+
def taskCustomFlags(self) -> Optional[int]:
|
|
146
|
+
"""
|
|
147
|
+
Custom flags set on the task.
|
|
148
|
+
"""
|
|
149
|
+
return self.getNamedProp('8139', constants.ps.PSETID_TASK)
|
|
150
|
+
|
|
151
|
+
@functools.cached_property
|
|
152
|
+
def taskDateCompleted(self) -> Optional[datetime.datetime]:
|
|
153
|
+
"""
|
|
154
|
+
The date when the user completed work on the task.
|
|
155
|
+
"""
|
|
156
|
+
return self.getNamedProp('810F', constants.ps.PSETID_TASK)
|
|
157
|
+
|
|
158
|
+
@functools.cached_property
|
|
159
|
+
def taskDeadOccurrence(self) -> bool:
|
|
160
|
+
"""
|
|
161
|
+
Indicates whether a new recurring task remains to be generated.
|
|
162
|
+
|
|
163
|
+
Set to ``False`` on a new Task object and ``True`` when the client
|
|
164
|
+
generates the last recurring task.
|
|
165
|
+
"""
|
|
166
|
+
return bool(self.getNamedProp('8109', constants.ps.PSETID_TASK))
|
|
167
|
+
|
|
168
|
+
@functools.cached_property
|
|
169
|
+
def taskDueDate(self) -> Optional[datetime.datetime]:
|
|
170
|
+
"""
|
|
171
|
+
Specifies the date by which the user expects work on the task to be
|
|
172
|
+
complete.
|
|
173
|
+
"""
|
|
174
|
+
return self.getNamedProp('8105', constants.ps.PSETID_TASK)
|
|
175
|
+
|
|
176
|
+
@functools.cached_property
|
|
177
|
+
def taskEstimatedEffort(self) -> Optional[int]:
|
|
178
|
+
"""
|
|
179
|
+
Indicates the number of minutes that the user expects to work on a task.
|
|
180
|
+
"""
|
|
181
|
+
return self.getNamedProp('8111', constants.ps.PSETID_TASK)
|
|
182
|
+
|
|
183
|
+
@functools.cached_property
|
|
184
|
+
def taskFCreator(self) -> bool:
|
|
185
|
+
"""
|
|
186
|
+
Indicates that the task object was originally created by the action of
|
|
187
|
+
the current user or user agent instead of by the processing of a task
|
|
188
|
+
request.
|
|
189
|
+
"""
|
|
190
|
+
return bool(self.getNamedProp('811E', constants.ps.PSETID_TASK))
|
|
191
|
+
|
|
192
|
+
@functools.cached_property
|
|
193
|
+
def taskFFixOffline(self) -> bool:
|
|
194
|
+
"""
|
|
195
|
+
Indicates whether the value of the taskOwner property is correct.
|
|
196
|
+
"""
|
|
197
|
+
return bool(self.getNamedProp('812C', constants.ps.PSETID_TASK))
|
|
198
|
+
|
|
199
|
+
@functools.cached_property
|
|
200
|
+
def taskFRecurring(self) -> bool:
|
|
201
|
+
"""
|
|
202
|
+
Indicates whether the task includes a recurrence pattern.
|
|
203
|
+
"""
|
|
204
|
+
return bool(self.getNamedProp('8126', constants.ps.PSETID_TASK))
|
|
205
|
+
|
|
206
|
+
@functools.cached_property
|
|
207
|
+
def taskGlobalID(self) -> Optional[bytes]:
|
|
208
|
+
"""
|
|
209
|
+
Specifies a unique GUID for this task, used to locate an existing task
|
|
210
|
+
upon receipt of a task response or task update.
|
|
211
|
+
"""
|
|
212
|
+
return self.getNamedProp('8519', constants.ps.PSETID_COMMON)
|
|
213
|
+
|
|
214
|
+
@functools.cached_property
|
|
215
|
+
def taskHistory(self) -> Optional[TaskHistory]:
|
|
216
|
+
"""
|
|
217
|
+
Indicates the type of change that was last made to the Task object.
|
|
218
|
+
"""
|
|
219
|
+
return self.getNamedAs('811A', constants.ps.PSETID_TASK, TaskHistory)
|
|
220
|
+
|
|
221
|
+
@functools.cached_property
|
|
222
|
+
def taskLastDelegate(self) -> Optional[str]:
|
|
223
|
+
"""
|
|
224
|
+
Contains the name of the user who most recently assigned the task, or
|
|
225
|
+
the user to whom it was most recently assigned.
|
|
226
|
+
"""
|
|
227
|
+
return self.getNamedProp('8125', constants.ps.PSETID_TASK)
|
|
228
|
+
|
|
229
|
+
@functools.cached_property
|
|
230
|
+
def taskLastUpdate(self) -> Optional[datetime.datetime]:
|
|
231
|
+
"""
|
|
232
|
+
The date and time of the most recent change made to the task object.
|
|
233
|
+
"""
|
|
234
|
+
return self.getNamedProp('8115', constants.ps.PSETID_TASK)
|
|
235
|
+
|
|
236
|
+
@functools.cached_property
|
|
237
|
+
def taskLastUser(self) -> Optional[str]:
|
|
238
|
+
"""
|
|
239
|
+
Contains the name of the most recent user to have been the owner of the
|
|
240
|
+
task.
|
|
241
|
+
"""
|
|
242
|
+
return self.getNamedProp('8122', constants.ps.PSETID_TASK)
|
|
243
|
+
|
|
244
|
+
@functools.cached_property
|
|
245
|
+
def taskMode(self) -> Optional[TaskMode]:
|
|
246
|
+
"""
|
|
247
|
+
Used in a task communication.
|
|
248
|
+
|
|
249
|
+
Should be 0 (UNASSIGNED) on task objects.
|
|
250
|
+
"""
|
|
251
|
+
return self.getNamedAs('8518', constants.ps.PSETID_COMMON, TaskMode)
|
|
252
|
+
|
|
253
|
+
@functools.cached_property
|
|
254
|
+
def taskMultipleRecipients(self) -> Optional[TaskMultipleRecipients]:
|
|
255
|
+
"""
|
|
256
|
+
Returns a union of flags that specify optimization hints about the
|
|
257
|
+
recipients of a Task object.
|
|
258
|
+
"""
|
|
259
|
+
return self.getNamedAs('8120', constants.ps.PSETID_TASK, TaskMultipleRecipients)
|
|
260
|
+
|
|
261
|
+
@functools.cached_property
|
|
262
|
+
def taskNoCompute(self) -> Optional[bool]:
|
|
263
|
+
"""
|
|
264
|
+
This value is not used and has no impact on a Task, but is provided for
|
|
265
|
+
completeness.
|
|
266
|
+
"""
|
|
267
|
+
return self.getNamedProp('8124', constants.ps.PSETID_TASK)
|
|
268
|
+
|
|
269
|
+
@functools.cached_property
|
|
270
|
+
def taskOrdinal(self) -> Optional[int]:
|
|
271
|
+
"""
|
|
272
|
+
Specifies a number that aids custom sorting of Task objects.
|
|
273
|
+
"""
|
|
274
|
+
return self.getNamedAs('8123', constants.ps.PSETID_TASK, unsignedToSignedInt)
|
|
275
|
+
|
|
276
|
+
@functools.cached_property
|
|
277
|
+
def taskOwner(self) -> Optional[str]:
|
|
278
|
+
"""
|
|
279
|
+
Contains the name of the owner of the task.
|
|
280
|
+
"""
|
|
281
|
+
return self.getNamedProp('811F', constants.ps.PSETID_TASK)
|
|
282
|
+
|
|
283
|
+
@functools.cached_property
|
|
284
|
+
def taskOwnership(self) -> Optional[TaskOwnership]:
|
|
285
|
+
"""
|
|
286
|
+
Contains the name of the owner of the task.
|
|
287
|
+
"""
|
|
288
|
+
return self.getNamedAs('8129', constants.ps.PSETID_TASK, TaskOwnership)
|
|
289
|
+
|
|
290
|
+
@functools.cached_property
|
|
291
|
+
def taskRecurrence(self) -> Optional[RecurrencePattern]:
|
|
292
|
+
"""
|
|
293
|
+
Contains a RecurrencePattern structure that provides information about
|
|
294
|
+
recurring tasks.
|
|
295
|
+
"""
|
|
296
|
+
return self.getNamedAs('8116', constants.ps.PSETID_TASK, RecurrencePattern)
|
|
297
|
+
|
|
298
|
+
@functools.cached_property
|
|
299
|
+
def taskResetReminder(self) -> bool:
|
|
300
|
+
"""
|
|
301
|
+
Indicates whether future recurring tasks need reminders.
|
|
302
|
+
"""
|
|
303
|
+
return bool(self.getNamedProp('8107', constants.ps.PSETID_TASK))
|
|
304
|
+
|
|
305
|
+
@functools.cached_property
|
|
306
|
+
def taskRole(self) -> Optional[str]:
|
|
307
|
+
"""
|
|
308
|
+
This value is not used and has no impact on a Task, but is provided for
|
|
309
|
+
completeness.
|
|
310
|
+
"""
|
|
311
|
+
return self.getNamedProp('8127', constants.ps.PSETID_TASK)
|
|
312
|
+
|
|
313
|
+
@functools.cached_property
|
|
314
|
+
def taskStartDate(self) -> Optional[datetime.datetime]:
|
|
315
|
+
"""
|
|
316
|
+
Specifies the date on which the user expects work on the task to begin.
|
|
317
|
+
"""
|
|
318
|
+
return self.getNamedProp('8104', constants.ps.PSETID_TASK)
|
|
319
|
+
|
|
320
|
+
@functools.cached_property
|
|
321
|
+
def taskState(self) -> Optional[TaskState]:
|
|
322
|
+
"""
|
|
323
|
+
Indicates the current assignment state of the Task object.
|
|
324
|
+
"""
|
|
325
|
+
return self.getNamedAs('8113', constants.ps.PSETID_TASK, TaskState)
|
|
326
|
+
|
|
327
|
+
@functools.cached_property
|
|
328
|
+
def taskStatus(self) -> Optional[TaskStatus]:
|
|
329
|
+
"""
|
|
330
|
+
The completion status of a task.
|
|
331
|
+
"""
|
|
332
|
+
return self.getNamedAs('8101', constants.ps.PSETID_TASK, TaskStatus)
|
|
333
|
+
|
|
334
|
+
@functools.cached_property
|
|
335
|
+
def taskStatusOnComplete(self) -> bool:
|
|
336
|
+
"""
|
|
337
|
+
Indicates whether the task assignee has been requested to send an email
|
|
338
|
+
message upon completion of the assigned task.
|
|
339
|
+
"""
|
|
340
|
+
return bool(self.getNamedProp('8119', constants.ps.PSETID_TASK))
|
|
341
|
+
|
|
342
|
+
@functools.cached_property
|
|
343
|
+
def taskUpdates(self) -> bool:
|
|
344
|
+
"""
|
|
345
|
+
Indicates whether the task assignee has been requested to send a task
|
|
346
|
+
update when the assigned Task object changes.
|
|
347
|
+
"""
|
|
348
|
+
return bool(self.getNamedProp('811B', constants.ps.PSETID_TASK))
|
|
349
|
+
|
|
350
|
+
@functools.cached_property
|
|
351
|
+
def taskVersion(self) -> Optional[int]:
|
|
352
|
+
"""
|
|
353
|
+
Indicates which copy is the latest update of a Task object.
|
|
354
|
+
"""
|
|
355
|
+
return self.getNamedProp('8112', constants.ps.PSETID_TASK)
|
|
356
|
+
|
|
357
|
+
@functools.cached_property
|
|
358
|
+
def teamTask(self) -> Optional[bool]:
|
|
359
|
+
"""
|
|
360
|
+
This value is not used and has no impact on a Task, but is provided for
|
|
361
|
+
completeness.
|
|
362
|
+
"""
|
|
363
|
+
return self.getNamedProp('8103', constants.ps.PSETID_TASK)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'TaskRequest',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import functools
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from typing import cast, Optional
|
|
10
|
+
|
|
11
|
+
from .. import constants
|
|
12
|
+
from ..enums import ErrorBehavior, TaskMode, TaskRequestType
|
|
13
|
+
from ..exceptions import StandardViolationError
|
|
14
|
+
from .message_base import MessageBase
|
|
15
|
+
from .task import Task
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
logger.addHandler(logging.NullHandler())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TaskRequest(MessageBase):
|
|
23
|
+
"""
|
|
24
|
+
Class for handling Task Request objects, including Task Accept, Task
|
|
25
|
+
Decline, and Task Update.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
|
|
30
|
+
# So this is rather weird. Looks like TaskRequest does not rely on
|
|
31
|
+
# headers at all, simply using the body itself for all the data to
|
|
32
|
+
# print. So I guess we just return None and handle that.
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
@functools.cached_property
|
|
36
|
+
def processed(self) -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Indicates whether a client has already processed a received task
|
|
39
|
+
communication.
|
|
40
|
+
"""
|
|
41
|
+
return bool(self.getPropertyVal('7D01000B'))
|
|
42
|
+
|
|
43
|
+
@functools.cached_property
|
|
44
|
+
def taskMode(self) -> Optional[TaskMode]:
|
|
45
|
+
"""
|
|
46
|
+
The assignment status of the embedded Task object.
|
|
47
|
+
"""
|
|
48
|
+
return self.getNamedAs('8518', constants.ps.PSETID_COMMON, TaskMode)
|
|
49
|
+
|
|
50
|
+
@functools.cached_property
|
|
51
|
+
def taskObject(self) -> Optional[Task]:
|
|
52
|
+
"""
|
|
53
|
+
The task object embedded in this Task Request object.
|
|
54
|
+
|
|
55
|
+
This function does all of the most basic validation, and so will log
|
|
56
|
+
most issues or throw exceptions if there are too many problems.
|
|
57
|
+
|
|
58
|
+
:raises StandardViolationError: A standard was blatently violated in a
|
|
59
|
+
way that program does not tolerate.
|
|
60
|
+
"""
|
|
61
|
+
# Get the task object.
|
|
62
|
+
#
|
|
63
|
+
# The task object MUST be the first attachment, but we will be
|
|
64
|
+
# lenient and allow it to be in any position. It not existing,
|
|
65
|
+
# however, will not be tolerated.
|
|
66
|
+
task = next(((index, att) for index, att in self.attachments if isinstance(att.data, Task)), None)
|
|
67
|
+
|
|
68
|
+
if task is None:
|
|
69
|
+
if ErrorBehavior.STANDARDS_VIOLATION in self.errorBehavior:
|
|
70
|
+
logger.error('Task object not found on TaskRequest object.')
|
|
71
|
+
return None
|
|
72
|
+
raise StandardViolationError('Task object not found on TaskRequest object.')
|
|
73
|
+
|
|
74
|
+
# We know we have the task, let's make sure it's at index 0. If not,
|
|
75
|
+
# log it.
|
|
76
|
+
if task[0] != 0:
|
|
77
|
+
logger.warning('Embedded task object was not located at index 0.')
|
|
78
|
+
|
|
79
|
+
return cast(Task, task[1])
|
|
80
|
+
|
|
81
|
+
@functools.cached_property
|
|
82
|
+
def taskRequestType(self) -> Optional[TaskRequestType]:
|
|
83
|
+
"""
|
|
84
|
+
The type of task request.
|
|
85
|
+
"""
|
|
86
|
+
return self.getStringStreamAs('__substg1.0_001A', TaskRequestType.fromClassType)
|
extract_msg/null_date.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'NullDate'
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NullDate(datetime.datetime):
|
|
12
|
+
"""
|
|
13
|
+
Version of datetime.datetime intended to represent a value of NULL.
|
|
14
|
+
|
|
15
|
+
Some properties use different values for a null date, and those need to be
|
|
16
|
+
differentiated when packing the data back into an MSG file, which is why
|
|
17
|
+
this class exists. Comparisons between NullDate instances will always say
|
|
18
|
+
the two dates are equal.
|
|
19
|
+
|
|
20
|
+
:attribute filetime: An optional value that can be set to the filetime a
|
|
21
|
+
null date should convert back to.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
filetime: Optional[int] = None
|
|
25
|
+
|
|
26
|
+
def __eq__(self, other) -> bool:
|
|
27
|
+
if isinstance(other, NullDate):
|
|
28
|
+
return True
|
|
29
|
+
return super.__eq__(self, other)
|
|
30
|
+
|
|
31
|
+
def __ne__(self, other) -> bool:
|
|
32
|
+
if isinstance(other, NullDate):
|
|
33
|
+
return False
|
|
34
|
+
return super.__eq__(self, other)
|