Source code for mailos.check_emails
"""Email checking functions."""
import email
import imaplib
import sys
import time
from datetime import datetime
from apscheduler.schedulers.background import BackgroundScheduler
from mailos.reply import handle_email_reply
from mailos.utils.attachment_utils import AttachmentManager
from mailos.utils.config_utils import load_config, update_checker_field
from mailos.utils.email_utils import get_email_body
from mailos.utils.logger_utils import logger
from mailos.utils.reply_utils import should_reply
[docs]
def check_emails(checker_config):
"""Check emails for a given checker configuration."""
try:
logger.info(f"Connecting to {checker_config['imap_server']}...")
mail = imaplib.IMAP4_SSL(
checker_config["imap_server"], checker_config["imap_port"]
)
mail.login(checker_config["monitor_email"], checker_config["password"])
logger.info("Connected successfully")
# Initialize attachment manager
attachment_manager = AttachmentManager()
# Select inbox
status, messages = mail.select("INBOX")
logger.info(f"Inbox select status: {status}")
# Search for unread emails
logger.info("Searching for unread emails...")
result, data = mail.search(None, "UNSEEN")
if result == "OK":
if not data[0]:
logger.info("No unread emails found")
else:
email_ids = data[0].split()
logger.info(f"Found {len(email_ids)} unread emails")
for num in email_ids:
result, email_data = mail.fetch(num, "(RFC822)")
if result == "OK":
email_body = email_data[0][1]
email_message = email.message_from_bytes(email_body)
# Debug: Log email structure
logger.info("Email structure:")
for part in email_message.walk():
logger.info(f"Content type: {part.get_content_type()}")
logger.info(
f"Content Disposition: "
f"{part.get('Content-Disposition')}"
)
if part.get_filename():
logger.info(f"Found attachment: {part.get_filename()}")
# Extract attachments if present
sender_email = email.utils.parseaddr(email_message["from"])[1]
logger.info(f"Processing attachments from {sender_email}")
try:
attachments = attachment_manager.extract_attachments(
email_message, sender_email
)
if attachments:
logger.info(
f"Saved {len(attachments)} attachments from "
f"{sender_email}"
)
for att in attachments:
logger.info(
f"Saved attachment: {att['original_name']} -> "
f"{att['saved_name']}"
)
logger.info(f"Saved to path: {att['path']}")
else:
logger.info("No attachments found in the email")
except Exception as e:
logger.error(
f"Error extracting attachments: {str(e)}", exc_info=True
)
attachments = []
# Create a properly formatted email_data dictionary
parsed_email = {
"from": email_message["from"],
"subject": email_message["subject"],
"body": get_email_body(email_message),
"msg_date": email_message["date"],
"message_id": email_message["message-id"]
or f"generated-{num.decode()}",
"attachments": attachments,
}
logger.info(
f"New email found: Subject='{parsed_email['subject']}'"
f"From='{parsed_email['from']}'"
)
# Optionally mark as read after processing
mail.store(num, "+FLAGS", "\\Seen")
if checker_config.get("auto_reply", False) and should_reply(
parsed_email
):
handle_email_reply(checker_config, parsed_email)
else:
logger.error(f"Failed to fetch email {num}: {result}")
else:
logger.error(f"Search failed: {result}")
# Manage attachment storage
attachment_manager.manage_storage_space()
# Update last_run timestamp using the dedicated function
checker_id = checker_config.get("id")
if checker_id:
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if update_checker_field(checker_id, "last_run", current_time):
logger.info(f"Updated last_run for checker ID: {checker_id}")
else:
logger.warning(
f"Failed to update last_run for checker ID: {checker_id}"
)
else:
logger.warning("Checker has no ID, cannot update last_run timestamp")
mail.close()
mail.logout()
logger.info("Connection closed")
except imaplib.IMAP4.error as e:
logger.error(f"IMAP error for {checker_config['monitor_email']}: {str(e)}")
except Exception as e:
logger.error(f"Error processing email: {str(e)}", exc_info=True)
[docs]
def main():
"""Check emails for all enabled checkers."""
logger.info("Starting email check...")
config = load_config()
if not config:
logger.info("No configuration found")
return
for checker in config.get("checkers", []):
if checker.get("enabled"):
logger.info(f"Checking {checker['monitor_email']}...")
# TODO: Add asyncio support for parallel email checking
# TODO: add validation for chekers for the same email
check_emails(checker)
[docs]
def init_scheduler():
"""Initialize the scheduler for email checking."""
scheduler = BackgroundScheduler()
scheduler.add_job(main, "interval", minutes=1)
scheduler.start()
return scheduler
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--once":
logger.info("Running single check...")
main()
logger.info("Single check completed")
else:
logger.info("Starting scheduler...")
scheduler = init_scheduler()
try:
while True:
time.sleep(1)
except (KeyboardInterrupt, SystemExit):
scheduler.shutdown()