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.
Files changed (95) hide show
  1. extract_msg/__init__.py +71 -0
  2. extract_msg/__main__.py +115 -0
  3. extract_msg/_rtf/__init__.py +21 -0
  4. extract_msg/_rtf/create_doc.py +29 -0
  5. extract_msg/_rtf/inject_rtf.py +174 -0
  6. extract_msg/_rtf/token.py +35 -0
  7. extract_msg/_rtf/tokenize_rtf.py +253 -0
  8. extract_msg/attachments/__init__.py +144 -0
  9. extract_msg/attachments/attachment.py +188 -0
  10. extract_msg/attachments/attachment_base.py +730 -0
  11. extract_msg/attachments/broken_att.py +42 -0
  12. extract_msg/attachments/custom_att.py +173 -0
  13. extract_msg/attachments/custom_att_handler/__init__.py +80 -0
  14. extract_msg/attachments/custom_att_handler/custom_handler.py +134 -0
  15. extract_msg/attachments/custom_att_handler/lnk_obj_att.py +109 -0
  16. extract_msg/attachments/custom_att_handler/outlook_image_dib.py +141 -0
  17. extract_msg/attachments/emb_msg_att.py +134 -0
  18. extract_msg/attachments/signed_att.py +234 -0
  19. extract_msg/attachments/unsupported_att.py +40 -0
  20. extract_msg/attachments/web_att.py +74 -0
  21. extract_msg/constants/__init__.py +294 -0
  22. extract_msg/constants/ps.py +46 -0
  23. extract_msg/constants/re.py +42 -0
  24. extract_msg/constants/st.py +110 -0
  25. extract_msg/data/logging-config/logging-nt.json +64 -0
  26. extract_msg/data/logging-config/logging-posix.json +64 -0
  27. extract_msg/encoding/__init__.py +277 -0
  28. extract_msg/encoding/_dt/__init__.py +4 -0
  29. extract_msg/encoding/_dt/_mac_ce.py +8 -0
  30. extract_msg/encoding/_dt/_mac_cyrillic.py +8 -0
  31. extract_msg/encoding/_dt/_mac_greek.py +8 -0
  32. extract_msg/encoding/_dt/_mac_iceland.py +8 -0
  33. extract_msg/encoding/_dt/_mac_turkish.py +8 -0
  34. extract_msg/encoding/_dt/_win874_dec.py +8 -0
  35. extract_msg/encoding/_dt/_win950_dec.py +6 -0
  36. extract_msg/encoding/utils.py +329 -0
  37. extract_msg/enums.py +2067 -0
  38. extract_msg/exceptions.py +170 -0
  39. extract_msg/msg_classes/__init__.py +49 -0
  40. extract_msg/msg_classes/appointment.py +195 -0
  41. extract_msg/msg_classes/calendar.py +92 -0
  42. extract_msg/msg_classes/calendar_base.py +496 -0
  43. extract_msg/msg_classes/contact.py +1335 -0
  44. extract_msg/msg_classes/journal.py +131 -0
  45. extract_msg/msg_classes/meeting_cancellation.py +108 -0
  46. extract_msg/msg_classes/meeting_exception.py +55 -0
  47. extract_msg/msg_classes/meeting_forward.py +115 -0
  48. extract_msg/msg_classes/meeting_related.py +65 -0
  49. extract_msg/msg_classes/meeting_request.py +189 -0
  50. extract_msg/msg_classes/meeting_response.py +74 -0
  51. extract_msg/msg_classes/message.py +12 -0
  52. extract_msg/msg_classes/message_base.py +1382 -0
  53. extract_msg/msg_classes/message_signed.py +17 -0
  54. extract_msg/msg_classes/message_signed_base.py +168 -0
  55. extract_msg/msg_classes/msg.py +1172 -0
  56. extract_msg/msg_classes/post.py +53 -0
  57. extract_msg/msg_classes/sticky_note.py +67 -0
  58. extract_msg/msg_classes/task.py +363 -0
  59. extract_msg/msg_classes/task_request.py +86 -0
  60. extract_msg/null_date.py +34 -0
  61. extract_msg/ole_writer.py +1086 -0
  62. extract_msg/open_msg.py +204 -0
  63. extract_msg/properties/__init__.py +23 -0
  64. extract_msg/properties/named.py +481 -0
  65. extract_msg/properties/prop.py +506 -0
  66. extract_msg/properties/properties_store.py +476 -0
  67. extract_msg/py.typed +0 -0
  68. extract_msg/recipient.py +362 -0
  69. extract_msg/structures/__init__.py +34 -0
  70. extract_msg/structures/_helpers.py +345 -0
  71. extract_msg/structures/business_card.py +422 -0
  72. extract_msg/structures/cfoas.py +108 -0
  73. extract_msg/structures/contact_link_entry.py +51 -0
  74. extract_msg/structures/dev_mode_a.py +621 -0
  75. extract_msg/structures/dv_target_device.py +168 -0
  76. extract_msg/structures/entry_id.py +697 -0
  77. extract_msg/structures/misc_id.py +222 -0
  78. extract_msg/structures/mon_stream.py +55 -0
  79. extract_msg/structures/odt.py +74 -0
  80. extract_msg/structures/ole_pres.py +302 -0
  81. extract_msg/structures/ole_stream_struct.py +117 -0
  82. extract_msg/structures/recurrence_pattern.py +199 -0
  83. extract_msg/structures/report_tag.py +127 -0
  84. extract_msg/structures/system_time.py +158 -0
  85. extract_msg/structures/time_zone_definition.py +111 -0
  86. extract_msg/structures/time_zone_struct.py +102 -0
  87. extract_msg/structures/toc_entry.py +137 -0
  88. extract_msg/structures/tz_rule.py +148 -0
  89. extract_msg/utils.py +1270 -0
  90. extract_msg-0.48.2.dist-info/LICENSE.txt +674 -0
  91. extract_msg-0.48.2.dist-info/METADATA +321 -0
  92. extract_msg-0.48.2.dist-info/RECORD +95 -0
  93. extract_msg-0.48.2.dist-info/WHEEL +5 -0
  94. extract_msg-0.48.2.dist-info/entry_points.txt +2 -0
  95. 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)
@@ -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)