PyEmailerAJM 1.6__tar.gz → 1.7__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.
- {pyemailerajm-1.6 → pyemailerajm-1.7}/PKG-INFO +5 -2
- pyemailerajm-1.7/PyEmailerAJM/__init__.py +8 -0
- pyemailerajm-1.7/PyEmailerAJM/_version.py +1 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/PyEmailerAJM/py_emailer_ajm.py +12 -77
- {pyemailerajm-1.6 → pyemailerajm-1.7}/PyEmailerAJM.egg-info/PKG-INFO +5 -2
- {pyemailerajm-1.6 → pyemailerajm-1.7}/PyEmailerAJM.egg-info/SOURCES.txt +0 -2
- pyemailerajm-1.7/PyEmailerAJM.egg-info/requires.txt +4 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/setup.py +1 -1
- pyemailerajm-1.6/PyEmailerAJM/__init__.py +0 -8
- pyemailerajm-1.6/PyEmailerAJM/_version.py +0 -1
- pyemailerajm-1.6/PyEmailerAJM/errs.py +0 -6
- pyemailerajm-1.6/PyEmailerAJM/msg.py +0 -230
- pyemailerajm-1.6/PyEmailerAJM.egg-info/requires.txt +0 -1
- {pyemailerajm-1.6 → pyemailerajm-1.7}/LICENSE.txt +0 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/PyEmailerAJM/helpers.py +0 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/PyEmailerAJM.egg-info/dependency_links.txt +0 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/PyEmailerAJM.egg-info/top_level.txt +0 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/README.md +0 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/setup.cfg +0 -0
- {pyemailerajm-1.6 → pyemailerajm-1.7}/tests/test_PyEmailerAJM.py +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyEmailerAJM
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7
|
|
4
4
|
Summary: Allows for automating sending Email with the Outlook Desktop client. Future releases will add more client support
|
|
5
5
|
Home-page: https://github.com/amcsparron2793-Water/PyEmailer
|
|
6
|
-
Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.
|
|
6
|
+
Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.7.tar.gz
|
|
7
7
|
Author: Amcsparron
|
|
8
8
|
Author-email: amcsparron@albanyny.gov
|
|
9
9
|
License: MIT License
|
|
10
10
|
Keywords: Outlook,Email,Automation
|
|
11
11
|
License-File: LICENSE.txt
|
|
12
12
|
Requires-Dist: pywin32
|
|
13
|
+
Requires-Dist: extract_msg
|
|
14
|
+
Requires-Dist: email_validator
|
|
15
|
+
Requires-Dist: questionary
|
|
13
16
|
Dynamic: author
|
|
14
17
|
Dynamic: author-email
|
|
15
18
|
Dynamic: download-url
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from PyEmailerAJM.backend import deprecated
|
|
2
|
+
from PyEmailerAJM.backend.errs import EmailerNotSetupError, DisplayManualQuit
|
|
3
|
+
from PyEmailerAJM.msg import Msg, FailedMsg
|
|
4
|
+
from PyEmailerAJM.searchers import BaseSearcher, SubjectSearcher
|
|
5
|
+
from PyEmailerAJM.py_emailer_ajm import PyEmailer, EmailerInitializer
|
|
6
|
+
|
|
7
|
+
__all__ = ['EmailerNotSetupError', 'DisplayManualQuit', 'deprecated',
|
|
8
|
+
'Msg', 'FailedMsg', 'PyEmailer', 'EmailerInitializer']
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.7'
|
|
@@ -5,11 +5,9 @@ py_emailer_ajm.py
|
|
|
5
5
|
install win32 with pip install pywin32
|
|
6
6
|
"""
|
|
7
7
|
# imports
|
|
8
|
-
from abc import abstractmethod
|
|
9
8
|
from os import environ
|
|
10
9
|
from os.path import isfile, join, isdir
|
|
11
10
|
from tempfile import gettempdir
|
|
12
|
-
from typing import List
|
|
13
11
|
|
|
14
12
|
# install win32 with pip install pywin32
|
|
15
13
|
import win32com.client as win32
|
|
@@ -23,11 +21,12 @@ import questionary
|
|
|
23
21
|
# this is usually thrown when questionary is used in the dev/Non Win32 environment
|
|
24
22
|
# noinspection PyProtectedMember
|
|
25
23
|
from prompt_toolkit.output.win32 import NoConsoleScreenBufferError
|
|
26
|
-
from win32com.client import CDispatch
|
|
27
24
|
|
|
28
|
-
from PyEmailerAJM import EmailerNotSetupError, DisplayManualQuit
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
from PyEmailerAJM import (EmailerNotSetupError, DisplayManualQuit,
|
|
26
|
+
deprecated,
|
|
27
|
+
Msg, FailedMsg)
|
|
28
|
+
from PyEmailerAJM.backend import BasicEmailFolderChoices
|
|
29
|
+
from PyEmailerAJM.searchers import SubjectSearcher
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
class EmailerInitializer:
|
|
@@ -52,6 +51,7 @@ class EmailerInitializer:
|
|
|
52
51
|
self.auto_send = auto_send
|
|
53
52
|
self.send_emails = send_emails
|
|
54
53
|
|
|
54
|
+
# TODO: replace me with EasyLoggerAJM
|
|
55
55
|
def _initialize_logger(self, logger=None, **kwargs):
|
|
56
56
|
if logger:
|
|
57
57
|
self._logger = logger
|
|
@@ -121,74 +121,7 @@ class EmailerInitializer:
|
|
|
121
121
|
return self.email_app, self.namespace
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
class
|
|
125
|
-
# Constants for prefixes
|
|
126
|
-
FW_PREFIXES = ['FW:', 'FWD:']
|
|
127
|
-
RE_PREFIX = 'RE:'
|
|
128
|
-
|
|
129
|
-
@abstractmethod
|
|
130
|
-
def GetMessages(self):
|
|
131
|
-
...
|
|
132
|
-
|
|
133
|
-
def find_messages_by_subject(self, search_subject: str, include_fw: bool = True, include_re: bool = True,
|
|
134
|
-
partial_match_ok: bool = False) -> List[CDispatch]:
|
|
135
|
-
"""Returns a list of messages matching the given subject, ignoring prefixes based on flags."""
|
|
136
|
-
|
|
137
|
-
# Normalize search subject
|
|
138
|
-
normalized_subject = self._normalize_subject(search_subject)
|
|
139
|
-
matched_messages = []
|
|
140
|
-
print("partial match ok: ", partial_match_ok)
|
|
141
|
-
|
|
142
|
-
for message in self.GetMessages():
|
|
143
|
-
normalized_message_subject = self._normalize_subject(message.subject)
|
|
144
|
-
|
|
145
|
-
if (self._is_exact_match(normalized_message_subject, normalized_subject) or
|
|
146
|
-
(partial_match_ok and self._is_partial_match(normalized_message_subject,
|
|
147
|
-
normalized_subject))):
|
|
148
|
-
matched_messages.append(message)
|
|
149
|
-
continue
|
|
150
|
-
|
|
151
|
-
if include_fw and self._matches_prefix(normalized_message_subject,
|
|
152
|
-
self.__class__.FW_PREFIXES,
|
|
153
|
-
normalized_subject,
|
|
154
|
-
partial_match_ok):
|
|
155
|
-
matched_messages.append(message)
|
|
156
|
-
continue
|
|
157
|
-
|
|
158
|
-
if include_re and self._matches_prefix(normalized_message_subject,
|
|
159
|
-
[self.__class__.RE_PREFIX],
|
|
160
|
-
normalized_subject,
|
|
161
|
-
partial_match_ok):
|
|
162
|
-
matched_messages.append(message)
|
|
163
|
-
|
|
164
|
-
return [m() for m in matched_messages]
|
|
165
|
-
|
|
166
|
-
@staticmethod
|
|
167
|
-
def _normalize_subject(subject: str) -> str:
|
|
168
|
-
"""Normalize the given subject by converting to lowercase and stripping whitespace."""
|
|
169
|
-
return subject.lower().strip()
|
|
170
|
-
|
|
171
|
-
def _matches_prefix(self, message_subject: str, prefixes: list, search_subject: str,
|
|
172
|
-
partial_match_ok: bool = False) -> bool:
|
|
173
|
-
"""Checks if the message subject matches the search subject after removing a prefix."""
|
|
174
|
-
for prefix in prefixes:
|
|
175
|
-
if message_subject.startswith(prefix.lower()):
|
|
176
|
-
stripped_subject = message_subject.split(prefix.lower(), 1)[1].strip()
|
|
177
|
-
return (self._is_exact_match(stripped_subject, search_subject) if not partial_match_ok
|
|
178
|
-
else self._is_partial_match(stripped_subject, search_subject))
|
|
179
|
-
return False
|
|
180
|
-
|
|
181
|
-
@staticmethod
|
|
182
|
-
def _is_exact_match(message_subject: str, search_subject: str) -> bool:
|
|
183
|
-
"""Checks if the subject matches exactly."""
|
|
184
|
-
return message_subject == search_subject
|
|
185
|
-
|
|
186
|
-
@staticmethod
|
|
187
|
-
def _is_partial_match(message_subject: str, search_subject: str) -> bool:
|
|
188
|
-
return search_subject in message_subject
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
class PyEmailer(EmailerInitializer, _SubjectSearcher):
|
|
124
|
+
class PyEmailer(EmailerInitializer, SubjectSearcher):
|
|
192
125
|
# the email tab_char
|
|
193
126
|
tab_char = ' '
|
|
194
127
|
signature_dir_path = join((environ['USERPROFILE']),
|
|
@@ -496,12 +429,14 @@ if __name__ == "__main__":
|
|
|
496
429
|
module_name = __file__.split('\\')[-1].split('.py')[0]
|
|
497
430
|
|
|
498
431
|
em = PyEmailer(display_window=False, send_emails=True, auto_send=False, use_default_logger=True)
|
|
499
|
-
m = em.find_messages_by_subject('Andrew', partial_match_ok=True
|
|
432
|
+
m = em.find_messages_by_subject('Andrew', partial_match_ok=True)
|
|
500
433
|
print([type(x) for x in m])
|
|
501
434
|
# __setup_and_send_test(em)
|
|
502
435
|
# __failed_sends_test(em)
|
|
503
|
-
|
|
504
|
-
#
|
|
436
|
+
x = em.find_messages_by_subject("GIS Request", partial_match_ok=True)
|
|
437
|
+
# [x.name for x in m.ItemProperties]
|
|
438
|
+
print([(m.__class__, m.sender, m.sender_email_type, m.subject)
|
|
439
|
+
for m in [Msg(y) for y in x]]) # for m in x])
|
|
505
440
|
# property_accessor = x[0].PropertyAccessor
|
|
506
441
|
# print(x[0].Sender.GetExchangeUser().PrimarySmtpAddress)
|
|
507
442
|
# print(property_accessor.GetProperty("PR_EMAIL_ADDRESS"))
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyEmailerAJM
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7
|
|
4
4
|
Summary: Allows for automating sending Email with the Outlook Desktop client. Future releases will add more client support
|
|
5
5
|
Home-page: https://github.com/amcsparron2793-Water/PyEmailer
|
|
6
|
-
Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.
|
|
6
|
+
Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.7.tar.gz
|
|
7
7
|
Author: Amcsparron
|
|
8
8
|
Author-email: amcsparron@albanyny.gov
|
|
9
9
|
License: MIT License
|
|
10
10
|
Keywords: Outlook,Email,Automation
|
|
11
11
|
License-File: LICENSE.txt
|
|
12
12
|
Requires-Dist: pywin32
|
|
13
|
+
Requires-Dist: extract_msg
|
|
14
|
+
Requires-Dist: email_validator
|
|
15
|
+
Requires-Dist: questionary
|
|
13
16
|
Dynamic: author
|
|
14
17
|
Dynamic: author-email
|
|
15
18
|
Dynamic: download-url
|
|
@@ -17,7 +17,7 @@ setup(
|
|
|
17
17
|
url='https://github.com/amcsparron2793-Water/PyEmailer',
|
|
18
18
|
download_url=f'https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/{get_property("__version__", project_name)}.tar.gz',
|
|
19
19
|
keywords=["Outlook", "Email", "Automation"],
|
|
20
|
-
install_requires=['pywin32'],
|
|
20
|
+
install_requires=['pywin32', 'extract_msg', 'email_validator', 'questionary'],
|
|
21
21
|
license='MIT License',
|
|
22
22
|
author='Amcsparron',
|
|
23
23
|
author_email='amcsparron@albanyny.gov',
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
from PyEmailerAJM.errs import EmailerNotSetupError, DisplayManualQuit
|
|
2
|
-
from PyEmailerAJM.helpers import deprecated, BasicEmailFolderChoices
|
|
3
|
-
from PyEmailerAJM.msg import Msg, FailedMsg
|
|
4
|
-
from PyEmailerAJM.py_emailer_ajm import PyEmailer, EmailerInitializer
|
|
5
|
-
|
|
6
|
-
__all__ = ['EmailerNotSetupError', 'DisplayManualQuit', 'deprecated',
|
|
7
|
-
'BasicEmailFolderChoices', 'Msg', 'FailedMsg',
|
|
8
|
-
'PyEmailer', 'EmailerInitializer']
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '1.6'
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
from abc import abstractmethod
|
|
2
|
-
from os.path import isfile, isabs, abspath, join
|
|
3
|
-
from tempfile import gettempdir
|
|
4
|
-
|
|
5
|
-
import win32com.client as win32
|
|
6
|
-
import datetime
|
|
7
|
-
import extract_msg
|
|
8
|
-
from bs4 import BeautifulSoup
|
|
9
|
-
from logging import Logger, getLogger, warning
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class _BasicMsgProperties:
|
|
13
|
-
def __init__(self, email_item: win32.CDispatch):
|
|
14
|
-
self.email_item = email_item
|
|
15
|
-
|
|
16
|
-
@classmethod
|
|
17
|
-
@abstractmethod
|
|
18
|
-
def _validate_and_add_attachments(cls, email_item: win32.CDispatch, attachment_list: list = None):
|
|
19
|
-
...
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def sender(self):
|
|
23
|
-
if hasattr(self.email_item, 'SenderEmailType') and self.email_item.SenderEmailType == 'EX':
|
|
24
|
-
return self.email_item.Sender.GetExchangeUser().PrimarySmtpAddress
|
|
25
|
-
# return self.email_item.Sender if hasattr(self.email_item, 'Sender') else self.email_item.sender
|
|
26
|
-
else:
|
|
27
|
-
return self.email_item.SenderEmailAddress
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def sender_name(self):
|
|
31
|
-
return self.email_item.Sender if hasattr(self.email_item, 'Sender') else self.email_item.sender
|
|
32
|
-
|
|
33
|
-
@property
|
|
34
|
-
def to(self):
|
|
35
|
-
return self.email_item.To if hasattr(self.email_item, 'To') else self.email_item.to
|
|
36
|
-
|
|
37
|
-
def cc(self):
|
|
38
|
-
return self.email_item.CC if hasattr(self.email_item, 'CC') else self.email_item.cc
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def subject(self):
|
|
42
|
-
return self.email_item.Subject if hasattr(self.email_item, 'Subject') else self.email_item.subject
|
|
43
|
-
|
|
44
|
-
@property
|
|
45
|
-
def received_time(self):
|
|
46
|
-
#not_future = self.email_item.ReceivedTime.year < datetime.datetime.now().year
|
|
47
|
-
return self.email_item.ReceivedTime #if not_future else None
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def body(self):
|
|
51
|
-
return self.email_item.HTMLBody if hasattr(self.email_item, 'HTMLBody') else self.email_item.htmlBody
|
|
52
|
-
|
|
53
|
-
@property
|
|
54
|
-
def attachments(self):
|
|
55
|
-
return self.email_item.Attachments
|
|
56
|
-
|
|
57
|
-
@attachments.setter
|
|
58
|
-
def attachments(self, value: list):
|
|
59
|
-
self._validate_and_add_attachments(self.email_item, value)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class Msg(_BasicMsgProperties):
|
|
63
|
-
def __init__(self, email_item: win32.CDispatch or extract_msg.Message, **kwargs):
|
|
64
|
-
super().__init__(email_item)
|
|
65
|
-
self._logger: Logger = kwargs.get('logger', getLogger(__name__))
|
|
66
|
-
self.send_success = False
|
|
67
|
-
|
|
68
|
-
def __call__(self, *args, **kwargs):
|
|
69
|
-
return self.email_item
|
|
70
|
-
|
|
71
|
-
@classmethod
|
|
72
|
-
def SetupMsg(cls, sender, recipient, subject, body, email_item: win32.CDispatch, attachments: list = None, **kwargs):
|
|
73
|
-
email_item.To = recipient
|
|
74
|
-
email_item.Sender = sender
|
|
75
|
-
email_item.Subject = subject
|
|
76
|
-
email_item.HtmlBody = body
|
|
77
|
-
email_item.cc = kwargs.get('cc', '')
|
|
78
|
-
email_item.Bcc = kwargs.get('bcc', '')
|
|
79
|
-
|
|
80
|
-
cls._validate_and_add_attachments(email_item, attachments)
|
|
81
|
-
return cls(email_item, **kwargs)
|
|
82
|
-
|
|
83
|
-
@classmethod
|
|
84
|
-
def _validate_and_add_attachments(cls, email_item: win32.CDispatch, attachment_list: list = None):
|
|
85
|
-
""" Validate and attach files to the email_item. """
|
|
86
|
-
if not attachment_list:
|
|
87
|
-
warning("No attachments detected")
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
if not isinstance(attachment_list, list):
|
|
91
|
-
raise TypeError("Attachments must be provided as a list")
|
|
92
|
-
|
|
93
|
-
def _absolute_file_path(file_path):
|
|
94
|
-
"""Returns absolute path if valid; raises FileNotFoundError otherwise."""
|
|
95
|
-
if not isabs(file_path):
|
|
96
|
-
file_path = abspath(file_path)
|
|
97
|
-
if not isfile(file_path):
|
|
98
|
-
raise FileNotFoundError(f"File {file_path} could not be attached.")
|
|
99
|
-
return file_path
|
|
100
|
-
|
|
101
|
-
for attachment in attachment_list:
|
|
102
|
-
email_item.attachments.Add(_absolute_file_path(attachment))
|
|
103
|
-
|
|
104
|
-
def SaveAllEmailAttachments(self, save_dir_path):
|
|
105
|
-
all_attachment_paths = set()
|
|
106
|
-
for attachment in self.attachments:
|
|
107
|
-
full_save_path = join(save_dir_path, str(attachment))
|
|
108
|
-
try:
|
|
109
|
-
attachment.SaveAsFile(full_save_path)
|
|
110
|
-
all_attachment_paths.add(full_save_path)
|
|
111
|
-
self._logger.debug(f"{full_save_path} saved from email with subject {self.subject}")
|
|
112
|
-
except Exception as e:
|
|
113
|
-
self._logger.error(e, exc_info=True)
|
|
114
|
-
raise e
|
|
115
|
-
return all_attachment_paths
|
|
116
|
-
|
|
117
|
-
def display(self):
|
|
118
|
-
# print(f"Displaying the email in {self.email_app_name}, this window might open minimized.")
|
|
119
|
-
# self._logger.info(f"Displaying the email in {self.email_app_name}, this window might open minimized.")
|
|
120
|
-
try:
|
|
121
|
-
self().Display(True)
|
|
122
|
-
except Exception as e:
|
|
123
|
-
self._logger.error(e, exc_info=True)
|
|
124
|
-
raise e
|
|
125
|
-
|
|
126
|
-
def send(self):
|
|
127
|
-
try:
|
|
128
|
-
# if the send fails, self.to is NULL, so this needs to be saved in a local variable
|
|
129
|
-
attempted_recipient = self.to
|
|
130
|
-
self.send_success = False
|
|
131
|
-
self().Send()
|
|
132
|
-
# print(f"Mail sent to {self._recipient}")
|
|
133
|
-
self.send_success = True
|
|
134
|
-
self._logger.info(f"Mail successfully sent to {attempted_recipient}")
|
|
135
|
-
except Exception as e:
|
|
136
|
-
self._logger.error(e, exc_info=True)
|
|
137
|
-
raise e
|
|
138
|
-
|
|
139
|
-
def _ValidateResponseMsg(self):
|
|
140
|
-
if isinstance(self(), win32.CDispatch):
|
|
141
|
-
self._logger.debug("passed in msg is CDispatch instance")
|
|
142
|
-
if hasattr(self(), 'HtmlBody') or hasattr(self(), 'htmlBody'):
|
|
143
|
-
self._logger.debug("passed in msg has 'HtmlBody' or 'htmlBody' attr")
|
|
144
|
-
|
|
145
|
-
if (not isinstance(self(), win32.CDispatch)
|
|
146
|
-
or not hasattr(self(), ('HtmlBody' or 'htmlBody'))):
|
|
147
|
-
raise AttributeError("msg attr must have 'HtmlBody' attr AND be a CDispatch instance")
|
|
148
|
-
return self()
|
|
149
|
-
|
|
150
|
-
def _msg_is_recent(self, recent_days_cap=1):
|
|
151
|
-
if self.received_time is not None:
|
|
152
|
-
abs_diff = abs(self.received_time - datetime.datetime.now(tz=self.received_time.tzinfo))
|
|
153
|
-
return abs_diff <= datetime.timedelta(days=recent_days_cap)
|
|
154
|
-
print(f"msg with subject \'{self.email_item.Subject}\' has no received time. defaulting to false")
|
|
155
|
-
self._logger.debug(f"msg with subject \'{self.email_item.Subject}\' has no received time. defaulting to false")
|
|
156
|
-
return False
|
|
157
|
-
|
|
158
|
-
def return_as_failed_send(self):
|
|
159
|
-
return FailedMsg(self())
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
class FailedMsg(Msg):
|
|
163
|
-
ERR_SKIP_STRING = "err {}: skipping this message"
|
|
164
|
-
DEFAULT_TEMP_SAVE_PATH = gettempdir()
|
|
165
|
-
|
|
166
|
-
def _message_filter_checks(self, **kwargs) -> bool:
|
|
167
|
-
recent_days_cap = kwargs.get('recent_days_cap', 1)
|
|
168
|
-
return self._msg_is_recent(recent_days_cap)
|
|
169
|
-
|
|
170
|
-
def _fetch_failed_msg_details(self, **kwargs):
|
|
171
|
-
temp_attachment_save_path = kwargs.get('temp_attachment_save_path',
|
|
172
|
-
self.__class__.DEFAULT_TEMP_SAVE_PATH)
|
|
173
|
-
try:
|
|
174
|
-
attachment_msg_path = self.SaveAllEmailAttachments(temp_attachment_save_path)
|
|
175
|
-
print('saved_attachments')
|
|
176
|
-
except Exception as e:
|
|
177
|
-
self._logger.warning(self.__class__.ERR_SKIP_STRING.format(f'({e})'))
|
|
178
|
-
return e
|
|
179
|
-
if len(attachment_msg_path) == 1:
|
|
180
|
-
return next(iter(attachment_msg_path))
|
|
181
|
-
return attachment_msg_path
|
|
182
|
-
|
|
183
|
-
def process_failed_msg(self, post_master_msg, **kwargs):
|
|
184
|
-
recent_days_cap = kwargs.get('recent_days_cap', 1)
|
|
185
|
-
try:
|
|
186
|
-
self.email_item = post_master_msg
|
|
187
|
-
self._ValidateResponseMsg()
|
|
188
|
-
except AttributeError as e:
|
|
189
|
-
self._logger.warning(self.__class__.ERR_SKIP_STRING.format(f'({e})'))
|
|
190
|
-
return e, None, None
|
|
191
|
-
|
|
192
|
-
if self._msg_is_recent(recent_days_cap):
|
|
193
|
-
attachment_msg = self._fetch_failed_msg_details()
|
|
194
|
-
if isinstance(attachment_msg, Exception):
|
|
195
|
-
return attachment_msg, None, None
|
|
196
|
-
else:
|
|
197
|
-
if isinstance(attachment_msg, str):
|
|
198
|
-
fmd = _FailedMessageDetails.extract_msg_from_attachment(attachment_msg)
|
|
199
|
-
return fmd.process_failed_details_msg() #self._process_failed_details_msg(attachment_msg)
|
|
200
|
-
return None, None, None
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
class _FailedMessageDetails(FailedMsg):
|
|
204
|
-
@classmethod
|
|
205
|
-
def extract_msg_from_attachment(cls, parent_msg: str):
|
|
206
|
-
return cls(extract_msg.Message(parent_msg))
|
|
207
|
-
|
|
208
|
-
def _extract_from_failed_details_msg(self, para):
|
|
209
|
-
email_of_err = para.findNext('p').get_text().strip().split('(')[0].strip()
|
|
210
|
-
err_reason = para.findNext('p').findNext('p').get_text()
|
|
211
|
-
send_time = self().date.ctime()
|
|
212
|
-
failed_subject = self.subject
|
|
213
|
-
|
|
214
|
-
err_details = {'email_of_err': email_of_err, 'err_reason': err_reason,
|
|
215
|
-
'send_time': send_time, 'failed_subject': failed_subject}
|
|
216
|
-
# print(f"Email of err: {email_of_err},\nErr reason: {err_reason}\nSend time: {send_time}")
|
|
217
|
-
return err_details #email_of_err, err_reason, send_time
|
|
218
|
-
|
|
219
|
-
def process_failed_details_msg(self, **kwargs):
|
|
220
|
-
detail_marker_string = kwargs.get('detail_marker_string',
|
|
221
|
-
"Delivery has failed to these recipients or groups:")
|
|
222
|
-
|
|
223
|
-
soup = BeautifulSoup(self.body, features="html.parser")
|
|
224
|
-
|
|
225
|
-
all_p = soup.find_all(name='p') # , attrs={'class': 'MsoNormal'})
|
|
226
|
-
|
|
227
|
-
for para in all_p:
|
|
228
|
-
if detail_marker_string in para.get_text():
|
|
229
|
-
return {** self._extract_from_failed_details_msg(para)}
|
|
230
|
-
return None, None, None
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
pywin32
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|