From 570792ba37be3a007aef0d0a7fae3f2c89a9dc77 Mon Sep 17 00:00:00 2001 From: b3yond Date: Fri, 23 Mar 2018 15:51:52 +0100 Subject: [PATCH] reworked mastodon to work with frontend. user, config, logging -> new files. --- backend.py | 107 ++++++++++++++++++++++++++------------------------- db.py | 63 ++++-------------------------- frontend.py | 10 +++-- prepare.py | 19 +++++++++ retootbot.py | 71 +++++++++------------------------- sendmail.py | 12 +++--- user.py | 75 ++++++++++++++++++++++++++++++++++++ 7 files changed, 188 insertions(+), 169 deletions(-) create mode 100644 prepare.py create mode 100644 user.py diff --git a/backend.py b/backend.py index b0e6d08..669d96a 100755 --- a/backend.py +++ b/backend.py @@ -1,73 +1,76 @@ #!/usr/bin/env python3 -import logging -import pytoml as toml +import prepare import time + import sendmail +from db import DB from retootbot import RetootBot -from retweetbot import RetweetBot -from mailbot import Mailbot +# from retweetbot import RetweetBot +# from mailbot import Mailbot from trigger import Trigger -def get_logger(config): - logpath = config['logging']['logpath'] - logger = logging.getLogger() - fh = logging.FileHandler(logpath) - fh.setLevel(logging.DEBUG) - logger.addHandler(fh) - return logger -def get_config(): - # read config in TOML format (https://github.com/toml-lang/toml#toml) - with open('config.toml') as configfile: - config = toml.load(configfile) - return config +def get_users(db): + user_rows = db.get_users() + users = {} + for row in user_rows: + users[row[0]] = [] + return users + + +def init_bots(config, logger, db, users): + for uid in users: + users[uid].append(RetootBot(config, logger, uid, db)) + # users[uid].append(RetweetBot(config, uid, db)) + # users[uid].append(Mailbot(config, uid, db)) + return users + def run(): - config = get_config() - logger = get_logger(config) + config = prepare.get_config() + logger = prepare.get_logger(config) + db = DB() # set trigger trigger = Trigger(config) - # initialize bots - bots = [] - if config["muser"]["enabled"] != "false": - bots.append(RetootBot(config)) - if config["tuser"]["enabled"] != "false": - bots.append(RetweetBot(config)) - if config["mail"]["enabled"] != "false": - bots.append(Mailbot(config)) + while True: + # get a dictionary { uid : [ Bot objects ] } + users = get_users(db) + + # initialize bots + users = init_bots(config, logger, db, users) - try: - statuses = [] - while True: - for bot in bots: - reports = bot.crawl() - for status in reports: - if not trigger.is_ok(status.text): - continue - for bot2 in bots: - if bot == bot2: - bot2.repost(status) - else: - bot2.post(status) - time.sleep(60) # twitter rate limit >.< - except KeyboardInterrupt: - print("Good bye. Remember to restart the bot!") - except: - logger.error('Shutdown', exc_info=True) - for bot in bots: - bot.save_last() - mailer = sendmail.Mailer(config) try: - mailer.send('', config['mail']['contact'], - 'Ticketfrei Crash Report', - attachment=config['logging']['logpath']) + for uid in users: + for bot in users[uid]: + reports = bot.crawl() + for status in reports: + if not trigger.is_ok(status.text): + continue + for bot2 in users[uid]: + if bot == bot2: + bot2.repost(status) + else: + bot2.post(status) + time.sleep(60) # twitter rate limit >.< + except KeyboardInterrupt: + print("Good bye. Remember to restart the bot!") except: - logger.error('Mail sending failed', exc_info=True) + logger.error('Shutdown', exc_info=True) + for uid in users: + for bot in users[uid]: + bot.save_last() + mailer = sendmail.Mailer(config) + try: + mailer.send('', config['mail']['contact'], + 'Ticketfrei Crash Report', + attachment=config['logging']['logpath']) + except: + logger.error('Mail sending failed', exc_info=True) if __name__ == '__main__': - run() \ No newline at end of file + run() diff --git a/db.py b/db.py index ad19b4b..8cd1fef 100644 --- a/db.py +++ b/db.py @@ -1,18 +1,18 @@ -from bottle import redirect, request, response +from bottle import redirect, request from functools import wraps from inspect import Signature import jwt from os import path, urandom from pylibscrypt import scrypt_mcf, scrypt_mcf_check import sqlite3 -import backend -from mastodon import Mastodon +import prepare +from user import User class DB(object): def __init__(self): - self.config = backend.get_config() - self.logger = backend.get_logger(self.config) + self.config = prepare.get_config() + self.logger = prepare.get_logger(self.config) dbfile = path.join(path.dirname(path.abspath(__file__)), 'ticketfrei.sqlite') self.conn = sqlite3.connect(dbfile) @@ -132,56 +132,9 @@ class DB(object): def close(self): self.conn.close() - -class User(object): - def __init__(self, db, uid): - # set cookie - response.set_cookie('uid', uid, secret=db.secret, path='/') - self.db = db - self.uid = uid - - def state(self): - return dict(foo='bar') - - def save_request_token(self, token): - self.db.cur.execute("INSERT INTO twitter_request_tokens(user_id, request_token) VALUES(?, ?);", - (self.uid, token)) - self.db.conn.commit() - - def get_request_token(self): - self.db.cur.execute("SELECT request_token FROM twitter_request_tokens WHERE user_id = ?;", (id,)) - request_token = self.db.cur.fetchone()[0] - self.db.cur.execute("DELETE FROM twitter_request_tokens WHERE user_id = ?;", (id,)) - self.db.conn.commit() - return request_token - - def save_twitter_token(self, access_token, access_token_secret): - self.db.cur.execute( - "INSERT INTO twitter_accounts(user_id, access_token_key, access_token_secret) VALUES(?, ?, ?);", - (id, access_token, access_token_secret)) - self.db.conn.commit() - - def get_mastodon_app_keys(self, instance): - self.db.cur.execute("SELECT client_id, client_secret FROM mastodon_instances WHERE instance = ?;", (instance, )) - try: - row = self.db.cur.fetchone() - client_id = row[0] - client_secret = row[1] - return client_id, client_secret - except TypeError: - app_name = "ticketfrei" + str(self.db.secret)[0:4] - client_id, client_secret = Mastodon.create_app(app_name, api_base_url=instance) - self.db.cur.execute("INSERT INTO mastodon_instances(instance, client_id, client_secret) VALUES(?, ?, ?);", - (instance, client_id, client_secret)) - self.db.conn.commit() - return client_id, client_secret - - def save_masto_token(self, access_token, instance): - self.db.cur.execute("SELECT id FROM mastodon_instances WHERE instance = ?;", (instance, )) - instance_id = self.db.cur.fetchone()[0] - self.db.cur.execute("INSERT INTO mastodon_accounts(user_id, access_token, instance_id, active) " - "VALUES(?, ?, ?, ?);", (self.uid, access_token, instance_id, 1)) - self.db.conn.commit() + def get_users(self): + self.cur.execute("SELECT id FROM user WHERE enabled=1;") + return self.cur.fetchall() class DBPlugin(object): diff --git a/frontend.py b/frontend.py index eb57ec8..b57ccfe 100644 --- a/frontend.py +++ b/frontend.py @@ -5,6 +5,7 @@ import tweepy import sendmail import smtplib from mastodon import Mastodon +import prepare @get('/') @@ -25,12 +26,12 @@ def register_post(db): return dict(error='Email address already in use.') # send confirmation mail confirm_link = request.url + "/../confirm/" + db.token(email, password) - db.send_confirmation_mail(confirm_link, email) + send_confirmation_mail(db.config, confirm_link, email) return dict(info='Confirmation mail sent.') -def send_confirmation_mail(self, confirm_link, email): - m = sendmail.Mailer(self.config) +def send_confirmation_mail(config, confirm_link, email): + m = sendmail.Mailer(config) try: m.send("Complete your registration here: " + confirm_link, email, "[Ticketfrei] Confirm your account") except smtplib.SMTPRecipientsRefused: @@ -142,5 +143,6 @@ def login_mastodon(user): return dict(error='Login to Mastodon failed.') +config = prepare.get_config() bottle.install(DBPlugin('/')) -bottle.run(host='localhost', port=8080) +bottle.run(host=config['web']['host'], port=8080) diff --git a/prepare.py b/prepare.py new file mode 100644 index 0000000..58c482a --- /dev/null +++ b/prepare.py @@ -0,0 +1,19 @@ +import logging +import pytoml as toml + +def get_logger(config): + logpath = config['logging']['logpath'] + logger = logging.getLogger() + fh = logging.FileHandler(logpath) + fh.setLevel(logging.DEBUG) + logger.addHandler(fh) + return logger + + +def get_config(): + # read config in TOML format (https://github.com/toml-lang/toml#toml) + with open('config.toml') as configfile: + config = toml.load(configfile) + return config + + diff --git a/retootbot.py b/retootbot.py index dba9e48..c1f6fc9 100755 --- a/retootbot.py +++ b/retootbot.py @@ -1,65 +1,31 @@ #!/usr/bin/env python3 import mastodon -import os -import pickle import re -import time -import trigger -import sendmail +# import time +# import trigger +# import sendmail import report -import backend +from user import User + class RetootBot(object): - def __init__(self, config): + def __init__(self, config, logger, uid, db): self.config = config - self.logger = backend.get_logger(config) - self.client_id = self.register() - self.m = self.login() + self.logger = logger + self.user = User(db, uid) + client_id, client_secret, access_token, instance_url = self.user.get_masto_credentials() + self.m = mastodon.Mastodon(client_id=client_id, client_secret=client_secret, + access_token=access_token, api_base_url=instance_url) # load state try: - with open('seen_toots.pickle', 'rb') as f: - self.seen_toots = pickle.load(f) - except IOError: - self.seen_toots = set() - - def register(self): - client_id = os.path.join( - 'appkeys', - self.config['mapp']['name'] + - '@' + self.config['muser']['server'] - ) - - if not os.path.isfile(client_id): - mastodon.Mastodon.create_app( - self.config['mapp']['name'], - api_base_url=self.config['muser']['server'], - to_file=client_id - ) - return client_id - - def login(self): - m = mastodon.Mastodon( - client_id=self.client_id, - api_base_url=self.config['muser']['server'] - ) - m.log_in( - self.config['muser']['email'], - self.config['muser']['password'] - ) - return m + self.seen_toots = self.user.get_seen_toot() + except TypeError: + self.seen_toots = 0 def save_last(self): - """ save the last seen toot """ - try: - with os.fdopen(os.open('seen_toots.pickle.part', os.O_WRONLY | os.O_EXCL | os.O_CREAT), 'wb') as f: - pickle.dump(self.seen_toots, f) - except FileExistsError: - os.unlink('seen_toots.pickle.part') - with os.fdopen(os.open('seen_toots.pickle.part', os.O_WRONLY | os.O_EXCL | os.O_CREAT), 'wb') as f: - pickle.dump(self.seen_toots, f) - os.rename('seen_toots.pickle.part', 'seen_toots.pickle') + self.user.save_seen_toot(self.seen_toots) def crawl(self): """ @@ -74,9 +40,9 @@ class RetootBot(object): self.logger.error("Unknown Mastodon API Error.", exc_info=True) return mentions for status in all: - if (status['type'] == 'mention' and status['status']['id'] not in self.seen_toots): + if status['type'] == 'mention' and status['status']['id'] > self.seen_toots: # save state - self.seen_toots.add(status['status']['id']) + self.seen_toots = status['status']['id'] self.save_last() # add mention to mentions text = re.sub(r'<[^>]*>', '', status['status']['content']) @@ -124,7 +90,7 @@ class RetootBot(object): # return mentions for mirroring return retoots - +""" if __name__ == '__main__': config = backend.get_config() @@ -146,3 +112,4 @@ if __name__ == '__main__': attachment=config['logging']['logpath']) except: bot.logger.error('Mail sending failed', exc_info=True) +""" \ No newline at end of file diff --git a/sendmail.py b/sendmail.py index 93dc272..348aefb 100755 --- a/sendmail.py +++ b/sendmail.py @@ -22,14 +22,13 @@ class Mailer(object): """ # This generates the From address by stripping the part until the first # period from the mail server address and won't work always. - self.fromaddr = config["mail"]["user"] + "@" + \ - config["mail"]["mailserver"].partition(".")[2] + self.fromaddr = config["web"]["user"] + "@" + config["web"]["mailserver"].partition(".")[2] # starts a client session with the SMTP server - self.s = smtplib.SMTP(config["mail"]["mailserver"]) + self.s = smtplib.SMTP(config["web"]["mailserver"]) context = ssl.create_default_context() self.s.starttls(context=context) - self.s.login(config["mail"]["user"], config["mail"]["passphrase"]) + self.s.login(config["web"]["user"], config["web"]["passphrase"]) def send(self, text, recipient, subject, attachment=None): """ @@ -66,8 +65,9 @@ class Mailer(object): # For testing: if __name__ == '__main__': - import backend - config = backend.get_config() + import prepare + + config = prepare.get_config() m = Mailer(config) print(m.send("This is a test mail.", m.fromaddr, "Test")) diff --git a/user.py b/user.py new file mode 100644 index 0000000..3dfc9fb --- /dev/null +++ b/user.py @@ -0,0 +1,75 @@ +from bottle import response +from mastodon import Mastodon + + +class User(object): + def __init__(self, db, uid): + # set cookie + response.set_cookie('uid', uid, secret=db.secret, path='/') + self.db = db + self.uid = uid + + def get_masto_credentials(self): + self.db.cur.execute("SELECT access_token, instance_id FROM mastodon_accounts WHERE user_id = ? AND active = 1;", + (self.uid, )) + row = self.db.cur.fetchone() + self.db.cur.execute("SELECT instance, client_id, client_secret FROM mastodon_instances WHERE id = ?;", + (row[1], )) + instance = self.db.cur.fetchone() + return instance[1], instance[2], row[0], instance[0] + + def get_mastodon_account_id(self): + self.db.cur.execute("SELECT id FROM mastodon_accounts WHERE user_id = ?;", (self.uid, )) + return self.db.cur.fetchone()[0] + + def get_seen_toot(self): + self.db.cur.execute("SELECT toot_id FROM seen_toots WHERE user_id = ? AND mastodon_accounts_id = ?;", + (self.uid, self.get_mastodon_account_id())) + return self.db.cur.fetchone()[0] + + def save_seen_toot(self, toot_id): + self.db.cur.execute("UPDATE seen_toots SET toot_id = ? WHERE user_id = ? AND mastodon_accounts_id = ?;", + (toot_id, self.uid, self.get_mastodon_account_id())) + + def state(self): + return dict(foo='bar') + + def save_request_token(self, token): + self.db.cur.execute("INSERT INTO twitter_request_tokens(user_id, request_token) VALUES(?, ?);", + (self.uid, token)) + self.db.conn.commit() + + def get_request_token(self): + self.db.cur.execute("SELECT request_token FROM twitter_request_tokens WHERE user_id = ?;", (id,)) + request_token = self.db.cur.fetchone()[0] + self.db.cur.execute("DELETE FROM twitter_request_tokens WHERE user_id = ?;", (id,)) + self.db.conn.commit() + return request_token + + def save_twitter_token(self, access_token, access_token_secret): + self.db.cur.execute( + "INSERT INTO twitter_accounts(user_id, access_token_key, access_token_secret) VALUES(?, ?, ?);", + (id, access_token, access_token_secret)) + self.db.conn.commit() + + def get_mastodon_app_keys(self, instance): + self.db.cur.execute("SELECT client_id, client_secret FROM mastodon_instances WHERE instance = ?;", (instance, )) + try: + row = self.db.cur.fetchone() + client_id = row[0] + client_secret = row[1] + return client_id, client_secret + except TypeError: + app_name = "ticketfrei" + str(self.db.secret)[0:4] + client_id, client_secret = Mastodon.create_app(app_name, api_base_url=instance) + self.db.cur.execute("INSERT INTO mastodon_instances(instance, client_id, client_secret) VALUES(?, ?, ?);", + (instance, client_id, client_secret)) + self.db.conn.commit() + return client_id, client_secret + + def save_masto_token(self, access_token, instance): + self.db.cur.execute("SELECT id FROM mastodon_instances WHERE instance = ?;", (instance, )) + instance_id = self.db.cur.fetchone()[0] + self.db.cur.execute("INSERT INTO mastodon_accounts(user_id, access_token, instance_id, active) " + "VALUES(?, ?, ?, ?);", (self.uid, access_token, instance_id, 1)) + self.db.conn.commit()