Skip to content

Commit

Permalink
finished forward-compatibility.
Browse files Browse the repository at this point in the history
  • Loading branch information
MMF committed Sep 14, 2018
1 parent 16b8e0d commit 5063be9
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 37 deletions.
94 changes: 69 additions & 25 deletions Handlers.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,88 @@
"""" handlers to be used for incoming messages """

import logging
from smtplib import SMTP
from typing import Set

import pyzmail

logger = None # set from main

class Handlers:
"""
This class contains handlers for all possible domains.
It won't initialise clients or fetch messages, this should be done externally (separation of concerns)
"""

def __init__(self, sender: SMTP, this_mail: str, logger: logging.Logger):
"""
:param sender: initialised smtp_client
:param this_mail: mail_address to use as sender
:param logger: global logging object
"""

self._logger: logging.Logger = logger
self._this: str = this_mail
self._sender: SMTP = sender

# TODO invert list? 1 tag : n handlers OR 1 handler : n tags
self._mail_handlers = {"mailrobot@mail.xing.com": self._format_xing}

def handle(self, source: str, mail_raw: bytes):
"""
Call to let mail be handled
:param source: address of source
:param mail_raw: mail body
"""

self._mail_handlers[source](mail_raw)

def _format_xing(self, mail_raw: bytes):
"""" call with RFC 822 Mail body """

def format_xing(mail_raw: bytes, smtp_client: SMTP):
"""" call with RFC 822 Mail body """
self._logger.info("decoding XING message")

logger.info("decoding XING message")
mail = pyzmail.PyzMessage.factory(mail_raw)

mail = pyzmail.PyzMessage.factory(mail_raw)
mail_TO = mail.get_address("to") # mail adress only
mail_FROM = mail.get_address("from")
mail_SUBJECT = mail.get_subject()
# X-Forward To?

mail_TO = mail.get_address("to") # mail adress only
mail_FROM = mail.get_address("from")
mail_SUBJECT = "[Decoded] " + mail.get_subject("XING for your convenience")
# this is the magic bit, XING shows message in text_part but hides it in (visible) html_part
payload = mail.text_part.get_payload().decode(mail.text_part.charset)
self._logger.info(f"From: {mail_FROM}, mail_TO: {mail_TO}, Subject: {mail_SUBJECT}, Size: {len(payload)}")

# FIXME X-Forward To?
self._logger.debug("Adding signature")
payload += "\n-[Decoded using ReMailer from Modisch Fabrications]-"

# this is the magic bit, XING shows message in text_part but hides it in (visible) html_part
payload = mail.text_part.get_payload().decode(mail.text_part.charset)
logger.info(f"From: {mail_FROM}, mail_TO: {mail_TO}, Subject: {mail_SUBJECT}, Size: {len(payload)}")
# TODO Collapse whitespace and reformat

# TODO Collapse whitespace and reformat
# compare against address, not name
if mail_TO[1] == self._this:
target = mail_FROM
else:
target = mail_TO

logger.debug("Adding signature")
payload += "\n-[Decoded using ReMailer from Modisch Fabrications]-"
source = self._this

if mail_TO == this_mail:
target = mail_FROM
else:
target = mail_TO
subject = "[Decoded] " + mail_SUBJECT

source = this_mail
self._logger.info(f"Formatting done, sending back to {target}")
self._sender.sendmail(source, target, f"Subject: {subject}\n{payload}".encode())
self._logger.info("Sent successfully")

logger.info(f"Formatting done, sending back to {target}")
smtp_client.sendmail(source, target, f"Subject: {mail_SUBJECT}\n{payload}".encode())
def _format_no_ip(self, mail_raw: bytes):
"""" extracts key to press """
# TODO fill with life

@property
def sources(self) -> Set:
"""
def format_no_ip():
"""" extracts key to press """
# TODO do that
:return: list of
"""
return set(self._mail_handlers.keys())

# end
2 changes: 2 additions & 0 deletions IMAPTest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""" simple test cases for receiving messages """

import unittest

from main import *
Expand Down
26 changes: 14 additions & 12 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#!python3
"""" read README.md for details """


import configparser
import logging
Expand All @@ -12,12 +14,10 @@
from imapclient import IMAPClient

# custom
import Handlers
from Handlers import Handlers

# uses SMTP for sending and IMAP for receiving
# this script should be automated with a crontab or similar
#
# https://automatetheboringstuff.com/chapter16/

# TODO messages with unknown domain should be answered with usage instructions
# get all mails (old are deleted), sort by "FROM" and if forwarded or not
Expand All @@ -28,9 +28,6 @@
smtp_provider = {"gmail.com": "smtp.gmail.com", "yahoo.com": "smtp.mail.yahoo.com"}
imap_provider = {"gmail.com": "imap.gmail.com", "yahoo.com": "imap.mail.yahoo.com"}

# TODO invert list? 1 tag : n handlers OR 1 handler : n tags
mail_handlers = {"mailrobot@mail.xing.com": Handlers.format_xing}

# TODO read from arguments or environment
t_restart = 1800
save_mode = True
Expand Down Expand Up @@ -113,7 +110,7 @@ def connect_smtp(logger, mail: str, password: str) -> SMTP:
smtp_domain = smtp_provider[host]
except KeyError:
logger.error(f"SMTP-Provider {host} is unknown, exiting")
# TODO try smtp.XX before giving up
# could try smtp.XX before giving up
exit(2)

logger.info(f"Connecting to {smtp_domain}")
Expand Down Expand Up @@ -167,7 +164,7 @@ def connect_imap(logger, mail: str, password: str) -> IMAPClient:
logger.info(f"Connecting to {imap_domain}")
imap_obj = IMAPClient(imap_domain, ssl=True)

logger.info(f"Logging in") # TODO use special gmail key if needed
logger.info(f"Logging in")

response = imap_obj.login(mail, password)
logger.debug(response)
Expand All @@ -178,8 +175,11 @@ def connect_imap(logger, mail: str, password: str) -> IMAPClient:


def main():
"""
start program
"""

logger = get_logger()
Handlers.logger = logger

logger.info(f"\n\n--- Welcome to {project_name} ---\n")

Expand Down Expand Up @@ -208,10 +208,12 @@ def main():
logger.info("Ready! Starting to work on messages")
# Threading could encapsulate around here acting through a worker queue

handlers = Handlers(smtp_client, mail_address, logger)

imap_client.select_folder("INBOX", readonly=save_mode) # False to delete mail after processing

# search original FROM (before forwarding) and match to dictionary of handlers
for domain, handler in mail_handlers.items(): # search for mail for all handlers
for domain in handlers.sources: # search for mail for all handlers
logger.debug(f"Searching for domain {domain}")

if extended_search:
Expand All @@ -229,7 +231,7 @@ def main():
part_to_fetch = "BODY[]" # ENVELOPE, RFC822, BODY[] possible
wrapped_mail = imap_client.fetch(mail_id, [part_to_fetch])
mail = wrapped_mail[mail_id][part_to_fetch.encode()]
handler(mail, smtp_client) # TODO not every handler needs to send, better place for client?
handlers.handle(domain, mail)

# delete when everything was analysed
if mail_UIDs:
Expand All @@ -251,4 +253,4 @@ def main():
# the only reason to stop is an exception
while True:
main()
time.sleep(t_restart)
time.sleep(t_restart) # better than restarting externally?

0 comments on commit 5063be9

Please sign in to comment.