2018-11-01 14:25:47 +00:00
#!/usr/bin/env python3
#Curious Greg - Curious Cat to Mastodon crossposter
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
2018-11-11 11:31:33 +00:00
import requests , json , hashlib , urllib , time , re
2018-11-01 14:25:47 +00:00
from mastodon import Mastodon
2018-11-04 23:17:28 +00:00
from flask import Flask , render_template , request , session , redirect , url_for
2018-11-10 12:56:45 +00:00
import mysql . connector
2018-11-06 10:30:48 +00:00
import bcrypt
2018-11-01 14:25:47 +00:00
2018-11-02 02:35:40 +00:00
cfg = json . load ( open ( " meta.json " ) )
2018-11-10 06:38:43 +00:00
scopes = [ " read:accounts " , " write:statuses " ]
2018-11-11 12:04:30 +00:00
settings = {
" cw " : False ,
# "disabled": False,
}
2018-11-02 02:35:40 +00:00
2018-11-10 12:56:45 +00:00
db = mysql . connector . connect ( user = cfg [ ' dbuser ' ] , password = cfg [ ' dbpass ' ] , database = cfg [ ' dbname ' ] )
2018-11-01 14:25:47 +00:00
c = db . cursor ( )
2018-11-11 12:04:30 +00:00
dc = db . cursor ( dictionary = True )
2018-11-11 07:30:33 +00:00
# MariaDB [curiousgreg]> DESCRIBE data;
# +---------------------+--------------+------+-----+-------------------------------------------+-----------------------------+
# | Field | Type | Null | Key | Default | Extra |
# +---------------------+--------------+------+-----+-------------------------------------------+-----------------------------+
# | username | varchar(64) | NO | PRI | NULL | |
# | instance | varchar(128) | NO | PRI | NULL | |
# | password | tinytext | NO | | NULL | |
# | avi | text | NO | | NULL | |
# | secret | tinytext | NO | | NULL | |
# | client_id | varchar(128) | NO | | NULL | |
# | client_secret | tinytext | NO | | NULL | |
# | cc | tinytext | YES | | NULL | |
# | ccavi | varchar(128) | YES | | https://lynnesbian.space/res/ceres/cc.png | |
# | latest_post | tinytext | YES | | NULL | |
# | latest_timestamp | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
# | time_between_checks | int(11) | YES | | NULL | |
2018-11-11 11:31:33 +00:00
# | settings | longtext | YES | | NULL | |
2018-11-11 07:30:33 +00:00
# +---------------------+--------------+------+-----+-------------------------------------------+-----------------------------+
2018-11-11 11:31:33 +00:00
c . execute ( " CREATE TABLE IF NOT EXISTS `data` (username VARCHAR(64) NOT NULL, instance VARCHAR(128) NOT NULL, password TINYTEXT NOT NULL, avi TEXT NOT NULL, secret TINYTEXT NOT NULL, client_id VARCHAR(128) NOT NULL, client_secret TINYTEXT NOT NULL, cc TINYTEXT, ccavi VARCHAR(128) DEFAULT ' https://lynnesbian.space/res/ceres/cc.png ' , latest_post TINYTEXT, latest_timestamp TIMESTAMP, time_between_checks INT, settings LONGTEXT, PRIMARY KEY(username, instance)) " )
2018-11-01 15:17:02 +00:00
2018-11-02 02:59:29 +00:00
app = Flask ( cfg [ ' name ' ] )
2018-11-02 03:13:08 +00:00
app . secret_key = cfg [ ' flask_key ' ]
2018-11-02 02:59:29 +00:00
2018-11-02 03:13:08 +00:00
@app.route ( ' / ' )
def main ( ) :
2018-11-04 23:17:28 +00:00
if ' acct ' not in session :
return render_template ( " landing_page.html " )
else :
return redirect ( url_for ( ' home ' ) )
@app.route ( ' /home ' )
def home ( ) :
2018-11-06 09:11:03 +00:00
if ' acct ' in session :
2018-11-11 07:30:33 +00:00
if ' cc ' not in session :
session [ ' cc ' ] = " None "
if session [ ' cc ' ] == " None " :
#every time home is rendered without cc being set
cc = c . execute ( " SELECT cc FROM `data` WHERE client_id LIKE ? AND instance LIKE ? " , ( session [ ' client_id ' ] , session [ ' instance ' ] ) ) . fetchone ( ) [ 0 ]
if cc != ' ' :
session [ ' cc ' ] = cc
if ' last_avi_update ' not in session or session [ ' last_avi_update ' ] + ( 24 * 60 * 60 ) < time . time ( ) :
#avatars haven't been updated for over 24 hours
# avis = c.execute("SELECT avi, ccavi FROM `data` WHERE client_id LIKE ?", (session['client_id'],)).fetchone()
client = Mastodon ( client_id = session [ ' client_id ' ] , client_secret = session [ ' client_secret ' ] , api_base_url = session [ ' instance ' ] )
session [ ' avi ' ] = client . account_verify_credentials ( ) [ ' avatar ' ]
if session [ ' cc ' ] != None :
#update cc avi too
r = requests . get ( " https://curiouscat.me/api/v2/profile?username= {} " . format ( session [ ' cc ' ] ) )
j = r . json ( )
session [ ' ccavi ' ] = j [ ' userData ' ] [ ' avatar ' ]
c . execute ( " UPDATE data SET avi = ?, ccavi = ? WHERE client_id LIKE ? AND instance LIKE ? " , ( session [ ' avi ' ] , session [ ' ccavi ' ] , session [ ' client_id ' ] , session [ ' instance ' ] ) )
else :
c . execute ( " UPDATE data SET avi = ? WHERE client_id LIKE ? AND instance LIKE ? " , ( session [ ' avi ' ] , session [ ' client_id ' ] , session [ ' instance ' ] ) )
return render_template ( " home.html " )
2018-11-06 09:11:03 +00:00
else :
return redirect ( url_for ( ' main ' ) )
2018-11-02 02:59:29 +00:00
2018-11-06 11:22:10 +00:00
@app.route ( ' /debug ' ) #TODO: remove this before making the site live ;p
def print_debug_info ( ) :
return json . dumps ( session . _get_current_object ( ) )
@app.route ( ' /login ' )
def log_in ( ) :
if ' acct ' in session :
#user is probably already logged in. if they aren't, home() will handle things and redirect them back here
return redirect ( url_for ( ' home ' ) )
return render_template ( " login.html " )
# return(json.dumps(client_info))
#internal stuff
2018-11-02 02:59:29 +00:00
@app.route ( ' /internal/auth_a ' )
2018-11-06 11:57:47 +00:00
def internal_auth_a ( ) : #TODO: prevent these endpoints from being spammed somehow
2018-11-04 11:36:25 +00:00
2018-11-11 07:30:33 +00:00
session [ ' instance ' ] = request . args . get ( ' instance ' , default = ' mastodon.social ' , type = str )
if not session [ ' instance ' ] . startswith ( " https:// " ) :
session [ ' instance ' ] = " https:// {} " . format ( session [ ' instance ' ] )
2018-11-06 09:11:03 +00:00
session [ ' client_id ' ] , session [ ' client_secret ' ] = Mastodon . create_app ( cfg [ ' name ' ] ,
2018-11-11 07:30:33 +00:00
api_base_url = session [ ' instance ' ] ,
2018-11-10 06:38:43 +00:00
scopes = scopes ,
2018-11-06 09:11:03 +00:00
website = cfg [ ' website ' ] ,
2018-11-10 06:38:43 +00:00
redirect_uris = [ ' https://cg.lynnesbian.space/internal/auth_b ' , ' http://localhost:5000/internal/auth_b ' ]
2018-11-04 23:17:28 +00:00
)
2018-11-11 07:30:33 +00:00
client = Mastodon ( client_id = session [ ' client_id ' ] , client_secret = session [ ' client_secret ' ] , api_base_url = session [ ' instance ' ] )
2018-11-10 06:38:43 +00:00
url = client . auth_request_url ( client_id = session [ ' client_id ' ] , redirect_uris = ' http://localhost:5000/internal/auth_b ' , scopes = scopes )
return redirect ( url , code = 307 )
2018-11-02 07:52:33 +00:00
2018-11-06 09:11:03 +00:00
@app.route ( ' /internal/auth_b ' )
def internal_auth_b ( ) :
#write details to DB
2018-11-11 07:30:33 +00:00
client = Mastodon ( client_id = session [ ' client_id ' ] , client_secret = session [ ' client_secret ' ] , api_base_url = session [ ' instance ' ] )
2018-11-10 06:38:43 +00:00
session [ ' secret ' ] = client . log_in ( code = request . args . get ( ' code ' ) , scopes = scopes , redirect_uri = ' http://localhost:5000/internal/auth_b ' )
acct_info = client . account_verify_credentials ( )
session [ ' username ' ] = acct_info [ ' username ' ]
session [ ' avi ' ] = acct_info [ ' avatar ' ]
2018-11-11 07:30:33 +00:00
session [ ' acct ' ] = " @ {} @ {} " . format ( session [ ' username ' ] , session [ ' instance ' ] . replace ( " https:// " , " " ) )
if c . execute ( " SELECT COUNT(*) FROM data WHERE username LIKE ? AND instance LIKE ? " , ( session [ ' username ' ] , session [ ' instance ' ] ) ) . fetchone ( ) [ 0 ] > 0 :
2018-11-06 10:30:48 +00:00
#user already has an account with CG
2018-11-11 07:30:33 +00:00
#update the user's info to use the new info we just got, then redirect them to the login page
c . execute ( " UPDATE data SET client_id = ?, client_secret = ?, secret = ?, avi = ? WHERE username LIKE ? AND instance LIKE ? " , ( session [ ' client_id ' ] , session [ ' client_secret ' ] , session [ ' secret ' ] , session [ ' avi ' ] , session [ ' username ' ] , session [ ' instance ' ] ) )
2018-11-06 10:30:48 +00:00
return redirect ( url_for ( ' log_in ' ) )
2018-11-06 11:57:47 +00:00
else :
return redirect ( url_for ( ' home ' ) )
2018-11-06 09:11:03 +00:00
2018-11-06 11:22:10 +00:00
@app.route ( ' /internal/do_login ' )
def do_login ( ) :
2018-11-11 11:31:33 +00:00
pw_in = request . form [ ' pw ' ]
pw_hashed = hashlib . sha256 ( pw_in . encode ( ' utf-8 ' ) )
acct = request . form [ ' acct ' ]
session [ ' username ' ] = re . match ( " ^@[^@]* " , acct ) . group ( 0 )
session [ ' instance ' ] = " https:// {} " . format ( re . search ( " @([^@]+)$ " , acct ) . group ( 1 ) )
2018-11-11 12:04:30 +00:00
data = dc . execute ( " SELECT * FROM data WHERE username LIKE ? AND password LIKE ? " , ( session [ ' username ' ] , session [ ' instance ' ] ) ) . fetch_one ( )
if bcrypt . checkpw ( pw_hashed , data [ ' password ' ] ) :
#password is correct, log the user in
for item in [ ' username ' , ' instance ' , ' avi ' , ' secret ' , ' client_id ' , ' client_secret ' , ' cc ' , ' ccavi ' ] :
session [ item ] = data [ item ]
return redirect ( ' /home ' )
else :
return redirect ( ' /login?invalid ' )
2018-11-06 10:30:48 +00:00
2018-11-06 11:22:10 +00:00
@app.route ( ' /create_password ' )
def create_password ( ) :
2018-11-11 07:30:33 +00:00
return render_template ( " create_password.html " , bg = " \" background-image:url( ' {} ' ) \" " . format ( session [ ' avi ' ] ) )
2018-11-06 11:57:47 +00:00
2018-11-06 23:10:50 +00:00
@app.route ( ' /internal/create_account ' , methods = [ ' POST ' ] )
2018-11-06 11:57:47 +00:00
def create_account ( ) :
2018-11-11 07:30:33 +00:00
pw_in = request . form [ ' pw ' ]
if len ( pw_in < 6 ) or pw_in == ' password ' :
return redirect ( ' /create_password?invalid ' )
pw_hashed = hashlib . sha256 ( pw_in . encode ( ' utf-8 ' ) )
pw = bcrypt . hashpw ( pw_hashed , bcrypt . gensalt ( 15 ) )
c . execute ( " INSERT INTO data (username, instance, avi, password, secret, client_id, client_secret) VALUES (?, ?, ?, ?, ?) " , ( session [ ' username ' ] , pw , session [ ' instance ' ] , session [ ' secret ' ] , session [ ' client_id ' ] , session [ ' client_secret ' ] ) )
2018-11-06 11:57:47 +00:00
db . commit ( )
2018-11-11 07:30:33 +00:00
return redirect ( url_for ( ' home ' ) )