reworked mastodon to work with frontend. user, config, logging -> new files.
This commit is contained in:
parent
c612a9dee0
commit
570792ba37
63
backend.py
63
backend.py
|
@ -1,54 +1,56 @@
|
||||||
#!/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)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# get a dictionary { uid : [ Bot objects ] }
|
||||||
|
users = get_users(db)
|
||||||
|
|
||||||
# initialize bots
|
# initialize bots
|
||||||
bots = []
|
users = init_bots(config, logger, db, users)
|
||||||
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))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
statuses = []
|
for uid in users:
|
||||||
while True:
|
for bot in users[uid]:
|
||||||
for bot in bots:
|
|
||||||
reports = bot.crawl()
|
reports = bot.crawl()
|
||||||
for status in reports:
|
for status in reports:
|
||||||
if not trigger.is_ok(status.text):
|
if not trigger.is_ok(status.text):
|
||||||
continue
|
continue
|
||||||
for bot2 in bots:
|
for bot2 in users[uid]:
|
||||||
if bot == bot2:
|
if bot == bot2:
|
||||||
bot2.repost(status)
|
bot2.repost(status)
|
||||||
else:
|
else:
|
||||||
|
@ -58,7 +60,8 @@ def run():
|
||||||
print("Good bye. Remember to restart the bot!")
|
print("Good bye. Remember to restart the bot!")
|
||||||
except:
|
except:
|
||||||
logger.error('Shutdown', exc_info=True)
|
logger.error('Shutdown', exc_info=True)
|
||||||
for bot in bots:
|
for uid in users:
|
||||||
|
for bot in users[uid]:
|
||||||
bot.save_last()
|
bot.save_last()
|
||||||
mailer = sendmail.Mailer(config)
|
mailer = sendmail.Mailer(config)
|
||||||
try:
|
try:
|
||||||
|
|
63
db.py
63
db.py
|
@ -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):
|
||||||
|
|
10
frontend.py
10
frontend.py
|
@ -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
19
prepare.py
Normal 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
|
||||||
|
|
||||||
|
|
71
retootbot.py
71
retootbot.py
|
@ -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)
|
||||||
|
"""
|
12
sendmail.py
12
sendmail.py
|
@ -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
75
user.py
Normal 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()
|
Loading…
Reference in a new issue