Initial commit

This commit is contained in:
Stacy Brock
2021-03-30 09:46:40 -07:00
commit 11f692cc42
8 changed files with 377 additions and 0 deletions

154
mail-filter.py Normal file
View File

@@ -0,0 +1,154 @@
import configparser
import logging
import logging.handlers
import os
import pendulum
import signal
import time
from importlib.machinery import SourceFileLoader
from O365 import Account, FileSystemTokenBackend
SCRIPTPATH = os.path.dirname(os.path.abspath(__file__))
# parse config file
config = {}
configfile = configparser.ConfigParser()
configfile.read(SCRIPTPATH + '/mail-filter.conf')
config['FILTERS_FILE'] = configfile.get('main', 'Filters')
config['IS_DEBUG'] = configfile.getboolean('main', 'EnableDebugging')
config['CHECK_INTERVAL'] = int(configfile.get('main', 'MailCheckInterval'))
config['LOG_DIR'] = configfile.get('logging', 'LogDir')
config['TIMEZONE'] = configfile.get('logging', 'Timezone')
config['APP_CLIENT_ID'] = os.getenv('APP_CLIENT_ID')
config['APP_SECRET_KEY'] = os.getenv('APP_SECRET_KEY')
config['APP_TENANT_ID'] = os.getenv('APP_TENANT_ID')
# convert timestamp to local time
def local_time(record, datefmt=None):
return pendulum.from_timestamp(
record.created,
tz=pendulum.timezone(config['TIMEZONE'])
).strftime('%Y-%m-%d %H:%M:%S %z')
# set up logger
logger = logging.getLogger('o365mf')
if config['IS_DEBUG']:
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
'%(asctime)s %(module)s [%(levelname)s] %(message)s')
formatter.formatTime = local_time
log_filename = f"{config['LOG_DIR']}/mail-filter.log"
handler = logging.handlers.TimedRotatingFileHandler(
log_filename, when='midnight', backupCount=5)
handler.setFormatter(formatter)
logger.addHandler(handler)
class O365MailFilter(object):
_scopes = [
'basic',
'https://graph.microsoft.com/Mail.ReadWrite'
]
def __init__(self, config):
self._config = config
self._is_canceled = False
self._folders = {}
self._normalized = {}
# auth with O365
self._authenticate()
def _authenticate(self):
token_backend = FileSystemTokenBackend(token_path='.cache',
token_filename='token.txt')
self._account = Account(
(self._config['APP_CLIENT_ID'], self._config['APP_SECRET_KEY']),
tenant_id=self._config['APP_TENANT_ID'],
token_backend=token_backend
)
if not self._account.is_authenticated:
self._account.authenticate(scopes=self._scopes)
logger.info('Authentication successful')
def _load_filters(self):
""" load filter code from a file on disk """
loader = SourceFileLoader('filters', self._config['FILTERS_FILE'])
module = loader.load_module()
module.normalize_lists(self)
# make 'filter_message()' implemented in the file available for use
# within this class as 'self._filter_message()'
self._filter_message = module.filter_message
def _load_folders(self):
""" retrieve folders for this mailbox and cache their ids """
self._folders = {}
mailbox = self._account.mailbox()
folders = mailbox.get_folders()
for folder in folders:
self._folders[folder.name] = folder.folder_id
def _repr_message(self, message):
""" returns a str representation of a message suitable for logging """
# to = ','.join([r.address for r in message.to])
return f"[FROM: {message.sender.address} SUBJ: {message.subject}]"
def _log_result(self, message, result):
logger.info(f"{self._repr_message(message)} RESULT: {result}")
def filter(self):
self._load_filters()
self._load_folders()
mailbox = self._account.mailbox()
inbox = mailbox.inbox_folder()
# set limit to max allowed by O365, which is 999 messages
# we have to explicitly set a limit value or the O365 library will not
# paginate results correctly
limit = self._account.protocol.max_top_value
query = inbox.new_query()
query = query.on_attribute('isRead').equals(False).select(
'to_recipients', 'from', 'subject', 'body',
'internet_message_headers'
)
messages = inbox.get_messages(query=query, limit=limit, batch=25)
for message in messages:
self._filter_message(self, message)
def run(self):
""" run filter as a loop """
while not self._is_canceled:
self.filter()
time.sleep(self._config['CHECK_INTERVAL'])
logger.info('Done.')
def exit(self):
self._is_canceled = True
logger.info('Initializing O365 mail filter...')
o365mf = O365MailFilter(config)
def exit(signum, frame):
""" signal handler for a clean exit """
logger.info(f"Caught signal {signum}, exiting...")
o365mf.exit()
if __name__ == '__main__':
# register signal handlers
signal.signal(signal.SIGTERM, exit)
signal.signal(signal.SIGHUP, exit)
signal.signal(signal.SIGINT, exit)
# run it
o365mf.run()