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,422 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'BusinessCardDisplayDefinition',
|
|
6
|
+
'FieldInfo',
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import struct
|
|
12
|
+
|
|
13
|
+
from typing import List, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
from ._helpers import BytesReader
|
|
16
|
+
from .. import constants
|
|
17
|
+
from ..enums import (
|
|
18
|
+
BCImageAlignment, BCImageSource, BCLabelFormat, BCTemplateID,
|
|
19
|
+
BCTextFormat
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
logger.addHandler(logging.NullHandler())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BusinessCardDisplayDefinition:
|
|
28
|
+
"""
|
|
29
|
+
Data structure for PidLidBusinessCardDisplayDefinition.
|
|
30
|
+
|
|
31
|
+
Contains information used to contruct a business card for a contact.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, data: bytes):
|
|
35
|
+
reader = BytesReader(data)
|
|
36
|
+
unpacked = constants.st.ST_BC_HEAD.unpack(reader.read(13))
|
|
37
|
+
# Because doc says it must be ignored, we don't check the reserved here.
|
|
38
|
+
reader.read(4)
|
|
39
|
+
self.__majorVersion = unpacked[0]
|
|
40
|
+
if self.__majorVersion < 3:
|
|
41
|
+
raise ValueError('Major version was less than 3.')
|
|
42
|
+
self.__minorVersion = unpacked[1]
|
|
43
|
+
self.__templateID = BCTemplateID(unpacked[2])
|
|
44
|
+
countOfFields = unpacked[3]
|
|
45
|
+
if unpacked[4] != 16:
|
|
46
|
+
raise ValueError('Value of FieldInfoSize was not 16.')
|
|
47
|
+
extraInfoSize = unpacked[5]
|
|
48
|
+
self.__imageAlignment = BCImageAlignment(unpacked[6])
|
|
49
|
+
self.__imageSource = BCImageSource(min(unpacked[7], 1))
|
|
50
|
+
self.__backgroundColor = unpacked[8:11]
|
|
51
|
+
self.__imageArea = unpacked[11]
|
|
52
|
+
extraInfoField = data[17 + 16 * countOfFields:]
|
|
53
|
+
extraInfoField = data[:extraInfoSize]
|
|
54
|
+
self.__fields = [FieldInfo(reader.read(16), extraInfoField)
|
|
55
|
+
for _ in range(countOfFields)]
|
|
56
|
+
|
|
57
|
+
def __bytes__(self) -> bytes:
|
|
58
|
+
return self.toBytes()
|
|
59
|
+
|
|
60
|
+
def toBytes(self) -> bytes:
|
|
61
|
+
# Pregenerate the extraInfo field.
|
|
62
|
+
offsets = []
|
|
63
|
+
extraInfo = b''
|
|
64
|
+
for info in self.__fields:
|
|
65
|
+
if info.labelText:
|
|
66
|
+
offsets.append(len(extraInfo))
|
|
67
|
+
extraInfo += info.labelText.encode('utf-16-le') + b'\x00\x00'
|
|
68
|
+
else:
|
|
69
|
+
offsets.append(0xFFFE)
|
|
70
|
+
|
|
71
|
+
if len(extraInfo) > 255:
|
|
72
|
+
raise ValueError('Label data can only be 127 characters total, including null characters.')
|
|
73
|
+
|
|
74
|
+
# Now that we have what we need, pack the header.
|
|
75
|
+
ret = constants.st.ST_BC_HEAD.pack(
|
|
76
|
+
self.__majorVersion,
|
|
77
|
+
self.__minorVersion,
|
|
78
|
+
self.__templateID,
|
|
79
|
+
len(self.__fields),
|
|
80
|
+
16,
|
|
81
|
+
len(extraInfo),
|
|
82
|
+
self.__imageAlignment,
|
|
83
|
+
self.__imageSource,
|
|
84
|
+
*self.__backgroundColor,
|
|
85
|
+
self.__imageArea
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Add the reserved.
|
|
89
|
+
ret += b'\x00\x00\x00\x00'
|
|
90
|
+
|
|
91
|
+
# Add each FieldInfo structure.
|
|
92
|
+
for index, info in enumerate(self.__fields):
|
|
93
|
+
ret += info.toBytes(offsets[index])
|
|
94
|
+
|
|
95
|
+
return ret + extraInfo
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def backgroundColor(self) -> Tuple[int, int, int]:
|
|
99
|
+
"""
|
|
100
|
+
A tuple of the RGB value of the color of the background.
|
|
101
|
+
"""
|
|
102
|
+
return self.__backgroundColor
|
|
103
|
+
|
|
104
|
+
@backgroundColor.setter
|
|
105
|
+
def backgroundColor(self, value: Tuple[int, int, int]) -> None:
|
|
106
|
+
if not isinstance(value, tuple) or len(value) != 3:
|
|
107
|
+
raise TypeError(':property backgroundColor: MUST be a tuple of 3 ints.')
|
|
108
|
+
# Quickly try to pack the ints and raise a value error if that fails.
|
|
109
|
+
try:
|
|
110
|
+
constants.st.ST_RGB(*value)
|
|
111
|
+
except struct.error:
|
|
112
|
+
raise ValueError('Value for :property backgroundColor: not in range.')
|
|
113
|
+
|
|
114
|
+
self.__backgroundColor = value
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def fields(self) -> List[FieldInfo]:
|
|
118
|
+
"""
|
|
119
|
+
The field info structures.
|
|
120
|
+
"""
|
|
121
|
+
return self.__fields
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def imageAlignment(self) -> BCImageAlignment:
|
|
125
|
+
"""
|
|
126
|
+
The alignment of the image within the image area.
|
|
127
|
+
|
|
128
|
+
Ignored if card is text only.
|
|
129
|
+
"""
|
|
130
|
+
return self.__imageAlignment
|
|
131
|
+
|
|
132
|
+
@imageAlignment.setter
|
|
133
|
+
def imageAlignment(self, value: BCImageAlignment) -> None:
|
|
134
|
+
if not isinstance(value, BCImageAlignment):
|
|
135
|
+
raise TypeError(':property imageAlignment: MUST be an instance of BCImageAlignment.')
|
|
136
|
+
|
|
137
|
+
self.__imageAlignment = value
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def imageArea(self) -> int:
|
|
141
|
+
"""
|
|
142
|
+
An integer that specified the percent of space that the image will
|
|
143
|
+
occupy on the business card.
|
|
144
|
+
|
|
145
|
+
Should be between 4 and 50.
|
|
146
|
+
"""
|
|
147
|
+
return self.__imageArea
|
|
148
|
+
|
|
149
|
+
@imageArea.setter
|
|
150
|
+
def imageArea(self, value: int) -> None:
|
|
151
|
+
if not isinstance(value, int):
|
|
152
|
+
raise TypeError(':property imageArea: MUST be an int.')
|
|
153
|
+
if value < 0:
|
|
154
|
+
raise ValueError(':property imageArea: MUST be positive.')
|
|
155
|
+
if value > 0xFF:
|
|
156
|
+
raise ValueError(':property imageArea: MUST be less than 0x100.')
|
|
157
|
+
|
|
158
|
+
if value < 4 or value > 50:
|
|
159
|
+
logger.warning(f'Business card image area was set to a value outside of suggested range of [4, 50] (got {value}).')
|
|
160
|
+
|
|
161
|
+
self.__imageArea = value
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def imageSource(self) -> BCImageSource:
|
|
165
|
+
"""
|
|
166
|
+
The source of the image.
|
|
167
|
+
"""
|
|
168
|
+
return self.__imageSource
|
|
169
|
+
|
|
170
|
+
@imageSource.setter
|
|
171
|
+
def imageSource(self, value: BCImageSource) -> None:
|
|
172
|
+
if not isinstance(value, BCImageSource):
|
|
173
|
+
raise TypeError(':property imageSource: MUST be an instance of BCImageSource.')
|
|
174
|
+
|
|
175
|
+
self.__imageSource = value
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def majorVersion(self) -> int:
|
|
179
|
+
"""
|
|
180
|
+
An 8-bit value that specified the major version number.
|
|
181
|
+
|
|
182
|
+
Must be 3 or greater.
|
|
183
|
+
"""
|
|
184
|
+
return self.__majorVersion
|
|
185
|
+
|
|
186
|
+
@majorVersion.setter
|
|
187
|
+
def majorVersion(self, value: int) -> None:
|
|
188
|
+
if not isinstance(value, int):
|
|
189
|
+
raise TypeError(':property majorVersion: MUST be an int.')
|
|
190
|
+
if value < 3:
|
|
191
|
+
raise ValueError(':property majorVersion: MUST be 3 or greater.')
|
|
192
|
+
if value > 0xFF:
|
|
193
|
+
raise ValueError(':property majorVersion: MUST be less than 0x100.')
|
|
194
|
+
|
|
195
|
+
self.__majorVersion = value
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def minorVersion(self) -> int:
|
|
199
|
+
"""
|
|
200
|
+
An 8-bit value that specifies the minor version number.
|
|
201
|
+
|
|
202
|
+
SHOULD be set to 0.
|
|
203
|
+
"""
|
|
204
|
+
return self.__minorVersion
|
|
205
|
+
|
|
206
|
+
@minorVersion.setter
|
|
207
|
+
def minorVersion(self, value: int) -> None:
|
|
208
|
+
if not isinstance(value, int):
|
|
209
|
+
raise TypeError(':property minorVersion: MUST be an int.')
|
|
210
|
+
if value < 0:
|
|
211
|
+
raise ValueError(':property minorVersion: MUST be positive.')
|
|
212
|
+
if value > 0xFF:
|
|
213
|
+
raise ValueError(':property minorVersion: MUST be less than 0x100.')
|
|
214
|
+
|
|
215
|
+
if value != 0:
|
|
216
|
+
logger.warning('Business card minor version set to non-zero value.')
|
|
217
|
+
|
|
218
|
+
self.__minorVersion = value
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def templateID(self) -> BCTemplateID:
|
|
222
|
+
"""
|
|
223
|
+
The layout of the business card.
|
|
224
|
+
"""
|
|
225
|
+
return self.__templateID
|
|
226
|
+
|
|
227
|
+
@templateID.setter
|
|
228
|
+
def templateID(self, value: BCTemplateID):
|
|
229
|
+
if not isinstance(value, BCTemplateID):
|
|
230
|
+
raise TypeError(':property templateID: MUST be an instance of BCTemplateID.')
|
|
231
|
+
|
|
232
|
+
self.__templateID = value
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class FieldInfo:
|
|
237
|
+
def __init__(self, data: Optional[bytes] = None, extraInfo: Optional[bytes] = None):
|
|
238
|
+
if not data:
|
|
239
|
+
self.__textPropertyID = 0
|
|
240
|
+
self.__textFormat = BCTextFormat.DEFAULT
|
|
241
|
+
self.__labelFormat = BCLabelFormat.NO_LABEL
|
|
242
|
+
self.__fontSize = 0
|
|
243
|
+
self.__labelText = None
|
|
244
|
+
self.__valueFontColor = (0, 0, 0)
|
|
245
|
+
self.__labelFontColor = (0, 0, 0)
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
if extraInfo is None:
|
|
249
|
+
raise ValueError(':param extraInfo: MUST NOT be None if data is provided.')
|
|
250
|
+
|
|
251
|
+
unpacked = constants.st.ST_BC_FIELD_INFO.unpack(data)
|
|
252
|
+
self.__textPropertyID = unpacked[0]
|
|
253
|
+
self.__textFormat = BCTextFormat(unpacked[1])
|
|
254
|
+
self.__labelFormat = BCLabelFormat(unpacked[2])
|
|
255
|
+
self.__fontSize = unpacked[3]
|
|
256
|
+
self.__labelText = None if unpacked[4] == 0xFFFE else BytesReader(extraInfo[unpacked[4]:]).readUtf16String()
|
|
257
|
+
self.__valueFontColor = unpacked[5:8]
|
|
258
|
+
self.__labelFontColor = unpacked[8:11]
|
|
259
|
+
|
|
260
|
+
def toBytes(self, offset: int) -> bytes:
|
|
261
|
+
"""
|
|
262
|
+
Converts to bytes using the offset into the ExtraInfo field.
|
|
263
|
+
|
|
264
|
+
:raise ValueError: The offset was out of range.
|
|
265
|
+
"""
|
|
266
|
+
if offset < 0 or offset > 0xFFFF:
|
|
267
|
+
raise ValueError('Offset is out of range.')
|
|
268
|
+
|
|
269
|
+
if not self.__labelText:
|
|
270
|
+
offset = 0xFFFE
|
|
271
|
+
|
|
272
|
+
return constants.st.ST_BC_FIELD_INFO.pack(
|
|
273
|
+
self.__textPropertyID,
|
|
274
|
+
self.__textFormat,
|
|
275
|
+
self.__labelFormat,
|
|
276
|
+
self.__fontSize,
|
|
277
|
+
offset,
|
|
278
|
+
*self.__valueFontColor,
|
|
279
|
+
*self.__labelFontColor
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def fontSize(self) -> int:
|
|
284
|
+
"""
|
|
285
|
+
An integer that specifies the font size, in points, of the text field.
|
|
286
|
+
|
|
287
|
+
MUST be between 3 and 32, or MUST be 0 if the text field is displayed as
|
|
288
|
+
an empty line.
|
|
289
|
+
"""
|
|
290
|
+
return self.__fontSize
|
|
291
|
+
|
|
292
|
+
@fontSize.setter
|
|
293
|
+
def fontSize(self, value: int) -> None:
|
|
294
|
+
if not isinstance(value, int):
|
|
295
|
+
raise TypeError(':property fontSize: MUST be an int.')
|
|
296
|
+
|
|
297
|
+
# Unsure if this is meant to be inclusive or exclusive.
|
|
298
|
+
if value == 0 or 3 <= value <= 32:
|
|
299
|
+
self.__fontSize = value
|
|
300
|
+
else:
|
|
301
|
+
raise ValueError(':property fontSize: MUST be 0 or between 3 and 32, inclusive.')
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def labelFontColor(self) -> Tuple[int, int, int]:
|
|
305
|
+
"""
|
|
306
|
+
A tuple of the RGB value of the color of the label.
|
|
307
|
+
|
|
308
|
+
Each channel is a number in range [0, 256).
|
|
309
|
+
"""
|
|
310
|
+
return self.__labelFontColor
|
|
311
|
+
|
|
312
|
+
@labelFontColor.setter
|
|
313
|
+
def labelFontColor(self, value: Tuple[int, int, int]) -> None:
|
|
314
|
+
if not isinstance(value, tuple) or len(value) != 3:
|
|
315
|
+
raise TypeError(':property labelFontColor: MUST be a tuple of 3 ints.')
|
|
316
|
+
# Quickly try to pack the ints and raise a value error if that fails.
|
|
317
|
+
try:
|
|
318
|
+
constants.st.ST_RGB(*value)
|
|
319
|
+
except struct.error:
|
|
320
|
+
raise ValueError('Value for :property labelFontColor: not in range.')
|
|
321
|
+
|
|
322
|
+
self.__labelFontColor = value
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def labelFormat(self) -> BCLabelFormat:
|
|
326
|
+
"""
|
|
327
|
+
The format to use for the label.
|
|
328
|
+
"""
|
|
329
|
+
return self.__labelFormat
|
|
330
|
+
|
|
331
|
+
@labelFormat.setter
|
|
332
|
+
def labelFormat(self, value: BCLabelFormat) -> None:
|
|
333
|
+
if not isinstance(value, BCLabelFormat):
|
|
334
|
+
raise TypeError(':property labelFormat: MUST be an instance of BCLabelFormat.')
|
|
335
|
+
# Check mutually exclusive flags.
|
|
336
|
+
if BCLabelFormat.ALIGN_LEFT in value and BCLabelFormat.ALIGN_RIGHT in value:
|
|
337
|
+
raise ValueError('BCLabelFormat.ALIGN_LEFT and BCLabelFormat.ALIGN_RIGHT are mutually exclusive.')
|
|
338
|
+
|
|
339
|
+
if value and BCLabelFormat.ALIGN_LEFT not in value and BCLabelFormat.ALIGN_RIGHT not in value:
|
|
340
|
+
raise ValueError(':property labelFormat: MUST have the ALIGN_LEFT or ALIGN_RIGHT bit set if any other bits are set.')
|
|
341
|
+
|
|
342
|
+
if BCLabelFormat > 0x110:
|
|
343
|
+
raise ValueError('Unknown bits set for :property labelFormat:.')
|
|
344
|
+
|
|
345
|
+
self.__textPropertyID = value
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def labelText(self) -> Optional[str]:
|
|
349
|
+
"""
|
|
350
|
+
The text of the label, if it exists.
|
|
351
|
+
"""
|
|
352
|
+
return self.__labelText
|
|
353
|
+
|
|
354
|
+
@labelText.setter
|
|
355
|
+
def labelText(self, value: Optional[str]) -> None:
|
|
356
|
+
if not value:
|
|
357
|
+
self.__labelText = None
|
|
358
|
+
elif not isinstance(value, str):
|
|
359
|
+
raise TypeError(':property labelText: MUST be a str or None.')
|
|
360
|
+
|
|
361
|
+
self.__labelText = value
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def textFormat(self) -> BCTextFormat:
|
|
365
|
+
"""
|
|
366
|
+
An enum value representing the formatting to use for the text.
|
|
367
|
+
"""
|
|
368
|
+
return self.__textFormat
|
|
369
|
+
|
|
370
|
+
@textFormat.setter
|
|
371
|
+
def textFormat(self, value: BCTextFormat) -> None:
|
|
372
|
+
if not isinstance(value, BCTextFormat):
|
|
373
|
+
raise TypeError(':property textFormat: MUST be an instance of BCTextFormat.')
|
|
374
|
+
# Check mutually exclusive flags.
|
|
375
|
+
if BCTextFormat.CENTER in value and BCTextFormat.RIGHT in value:
|
|
376
|
+
raise ValueError('BCTextFormat.RIGHT and BCTextFormat.CENTER are mutually exclusive.')
|
|
377
|
+
|
|
378
|
+
if BCTextFormat > 0x101111:
|
|
379
|
+
raise ValueError('Unknown bits set for :property textFormat:.')
|
|
380
|
+
|
|
381
|
+
self.__textPropertyID = value
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def textPropertyID(self) -> int:
|
|
385
|
+
"""
|
|
386
|
+
The property to be used for the text field.
|
|
387
|
+
|
|
388
|
+
If the value is 0, it represents an empty field.
|
|
389
|
+
"""
|
|
390
|
+
return self.__textPropertyID
|
|
391
|
+
|
|
392
|
+
@textPropertyID.setter
|
|
393
|
+
def textPropertyID(self, value: int) -> None:
|
|
394
|
+
if not isinstance(value, int):
|
|
395
|
+
raise TypeError(':property textPropertyID: MUST be an int.')
|
|
396
|
+
if value < 0:
|
|
397
|
+
raise ValueError(':property textPropertyID: MUST be positive.')
|
|
398
|
+
if value > 0xFFFF:
|
|
399
|
+
raise ValueError(':property textPropertyID: MUST NOT be greater than 0xFFFF.')
|
|
400
|
+
|
|
401
|
+
self.__textPropertyID = value
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def valueFontColor(self) -> Tuple[int, int, int]:
|
|
405
|
+
"""
|
|
406
|
+
A tuple of the RGB value of the color of the text field.
|
|
407
|
+
|
|
408
|
+
Each channel is a number in range [0, 256).
|
|
409
|
+
"""
|
|
410
|
+
return self.__valueFontColor
|
|
411
|
+
|
|
412
|
+
@valueFontColor.setter
|
|
413
|
+
def valueFontColor(self, value: Tuple[int, int, int]) -> None:
|
|
414
|
+
if not isinstance(value, tuple) or len(value) != 3:
|
|
415
|
+
raise TypeError(':property valueFontColor: MUST be a tuple of 3 ints.')
|
|
416
|
+
# Quickly try to pack the ints and raise a value error if that fails.
|
|
417
|
+
try:
|
|
418
|
+
constants.st.ST_RGB(*value)
|
|
419
|
+
except struct.error:
|
|
420
|
+
raise ValueError('Value for :property valueFontColor: not in range.')
|
|
421
|
+
|
|
422
|
+
self.__valueFontColor = value
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'ClipboardFormatOrAnsiString',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import Optional, Union
|
|
7
|
+
|
|
8
|
+
from .. import constants
|
|
9
|
+
from ._helpers import BytesReader
|
|
10
|
+
from ..enums import ClipboardFormat
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ClipboardFormatOrAnsiString:
|
|
14
|
+
def __init__(self, reader: Optional[Union[bytes, BytesReader]] = None):
|
|
15
|
+
if reader is None:
|
|
16
|
+
self.__markerOrLength = 0
|
|
17
|
+
self.__clipboardFormat = None
|
|
18
|
+
self.__ansiString = None
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
if isinstance(reader, bytes):
|
|
22
|
+
reader = BytesReader(reader)
|
|
23
|
+
|
|
24
|
+
self.__markerOrLength = reader.readUnsignedInt()
|
|
25
|
+
if self.__markerOrLength > 0xFFFFFFFD:
|
|
26
|
+
self.__ansiString = None
|
|
27
|
+
self.__clipboardFormat = ClipboardFormat(reader.readUnsignedInt())
|
|
28
|
+
elif self.__markerOrLength > 0:
|
|
29
|
+
self.__ansiString = reader.read(self.__markerOrLength)
|
|
30
|
+
self.__clipboardFormat = None
|
|
31
|
+
else:
|
|
32
|
+
self.__ansiString = None
|
|
33
|
+
self.__clipboardFormat = None
|
|
34
|
+
|
|
35
|
+
def __bytes__(self) -> bytes:
|
|
36
|
+
return self.toBytes()
|
|
37
|
+
|
|
38
|
+
def toBytes(self) -> bytes:
|
|
39
|
+
ret = constants.st.ST_LE_UI32.pack(self.markerOrLength)
|
|
40
|
+
if self.markerOrLength > 0xFFFFFFFD:
|
|
41
|
+
ret += constants.st.ST_LE_UI32.pack(self.clipboardFormat)
|
|
42
|
+
elif self.markerOrLength > 0:
|
|
43
|
+
ret += self.__ansiString
|
|
44
|
+
return ret
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def ansiString(self) -> Optional[bytes]:
|
|
48
|
+
"""
|
|
49
|
+
The null-terminated ANSI string, as bytes, of the name of a registered
|
|
50
|
+
clipboard format. Only set if markerOrLength is not ``0x00000000``,
|
|
51
|
+
``0xFFFFFFFE``, or ``0xFFFFFFFF``.
|
|
52
|
+
|
|
53
|
+
Setting this will modify the markerOrLength field automatically.
|
|
54
|
+
"""
|
|
55
|
+
return self.__ansiString
|
|
56
|
+
|
|
57
|
+
@ansiString.setter
|
|
58
|
+
def ansiString(self, val: bytes) -> None:
|
|
59
|
+
if not val:
|
|
60
|
+
raise ValueError('Cannot set :property ansiString: to None or empty bytes.')
|
|
61
|
+
|
|
62
|
+
self.__ansiString = val
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def clipboardFormat(self) -> Optional[ClipboardFormat]:
|
|
66
|
+
"""
|
|
67
|
+
The clipboard format, if any.
|
|
68
|
+
|
|
69
|
+
To set this, make sure that :property markerOrLength: is ``0xFFFFFFFE``
|
|
70
|
+
or ``0xFFFFFFFF`` *before* setting.
|
|
71
|
+
"""
|
|
72
|
+
return self.__clipboardFormat
|
|
73
|
+
|
|
74
|
+
@clipboardFormat.setter
|
|
75
|
+
def clipboardFormat(self, val: ClipboardFormat) -> None:
|
|
76
|
+
if not val:
|
|
77
|
+
raise ValueError('Cannot set clipboard format to None.')
|
|
78
|
+
if self.markerOrLength < 0xFFFFFFFE:
|
|
79
|
+
raise ValueError('Cannot set the clipboard format while the marker or length is not 0xFFFFFFFE or 0xFFFFFFFF')
|
|
80
|
+
self.__clipboardFormat = val
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def markerOrLength(self) -> int:
|
|
84
|
+
"""
|
|
85
|
+
If set the 0x00000000, then neither the format property nor the
|
|
86
|
+
ansiString property will be set. If it is 0xFFFFFFFF or 0xFFFFFFFE, then
|
|
87
|
+
the clipboardFormat property will be set. Otherwise, the ansiString
|
|
88
|
+
property
|
|
89
|
+
will be set.
|
|
90
|
+
"""
|
|
91
|
+
return self.__markerOrLength
|
|
92
|
+
|
|
93
|
+
@markerOrLength.setter
|
|
94
|
+
def markerOrLength(self, val: int) -> None:
|
|
95
|
+
if val < 0:
|
|
96
|
+
raise ValueError(':property markerOrLength: must be a positive integer.')
|
|
97
|
+
if val > 0xFFFFFFFF:
|
|
98
|
+
raise ValueError(':property markerOrLength: cannot be greater than 0xFFFFFFFF')
|
|
99
|
+
|
|
100
|
+
if val == 0:
|
|
101
|
+
self.__ansiString = None
|
|
102
|
+
self.__clipboardFormat = None
|
|
103
|
+
elif val > 0xFFFFFFFD:
|
|
104
|
+
self.__ansiString = None
|
|
105
|
+
self.__clipboardFormat = ClipboardFormat.CF_BITMAP
|
|
106
|
+
else:
|
|
107
|
+
raise ValueError('Cannot set :property markerOrLength: to a length value. Set :property ansiString: instead.')
|
|
108
|
+
self.__markerOrLength = val
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'ContactLinkEntry',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from ._helpers import BytesReader
|
|
9
|
+
from ..constants import st
|
|
10
|
+
from .entry_id import EntryID
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ContactLinkEntry:
|
|
14
|
+
entries: List[EntryID]
|
|
15
|
+
|
|
16
|
+
def __init__(self, data: bytes):
|
|
17
|
+
# My experience with this data almost entirely doesn't match the
|
|
18
|
+
# documentation, so I'm just going to do what I see and not what I'm
|
|
19
|
+
# told.
|
|
20
|
+
reader = BytesReader(data)
|
|
21
|
+
count = reader.readUnsignedInt()
|
|
22
|
+
# Ignore this field.
|
|
23
|
+
reader.read(4)
|
|
24
|
+
self.entries = []
|
|
25
|
+
for _ in range(count):
|
|
26
|
+
size = reader.readUnsignedInt()
|
|
27
|
+
self.entries.append(EntryID.autoCreate(reader.read(size)))
|
|
28
|
+
if (size & 3) != 0:
|
|
29
|
+
reader.read(4 - (size & 3))
|
|
30
|
+
|
|
31
|
+
def __bytes__(self) -> bytes:
|
|
32
|
+
return self.toBytes()
|
|
33
|
+
|
|
34
|
+
def toBytes(self) -> bytes:
|
|
35
|
+
ret = st.ST_LE_UI32.pack(len(self.entries))
|
|
36
|
+
|
|
37
|
+
# Need to handle the data before hand.
|
|
38
|
+
data = b''
|
|
39
|
+
for entry in self.entries:
|
|
40
|
+
entryData = entry.toBytes()
|
|
41
|
+
# Size goes before data.
|
|
42
|
+
data += st.ST_LE_UI32.pack(edLen := len(entryData))
|
|
43
|
+
data += entryData
|
|
44
|
+
# Handle padding.
|
|
45
|
+
if edLen & 3:
|
|
46
|
+
data += b'\x00' * (4 - edLen)
|
|
47
|
+
|
|
48
|
+
ret += st.ST_LE_UI32.pack(len(data))
|
|
49
|
+
ret += data
|
|
50
|
+
|
|
51
|
+
return ret
|