extract-msg 0.48.0__tar.gz → 0.48.2__tar.gz

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 (103) hide show
  1. {extract_msg-0.48.0 → extract_msg-0.48.2}/CHANGELOG.md +10 -0
  2. {extract_msg-0.48.0 → extract_msg-0.48.2}/PKG-INFO +5 -4
  3. {extract_msg-0.48.0 → extract_msg-0.48.2}/README.rst +4 -3
  4. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/__init__.py +2 -2
  5. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/__main__.py +4 -3
  6. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/attachment_base.py +47 -16
  7. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/message_base.py +30 -10
  8. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/utils.py +13 -2
  9. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg.egg-info/PKG-INFO +5 -4
  10. {extract_msg-0.48.0 → extract_msg-0.48.2}/setup.cfg +1 -1
  11. {extract_msg-0.48.0 → extract_msg-0.48.2}/LICENSE.txt +0 -0
  12. {extract_msg-0.48.0 → extract_msg-0.48.2}/MANIFEST.in +0 -0
  13. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/_rtf/__init__.py +0 -0
  14. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/_rtf/create_doc.py +0 -0
  15. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/_rtf/inject_rtf.py +0 -0
  16. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/_rtf/token.py +0 -0
  17. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/_rtf/tokenize_rtf.py +0 -0
  18. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/__init__.py +0 -0
  19. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/attachment.py +0 -0
  20. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/broken_att.py +0 -0
  21. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/custom_att.py +0 -0
  22. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/custom_att_handler/__init__.py +0 -0
  23. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/custom_att_handler/custom_handler.py +0 -0
  24. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/custom_att_handler/lnk_obj_att.py +0 -0
  25. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/custom_att_handler/outlook_image_dib.py +0 -0
  26. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/emb_msg_att.py +0 -0
  27. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/signed_att.py +0 -0
  28. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/unsupported_att.py +0 -0
  29. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/attachments/web_att.py +0 -0
  30. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/constants/__init__.py +0 -0
  31. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/constants/ps.py +0 -0
  32. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/constants/re.py +0 -0
  33. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/constants/st.py +0 -0
  34. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/data/logging-config/logging-nt.json +0 -0
  35. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/data/logging-config/logging-posix.json +0 -0
  36. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/__init__.py +0 -0
  37. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/__init__.py +0 -0
  38. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/_mac_ce.py +0 -0
  39. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/_mac_cyrillic.py +0 -0
  40. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/_mac_greek.py +0 -0
  41. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/_mac_iceland.py +0 -0
  42. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/_mac_turkish.py +0 -0
  43. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/_win874_dec.py +0 -0
  44. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/_dt/_win950_dec.py +0 -0
  45. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/encoding/utils.py +0 -0
  46. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/enums.py +0 -0
  47. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/exceptions.py +0 -0
  48. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/__init__.py +0 -0
  49. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/appointment.py +0 -0
  50. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/calendar.py +0 -0
  51. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/calendar_base.py +0 -0
  52. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/contact.py +0 -0
  53. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/journal.py +0 -0
  54. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/meeting_cancellation.py +0 -0
  55. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/meeting_exception.py +0 -0
  56. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/meeting_forward.py +0 -0
  57. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/meeting_related.py +0 -0
  58. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/meeting_request.py +0 -0
  59. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/meeting_response.py +0 -0
  60. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/message.py +0 -0
  61. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/message_signed.py +0 -0
  62. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/message_signed_base.py +0 -0
  63. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/msg.py +0 -0
  64. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/post.py +0 -0
  65. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/sticky_note.py +0 -0
  66. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/task.py +0 -0
  67. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/msg_classes/task_request.py +0 -0
  68. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/null_date.py +0 -0
  69. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/ole_writer.py +0 -0
  70. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/open_msg.py +0 -0
  71. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/properties/__init__.py +0 -0
  72. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/properties/named.py +0 -0
  73. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/properties/prop.py +0 -0
  74. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/properties/properties_store.py +0 -0
  75. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/py.typed +0 -0
  76. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/recipient.py +0 -0
  77. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/__init__.py +0 -0
  78. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/_helpers.py +0 -0
  79. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/business_card.py +0 -0
  80. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/cfoas.py +0 -0
  81. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/contact_link_entry.py +0 -0
  82. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/dev_mode_a.py +0 -0
  83. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/dv_target_device.py +0 -0
  84. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/entry_id.py +0 -0
  85. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/misc_id.py +0 -0
  86. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/mon_stream.py +0 -0
  87. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/odt.py +0 -0
  88. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/ole_pres.py +0 -0
  89. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/ole_stream_struct.py +0 -0
  90. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/recurrence_pattern.py +0 -0
  91. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/report_tag.py +0 -0
  92. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/system_time.py +0 -0
  93. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/time_zone_definition.py +0 -0
  94. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/time_zone_struct.py +0 -0
  95. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/toc_entry.py +0 -0
  96. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg/structures/tz_rule.py +0 -0
  97. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg.egg-info/SOURCES.txt +0 -0
  98. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg.egg-info/dependency_links.txt +0 -0
  99. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg.egg-info/entry_points.txt +0 -0
  100. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg.egg-info/requires.txt +0 -0
  101. {extract_msg-0.48.0 → extract_msg-0.48.2}/extract_msg.egg-info/top_level.txt +0 -0
  102. {extract_msg-0.48.0 → extract_msg-0.48.2}/requirements.txt +0 -0
  103. {extract_msg-0.48.0 → extract_msg-0.48.2}/setup.py +0 -0
@@ -1,3 +1,13 @@
1
+ **v0.48.2**
2
+ * Fixed bugs with `MessageBase.asEmailMessage()`. Numerous improvements to how it handles the data.
3
+
4
+ **v0.48.1**
5
+ * Added an option (`-s`, `--stdin`) to the command line to take an MSG file from stdin. This allows the user to pipe the MSG data from another program directly instead of having to write a middleman that uses the `extract-msg` library directly or having to write the file to the disk first.
6
+ * Changed main function to allow for manual argument list to be passed to it.
7
+ * Added attributes to `AttachmentBase` for creation and modification time. These can be accessed through `createdAt` or `creationTime` and `lastModificationTime` or `modifiedAt`.
8
+ * Changed `OleWriter` tests to output the name of the test file being done if an error occurs.
9
+ * Added tests for some command line stuff.
10
+
1
11
  **v0.48.0**
2
12
  * Adjusted error handling for named properties to handle critical streams being missing and to allow suppression of those errors.
3
13
  * Adjusted error handling for named properties to allow silencing of errors caused by invalid references to the name stream. If `ErrorBehavior.NAMED_NAME_STREAM` is provided to the `MSGFile` instance, a warning will be logged and that entry will simply be dropped.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: extract_msg
3
- Version: 0.48.0
3
+ Version: 0.48.2
4
4
  Summary: Extracts emails and attachments saved in Microsoft Outlook's .msg files
5
5
  Home-page: https://github.com/TeamMsgExtractor/msg-extractor
6
6
  Download-URL: https://github.com/TeamMsgExtractor/msg-extractor/archives/master
@@ -79,7 +79,7 @@ refer to the usage information provided from the program's help dialog:
79
79
 
80
80
  usage: extract_msg [-h] [--use-content-id] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH] [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH] [--wk-options [WKOPTIONS ...]]
81
81
  [--prepared-html] [--charset CHARSET] [--raw] [--rtf] [--allow-fallback] [--skip-body-not-found] [--zip ZIP] [--save-header] [--attachments-only] [--skip-hidden] [--no-folders] [--skip-embedded] [--extract-embedded]
82
- [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress]
82
+ [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress] [-s]
83
83
  msg [msg ...]
84
84
 
85
85
  extract_msg: Extracts emails and attachments saved in Microsoft Outlook's .msg files. https://github.com/TeamMsgExtractor/msg-extractor
@@ -125,6 +125,7 @@ refer to the usage information provided from the program's help dialog:
125
125
  --glob, --wildcard Interpret all paths as having wildcards. Incompatible with --out-name.
126
126
  --ignore-rtfde Ignores all errors thrown from RTFDE when trying to save. Useful for allowing fallback to continue when an exception happens.
127
127
  --progress Shows what file the program is currently working on during it's progress.
128
+ -s, --stdin Read file from stdin (only works with one file at a time).
128
129
 
129
130
  **To use this in your own script**, start by using:
130
131
 
@@ -277,8 +278,8 @@ your access to the newest major version of extract-msg.
277
278
  .. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
278
279
  :target: LICENSE.txt
279
280
 
280
- .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.0-blue.svg
281
- :target: https://pypi.org/project/extract-msg/0.48.0/
281
+ .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.2-blue.svg
282
+ :target: https://pypi.org/project/extract-msg/0.48.2/
282
283
 
283
284
  .. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
284
285
  :target: https://www.python.org/downloads/release/python-3810/
@@ -61,7 +61,7 @@ refer to the usage information provided from the program's help dialog:
61
61
 
62
62
  usage: extract_msg [-h] [--use-content-id] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH] [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH] [--wk-options [WKOPTIONS ...]]
63
63
  [--prepared-html] [--charset CHARSET] [--raw] [--rtf] [--allow-fallback] [--skip-body-not-found] [--zip ZIP] [--save-header] [--attachments-only] [--skip-hidden] [--no-folders] [--skip-embedded] [--extract-embedded]
64
- [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress]
64
+ [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress] [-s]
65
65
  msg [msg ...]
66
66
 
67
67
  extract_msg: Extracts emails and attachments saved in Microsoft Outlook's .msg files. https://github.com/TeamMsgExtractor/msg-extractor
@@ -107,6 +107,7 @@ refer to the usage information provided from the program's help dialog:
107
107
  --glob, --wildcard Interpret all paths as having wildcards. Incompatible with --out-name.
108
108
  --ignore-rtfde Ignores all errors thrown from RTFDE when trying to save. Useful for allowing fallback to continue when an exception happens.
109
109
  --progress Shows what file the program is currently working on during it's progress.
110
+ -s, --stdin Read file from stdin (only works with one file at a time).
110
111
 
111
112
  **To use this in your own script**, start by using:
112
113
 
@@ -259,8 +260,8 @@ your access to the newest major version of extract-msg.
259
260
  .. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
260
261
  :target: LICENSE.txt
261
262
 
262
- .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.0-blue.svg
263
- :target: https://pypi.org/project/extract-msg/0.48.0/
263
+ .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.2-blue.svg
264
+ :target: https://pypi.org/project/extract-msg/0.48.2/
264
265
 
265
266
  .. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
266
267
  :target: https://www.python.org/downloads/release/python-3810/
@@ -27,8 +27,8 @@ https://github.com/TeamMsgExtractor/msg-extractor
27
27
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
28
28
 
29
29
  __author__ = 'Destiny Peterson & Matthew Walker'
30
- __date__ = '2024-02-28'
31
- __version__ = '0.48.0'
30
+ __date__ = '2024-03-09'
31
+ __version__ = '0.48.2'
32
32
 
33
33
  __all__ = [
34
34
  # Modules:
@@ -10,12 +10,13 @@ import zipfile
10
10
 
11
11
  from extract_msg import __doc__, openMsg, utils
12
12
  from extract_msg.enums import ErrorBehavior
13
+ from typing import List
13
14
 
14
15
 
15
- def main() -> None:
16
+ def main(argv : List[str]) -> None:
16
17
  # Setup logging to stdout, indicate running from cli
17
18
  CLI_LOGGING = 'extract_msg_cli'
18
- args = utils.getCommandArgs(sys.argv[1:])
19
+ args = utils.getCommandArgs(argv[1:])
19
20
 
20
21
  # Determine where to save the files to.
21
22
  currentDir = os.getcwd() # Store this in case the path changes.
@@ -111,4 +112,4 @@ def main() -> None:
111
112
  _zip.close()
112
113
 
113
114
  if __name__ == '__main__':
114
- main()
115
+ main(sys.argv)
@@ -473,7 +473,7 @@ class AttachmentBase(abc.ABC):
473
473
  the first item specifies what the second value will be.
474
474
  """
475
475
 
476
- @functools.cached_property
476
+ @cached_property
477
477
  def attachmentEncoding(self) -> Optional[bytes]:
478
478
  """
479
479
  The encoding information about the attachment object.
@@ -483,7 +483,7 @@ class AttachmentBase(abc.ABC):
483
483
  """
484
484
  return self.getStream('__substg1.0_37020102')
485
485
 
486
- @functools.cached_property
486
+ @cached_property
487
487
  def additionalInformation(self) -> Optional[str]:
488
488
  """
489
489
  The additional information about the attachment.
@@ -495,7 +495,7 @@ class AttachmentBase(abc.ABC):
495
495
  """
496
496
  return self.getStringStream('__substg1.0_370F')
497
497
 
498
- @functools.cached_property
498
+ @cached_property
499
499
  def cid(self) -> Optional[str]:
500
500
  """
501
501
  Returns the Content ID of the attachment, if it exists.
@@ -526,8 +526,25 @@ class AttachmentBase(abc.ABC):
526
526
 
527
527
  @property
528
528
  def contentId(self) -> Optional[str]:
529
+ """
530
+ Alias of :attr:`cid`.
531
+ """
529
532
  return self.cid
530
533
 
534
+ @property
535
+ def createdAt(self) -> Optional[datetime.datetime]:
536
+ """
537
+ Alias of :attr:`creationTime`.
538
+ """
539
+ return self.creationTime
540
+
541
+ @cached_property
542
+ def creationTime(self) -> Optional[datetime.datetime]:
543
+ """
544
+ The time the attachment was created.
545
+ """
546
+ return self.getPropertyVal('30070040')
547
+
531
548
  @property
532
549
  @abc.abstractmethod
533
550
  def data(self) -> Optional[object]:
@@ -537,7 +554,7 @@ class AttachmentBase(abc.ABC):
537
554
  Returns ``None`` if there is no data to save.
538
555
  """
539
556
 
540
- @functools.cached_property
557
+ @cached_property
541
558
  def dataType(self) -> Optional[Type[object]]:
542
559
  """
543
560
  The class that the data type will use, if it can be retrieved.
@@ -560,14 +577,14 @@ class AttachmentBase(abc.ABC):
560
577
  """
561
578
  return self.__dir
562
579
 
563
- @functools.cached_property
580
+ @cached_property
564
581
  def displayName(self) -> Optional[str]:
565
582
  """
566
583
  Returns the display name of the folder.
567
584
  """
568
585
  return self.getStringStream('__substg1.0_3001')
569
586
 
570
- @functools.cached_property
587
+ @cached_property
571
588
  def exceptionReplaceTime(self) -> Optional[datetime.datetime]:
572
589
  """
573
590
  The original date and time at which the instance in the recurrence
@@ -577,48 +594,62 @@ class AttachmentBase(abc.ABC):
577
594
  """
578
595
  return self.getPropertyVal('7FF90040')
579
596
 
580
- @functools.cached_property
597
+ @cached_property
581
598
  def extension(self) -> Optional[str]:
582
599
  """
583
600
  The reported extension for the file.
584
601
  """
585
602
  return self.getStringStream('__substg1.0_3703')
586
603
 
587
- @functools.cached_property
604
+ @cached_property
588
605
  def hidden(self) -> bool:
589
606
  """
590
607
  Indicates whether an Attachment object is hidden from the end user.
591
608
  """
592
609
  return bool(self.getPropertyVal('7FFE000B'))
593
610
 
594
- @functools.cached_property
611
+ @cached_property
595
612
  def isAttachmentContactPhoto(self) -> bool:
596
613
  """
597
614
  Whether the attachment is a contact photo for a Contact object.
598
615
  """
599
616
  return bool(self.getPropertyVal('7FFF000B'))
600
617
 
601
- @functools.cached_property
618
+ @cached_property
619
+ def lastModificationTime(self) -> Optional[datetime.datetime]:
620
+ """
621
+ The last time the attachment was modified.
622
+ """
623
+ return self.getPropertyVal('30080040')
624
+
625
+ @cached_property
602
626
  def longFilename(self) -> Optional[str]:
603
627
  """
604
628
  Returns the long file name of the attachment, if it exists.
605
629
  """
606
630
  return self.getStringStream('__substg1.0_3707')
607
631
 
608
- @functools.cached_property
632
+ @cached_property
609
633
  def longPathname(self) -> Optional[str]:
610
634
  """
611
635
  The fully qualified path and file name with extension.
612
636
  """
613
637
  return self.getStringStream('__substg1.0_370D')
614
638
 
615
- @functools.cached_property
639
+ @cached_property
616
640
  def mimetype(self) -> Optional[str]:
617
641
  """
618
642
  The content-type mime header of the attachment, if specified.
619
643
  """
620
644
  return tryGetMimetype(self, self.getStringStream('__substg1.0_370E'))
621
645
 
646
+ @property
647
+ def modifiedAt(self) -> Optional[datetime.datetime]:
648
+ """
649
+ Alias of :attr:`lastModificationTime`.
650
+ """
651
+ return self.lastModificationTime
652
+
622
653
  @property
623
654
  def msg(self) -> MSGFile:
624
655
  """
@@ -631,7 +662,7 @@ class AttachmentBase(abc.ABC):
631
662
  raise ReferenceError('The MSGFile for this Attachment instance has been garbage collected.')
632
663
  return msg
633
664
 
634
- @functools.cached_property
665
+ @cached_property
635
666
  def name(self) -> Optional[str]:
636
667
  """
637
668
  The best name available for the file.
@@ -650,7 +681,7 @@ class AttachmentBase(abc.ABC):
650
681
  """
651
682
  return self.__namedProperties
652
683
 
653
- @functools.cached_property
684
+ @cached_property
654
685
  def payloadClass(self) -> Optional[str]:
655
686
  """
656
687
  The class name of an object that can display the contents of the
@@ -665,7 +696,7 @@ class AttachmentBase(abc.ABC):
665
696
  """
666
697
  return self.__props
667
698
 
668
- @functools.cached_property
699
+ @cached_property
669
700
  def renderingPosition(self) -> Optional[int]:
670
701
  """
671
702
  The offset, in rendered characters, to use when rendering the attachment
@@ -676,7 +707,7 @@ class AttachmentBase(abc.ABC):
676
707
  """
677
708
  return self.getPropertyVal('370B0003')
678
709
 
679
- @property
710
+ @cached_property
680
711
  def shortFilename(self) -> Optional[str]:
681
712
  """
682
713
  The short file name of the attachment, if it exists.
@@ -24,7 +24,10 @@ import RTFDE
24
24
  import RTFDE.exceptions
25
25
 
26
26
  from email import policy
27
+ from email.charset import Charset, QP
27
28
  from email.message import EmailMessage
29
+ from email.mime.multipart import MIMEMultipart
30
+ from email.mime.text import MIMEText
28
31
  from email.parser import HeaderParser
29
32
  from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Type, Union
30
33
 
@@ -151,13 +154,24 @@ class MessageBase(MSGFile):
151
154
 
152
155
  # Copy the headers.
153
156
  for key, value in self.header.items():
154
- ret[key] = value
157
+ if key.lower() != 'content-type':
158
+ ret[key] = value.replace('\r\n', '').replace('\n', '')
159
+
160
+ ret['Content-Type'] = 'multipart/mixed'
155
161
 
156
162
  # Attach the body to the EmailMessage instance.
163
+ msgMain = MIMEMultipart('related')
164
+ ret.attach(msgMain)
165
+ bodyParts = MIMEMultipart('alternative')
166
+ msgMain.attach(bodyParts)
167
+
168
+ c = Charset('utf-8')
169
+ c.body_encoding = QP
170
+
171
+ if self.body:
172
+ bodyParts.attach(MIMEText(self.body, 'plain', c))
157
173
  if self.htmlBody:
158
- ret.set_content(self.body, subtype = 'html', cte = 'quoted-printable')
159
- elif self.body:
160
- ret.set_content(self.body, cte = 'quoted-printable')
174
+ bodyParts.attach(MIMEText(self.htmlBody.decode('utf-8'), 'html', c))
161
175
 
162
176
  # Process attachments.
163
177
  for att in self.attachments:
@@ -167,7 +181,7 @@ class MessageBase(MSGFile):
167
181
  filename = att.getFilename()
168
182
  if filename.lower().endswith('.msg'):
169
183
  filename = filename[:-4] + '.eml'
170
- ret.add_attachment(
184
+ msgMain.add_attachment(
171
185
  att.data.asEmailMessage(),
172
186
  filename = filename,
173
187
  cid = att.contentId)
@@ -183,11 +197,17 @@ class MessageBase(MSGFile):
183
197
  raise ConversionError(f'Could not find a suitable method to attach attachment data type "{att.dataType}".')
184
198
  mime = att.mimetype or 'application/octet-stream'
185
199
  mainType, subType = mime.split('/')[0], mime.split('/')[-1]
186
- ret.add_attachment(data,
187
- maintype = mainType,
188
- subtype = subType,
189
- filename = att.getFilename(),
190
- cid = att.contentId)
200
+ # Need to do this manually instead of using add_attachment.
201
+ attachment = EmailMessage()
202
+ attachment.set_content(data,
203
+ maintype = mainType,
204
+ subtype = subType,
205
+ cid = att.contentId)
206
+ # This is just a very basic check.
207
+ attachment['Content-Disposition'] = f'{"inline" if att.hidden else "attachment"}; filename="{att.getFilename()}"'
208
+
209
+ # Add the attachment.
210
+ msgMain.attach(attachment)
191
211
 
192
212
  return ret
193
213
 
@@ -63,6 +63,7 @@ import os
63
63
  import pathlib
64
64
  import shutil
65
65
  import struct
66
+ import sys
66
67
  import weakref
67
68
  import zipfile
68
69
 
@@ -359,6 +360,7 @@ def getCommandArgs(args: Sequence[str]) -> argparse.Namespace:
359
360
  parser = argparse.ArgumentParser(description = constants.MAINDOC, prog = 'extract_msg')
360
361
  outFormat = parser.add_mutually_exclusive_group()
361
362
  inputFormat = parser.add_mutually_exclusive_group()
363
+ inputType = parser.add_mutually_exclusive_group(required = True)
362
364
  # --use-content-id, --cid
363
365
  parser.add_argument('--use-content-id', '--cid', dest='cid', action='store_true',
364
366
  help='Save attachments by their Content ID, if they have one. Useful when working with the HTML body.')
@@ -368,7 +370,7 @@ def getCommandArgs(args: Sequence[str]) -> argparse.Namespace:
368
370
  # --file-logging
369
371
  parser.add_argument('--file-logging', dest='fileLogging', action='store_true',
370
372
  help='Enables file logging. Implies --verbose level 1.')
371
- # --verbose
373
+ # -v, --verbose
372
374
  parser.add_argument('-v', '--verbose', dest='verbose', action='count', default=0,
373
375
  help='Turns on console logging. Specify more than once for higher verbosity.')
374
376
  # --log PATH
@@ -455,12 +457,19 @@ def getCommandArgs(args: Sequence[str]) -> argparse.Namespace:
455
457
  # --progress
456
458
  parser.add_argument('--progress', dest='progress', action='store_true',
457
459
  help='Shows what file the program is currently working on during it\'s progress.')
460
+ # -s, --stdout
461
+ inputType.add_argument('-s', '--stdin', dest='stdin', action='store_true',
462
+ help='Read file from stdin (only works with one file at a time).')
458
463
  # [MSG files]
459
- parser.add_argument('msgs', metavar='msg', nargs='+',
464
+ inputType.add_argument('msgs', metavar='msg', nargs='*', default=[],
460
465
  help='An MSG file to be parsed.')
461
466
 
462
467
  options = parser.parse_args(args)
463
468
 
469
+ if options.stdin:
470
+ # Read the MSG file from stdin and shove it into the msgs list.
471
+ options.msgs.append(sys.stdin.buffer.read())
472
+
464
473
  if options.outName and options.noFolders:
465
474
  raise IncompatibleOptionsError('--out-name is not compatible with --no-folders.')
466
475
 
@@ -502,6 +511,8 @@ def getCommandArgs(args: Sequence[str]) -> argparse.Namespace:
502
511
  if options.glob:
503
512
  if options.outName:
504
513
  raise IncompatibleOptionsError('--out-name is not supported when using wildcards.')
514
+ if options.stdin:
515
+ raise IncompatibleOptionsError('--stdin is not supported with using wildcards.')
505
516
  fileLists = []
506
517
  for path in options.msgs:
507
518
  fileLists += glob.glob(path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: extract-msg
3
- Version: 0.48.0
3
+ Version: 0.48.2
4
4
  Summary: Extracts emails and attachments saved in Microsoft Outlook's .msg files
5
5
  Home-page: https://github.com/TeamMsgExtractor/msg-extractor
6
6
  Download-URL: https://github.com/TeamMsgExtractor/msg-extractor/archives/master
@@ -79,7 +79,7 @@ refer to the usage information provided from the program's help dialog:
79
79
 
80
80
  usage: extract_msg [-h] [--use-content-id] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH] [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH] [--wk-options [WKOPTIONS ...]]
81
81
  [--prepared-html] [--charset CHARSET] [--raw] [--rtf] [--allow-fallback] [--skip-body-not-found] [--zip ZIP] [--save-header] [--attachments-only] [--skip-hidden] [--no-folders] [--skip-embedded] [--extract-embedded]
82
- [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress]
82
+ [--overwrite-existing] [--skip-not-implemented] [--out-name OUTNAME | --glob] [--ignore-rtfde] [--progress] [-s]
83
83
  msg [msg ...]
84
84
 
85
85
  extract_msg: Extracts emails and attachments saved in Microsoft Outlook's .msg files. https://github.com/TeamMsgExtractor/msg-extractor
@@ -125,6 +125,7 @@ refer to the usage information provided from the program's help dialog:
125
125
  --glob, --wildcard Interpret all paths as having wildcards. Incompatible with --out-name.
126
126
  --ignore-rtfde Ignores all errors thrown from RTFDE when trying to save. Useful for allowing fallback to continue when an exception happens.
127
127
  --progress Shows what file the program is currently working on during it's progress.
128
+ -s, --stdin Read file from stdin (only works with one file at a time).
128
129
 
129
130
  **To use this in your own script**, start by using:
130
131
 
@@ -277,8 +278,8 @@ your access to the newest major version of extract-msg.
277
278
  .. |License: GPL v3| image:: https://img.shields.io/badge/License-GPLv3-blue.svg
278
279
  :target: LICENSE.txt
279
280
 
280
- .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.0-blue.svg
281
- :target: https://pypi.org/project/extract-msg/0.48.0/
281
+ .. |PyPI3| image:: https://img.shields.io/badge/pypi-0.48.2-blue.svg
282
+ :target: https://pypi.org/project/extract-msg/0.48.2/
282
283
 
283
284
  .. |PyPI2| image:: https://img.shields.io/badge/python-3.8+-brightgreen.svg
284
285
  :target: https://www.python.org/downloads/release/python-3810/
@@ -1,5 +1,5 @@
1
1
  [bdist_wheel]
2
- universal = 1
2
+ python-tag = py3
3
3
 
4
4
  [options.extras_require]
5
5
  all =
File without changes
File without changes
File without changes