component email (imap): fetch all recent emails instead of only the last one.
This commit is contained in:
parent
81f63ef93e
commit
412db6a216
1 changed files with 52 additions and 28 deletions
|
|
@ -16,6 +16,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections.abc import Awaitable
|
||||
from email.message import EmailMessage
|
||||
from email.parser import BytesParser, Parser
|
||||
from email import policy
|
||||
|
|
@ -84,6 +85,7 @@ class IMAPClient(imap4.IMAP4Client):
|
|||
return
|
||||
self._idling = True
|
||||
self._idle_timer = reactor.callLater(29 * 60, self.on_idle_timeout)
|
||||
log.debug("Starting IDLE mode.")
|
||||
await self.sendCommand(
|
||||
imap4.Command(
|
||||
b"IDLE",
|
||||
|
|
@ -101,7 +103,7 @@ class IMAPClient(imap4.IMAP4Client):
|
|||
# Send DONE command to exit IDLE mode.
|
||||
self.sendLine(b"DONE")
|
||||
self._idling = False
|
||||
log.debug("IDLE mode terminated")
|
||||
log.debug("IDLE mode terminated.")
|
||||
|
||||
def on_idle_timeout(self):
|
||||
"""Called when IDLE mode timeout is reached."""
|
||||
|
|
@ -126,34 +128,53 @@ class IMAPClient(imap4.IMAP4Client):
|
|||
@param recent: Number of recent messages.
|
||||
"""
|
||||
log.debug(f"New messages: {exists}, Recent messages: {recent}")
|
||||
log.debug("Retrieving last message.")
|
||||
|
||||
if recent is None:
|
||||
log.debug("No recent messages, skipping fetch.")
|
||||
defer.ensureDeferred(self.idle())
|
||||
return
|
||||
|
||||
self.idle_exit()
|
||||
mess_data = await self.fetchMessage("*")
|
||||
for message in mess_data.values():
|
||||
try:
|
||||
content = message["RFC822"]
|
||||
except KeyError:
|
||||
log.warning(f"Can't find content for {message}.")
|
||||
continue
|
||||
else:
|
||||
if isinstance(content, str):
|
||||
parser = Parser(policy=policy.default)
|
||||
parser_method = parser.parsestr
|
||||
elif isinstance(content, bytes):
|
||||
parser = BytesParser(policy=policy.default)
|
||||
parser_method = parser.parsebytes
|
||||
else:
|
||||
log.error(f"Invalid content: {content}")
|
||||
continue
|
||||
|
||||
# We retrieve recent messages.
|
||||
recent_uids = await self.search(imap4.Query(recent=True), uid=True)
|
||||
message_set = imap4.MessageSet()
|
||||
for recent_uid in recent_uids:
|
||||
message_set.add(recent_uid)
|
||||
|
||||
try:
|
||||
mess_data = await self.fetchMessage(message_set, uid=True)
|
||||
|
||||
# Process all fetched messages.
|
||||
log.debug(f"Fetched {len(mess_data)} messages.")
|
||||
for message in mess_data.values():
|
||||
try:
|
||||
parsed = parser_method(content)
|
||||
except Exception as e:
|
||||
log.warning(f"Can't parse content of email: {e}")
|
||||
content = message["RFC822"]
|
||||
except KeyError:
|
||||
log.warning(f"Can't find content for {message}.")
|
||||
continue
|
||||
else:
|
||||
assert self.factory is not None
|
||||
factory = cast(IMAPClientFactory, self.factory)
|
||||
await factory.on_new_email(parsed)
|
||||
if isinstance(content, str):
|
||||
parser = Parser(policy=policy.default)
|
||||
parser_method = parser.parsestr
|
||||
elif isinstance(content, bytes):
|
||||
parser = BytesParser(policy=policy.default)
|
||||
parser_method = parser.parsebytes
|
||||
else:
|
||||
log.error(f"Invalid content: {content}.")
|
||||
continue
|
||||
try:
|
||||
parsed = parser_method(content)
|
||||
except Exception as e:
|
||||
log.warning(f"Can't parse content of email: {e}.")
|
||||
continue
|
||||
else:
|
||||
assert self.factory is not None
|
||||
factory = cast(IMAPClientFactory, self.factory)
|
||||
await factory.on_new_email(parsed)
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Error fetching recent messages: {e}")
|
||||
|
||||
defer.ensureDeferred(self.idle())
|
||||
|
||||
|
|
@ -215,13 +236,16 @@ class IMAPClientFactory(protocol.ClientFactory):
|
|||
def __init__(
|
||||
self,
|
||||
user_data: UserData,
|
||||
on_new_email: Callable[[EmailMessage], None],
|
||||
on_new_email: Callable[[EmailMessage], Awaitable[None]],
|
||||
connected: defer.Deferred,
|
||||
) -> None:
|
||||
"""Initialize the IMAP client factory.
|
||||
|
||||
@param username: The username to use for authentication.
|
||||
@param password: The password to use for authentication.
|
||||
@param user_data: User data containing credentials and other user-specific
|
||||
information.
|
||||
@param on_new_email: Called when a new email is received.
|
||||
@param connected: Deferred that will be fired when the IMAP connection is
|
||||
established.
|
||||
"""
|
||||
credentials = user_data.credentials
|
||||
self.user_data = user_data
|
||||
|
|
|
|||
Loading…
Reference in a new issue