from config import config import jwt import logging from os import urandom from pylibscrypt import scrypt_mcf import sqlite3 logger = logging.getLogger(__name__) class DB(object): def __init__(self, dbfile): self.conn = sqlite3.connect(dbfile) self.cur = self.conn.cursor() self.create() self.secret = urandom(32) def execute(self, *args, **kwargs): return self.cur.execute(*args, **kwargs) def commit(self): self.conn.commit() def close(self): self.conn.close() def create(self): # init db self.cur.executescript(''' CREATE TABLE IF NOT EXISTS user ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, passhash TEXT, enabled INTEGER DEFAULT 1 ); CREATE TABLE IF NOT EXISTS email ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, email TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS triggerpatterns ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, patterns TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS badwords ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, words TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS mastodon_instances ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, instance TEXT, client_id TEXT, client_secret TEXT ); CREATE TABLE IF NOT EXISTS mastodon_accounts ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, access_token TEXT, instance_id INTEGER, active INTEGER, FOREIGN KEY(user_id) REFERENCES user(id), FOREIGN KEY(instance_id) REFERENCES mastodon_instances(id) ); CREATE TABLE IF NOT EXISTS seen_toots ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, mastodon_accounts_id INTEGER, toot_id TEXT, FOREIGN KEY(user_id) REFERENCES user(id), FOREIGN KEY(mastodon_accounts_id) REFERENCES mastodon_accounts(id) ); CREATE TABLE IF NOT EXISTS twitter_request_tokens ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, request_token TEXT, request_token_secret TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS twitter_accounts ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, client_id TEXT, client_secret TEXT, active INTEGER, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS telegram_accounts ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, apikey TEXT, active INTEGER, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS seen_tweets ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, twitter_accounts_id INTEGER, tweet_id TEXT, FOREIGN KEY(user_id) REFERENCES user(id) FOREIGN KEY(twitter_accounts_id) REFERENCES twitter_accounts(id) ); CREATE TABLE IF NOT EXISTS seen_dms ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, twitter_accounts_id INTEGER, message_id TEXT, FOREIGN KEY(user_id) REFERENCES user(id) FOREIGN KEY(twitter_accounts_id) REFERENCES twitter_accounts(id) ); CREATE TABLE IF NOT EXISTS mailinglist ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, email TEXT, active INTEGER, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS cities ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, city TEXT, markdown TEXT, masto_link TEXT, twit_link TEXT, FOREIGN KEY(user_id) REFERENCES user(id), UNIQUE(user_id, city) ON CONFLICT IGNORE ); ''') def user_token(self, email, password): return jwt.encode({ 'email': email, 'passhash': scrypt_mcf( password.encode('utf-8') ).decode('ascii') }, self.secret).decode('ascii') def confirm(self, token, city): from user import User try: json = jwt.decode(token, self.secret) except jwt.DecodeError: return None # invalid token if 'passhash' in json.keys(): # create user self.execute("INSERT INTO user (passhash) VALUES(?);", (json['passhash'], )) uid = self.cur.lastrowid default_triggerpatterns = """ kontroll?e konti db vgn vag zivil sicherheit uniform station bus bahn tram linie nuernberg nürnberg s\d u\d\d? """ self.execute("""INSERT INTO triggerpatterns (user_id, patterns) VALUES(?, ?); """, (uid, default_triggerpatterns)) self.execute("INSERT INTO badwords (user_id, words) VALUES(?, ?);", (uid, "bastard")) else: uid = json['uid'] self.execute("INSERT INTO email (user_id, email) VALUES(?, ?);", (uid, json['email'])) self.execute("""INSERT INTO telegram_accounts (user_id, apikey, active) VALUES(?, ?, ?);""", (uid, "", 1)) self.commit() user = User(uid) user.set_city(city) return user def by_email(self, email): from user import User self.execute("SELECT user_id FROM email WHERE email=?;", (email, )) try: uid, = self.cur.fetchone() except TypeError: return None return User(uid) def by_city(self, city): from user import User self.execute("SELECT user_id FROM cities WHERE city=?", (city, )) try: uid, = self.cur.fetchone() except TypeError: return None return User(uid) def user_facing_properties(self, city): self.execute("""SELECT city, markdown, masto_link, twit_link FROM cities WHERE city=?;""", (city, )) try: city, markdown, masto_link, twit_link = self.cur.fetchone() return dict(city=city, markdown=markdown, masto_link=masto_link, twit_link=twit_link, mailinglist=city + "@" + config["web"]["host"]) except TypeError: return None @property def active_users(self): from user import User self.execute("SELECT id FROM user WHERE enabled=1;") return [User(uid) for uid, in self.cur.fetchall()] db = DB(config['database']['db_path'])