reworked mastodon to work with frontend. user, config, logging -> new files.

master
b3yond 2018-03-23 15:51:52 +01:00
parent e6c5d0d745
commit 878a578dec
7 changed files with 188 additions and 169 deletions

View File

@ -1,73 +1,76 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging import prepare
import pytoml as toml
import time import time
import sendmail import sendmail
from db import DB
from retootbot import RetootBot from retootbot import RetootBot
from retweetbot import RetweetBot # from retweetbot import RetweetBot
from mailbot import Mailbot # from mailbot import Mailbot
from trigger import Trigger 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(): def get_users(db):
# read config in TOML format (https://github.com/toml-lang/toml#toml) user_rows = db.get_users()
with open('config.toml') as configfile: users = {}
config = toml.load(configfile) for row in user_rows:
return config 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(): def run():
config = get_config() config = prepare.get_config()
logger = get_logger(config) logger = prepare.get_logger(config)
db = DB()
# set trigger # set trigger
trigger = Trigger(config) trigger = Trigger(config)
# initialize bots while True:
bots = [] # get a dictionary { uid : [ Bot objects ] }
if config["muser"]["enabled"] != "false": users = get_users(db)
bots.append(RetootBot(config))
if config["tuser"]["enabled"] != "false": # initialize bots
bots.append(RetweetBot(config)) users = init_bots(config, logger, db, users)
if config["mail"]["enabled"] != "false":
bots.append(Mailbot(config))
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: try:
mailer.send('', config['mail']['contact'], for uid in users:
'Ticketfrei Crash Report', for bot in users[uid]:
attachment=config['logging']['logpath']) 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: 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__': if __name__ == '__main__':
run() run()

63
db.py
View File

@ -1,18 +1,18 @@
from bottle import redirect, request, response from bottle import redirect, request
from functools import wraps from functools import wraps
from inspect import Signature from inspect import Signature
import jwt import jwt
from os import path, urandom from os import path, urandom
from pylibscrypt import scrypt_mcf, scrypt_mcf_check from pylibscrypt import scrypt_mcf, scrypt_mcf_check
import sqlite3 import sqlite3
import backend import prepare
from mastodon import Mastodon from user import User
class DB(object): class DB(object):
def __init__(self): def __init__(self):
self.config = backend.get_config() self.config = prepare.get_config()
self.logger = backend.get_logger(self.config) self.logger = prepare.get_logger(self.config)
dbfile = path.join(path.dirname(path.abspath(__file__)), dbfile = path.join(path.dirname(path.abspath(__file__)),
'ticketfrei.sqlite') 'ticketfrei.sqlite')
self.conn = sqlite3.connect(dbfile) self.conn = sqlite3.connect(dbfile)
@ -132,56 +132,9 @@ class DB(object):
def close(self): def close(self):
self.conn.close() self.conn.close()
def get_users(self):
class User(object): self.cur.execute("SELECT id FROM user WHERE enabled=1;")
def __init__(self, db, uid): return self.cur.fetchall()
# 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()
class DBPlugin(object): class DBPlugin(object):

View File

@ -5,6 +5,7 @@ import tweepy
import sendmail import sendmail
import smtplib import smtplib
from mastodon import Mastodon from mastodon import Mastodon
import prepare
@get('/') @get('/')
@ -25,12 +26,12 @@ def register_post(db):
return dict(error='Email address already in use.') return dict(error='Email address already in use.')
# send confirmation mail # send confirmation mail
confirm_link = request.url + "/../confirm/" + db.token(email, password) 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.') return dict(info='Confirmation mail sent.')
def send_confirmation_mail(self, confirm_link, email): def send_confirmation_mail(config, confirm_link, email):
m = sendmail.Mailer(self.config) m = sendmail.Mailer(config)
try: try:
m.send("Complete your registration here: " + confirm_link, email, "[Ticketfrei] Confirm your account") m.send("Complete your registration here: " + confirm_link, email, "[Ticketfrei] Confirm your account")
except smtplib.SMTPRecipientsRefused: except smtplib.SMTPRecipientsRefused:
@ -142,5 +143,6 @@ def login_mastodon(user):
return dict(error='Login to Mastodon failed.') return dict(error='Login to Mastodon failed.')
config = prepare.get_config()
bottle.install(DBPlugin('/')) bottle.install(DBPlugin('/'))
bottle.run(host='localhost', port=8080) bottle.run(host=config['web']['host'], port=8080)

19
prepare.py Normal file
View File

@ -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

View File

@ -1,65 +1,31 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import mastodon import mastodon
import os
import pickle
import re import re
import time # import time
import trigger # import trigger
import sendmail # import sendmail
import report import report
import backend from user import User
class RetootBot(object): class RetootBot(object):
def __init__(self, config): def __init__(self, config, logger, uid, db):
self.config = config self.config = config
self.logger = backend.get_logger(config) self.logger = logger
self.client_id = self.register() self.user = User(db, uid)
self.m = self.login() 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 # load state
try: try:
with open('seen_toots.pickle', 'rb') as f: self.seen_toots = self.user.get_seen_toot()
self.seen_toots = pickle.load(f) except TypeError:
except IOError: self.seen_toots = 0
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
def save_last(self): def save_last(self):
""" save the last seen toot """ self.user.save_seen_toot(self.seen_toots)
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')
def crawl(self): def crawl(self):
""" """
@ -74,9 +40,9 @@ class RetootBot(object):
self.logger.error("Unknown Mastodon API Error.", exc_info=True) self.logger.error("Unknown Mastodon API Error.", exc_info=True)
return mentions return mentions
for status in all: 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 # save state
self.seen_toots.add(status['status']['id']) self.seen_toots = status['status']['id']
self.save_last() self.save_last()
# add mention to mentions # add mention to mentions
text = re.sub(r'<[^>]*>', '', status['status']['content']) text = re.sub(r'<[^>]*>', '', status['status']['content'])
@ -124,7 +90,7 @@ class RetootBot(object):
# return mentions for mirroring # return mentions for mirroring
return retoots return retoots
"""
if __name__ == '__main__': if __name__ == '__main__':
config = backend.get_config() config = backend.get_config()
@ -146,3 +112,4 @@ if __name__ == '__main__':
attachment=config['logging']['logpath']) attachment=config['logging']['logpath'])
except: except:
bot.logger.error('Mail sending failed', exc_info=True) bot.logger.error('Mail sending failed', exc_info=True)
"""

View File

@ -22,14 +22,13 @@ class Mailer(object):
""" """
# This generates the From address by stripping the part until the first # This generates the From address by stripping the part until the first
# period from the mail server address and won't work always. # period from the mail server address and won't work always.
self.fromaddr = config["mail"]["user"] + "@" + \ self.fromaddr = config["web"]["user"] + "@" + config["web"]["mailserver"].partition(".")[2]
config["mail"]["mailserver"].partition(".")[2]
# starts a client session with the SMTP server # 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() context = ssl.create_default_context()
self.s.starttls(context=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): def send(self, text, recipient, subject, attachment=None):
""" """
@ -66,8 +65,9 @@ class Mailer(object):
# For testing: # For testing:
if __name__ == '__main__': if __name__ == '__main__':
import backend import prepare
config = backend.get_config()
config = prepare.get_config()
m = Mailer(config) m = Mailer(config)
print(m.send("This is a test mail.", m.fromaddr, "Test")) print(m.send("This is a test mail.", m.fromaddr, "Test"))

75
user.py Normal file
View File

@ -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()