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,131 @@
1
+ __all__ = [
2
+ 'Journal',
3
+ ]
4
+
5
+
6
+ import base64
7
+ import datetime
8
+ import functools
9
+ import json
10
+
11
+ from typing import List, Optional
12
+
13
+ from ..constants import HEADER_FORMAT_TYPE, ps
14
+ from ..enums import LogFlags
15
+ from .message_base import MessageBase
16
+ from ..utils import minutesToDurationStr
17
+
18
+
19
+ class Journal(MessageBase):
20
+ """
21
+ Class for parsing Journal messages.
22
+ """
23
+
24
+ def getJson(self) -> str:
25
+ return json.dumps({
26
+ 'subject': self.subject,
27
+ 'entryType': self.logTypeDesc,
28
+ 'company': self.companies[0] if self.companies else None,
29
+ 'start': self.logStart.__format__(self.datetimeFormat) if self.logStart else None,
30
+ 'end': self.logEnd.__format__(self.datetimeFormat) if self.logEnd else None,
31
+ 'duration': minutesToDurationStr(self.duration),
32
+ 'body': self.body,
33
+ # There is a good chance the body property won't exist, so this is a
34
+ # backup.
35
+ 'rtfBodyB64': base64.b64encode(self.rtfBody).decode('ascii') if self.rtfBody else None,
36
+ })
37
+
38
+ @functools.cached_property
39
+ def companies(self) -> Optional[List[str]]:
40
+ """
41
+ Contains a list of company names, each of which is accociated with a
42
+ contact this is precified in the contacts property.
43
+ """
44
+ return self.getNamedProp('8539', ps.PSETID_COMMON)
45
+
46
+ @functools.cached_property
47
+ def logDocumentPosted(self) -> bool:
48
+ """
49
+ Indicates whether the document was sent by email of posted to a server
50
+ folder during journaling.
51
+ """
52
+ return bool(self.getNamedProp('8711', ps.PSETID_LOG))
53
+
54
+ @functools.cached_property
55
+ def logDocumentPrinted(self) -> bool:
56
+ """
57
+ Indicates whether the document was printed during journaling.
58
+ """
59
+ return bool(self.getNamedProp('870E', ps.PSETID_LOG))
60
+
61
+ @functools.cached_property
62
+ def logDocumentRouted(self) -> bool:
63
+ """
64
+ Indicates whether the document was sent to a routing recipient during
65
+ journaling.
66
+ """
67
+ return bool(self.getNamedProp('8710', ps.PSETID_LOG))
68
+
69
+ @functools.cached_property
70
+ def logDocumentSaved(self) -> bool:
71
+ """
72
+ Indicates whether the document was saved during journaling.
73
+ """
74
+ return bool(self.getNamedProp('870F', ps.PSETID_LOG))
75
+
76
+ @functools.cached_property
77
+ def logDuration(self) -> int:
78
+ """
79
+ The duration, in minutes, of the activity.
80
+ """
81
+ return self.getNamedProp('8707', ps.PSETID_LOG, 0)
82
+
83
+ @functools.cached_property
84
+ def logEnd(self) -> Optional[datetime.datetime]:
85
+ """
86
+ The name of the activity that is being recorded.
87
+ """
88
+ return self.getNamedProp('8708', ps.PSETID_LOG)
89
+
90
+ @functools.cached_property
91
+ def logFlags(self) -> LogFlags:
92
+ """
93
+ The name of the activity that is being recorded.
94
+ """
95
+ return LogFlags(self.getNamedProp('870C', ps.PSETID_LOG, 0))
96
+
97
+ @functools.cached_property
98
+ def logStart(self) -> Optional[datetime.datetime]:
99
+ """
100
+ The name of the activity that is being recorded.
101
+ """
102
+ return self.getNamedProp('8706', ps.PSETID_LOG)
103
+
104
+ @functools.cached_property
105
+ def logType(self) -> Optional[str]:
106
+ """
107
+ The name of the activity that is being recorded.
108
+ """
109
+ return self.getNamedProp('8700', ps.PSETID_LOG)
110
+
111
+ @functools.cached_property
112
+ def logTypeDesc(self) -> Optional[str]:
113
+ """
114
+ The description of the activity that is being recorded.
115
+ """
116
+ return self.getNamedProp('8712', ps.PSETID_LOG)
117
+
118
+ @property
119
+ def headerFormatProperties(self) -> HEADER_FORMAT_TYPE:
120
+ return {
121
+ '-main details-': {
122
+ 'Subject': self.subject,
123
+ 'Entry Type': self.logTypeDesc,
124
+ 'Company': self.companies[0] if self.companies else None,
125
+ },
126
+ '-time-': {
127
+ 'Start': self.logStart.__format__(self.datetimeFormat) if self.logStart else None,
128
+ 'End': self.logEnd.__format__(self.datetimeFormat) if self.logEnd else None,
129
+ 'Duration': minutesToDurationStr(self.duration),
130
+ },
131
+ }
@@ -0,0 +1,108 @@
1
+ __all__ = [
2
+ 'MeetingCancellation',
3
+ ]
4
+
5
+
6
+ import json
7
+
8
+ from .. import constants
9
+ from ..enums import RecurPatternType, ResponseStatus
10
+ from .meeting_related import MeetingRelated
11
+
12
+
13
+ # The documentation for this only specifies restrictions on existing properties,
14
+ # so we just mostly leave this alone.
15
+ class MeetingCancellation(MeetingRelated):
16
+ """
17
+ Class for a Meeting Cancellation object.
18
+ """
19
+
20
+ def getJson(self) -> str:
21
+ meetingStatusString = {
22
+ ResponseStatus.NONE: None,
23
+ ResponseStatus.ORGANIZED: 'Meeting organizer',
24
+ ResponseStatus.TENTATIVE: 'Tentatively accepted',
25
+ ResponseStatus.ACCEPTED: 'Accepted',
26
+ ResponseStatus.DECLINED: 'Declined',
27
+ ResponseStatus.NOT_RESPONDED: 'Not yet responded',
28
+ }[self.responseStatus]
29
+
30
+ # Get the recurrence string.
31
+ recur = '(none)'
32
+ if self.appointmentRecur:
33
+ recur = {
34
+ RecurPatternType.DAY: 'Daily',
35
+ RecurPatternType.WEEK: 'Weekly',
36
+ RecurPatternType.MONTH: 'Monthly',
37
+ RecurPatternType.MONTH_NTH: 'Monthly',
38
+ RecurPatternType.MONTH_END: 'Monthly',
39
+ RecurPatternType.HJ_MONTH: 'Monthly',
40
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
41
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
42
+ }[self.appointmentRecur.patternType]
43
+
44
+ return json.dumps({
45
+ 'recurrence': recur,
46
+ 'recurrencePattern': self.recurrencePattern,
47
+ 'body': self.body,
48
+ 'meetingStatus': meetingStatusString,
49
+ 'organizer': self.organizer,
50
+ 'requiredAttendees': self.to,
51
+ 'optionalAttendees': self.cc,
52
+ 'resources': self.bcc,
53
+ 'start': self.startDate.__format__(self.datetimeFormat) if self.endDate else None,
54
+ 'end': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
55
+ })
56
+
57
+ @property
58
+ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
59
+ meetingStatusString = {
60
+ ResponseStatus.NONE: None,
61
+ ResponseStatus.ORGANIZED: 'Meeting organizer',
62
+ ResponseStatus.TENTATIVE: 'Tentatively accepted',
63
+ ResponseStatus.ACCEPTED: 'Accepted',
64
+ ResponseStatus.DECLINED: 'Declined',
65
+ ResponseStatus.NOT_RESPONDED: 'Not yet responded',
66
+ }[self.responseStatus]
67
+
68
+ # Get the recurrence string.
69
+ recur = '(none)'
70
+ if self.appointmentRecur:
71
+ recur = {
72
+ RecurPatternType.DAY: 'Daily',
73
+ RecurPatternType.WEEK: 'Weekly',
74
+ RecurPatternType.MONTH: 'Monthly',
75
+ RecurPatternType.MONTH_NTH: 'Monthly',
76
+ RecurPatternType.MONTH_END: 'Monthly',
77
+ RecurPatternType.HJ_MONTH: 'Monthly',
78
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
79
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
80
+ }[self.appointmentRecur.patternType]
81
+
82
+ return {
83
+ '-main info-': {
84
+ 'Subject': self.subject,
85
+ 'Location': self.location,
86
+ },
87
+ '-date-': {
88
+ 'Start': self.startDate.__format__(self.datetimeFormat) if self.startDate else None,
89
+ 'End': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
90
+ 'Show Time As': 'Free',
91
+ },
92
+ '-recurrence-': {
93
+ 'Recurrance': recur,
94
+ 'Recurrence Pattern': self.recurrencePattern,
95
+ },
96
+ '-status-': {
97
+ 'Meeting Status': meetingStatusString,
98
+ },
99
+ '-attendees-': {
100
+ 'Organizer': self.organizer,
101
+ 'Required Attendees': self.to,
102
+ 'Optional Attendees': self.cc,
103
+ 'Resources': self.bcc,
104
+ },
105
+ '-importance-': {
106
+ 'Importance': self.importanceString,
107
+ },
108
+ }
@@ -0,0 +1,55 @@
1
+ __all__ = [
2
+ 'MeetingException',
3
+ ]
4
+
5
+
6
+ import datetime
7
+ import functools
8
+
9
+ from typing import Optional
10
+
11
+ from .. import constants
12
+ from ..enums import SaveType
13
+ from .meeting_related import MeetingRelated
14
+
15
+
16
+ class MeetingException(MeetingRelated):
17
+ """
18
+ Class for handling Meeting Exceptions.
19
+ """
20
+
21
+ def save(self, **_) -> constants.SAVE_TYPE:
22
+ """
23
+ Meeting Exceptions are hidden attachments with no save behaviors.
24
+
25
+ If you want something to happen for saving, you can call the save of a
26
+ parent class or write your own code.
27
+ """
28
+ return (SaveType.NONE, None)
29
+
30
+ @functools.cached_property
31
+ def exceptionReplaceTime(self) -> Optional[datetime.datetime]:
32
+ """
33
+ The date and time within the recurrence pattern that the exception will
34
+ replace.
35
+
36
+ The value is specified in UTC.
37
+ """
38
+ return self.getNamedProp('8228', constants.ps.PSETID_APPOINTMENT)
39
+
40
+ @functools.cached_property
41
+ def fExceptionalBody(self) -> bool:
42
+ """
43
+ Indicates that the Exception Embedded Message object has a body that
44
+ differs from the Recurring Calendar object.
45
+
46
+ If ``True``, the Exception MUST have a body.
47
+ """
48
+ return bool(self.getNamedProp('8206', constants.ps.PSETID_APPOINTMENT))
49
+
50
+ @functools.cached_property
51
+ def fInvited(self) -> bool:
52
+ """
53
+ Indicates if invitations have been sent for this exception.
54
+ """
55
+ return bool(self.getNamedProp('8229', constants.ps.PSETID_APPOINTMENT))
@@ -0,0 +1,115 @@
1
+ __all__ = [
2
+ 'MeetingForwardNotification',
3
+ ]
4
+
5
+
6
+ import functools
7
+ import json
8
+
9
+ from typing import Optional
10
+
11
+ from .. import constants
12
+ from .meeting_related import MeetingRelated
13
+ from ..enums import RecurPatternType, ResponseStatus
14
+
15
+
16
+ class MeetingForwardNotification(MeetingRelated):
17
+ """
18
+ Class for handling Meeting Forward Notification objects.
19
+ """
20
+
21
+ def getJson(self) -> str:
22
+ meetingStatusString = {
23
+ ResponseStatus.NONE: None,
24
+ ResponseStatus.ORGANIZED: 'Meeting organizer',
25
+ ResponseStatus.TENTATIVE: 'Tentatively accepted',
26
+ ResponseStatus.ACCEPTED: 'Accepted',
27
+ ResponseStatus.DECLINED: 'Declined',
28
+ ResponseStatus.NOT_RESPONDED: 'Not yet responded',
29
+ }[self.responseStatus]
30
+
31
+ # Get the recurrence string.
32
+ recur = '(none)'
33
+ if self.appointmentRecur:
34
+ recur = {
35
+ RecurPatternType.DAY: 'Daily',
36
+ RecurPatternType.WEEK: 'Weekly',
37
+ RecurPatternType.MONTH: 'Monthly',
38
+ RecurPatternType.MONTH_NTH: 'Monthly',
39
+ RecurPatternType.MONTH_END: 'Monthly',
40
+ RecurPatternType.HJ_MONTH: 'Monthly',
41
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
42
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
43
+ }[self.appointmentRecur.patternType]
44
+
45
+ return json.dumps({
46
+ 'recurrence': recur,
47
+ 'recurrencePattern': self.recurrencePattern,
48
+ 'body': self.body,
49
+ 'meetingStatus': meetingStatusString,
50
+ 'organizer': self.organizer,
51
+ 'requiredAttendees': self.to,
52
+ 'optionalAttendees': self.cc,
53
+ 'resources': self.bcc,
54
+ 'start': self.startDate.__format__(self.datetimeFormat) if self.endDate else None,
55
+ 'end': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
56
+ })
57
+
58
+ @functools.cached_property
59
+ def forwardNotificationRecipients(self) -> Optional[bytes]:
60
+ """
61
+ Bytes containing a list of RecipientRow structures that indicate the
62
+ recipients of a meeting forward.
63
+
64
+ Incomplete, looks to be the same structure as
65
+ appointmentUnsendableRecipients, so we need more examples of this.
66
+ """
67
+ return self.getNamedProp('8261', constants.ps.PSETID_APPOINTMENT)
68
+
69
+ @property
70
+ def headerFormatProperties(self) -> constants.HEADER_FORMAT_TYPE:
71
+ # Get the recurrence string.
72
+ recur = '(none)'
73
+ if self.appointmentRecur:
74
+ recur = {
75
+ RecurPatternType.DAY: 'Daily',
76
+ RecurPatternType.WEEK: 'Weekly',
77
+ RecurPatternType.MONTH: 'Monthly',
78
+ RecurPatternType.MONTH_NTH: 'Monthly',
79
+ RecurPatternType.MONTH_END: 'Monthly',
80
+ RecurPatternType.HJ_MONTH: 'Monthly',
81
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
82
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
83
+ }[self.appointmentRecur.patternType]
84
+
85
+ return {
86
+ '-main info-': {
87
+ 'Subject': self.subject,
88
+ 'Location': self.location,
89
+ },
90
+ '-date-': {
91
+ 'Start': self.startDate.__format__(self.datetimeFormat) if self.startDate else None,
92
+ 'End': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
93
+ },
94
+ '-recurrence-': {
95
+ 'Recurrance': recur,
96
+ 'Recurrence Pattern': self.recurrencePattern,
97
+ },
98
+ '-attendees-': {
99
+ 'Organizer': self.organizer,
100
+ 'Required Attendees': self.to,
101
+ 'Optional Attendees': self.cc,
102
+ 'Resources': self.bcc,
103
+ },
104
+ '-importance-': {
105
+ 'Importance': self.importanceString,
106
+ },
107
+ }
108
+
109
+ @functools.cached_property
110
+ def promptSendUpdate(self) -> bool:
111
+ """
112
+ Indicates that the Meeting Forward Notification object was out-of-date
113
+ when it was received.
114
+ """
115
+ return bool(self.getNamedProp('8045', constants.ps.PSETID_COMMON))
@@ -0,0 +1,65 @@
1
+ __all__ = [
2
+ 'MeetingRelated',
3
+ ]
4
+
5
+
6
+ import datetime
7
+ import functools
8
+
9
+ from typing import Optional
10
+
11
+ from ..constants import ps
12
+ from .calendar_base import CalendarBase
13
+ from ..enums import ServerProcessingAction
14
+
15
+
16
+ class MeetingRelated(CalendarBase):
17
+ """
18
+ Base class for meeting-related objects.
19
+ """
20
+
21
+ @functools.cached_property
22
+ def attendeeCriticalChange(self) -> Optional[datetime.datetime]:
23
+ """
24
+ The date and time at which the meeting-related object was sent.
25
+ """
26
+ return self.getNamedProp('0001', ps.PSETID_MEETING)
27
+
28
+ @functools.cached_property
29
+ def processed(self) -> bool:
30
+ """
31
+ Indicates whether a client has processed a meeting-related object.
32
+ """
33
+ return bool(self.getPropertyVal('7D01000B'))
34
+
35
+ @functools.cached_property
36
+ def serverProcessed(self) -> bool:
37
+ """
38
+ Indicates that the Meeting Request object or Meeting Update object has
39
+ been processed.
40
+ """
41
+ return bool(self.getNamedProp('85CC', ps.PSETID_CALENDAR_ASSISTANT))
42
+
43
+ @functools.cached_property
44
+ def serverProcessingActions(self) -> Optional[ServerProcessingAction]:
45
+ """
46
+ A union of which actions have been taken on the Meeting Request object
47
+ or Meeting Update object.
48
+ """
49
+ return self.getNamedAs('85CD', ps.PSETID_CALENDAR_ASSISTANT, ServerProcessingAction)
50
+
51
+ @functools.cached_property
52
+ def timeZone(self) -> Optional[int]:
53
+ """
54
+ Specifies information about the time zone of a recurring meeting.
55
+
56
+ See PidLidTimeZone in [MS-OXOCAL] for details.
57
+ """
58
+ return self.getNamedProp('000C', ps.PSETID_MEETING)
59
+
60
+ @functools.cached_property
61
+ def where(self) -> Optional[str]:
62
+ """
63
+ PidLidWhere. Should be the same as location.
64
+ """
65
+ return self.getNamedProp('0002', ps.PSETID_MEETING)
@@ -0,0 +1,189 @@
1
+ __all__ = [
2
+ 'MeetingRequest',
3
+ ]
4
+
5
+
6
+ import datetime
7
+ import functools
8
+ import json
9
+
10
+ from typing import Optional
11
+
12
+ from ..constants import HEADER_FORMAT_TYPE, ps
13
+ from .meeting_related import MeetingRelated
14
+ from ..enums import BusyStatus, MeetingObjectChange, MeetingType, RecurCalendarType, RecurPatternType, ResponseStatus
15
+
16
+
17
+ class MeetingRequest(MeetingRelated):
18
+ """
19
+ Class for handling Meeting Request and Meeting Update objects.
20
+ """
21
+
22
+ def getJson(self) -> str:
23
+ meetingStatusString = {
24
+ ResponseStatus.NONE: None,
25
+ ResponseStatus.ORGANIZED: 'Meeting organizer',
26
+ ResponseStatus.TENTATIVE: 'Tentatively accepted',
27
+ ResponseStatus.ACCEPTED: 'Accepted',
28
+ ResponseStatus.DECLINED: 'Declined',
29
+ ResponseStatus.NOT_RESPONDED: 'Not yet responded',
30
+ }[self.responseStatus]
31
+
32
+ # Get the recurrence string.
33
+ recur = '(none)'
34
+ if self.appointmentRecur:
35
+ recur = {
36
+ RecurPatternType.DAY: 'Daily',
37
+ RecurPatternType.WEEK: 'Weekly',
38
+ RecurPatternType.MONTH: 'Monthly',
39
+ RecurPatternType.MONTH_NTH: 'Monthly',
40
+ RecurPatternType.MONTH_END: 'Monthly',
41
+ RecurPatternType.HJ_MONTH: 'Monthly',
42
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
43
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
44
+ }[self.appointmentRecur.patternType]
45
+
46
+ return json.dumps({
47
+ 'recurrence': recur,
48
+ 'recurrencePattern': self.recurrencePattern,
49
+ 'body': self.body,
50
+ 'meetingStatus': meetingStatusString,
51
+ 'organizer': self.organizer,
52
+ 'requiredAttendees': self.to,
53
+ 'optionalAttendees': self.cc,
54
+ 'resources': self.bcc,
55
+ 'start': self.startDate.__format__(self.datetimeFormat) if self.endDate else None,
56
+ 'end': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
57
+ })
58
+
59
+ @functools.cached_property
60
+ def appointmentMessageClass(self) -> Optional[str]:
61
+ """
62
+ Indicates the value of the PidTagMessageClass property of the Meeting
63
+ object that is to be generated from the Meeting Request object. MUST
64
+ start with "IPM.Appointment".
65
+ """
66
+ return self.getNamedProp('0024', ps.PSETID_MEETING)
67
+
68
+ @functools.cached_property
69
+ def calendarType(self) -> Optional[RecurCalendarType]:
70
+ """
71
+ The value of the CalendarType field from the PidLidAppointmentRecur
72
+ property if the Meeting Request object represents a recurring series or
73
+ an exception.
74
+ """
75
+ return self.getNamedAs('001C', ps.PSETID_MEETING, RecurCalendarType)
76
+
77
+ @functools.cached_property
78
+ def changeHighlight(self) -> Optional[MeetingObjectChange]:
79
+ """
80
+ Soecifies a bit field that indicates how the Meeting object has been
81
+ changed.
82
+
83
+ Returns a union of the set flags.
84
+ """
85
+ return self.getNamedAs('8204', ps.PSETID_APPOINTMENT, MeetingObjectChange)
86
+
87
+ @functools.cached_property
88
+ def forwardInstance(self) -> bool:
89
+ """
90
+ Indicates that the Meeting Request object represents an exception to a
91
+ recurring series, and it was forwarded (even when forwarded by the
92
+ organizer) rather than being an invitation sent by the organizer.
93
+ """
94
+ return bool(self.getNamedProp('820A', ps.PSETID_APPOINTMENT))
95
+
96
+ @property
97
+ def headerFormatProperties(self) -> HEADER_FORMAT_TYPE:
98
+ meetingStatusString = {
99
+ ResponseStatus.NONE: None,
100
+ ResponseStatus.ORGANIZED: 'Meeting organizer',
101
+ ResponseStatus.TENTATIVE: 'Tentatively accepted',
102
+ ResponseStatus.ACCEPTED: 'Accepted',
103
+ ResponseStatus.DECLINED: 'Declined',
104
+ ResponseStatus.NOT_RESPONDED: 'Not yet responded',
105
+ }[self.responseStatus]
106
+
107
+ # Get the recurrence string.
108
+ recur = '(none)'
109
+ if self.appointmentRecur:
110
+ recur = {
111
+ RecurPatternType.DAY: 'Daily',
112
+ RecurPatternType.WEEK: 'Weekly',
113
+ RecurPatternType.MONTH: 'Monthly',
114
+ RecurPatternType.MONTH_NTH: 'Monthly',
115
+ RecurPatternType.MONTH_END: 'Monthly',
116
+ RecurPatternType.HJ_MONTH: 'Monthly',
117
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
118
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
119
+ }[self.appointmentRecur.patternType]
120
+
121
+ showTime = None if self.appointmentNotAllowPropose else 'Tentative'
122
+
123
+ return {
124
+ '-main info-': {
125
+ 'Subject': self.subject,
126
+ 'Location': self.location,
127
+ },
128
+ '-date-': {
129
+ 'Start': self.startDate.__format__(self.datetimeFormat) if self.startDate else None,
130
+ 'End': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
131
+ 'Show Time As': showTime,
132
+ },
133
+ '-recurrence-': {
134
+ 'Recurrance': recur,
135
+ 'Recurrence Pattern': self.recurrencePattern,
136
+ },
137
+ '-status-': {
138
+ 'Meeting Status': meetingStatusString,
139
+ },
140
+ '-attendees-': {
141
+ 'Organizer': self.organizer,
142
+ 'Required Attendees': self.to,
143
+ 'Optional Attendees': self.cc,
144
+ 'Resources': self.bcc,
145
+ },
146
+ '-importance-': {
147
+ 'Importance': self.importanceString,
148
+ },
149
+ }
150
+
151
+
152
+ @functools.cached_property
153
+ def intendedBusyStatus(self) -> Optional[BusyStatus]:
154
+ """
155
+ The value of the busyStatus on the Meeting object in the organizer's
156
+ calendar at the time the Meeting Request object or Meeting Update object
157
+ was sent.
158
+ """
159
+ return self.getNamedAs('8224', ps.PSETID_APPOINTMENT, BusyStatus)
160
+
161
+ @functools.cached_property
162
+ def meetingType(self) -> Optional[MeetingType]:
163
+ """
164
+ The type of Meeting Request object or Meeting Update object.
165
+ """
166
+ return self.getNamedAs('0026', ps.PSETID_MEETING, MeetingType)
167
+
168
+ @functools.cached_property
169
+ def oldLocation(self) -> Optional[str]:
170
+ """
171
+ The original value of the location property before a meeting update.
172
+ """
173
+ return self.getNamedProp('0028', ps.PSETID_MEETING)
174
+
175
+ @functools.cached_property
176
+ def oldWhenEndWhole(self) -> Optional[datetime.datetime]:
177
+ """
178
+ The original value of the appointmentEndWhole property before a meeting
179
+ update.
180
+ """
181
+ return self.getNamedProp('002A', ps.PSETID_MEETING)
182
+
183
+ @functools.cached_property
184
+ def oldWhenStartWhole(self) -> Optional[datetime.datetime]:
185
+ """
186
+ The original value of the appointmentStartWhole property before a
187
+ meeting update.
188
+ """
189
+ return self.getNamedProp('0029', ps.PSETID_MEETING)