2018-01-05 13:16:24 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import sendmail
|
2018-01-05 16:13:41 +00:00
|
|
|
import ssl
|
|
|
|
import time
|
|
|
|
import trigger
|
2018-01-05 13:16:24 +00:00
|
|
|
import datetime
|
|
|
|
import email
|
2018-01-07 19:22:32 +00:00
|
|
|
import logging
|
2018-01-05 13:16:24 +00:00
|
|
|
import pytoml as toml
|
|
|
|
import imaplib
|
2018-01-18 11:42:37 +00:00
|
|
|
import report
|
2018-01-07 19:22:32 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2018-01-05 13:16:24 +00:00
|
|
|
|
2018-01-05 16:13:41 +00:00
|
|
|
|
2018-01-05 13:16:24 +00:00
|
|
|
class Mailbot(object):
|
|
|
|
"""
|
|
|
|
Bot which sends Mails if mentioned via twitter/mastodon, and tells
|
|
|
|
other bots that it received mails.
|
|
|
|
"""
|
|
|
|
|
2018-01-18 11:42:37 +00:00
|
|
|
def __init__(self, config, history_path="last_mail"):
|
2018-01-05 13:16:24 +00:00
|
|
|
"""
|
|
|
|
Creates a Bot who listens to mails and forwards them to other
|
|
|
|
bots.
|
|
|
|
|
|
|
|
:param config: (dictionary) config.toml as a dictionary of dictionaries
|
|
|
|
"""
|
|
|
|
self.config = config
|
|
|
|
|
2018-01-05 16:13:41 +00:00
|
|
|
self.history_path = history_path
|
|
|
|
self.last_mail = self.get_history(self.history_path)
|
|
|
|
|
2018-01-05 13:16:24 +00:00
|
|
|
try:
|
|
|
|
self.mailinglist = self.config["mail"]["list"]
|
|
|
|
except KeyError:
|
|
|
|
self.mailinglist = None
|
|
|
|
|
|
|
|
self.mailbox = imaplib.IMAP4_SSL(self.config["mail"]["imapserver"])
|
2018-01-05 16:13:41 +00:00
|
|
|
context = ssl.create_default_context()
|
|
|
|
try:
|
|
|
|
self.mailbox.starttls(ssl_context=context)
|
|
|
|
except:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error('StartTLS failed', exc_info=True)
|
2018-01-05 13:16:24 +00:00
|
|
|
try:
|
2018-01-05 16:13:41 +00:00
|
|
|
self.mailbox.login(self.config["mail"]["user"], self.config["mail"]["passphrase"])
|
2018-01-05 13:16:24 +00:00
|
|
|
except imaplib.IMAP4.error:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error("Login to mail server failed", exc_info=True)
|
|
|
|
try:
|
|
|
|
mailer = sendmail.Mailer(config)
|
|
|
|
mailer.send('', config['mail']['contact'],
|
|
|
|
'Ticketfrei Crash Report',
|
|
|
|
attachment=config['logging']['logpath'])
|
|
|
|
except:
|
|
|
|
logger.error('Mail sending failed', exc_info=True)
|
2018-01-05 13:16:24 +00:00
|
|
|
|
2018-01-18 12:19:11 +00:00
|
|
|
def repost(self, status):
|
2018-01-05 13:16:24 +00:00
|
|
|
"""
|
2018-01-18 11:42:37 +00:00
|
|
|
E-Mails don't have to be reposted - they already reached everyone on the mailing list.
|
2018-01-18 12:19:11 +00:00
|
|
|
The function still needs to be here because ticketfrei.py assumes it, and take the
|
|
|
|
report object they want to give us.
|
|
|
|
|
|
|
|
:param status: (report.Report object)
|
2018-01-18 11:42:37 +00:00
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
def crawl(self):
|
|
|
|
"""
|
|
|
|
crawl for new mails.
|
|
|
|
:return: msgs: (list of report.Report objects)
|
2018-01-05 13:16:24 +00:00
|
|
|
"""
|
2018-01-23 08:18:59 +00:00
|
|
|
try:
|
|
|
|
rv, data = self.mailbox.select("Inbox")
|
|
|
|
except imaplib.IMAP4.abort:
|
|
|
|
logger.error("Crawling Mail failed", exc_info=True)
|
|
|
|
rv = False
|
2018-01-05 16:13:41 +00:00
|
|
|
msgs = []
|
2018-01-05 13:16:24 +00:00
|
|
|
if rv == 'OK':
|
|
|
|
rv, data = self.mailbox.search(None, "ALL")
|
|
|
|
if rv != 'OK':
|
2018-01-05 16:13:41 +00:00
|
|
|
return msgs
|
2018-01-05 13:16:24 +00:00
|
|
|
|
|
|
|
for num in data[0].split():
|
|
|
|
rv, data = self.mailbox.fetch(num, '(RFC822)')
|
|
|
|
if rv != 'OK':
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error("Couldn't fetch mail %s %s" % (rv, str(data)))
|
2018-01-05 16:13:41 +00:00
|
|
|
return msgs
|
2018-01-05 13:16:24 +00:00
|
|
|
msg = email.message_from_bytes(data[0][1])
|
2018-01-05 16:13:41 +00:00
|
|
|
|
2018-01-18 13:23:11 +00:00
|
|
|
if not self.config['mail']['user'] + "@" + \
|
|
|
|
self.config["mail"]["mailserver"].partition(".")[2] in msg['From']:
|
|
|
|
# get a comparable date out of the email
|
|
|
|
date_tuple = email.utils.parsedate_tz(msg['Date'])
|
|
|
|
date_tuple = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
|
|
|
|
date = (date_tuple - datetime.datetime(1970, 1, 1)).total_seconds()
|
|
|
|
if date > self.get_history(self.history_path):
|
|
|
|
self.last_mail = date
|
|
|
|
self.save_last()
|
|
|
|
msgs.append(self.make_report(msg))
|
2018-01-05 16:13:41 +00:00
|
|
|
return msgs
|
|
|
|
|
|
|
|
def get_history(self, path):
|
2018-01-18 11:42:37 +00:00
|
|
|
"""
|
|
|
|
This counter is needed to keep track of your mails, so you
|
2018-01-05 16:13:41 +00:00
|
|
|
don't double parse them
|
|
|
|
|
|
|
|
:param path: string: contains path to the file where the ID of the
|
|
|
|
last_mail is stored.
|
|
|
|
:return: last_mail: ID of the last mail the bot parsed
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
with open(path, "r+") as f:
|
|
|
|
last_mail = f.read()
|
|
|
|
except IOError:
|
|
|
|
with open(path, "w+") as f:
|
|
|
|
last_mail = "0"
|
|
|
|
f.write(last_mail)
|
|
|
|
return float(last_mail)
|
|
|
|
|
2018-01-18 12:06:53 +00:00
|
|
|
def save_last(self):
|
2018-01-05 16:13:41 +00:00
|
|
|
""" Saves the last retweeted tweet in last_mention. """
|
|
|
|
with open(self.history_path, "w") as f:
|
|
|
|
f.write(str(self.last_mail))
|
|
|
|
|
2018-01-18 12:42:23 +00:00
|
|
|
def post(self, status):
|
2018-01-05 13:16:24 +00:00
|
|
|
"""
|
2018-01-18 11:42:37 +00:00
|
|
|
sends reports by other sources to a mailing list.
|
2018-01-05 16:13:41 +00:00
|
|
|
|
2018-01-18 12:42:23 +00:00
|
|
|
:param status: (report.Report object)
|
2018-01-05 13:16:24 +00:00
|
|
|
"""
|
2018-01-18 12:42:23 +00:00
|
|
|
mailer = sendmail.Mailer(self.config)
|
|
|
|
mailer.send(status.format(), self.mailinglist, "Warnung: Kontrolleure gesehen")
|
2018-01-05 13:16:24 +00:00
|
|
|
|
2018-01-18 11:42:37 +00:00
|
|
|
def make_report(self, msg):
|
2018-01-05 16:13:41 +00:00
|
|
|
"""
|
2018-01-18 11:42:37 +00:00
|
|
|
generates a report out of a mail
|
|
|
|
|
2018-01-05 16:13:41 +00:00
|
|
|
:param msg: email.parser.Message object
|
2018-01-18 11:42:37 +00:00
|
|
|
:return: post: report.Report object
|
2018-01-05 16:13:41 +00:00
|
|
|
"""
|
|
|
|
# get a comparable date out of the email
|
|
|
|
date_tuple = email.utils.parsedate_tz(msg['Date'])
|
|
|
|
date_tuple = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
|
|
|
|
date = (date_tuple-datetime.datetime(1970,1,1)).total_seconds()
|
|
|
|
|
|
|
|
author = msg.get("From") # get mail author from email header
|
|
|
|
# :todo take only the part before the @
|
|
|
|
|
|
|
|
text = msg.get_payload()
|
2018-01-18 11:42:37 +00:00
|
|
|
post = report.Report(author, "mail", text, None, date)
|
2018-01-05 16:13:41 +00:00
|
|
|
self.last_mail = date
|
2018-01-18 12:06:53 +00:00
|
|
|
self.save_last()
|
2018-01-05 16:13:41 +00:00
|
|
|
return post
|
|
|
|
|
2018-01-18 11:42:37 +00:00
|
|
|
def flow(self, trigger, statuses):
|
2018-01-05 16:13:41 +00:00
|
|
|
"""
|
2018-01-18 11:42:37 +00:00
|
|
|
to be iterated. uses trigger to separate the sheep from the goats
|
2018-01-05 16:13:41 +00:00
|
|
|
|
2018-01-18 11:42:37 +00:00
|
|
|
:param statuses: (list of report.Report objects)
|
|
|
|
:return: statuses: (list of report.Report objects)
|
2018-01-05 16:13:41 +00:00
|
|
|
"""
|
2018-01-18 13:23:11 +00:00
|
|
|
for status in statuses:
|
|
|
|
self.post(status)
|
2018-01-05 16:13:41 +00:00
|
|
|
|
2018-01-18 11:42:37 +00:00
|
|
|
msgs = self.crawl()
|
2018-01-05 16:13:41 +00:00
|
|
|
|
|
|
|
statuses = []
|
|
|
|
for msg in msgs:
|
2018-01-18 11:42:37 +00:00
|
|
|
if trigger.is_ok(msg.get_payload()):
|
|
|
|
statuses.append(msg)
|
2018-01-05 16:13:41 +00:00
|
|
|
return statuses
|
|
|
|
|
|
|
|
|
2018-01-05 13:16:24 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
# read config in TOML format (https://github.com/toml-lang/toml#toml)
|
|
|
|
with open('config.toml') as configfile:
|
|
|
|
config = toml.load(configfile)
|
|
|
|
|
2018-01-18 11:42:37 +00:00
|
|
|
# set log file
|
2018-01-18 12:06:53 +00:00
|
|
|
logger = logging.getLogger()
|
2018-01-07 19:22:32 +00:00
|
|
|
fh = logging.FileHandler(config['logging']['logpath'])
|
|
|
|
fh.setLevel(logging.DEBUG)
|
|
|
|
logger.addHandler(fh)
|
2018-01-05 13:16:24 +00:00
|
|
|
|
2018-01-18 11:42:37 +00:00
|
|
|
# initialise trigger
|
2018-01-07 19:22:32 +00:00
|
|
|
trigger = trigger.Trigger(config)
|
2018-01-18 11:42:37 +00:00
|
|
|
|
|
|
|
# initialise mail bot
|
|
|
|
m = Mailbot(config)
|
|
|
|
|
2018-01-05 16:13:41 +00:00
|
|
|
statuses = []
|
|
|
|
try:
|
|
|
|
while 1:
|
2018-01-18 11:42:37 +00:00
|
|
|
print("Received Reports: " + str(m.flow(trigger, statuses)))
|
2018-01-05 16:13:41 +00:00
|
|
|
time.sleep(1)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print("Good bye. Remember to restart the bot!")
|
|
|
|
except:
|
2018-01-07 19:22:32 +00:00
|
|
|
logger.error('Shutdown', exc_info=True)
|
2018-01-18 12:06:53 +00:00
|
|
|
m.save_last()
|
2018-01-07 19:22:32 +00:00
|
|
|
try:
|
|
|
|
mailer = sendmail.Mailer(config)
|
|
|
|
mailer.send('', config['mail']['contact'],
|
|
|
|
'Ticketfrei Crash Report',
|
|
|
|
attachment=config['logging']['logpath'])
|
|
|
|
except:
|
|
|
|
logger.error('Mail sending failed', exc_info=True)
|