1
0
Fork 0
mirror of https://github.com/Lynnesbian/FediBooks/ synced 2024-11-25 16:48:58 +00:00

Compare commits

..

22 commits

Author SHA1 Message Date
8f476ca981 fixed inconsistent css formatting 2019-09-02 20:45:58 +10:00
ec758d9d8f users can now toggle accounts the bot learns from 2019-09-02 20:44:03 +10:00
bca0da6c45 fixed wrong colour on visited links 2019-09-02 20:43:48 +10:00
9bbc669fcc cleaned up home template 2019-09-02 20:28:05 +10:00
f4465219d0 list accounts bot is learning from 2019-09-02 20:26:45 +10:00
ca6e06668f add enabled column to bot_learned_accounts 2019-09-02 20:14:03 +10:00
bf10de0e17 implemented adding accounts to bots 2019-09-02 20:07:16 +10:00
dc03ebed18 display bot's instance smaller than username, fixes #12 2019-09-02 18:18:47 +10:00
fad45bd6d3 add timeout to requests.get 2019-09-02 18:06:21 +10:00
9306a72b99 generic bot icon placeholder 2019-09-02 18:04:26 +10:00
46118ae9fa implemented bot deletion 2019-09-02 18:02:26 +10:00
494c1d8960 forgot to commit this 2019-09-02 17:59:57 +10:00
bf7571eba1 settings page reflects user's settings 2019-09-02 17:49:39 +10:00
913b5ed338 implemented bot toggle 2019-09-02 17:38:29 +10:00
3b7b479b1b show basic coming soon page for unimplemented features 2019-09-02 17:20:28 +10:00
887aab6a71 make the delete page look and work better 2019-09-02 16:59:49 +10:00
8f72d14243 correctly display bot enabled status 2019-09-02 16:49:21 +10:00
950db1de8e fix bot creation and home pages 2019-09-02 16:47:49 +10:00
879b53889d use bot handle as PK 2019-09-02 16:36:42 +10:00
4230986c2b fix old table name being used 2019-09-02 15:57:11 +10:00
a5d775d5e7 reduce size of bot id column 2019-09-02 15:55:51 +10:00
1d3f9fbc73 implement bot back button 2019-09-02 15:35:02 +10:00
11 changed files with 217 additions and 69 deletions

View file

@ -15,10 +15,9 @@ CREATE TABLE IF NOT EXISTS `credentials` (
`secret` VARCHAR(128) NOT NULL `secret` VARCHAR(128) NOT NULL
) ENGINE=INNODB; ) ENGINE=INNODB;
CREATE TABLE IF NOT EXISTS `bots` ( CREATE TABLE IF NOT EXISTS `bots` (
`id` BINARY(64) PRIMARY KEY, `handle` VARCHAR(128) PRIMARY KEY,
`user_id` INT NOT NULL, `user_id` INT NOT NULL,
`credentials_id` INT NOT NULL, `credentials_id` INT NOT NULL,
`handle` VARCHAR(128) NOT NULL,
`enabled` BOOLEAN DEFAULT 1, `enabled` BOOLEAN DEFAULT 1,
`replies_enabled` BOOLEAN DEFAULT 1, `replies_enabled` BOOLEAN DEFAULT 1,
`post_frequency` SMALLINT UNSIGNED DEFAULT 30, `post_frequency` SMALLINT UNSIGNED DEFAULT 30,
@ -37,15 +36,16 @@ CREATE TABLE IF NOT EXISTS `bots` (
CREATE TABLE IF NOT EXISTS `fedi_accounts` ( CREATE TABLE IF NOT EXISTS `fedi_accounts` (
`handle` VARCHAR(128) PRIMARY KEY, `handle` VARCHAR(128) PRIMARY KEY,
`outbox` VARCHAR(256), `outbox` VARCHAR(256),
`credentials_id` INT NOT NULL, `credentials_id` INT,
`icon` VARCHAR(512), `icon` VARCHAR(512),
`icon_update_time` DATETIME DEFAULT 0, `icon_update_time` DATETIME DEFAULT 0,
FOREIGN KEY (`credentials_id`) REFERENCES credentials(id) ON DELETE CASCADE FOREIGN KEY (`credentials_id`) REFERENCES credentials(id) ON DELETE CASCADE
) ENGINE=INNODB; ) ENGINE=INNODB;
CREATE TABLE IF NOT EXISTS `bot_learned_accounts` ( CREATE TABLE IF NOT EXISTS `bot_learned_accounts` (
`bot_id` BINARY(64) NOT NULL, `bot_id` VARCHAR(128) NOT NULL,
`fedi_id` VARCHAR(128) NOT NULL, `fedi_id` VARCHAR(128) NOT NULL,
FOREIGN KEY (`bot_id`) REFERENCES bots(id) ON DELETE CASCADE, `enabled` BOOLEAN DEFAULT 1,
FOREIGN KEY (`bot_id`) REFERENCES bots(handle) ON DELETE CASCADE,
FOREIGN KEY (`fedi_id`) REFERENCES fedi_accounts(handle) ON DELETE CASCADE FOREIGN KEY (`fedi_id`) REFERENCES fedi_accounts(handle) ON DELETE CASCADE
) ENGINE=INNODB; ) ENGINE=INNODB;
CREATE TABLE IF NOT EXISTS `posts` ( CREATE TABLE IF NOT EXISTS `posts` (
@ -54,14 +54,14 @@ CREATE TABLE IF NOT EXISTS `posts` (
`post_id` VARCHAR(64) NOT NULL, `post_id` VARCHAR(64) NOT NULL,
`content` TEXT NOT NULL, `content` TEXT NOT NULL,
`cw` BOOLEAN NOT NULL, `cw` BOOLEAN NOT NULL,
FOREIGN KEY (`fedi_id`) REFERENCES fedi_account(handle) ON DELETE CASCADE FOREIGN KEY (`fedi_id`) REFERENCES fedi_accounts(handle) ON DELETE CASCADE
) ENGINE=INNODB; ) ENGINE=INNODB;
CREATE TABLE IF NOT EXISTS `word_blacklist` ( CREATE TABLE IF NOT EXISTS `word_blacklist` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `id` INT AUTO_INCREMENT PRIMARY KEY,
`bot_id` BINARY(64) NOT NULL, `bot_id` VARCHAR(128) NOT NULL,
`phrase` VARCHAR(128) NOT NULL, `phrase` VARCHAR(128) NOT NULL,
`whole_word` BOOLEAN NOT NULL, `whole_word` BOOLEAN NOT NULL,
FOREIGN KEY (`bot_id`) REFERENCES bots(id) ON DELETE CASCADE FOREIGN KEY (`bot_id`) REFERENCES bots(handle) ON DELETE CASCADE
) ENGINE=INNODB; ) ENGINE=INNODB;
CREATE TABLE IF NOT EXISTS `contact_history` ( CREATE TABLE IF NOT EXISTS `contact_history` (
`user_id` INT NOT NULL, `user_id` INT NOT NULL,

BIN
static/bot_generic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -10,7 +10,7 @@ body {
.container { .container {
background-color: #444a5c; background-color: #444a5c;
padding:10px; padding: 10px;
} }
.light { .light {
background-color: #4d5366; background-color: #4d5366;
@ -26,6 +26,9 @@ body {
.small { .small {
font-size: 0.8em; font-size: 0.8em;
} }
.tiny {
font-size: 0.5em;
}
.centred { .centred {
text-align: center; text-align: center;
} }
@ -45,7 +48,11 @@ body {
.panel-icon { .panel-icon {
width: 100px; width: 100px;
height: 100px; height: 100px;
background: center/contain url("https://cloud.lynnesbian.space/s/JZCPTs3DzMLAspC/preview"); background: center/contain url("/img/bot_generic.png");
}
.panel-icon.large {
width: 150px;
height: 150px;
} }
.panel-icon.online, .panel-icon.offline { .panel-icon.online, .panel-icon.offline {
width: 105px; width: 105px;
@ -74,16 +81,16 @@ body {
.button { .button {
color: white; color: white;
line-height:1.2em; line-height: 1.2em;
padding:10px; padding: 10px;
text-decoration: none; text-decoration: none;
display: inline-block; display: inline-block;
margin: 5px 0; margin: 5px 0;
transition: 0.2s linear; transition: 0.2s linear;
border:none; border: none;
} }
.button:visited { .button:visited {
color:white; color: white;
} }
input.button, button.button { input.button, button.button {
font-size: 1em; font-size: 1em;
@ -122,7 +129,7 @@ a {
text-decoration: none; text-decoration: none;
} }
a:visited { a:visited {
color: white; color: mediumpurple;
} }
h1 { h1 {
@ -135,17 +142,17 @@ h1, h2, h3, h4, h5, h6 {
} }
form { form {
display:inline-block; display: inline-block;
} }
label.important { label.important {
font-size: 1.4em; font-size: 1.4em;
margin: 10px 0; margin: 10px 0;
font-weight: 300; font-weight: 300;
display:block; display: block;
} }
input, select { input, select {
font-size: 1.2em; font-size: 1.2em;
line-height:1.4em; line-height: 1.4em;
border: 3px grey solid; border: 3px grey solid;
border-radius: none; border-radius: none;
padding: 3px; padding: 3px;
@ -164,3 +171,10 @@ label {
form .row { form .row {
margin: 10px 0; margin: 10px 0;
} }
.coming-soon {
height: 200px;
width: 200px;
background: center/contain url("https://lynnesbian.space/img/bune.png");
display: inline-block;
}

View file

@ -9,7 +9,7 @@
<body> <body>
<div class="container centred"> <div class="container centred">
<h1 class="thin centred">Set accounts to learn from</h1> <h1 class="thin centred">Set accounts to learn from</h1>
<p class="large centred">@botname@example.com</p> <p class="large centred">{{ handle }}</p>
<p class="centred" style="margin: 50px 0;"> <p class="centred" style="margin: 50px 0;">
<a class="button btn-primary btn-large" href="/bot/accounts/add" role="button"><i class="fas fa-plus"></i> Add account</a> <a class="button btn-primary btn-large" href="/bot/accounts/add" role="button"><i class="fas fa-plus"></i> Add account</a>
<a class="button btn-secondary btn-large" href="/" role="button"><i class="fas fa-arrow-left"></i> Back</a> <a class="button btn-secondary btn-large" href="/" role="button"><i class="fas fa-arrow-left"></i> Back</a>
@ -17,16 +17,19 @@
</div> </div>
<div class="container" style="min-height: 300px;"> <div class="container" style="min-height: 300px;">
{% for user in users %}
<div class="row light"> <div class="row light">
<div class="panel-icon"></div> <div class="panel-icon {{ "online" if user['enabled'] else "offline" }}"></div>
<div class="panel-text"> <div class="panel-text">
<div class="panel-name">@user<span class="subtle">@instan.ce</span></div> {% set handle_list = user['fedi_id'].split('@') %}
<div class="panel-status">12345 posts stored</div> <div class="panel-name">@{{ handle_list[1] }}<span class="subtle tiny">@{{ handle_list[2] }}</span></div>
<div class="panel-status">{{ "Active" if user['enabled'] else "Inactive" }}</div>
</div> </div>
<div class="panel-actions"> <div class="panel-actions">
<a class="button btn-secondary" href="/bot/accounts/toggle/insert id here" title="Turn on/off"><i class="fas fa-power-off"></i></a><a class="button btn-dangerous" href="/bot/accounts/delete/insert id here" title="Delete"><i class="fas fa-trash"></i></a> <a class="button btn-secondary" href="/bot/accounts/toggle/{{ user['fedi_id'] }}" title="Turn on/off"><i class="fas fa-power-off"></i></a><a class="button btn-dangerous" href="/bot/accounts/delete/{{ user['fedi_id'] }}" title="Delete"><i class="fas fa-trash"></i></a>
</div> </div>
</div> </div>
{% endfor %}
</div> </div>
{% include 'footer.html' %} {% include 'footer.html' %}

View file

@ -9,7 +9,6 @@
<body> <body>
<div class="container"> <div class="container">
<h1 class="thin centred">Add account</h1> <h1 class="thin centred">Add account</h1>
<p class="large thin centred">Step {{ session['step'] }}</p>
</div> </div>
<div class="container centred"> <div class="container centred">
@ -38,7 +37,7 @@
<div class="container centred"> <div class="container centred">
<a href="/" class="button btn-secondary"><i class="fas fa-times"></i> Cancel</a> <a href="/" class="button btn-secondary"><i class="fas fa-times"></i> Cancel</a>
{% if session['step'] != 1 %} {% if session['step'] != 1 %}
<button class="button btn-secondary"><i class="fas fa-arrow-left"></i> Back</button> <a href="/bot/accounts/add/back" class="button btn-secondary"><i class="fas fa-arrow-left"></i> Back</a>
{% endif %} {% endif %}
<button class="button btn-primary"><i class="fas fa-arrow-right"></i> Next</button> <button class="button btn-primary"><i class="fas fa-arrow-right"></i> Next</button>
</div> </div>

View file

@ -40,9 +40,9 @@
<div class="container centred"> <div class="container centred">
<a href="/" class="button btn-secondary"><i class="fas fa-times"></i> Cancel</a> <a href="/" class="button btn-secondary"><i class="fas fa-times"></i> Cancel</a>
{% if session['step'] != 1 %} {% if session['step'] != 1 %}
<button class="button btn-secondary"><i class="fas fa-arrow-left"></i> Back</button> <a href="/bot/create/back" class="button btn-secondary"><i class="fas fa-arrow-left"></i> Back</a>
{% endif %} {% endif %}
{% if session['step'] < 5 %} {% if session['step'] < 4 %}
<button class="button btn-primary"><i class="fas fa-arrow-right"></i> Next</button> <button class="button btn-primary"><i class="fas fa-arrow-right"></i> Next</button>
{% else %} {% else %}
<a href="/" class="button btn-primary"><i class="fas fa-check"></i> Finish</a> <a href="/" class="button btn-primary"><i class="fas fa-check"></i> Finish</a>

View file

@ -9,14 +9,14 @@
<body> <body>
<div class="container centred"> <div class="container centred">
<h1 class="thin centred">Delete bot</h1> <h1 class="thin centred">Delete bot</h1>
<div class="panel-icon"></div>
</div> </div>
<div class="container centred"> <div class="container centred">
<form> <form method='POST'>
<div class="panel-icon large"></div>
<div class="container centred"> <div class="container centred">
<p>Are you sure you want to <strong>permanently</strong> delete bot name?</p> <p>Are you sure you want to <strong>permanently</strong> delete this bot?</p>
<p>The account on instan.ce will remain open, but your bot will stop posting from it.</p> <p>The account on {{ instance }} will remain open, but your bot will stop posting from it.</p>
<a class="button btn-secondary" href="/"><i class="fas fa-times"></i> Cancel</a> <a class="button btn-secondary" href="/"><i class="fas fa-times"></i> Cancel</a>
<button class="button btn-dangerous"><i class="fas fa-trash"></i> Delete bot</button> <button class="button btn-dangerous"><i class="fas fa-trash"></i> Delete bot</button>
</div> </div>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FediBooks</title>
{% include 'imports.html' %}
</head>
<body>
<div class="container centred light">
<h1 class="thin centred">Coming soon!</h1>
<div class="coming-soon"></div>
<br>
<a href="/" class="button btn-primary large"><i class="fas fa-home"></i> Home</a>
</div>
{% include 'footer.html' %}
</body>
</html>

View file

@ -19,13 +19,14 @@
<div class="container" style="min-height: 300px;"> <div class="container" style="min-height: 300px;">
{% for bot in bots %} {% for bot in bots %}
<div class="row light"> <div class="row light">
<div class="panel-icon {% if bot['enabled'] %}online{% else %}offline{% endif %}"></div> <div class="panel-icon {{ "online" if bot['enabled'] else "offline"}}"></div>
<div class="panel-text"> <div class="panel-text">
<div class="panel-name">{{ bot['handle'] }}</div> {% set handle_list = bot['handle'].split('@') %}
<div class="panel-status">{% if bot['enabled'] %}Online{% else %}Offline{% endif %}, learning from {{ bot_users[bot['id']] }} accounts</div> <div class="panel-name">@{{ handle_list[1] }}<span class="subtle tiny">@{{ handle_list[2] }}</span></div>
<div class="panel-status">{{ "Online" if bot['enabled'] else "Offline"}}, learning from {{ bot_users[bot['handle']] }} accounts</div>
</div> </div>
<div class="panel-actions"> <div class="panel-actions">
<a class="button btn-secondary" href="/bot/toggle/{{ bot['id'] }}" title="Turn on/off"><i class="fas fa-power-off"></i></a><a class="button btn-secondary" href="/bot/edit/{{ bot['id'] }}" title="Configure"><i class="fas fa-cog"></i></a><a class="button btn-secondary" href="/bot/accounts/{{ bot['id'] }}" title="Accounts learned from"><i class="fas fa-users"></i></a><a class="button btn-secondary" href="/bot/blacklist/{{ bot['id'] }}" title="Banned words"><i class="fas fa-strikethrough"></i></a><a class="button btn-secondary" href="/bot/chat/{{ bot['id'] }}" title="Chat"><i class="fas fa-comment"></i></a><a class="button btn-dangerous" href="/bot/delete/{{ bot['id'] }}" title="Delete"><i class="fas fa-trash"></i></a> <a class="button btn-secondary" href="/bot/toggle/{{ bot['handle'] }}" title="Turn on/off"><i class="fas fa-power-off"></i></a><a class="button btn-secondary" href="/bot/edit/{{ bot['handle'] }}" title="Configure"><i class="fas fa-cog"></i></a><a class="button btn-secondary" href="/bot/accounts/{{ bot['handle'] }}" title="Accounts learned from"><i class="fas fa-users"></i></a><a class="button btn-secondary" href="/bot/blacklist/{{ bot['handle'] }}" title="Banned words"><i class="fas fa-strikethrough"></i></a><a class="button btn-secondary" href="/bot/chat/{{ bot['handle'] }}" title="Chat"><i class="fas fa-comment"></i></a><a class="button btn-dangerous" href="/bot/delete/{{ bot['handle'] }}" title="Delete"><i class="fas fa-trash"></i></a>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View file

@ -20,7 +20,7 @@
<div class="row"> <div class="row">
<label for="email" class="large">Email address</label> <label for="email" class="large">Email address</label>
<input type="email" name="email" value="me@my.site"> <input type="email" name="email" value="{{ user['email'] }}">
</div> </div>
<div class="row"> <div class="row">
<label for="password" class="large">Password</label> <label for="password" class="large">Password</label>
@ -33,35 +33,35 @@
</div> </div>
<div class="row"> <div class="row">
<label for="download-error" class="large">When my bot(s) can't get new posts</label> <label for="fetch-error" class="large">When my bot(s) can't get new posts</label>
<select name="download-error"> <select name="fetch-error">
<option value="once">Once</option> <option value="always" {{ 'selected' if user['fetch'] == "always"}}>Always</option>
<option value="every-time">Every time</option> <option value="once" {{ 'selected' if user['fetch'] == "once"}}>Once</option>
<option value="never">Never</option> <option value="never" {{ 'selected' if user['fetch'] == "never"}}>Never</option>
</select> </select>
</div> </div>
<div class="row"> <div class="row">
<label for="post-error" class="large">When my bot(s) can't submit new posts</label> <label for="submit-error" class="large">When my bot(s) can't submit new posts</label>
<select name="post-error"> <select name="submit-error">
<option value="once">Once</option> <option value="always" {{ 'selected' if user['submit'] == "always"}}>Always</option>
<option value="every-time">Every time</option> <option value="once" {{ 'selected' if user['submit'] == "once"}}>Once</option>
<option value="never">Never</option> <option value="never" {{ 'selected' if user['submit'] == "never"}}>Never</option>
</select> </select>
</div> </div>
<div class="row"> <div class="row">
<label for="generation-error" class="large">When my bot(s) encounter an error generating new posts</label> <label for="generation-error" class="large">When my bot(s) encounter an error generating new posts</label>
<select name="generation-error"> <select name="generation-error">
<option value="once">Once</option> <option value="always" {{ 'selected' if user['generation'] == "always"}}>Always</option>
<option value="every-time">Every time</option> <option value="once" {{ 'selected' if user['generation'] == "once"}}>Once</option>
<option value="never">Never</option> <option value="never" {{ 'selected' if user['generation'] == "never"}}>Never</option>
</select> </select>
</div> </div>
<div class="row"> <div class="row">
<label for="reply-error" class="large">When my bot(s) can't send replies</label> <label for="reply-error" class="large">When my bot(s) can't send replies</label>
<select name="reply-error"> <select name="reply-error">
<option value="once">Once</option> <option value="always" {{ 'selected' if user['reply'] == "always"}}>Always</option>
<option value="every-time">Every time</option> <option value="once" {{ 'selected' if user['reply'] == "once"}}>Once</option>
<option value="never">Never</option> <option value="never" {{ 'selected' if user['reply'] == "never"}}>Never</option>
</select> </select>
</div> </div>

144
webui.py
View file

@ -1,4 +1,4 @@
from flask import Flask, render_template, session, request, redirect, url_for from flask import Flask, render_template, session, request, redirect, url_for, send_file
from flask_mysqldb import MySQL from flask_mysqldb import MySQL
from mastodon import Mastodon from mastodon import Mastodon
import requests import requests
@ -28,21 +28,22 @@ def home():
c.execute("SELECT COUNT(*) FROM `bots` WHERE user_id = %s", (session['user_id'],)) c.execute("SELECT COUNT(*) FROM `bots` WHERE user_id = %s", (session['user_id'],))
bot_count = c.fetchone()[0] bot_count = c.fetchone()[0]
active_count = None active_count = None
bots = None bots = {}
bot_users = None bot_users = None
if bot_count > 0: if bot_count > 0:
c.execute("SELECT COUNT(*) FROM `bots` WHERE user_id = %s AND enabled = TRUE", (session['user_id'],)) c.execute("SELECT COUNT(*) FROM `bots` WHERE user_id = %s AND enabled = TRUE", (session['user_id'],))
active_count = c.fetchone()[0] active_count = c.fetchone()[0]
dc = mysql.connection.cursor(MySQLdb.cursors.DictCursor) dc = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
dc.execute("SELECT * FROM `bots` WHERE user_id = %s", (session['user_id'],)) dc.execute("SELECT `handle`, `enabled` FROM `bots` WHERE user_id = %s", (session['user_id'],))
bots = dc.fetchall() bots = dc.fetchall()
dc.close() dc.close()
bot_users = {} bot_users = {}
for bot in bots: for bot in bots:
c.execute("SELECT COUNT(*) FROM `bot_learned_accounts` WHERE bot_id = %s", (bot['id'],)) # multiple SELECTS is slow, maybe SELECT all at once and filter with python?
bot_users[bot['id']] = c.fetchone()[0] c.execute("SELECT COUNT(*) FROM `bot_learned_accounts` WHERE bot_id = %s", (bot['handle'],))
bot_users[bot['handle']] = c.fetchone()[0]
c.close() c.close()
return render_template("home.html", bot_count = bot_count, active_count = active_count, bots = bots, bot_users = bot_users) return render_template("home.html", bot_count = bot_count, active_count = active_count, bots = bots, bot_users = bot_users)
@ -68,24 +69,122 @@ def show_signup_page(error = None):
@app.route("/settings") @app.route("/settings")
def settings(): def settings():
return render_template("settings.html") return render_template("coming_soon.html")
dc = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
dc.execute("SELECT * FROM `users` WHERE id = %s", (session['user_id'],))
user = dc.fetchone()
dc.close()
return render_template("settings.html", user = user)
@app.route("/bot/edit/<id>") @app.route("/bot/edit/<id>")
def bot_edit(id): def bot_edit(id):
return render_template("bot_edit.html") return render_template("coming_soon.html")
@app.route("/bot/delete/<id>") @app.route("/bot/delete/<id>", methods=['GET', 'POST'])
def bot_delete(id): def bot_delete(id):
return render_template("bot_delete.html") if bot_check(id):
if request.method == 'GET':
instance = id.split("@")[2]
return render_template("bot_delete.html", instance = instance)
else:
# delete bot by deleting its credentials
# FK constraint will delete bot
c = mysql.connection.cursor()
c.execute("SELECT `credentials_id` FROM `bots` WHERE `handle` = %s", (id,))
credentials_id = c.fetchone()[0]
c.execute("DELETE FROM `credentials` WHERE `id` = %s", (credentials_id,))
c.close()
mysql.connection.commit()
return redirect(url_for("home"), 303)
@app.route("/bot/toggle/<id>")
def bot_toggle(id):
if bot_check(id):
c = mysql.connection.cursor()
c.execute("UPDATE `bots` SET `enabled` = NOT `enabled` WHERE `handle` = %s", (id,))
mysql.connection.commit()
c.close()
return redirect(url_for("home"), 303)
@app.route("/bot/chat/<id>")
def bot_chat(id):
return render_template("coming_soon.html")
@app.route("/bot/blacklist/<id>")
def bot_blacklist(id):
return render_template("coming_soon.html")
@app.route("/bot/accounts/<id>") @app.route("/bot/accounts/<id>")
def bot_accounts(id): def bot_accounts(id):
return render_template("bot_accounts.html") if bot_check(id):
session['bot'] = id
c = mysql.connection.cursor()
c.execute("SELECT COUNT(*) FROM `bot_learned_accounts` WHERE `bot_id` = %s", (id,))
user_count = c.fetchone()[0]
users = {}
@app.route("/bot/accounts/add") if user_count > 0:
dc = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
dc.execute("SELECT `fedi_id`, `enabled` FROM `bot_learned_accounts` WHERE `bot_id` = %s", (id,))
users = dc.fetchall()
dc.close()
c.close()
return render_template("bot_accounts.html", users = users)
@app.route("/bot/accounts/add", methods = ['GET', 'POST'])
def bot_accounts_add(): def bot_accounts_add():
if request.method == 'POST':
if session['step'] == 1:
# look up user
handle_list = request.form['account'].split('@')
username = handle_list[1]
instance = handle_list[2]
# 1. download host-meta to find webfinger URL
r = requests.get("https://{}/.well-known/host-meta".format(instance), timeout=10)
# 2. use webfinger to find user's info page
#TODO: use more reliable method
uri = re.search(r'template="([^"]+)"', r.text).group(1)
uri = uri.format(uri = "{}@{}".format(username, instance))
r = requests.get(uri, headers={"Accept": "application/json"}, timeout=10)
j = r.json()
found = False
for link in j['links']:
if link['rel'] == 'self':
#this is a link formatted like "https://instan.ce/users/username", which is what we need
uri = link['href']
found = True
break
if not found:
return "Couldn't find a valid ActivityPub outbox URL."
# 3. format as outbox URL and check to make sure it works
outbox = "{}/outbox?page=true".format(uri)
r = requests.get(uri, headers={"Accept": "application/json"}, timeout=10)
if r.status_code == 200:
# success!!
c = mysql.connection.cursor()
c.execute("INSERT INTO `fedi_accounts` (`handle`, `outbox`) VALUES (%s, %s)", (request.form['account'], outbox))
c.execute("INSERT INTO `bot_learned_accounts` (`bot_id`, `fedi_id`) VALUES (%s, %s)", (session['bot'], request.form['account']))
c.close()
mysql.connection.commit()
return redirect("/bot/accounts/{}".format(session['bot']), 303)
return render_template("bot_accounts_add.html") return render_template("bot_accounts_add.html")
@app.route("/bot/accounts/toggle/<id>")
def bot_accounts_toggle(id):
c = mysql.connection.cursor()
c.execute("UPDATE `bot_learned_accounts` SET `enabled` = NOT `enabled` WHERE `fedi_id` = %s AND `bot_id` = %s", (id, session['bot']))
mysql.connection.commit()
c.close()
return redirect("/bot/accounts/{}".format(session['bot']), 303)
@app.route("/bot/create/", methods=['GET', 'POST']) @app.route("/bot/create/", methods=['GET', 'POST'])
def bot_create(): def bot_create():
#TODO: error handling #TODO: error handling
@ -95,7 +194,7 @@ def bot_create():
session['instance'] = re.match(r"^(?:https?:\/\/)?(.*)", request.form['instance']).group(1) session['instance'] = re.match(r"^(?:https?:\/\/)?(.*)", request.form['instance']).group(1)
# check for mastodon/pleroma # check for mastodon/pleroma
r = requests.get("https://{}/api/v1/instance".format(session['instance'])) r = requests.get("https://{}/api/v1/instance".format(session['instance']), timeout=10)
if r.status_code == 200: if r.status_code == 200:
j = r.json() j = r.json()
if "Pleroma" in j['version']: if "Pleroma" in j['version']:
@ -155,7 +254,8 @@ def bot_create():
session['step'] = 1 session['step'] = 1
return bot_create() return bot_create()
elif session['step'] == 4: else:
if session['step'] == 4:
try: try:
# test authentication # test authentication
client = Mastodon(client_id=session['client_id'], client_secret=session['client_secret'], api_base_url=session['instance']) client = Mastodon(client_id=session['client_id'], client_secret=session['client_secret'], api_base_url=session['instance'])
@ -172,10 +272,8 @@ def bot_create():
credentials_id = c.lastrowid credentials_id = c.lastrowid
mysql.connection.commit() mysql.connection.commit()
bot_id = hashlib.sha256(handle.encode('utf-8')).digest() c.execute("INSERT INTO `bots` (handle, user_id, credentials_id) VALUES (%s, %s, %s)", (handle, session['user_id'], credentials_id))
c.execute("INSERT INTO `bots` (id, user_id, credentials_id, handle) VALUES (%s, %s, %s, %s)", (bot_id, session['user_id'], credentials_id, handle))
mysql.connection.commit() mysql.connection.commit()
c.close() c.close()
# clean up unneeded variables # clean up unneeded variables
@ -187,6 +285,11 @@ def bot_create():
return render_template("bot_create.html") return render_template("bot_create.html")
@app.route("/bot/create/back")
def bot_create_back():
session['step'] -= 1
return redirect(url_for("bot_create"), 303)
@app.route("/do/authenticate_bot") @app.route("/do/authenticate_bot")
def do_authenticate_bot(): def do_authenticate_bot():
session['code'] = request.args.get('code') session['code'] = request.args.get('code')
@ -235,3 +338,12 @@ def do_login():
else: else:
return "invalid login" return "invalid login"
@app.route("/img/bot_generic.png")
def img_bot_generic():
return send_file("static/bot_generic.png", mimetype="image/png")
def bot_check(bot):
c = mysql.connection.cursor()
c.execute("SELECT COUNT(*) FROM `bots` WHERE `handle` = %s AND `user_id` = %s", (bot, session['user_id']))
return c.fetchone()[0] == 1