diff --git a/.gitignore b/.gitignore index 545a6b5..8b2b385 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ *.swp *.pyc .idea/ +__pycache__/ last_mention +last_mail ticketfrei.cfg seen_toots.pickle seen_toots.pickle.part diff --git a/mailbot.py b/mailbot.py index ec81b17..ec440ec 100644 --- a/mailbot.py +++ b/mailbot.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 import sendmail +import ssl +import time +import trigger import datetime import email import logger @@ -8,13 +11,14 @@ import pytoml as toml import imaplib import sys + class Mailbot(object): """ Bot which sends Mails if mentioned via twitter/mastodon, and tells other bots that it received mails. """ - def __init__(self, config, logger): + def __init__(self, config, trigger, logger, history_path="last_mail"): """ Creates a Bot who listens to mails and forwards them to other bots. @@ -22,19 +26,26 @@ class Mailbot(object): :param config: (dictionary) config.toml as a dictionary of dictionaries """ self.config = config + self.trigger = trigger self.logger = logger + self.history_path = history_path + self.last_mail = self.get_history(self.history_path) + try: self.mailinglist = self.config["mail"]["list"] except KeyError: self.mailinglist = None self.mailbox = imaplib.IMAP4_SSL(self.config["mail"]["imapserver"]) - # context = ssl.create_default_context() - # print(self.mailbox.starttls(ssl_context=context)) # print is a debug + context = ssl.create_default_context() try: - rv, data = self.mailbox.login(self.config["mail"]["user"], - self.config["mail"]["passphrase"]) + self.mailbox.starttls(ssl_context=context) + except: + logmsg = logger.generate_tb(sys.exc_info()) + logger.log(logmsg) + try: + self.mailbox.login(self.config["mail"]["user"], self.config["mail"]["passphrase"]) except imaplib.IMAP4.error: logmsg = "Login to mail server failed." logmsg = logmsg + logger.generate_tb(sys.exc_info()) @@ -47,53 +58,119 @@ class Mailbot(object): :return: """ rv, data = self.mailbox.select("Inbox") + msgs = [] if rv == 'OK': - rv, data = self.mailbox.search(None, "ALL") - print(data) rv, data = self.mailbox.search(None, "ALL") if rv != 'OK': - print("No messages found!") - return + return msgs for num in data[0].split(): rv, data = self.mailbox.fetch(num, '(RFC822)') if rv != 'OK': - print("ERROR getting message", num) - return + logmsg = "Didn't receive mail. Error: " + rv + str(data) + self.logger.log(logmsg) + return msgs msg = email.message_from_bytes(data[0][1]) - hdr = email.header.make_header(email.header.decode_header(msg['Subject'])) - subject = str(hdr) - print('Message %s: %s' % (num, subject)) - print('Raw Date:', msg['Date']) - # Now convert to local date-time - date_tuple = email.utils.parsedate_tz(msg['Date']) - if date_tuple: - local_date = datetime.datetime.fromtimestamp( - email.utils.mktime_tz(date_tuple)) - print ("Local Date:", local_date.strftime("%a, %d %b %Y %H:%M:%S")) - #print(msg.as_string()) - author = msg.get("From") # get mail author from email header - text = msg.get_payload() - print(author) - print(text) - # :todo check if they match trigger - # :todo return a nice list of warning messages - def send_warning(self, statuses): + # 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_mail() + msgs.append(msg) + return msgs + + def get_history(self, path): + """ This counter is needed to keep track of your mails, so you + 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 """ - sends warnings by twitter & mastodon to a mailing list. + 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) + + def save_last_mail(self): + """ Saves the last retweeted tweet in last_mention. """ + with open(self.history_path, "w") as f: + f.write(str(self.last_mail)) + + def send_report(self, statuses): + """ + sends reports by twitter & mastodon to a mailing list. + + :param statuses: (list) of status strings """ for status in statuses: mailer = sendmail.Mailer(self.config) mailer.send(status, self.mailinglist, "Warnung: Kontrolleure gesehen") + def to_social(self, msg): + """ + sends a report from the mailing list to social + :param msg: email.parser.Message object + :return: post: (string) of author + text + """ + # 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() + post = author + ": " + text + self.last_mail = date + self.save_last_mail() + return post + + def flow(self, statuses): + """ + to be iterated + + :param statuses: (list) of statuses to send to mailinglist + :return: list of statuses to post in mastodon & twitter + """ + self.send_report(statuses) + + msgs = self.listen() + + statuses = [] + for msg in msgs: + if self.trigger.is_ok(msg.get_payload()): + statuses.append(self.to_social(msg)) + return statuses + + 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) logger = logger.Logger(config) + trigger = trigger.Trigger(config) - m = Mailbot(config, logger) - m.listen() - + m = Mailbot(config, trigger, logger) + statuses = [] + try: + while 1: + print("Received Reports: " + str(m.flow(statuses))) + time.sleep(1) + except KeyboardInterrupt: + print("Good bye. Remember to restart the bot!") + except: + exc = sys.exc_info() # returns tuple [Exception type, Exception object, Traceback object] + message = logger.generate_tb(exc) + m.logger.log(message) + m.save_last_mail() + m.logger.shutdown(message) diff --git a/retweetbot.py b/retweetbot.py index 85a4e68..92861c8 100755 --- a/retweetbot.py +++ b/retweetbot.py @@ -22,14 +22,14 @@ class RetweetBot(object): last_mention: the ID of the last tweet which mentioned you """ - def __init__(self, trigger, config, logger, historypath="last_mention"): + def __init__(self, trigger, config, logger, history_path="last_mention"): """ Initializes the bot and loads all the necessary data. :param trigger: object of the trigger :param config: (dictionary) config.toml as a dictionary of dictionaries :param logger: object of the logger - :param historypath: Path to the file with ID of the last retweeted + :param history_path: Path to the file with ID of the last retweeted Tweet """ self.config = config @@ -42,8 +42,8 @@ class RetweetBot(object): keys[3]) # access_token_secret self.api = tweepy.API(auth) - self.historypath = historypath - self.last_mention = self.get_history(self.historypath) + self.history_path = history_path + self.last_mention = self.get_history(self.history_path) self.trigger = trigger self.waitcounter = 0 @@ -87,7 +87,7 @@ class RetweetBot(object): def save_last_mention(self): """ Saves the last retweeted tweet in last_mention. """ - with open(self.historypath, "w") as f: + with open(self.history_path, "w") as f: f.write(str(self.last_mention)) def waiting(self): @@ -136,7 +136,7 @@ class RetweetBot(object): logmsg = logmsg + self.logger.generate_tb(sys.exc_info()) self.logger.log(logmsg) self.waitcounter += 10 - return None + return [] def retweet(self, status): """ @@ -200,19 +200,18 @@ class RetweetBot(object): mentions = self.crawl_mentions() mastodon = [] - if mentions is not None: - for status in mentions: - # Is the Text of the Tweet in the triggerlist? - if self.trigger.is_ok(status.text): - # Retweet status - toot = self.retweet(status) - if toot: - mastodon.append(toot) + for status in mentions: + # Is the Text of the Tweet in the triggerlist? + if self.trigger.is_ok(status.text): + # Retweet status + toot = self.retweet(status) + if toot: + mastodon.append(toot) - # save the id so it doesn't get crawled again - if status.id > self.last_mention: - self.last_mention = status.id - self.save_last_mention() + # save the id so it doesn't get crawled again + if status.id > self.last_mention: + self.last_mention = status.id + self.save_last_mention() # Return Retweets for tooting on mastodon return mastodon