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

Compare commits

..

No commits in common. "1c67016fa6aba3cbfe8640a84cc0b642a0ee32a7" and "00369be990d74008c9a1deca14b1aaef349c0f04" have entirely different histories.

13 changed files with 76 additions and 201 deletions

View file

@ -27,7 +27,8 @@ If this doesn't work, try using ``pip`` instead. If it still doesn't work, you m
``` ```
CREATE DATABASE `fedibooks`; CREATE DATABASE `fedibooks`;
CREATE USER 'myuser' IDENTIFIED BY 'mypassword'; CREATE USER 'myuser' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON `fedibooks`.* TO 'myuser'; GRANT USAGE ON *.* TO 'myuser'@localhost IDENTIFIED BY 'mypassword';
GRANT ALL privileges ON `fedibooks`.* TO 'myuser'@localhost;
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
exit exit
``` ```

View file

@ -34,14 +34,20 @@ def extract_post(post):
text = text.rstrip("\n") # remove trailing newline(s) text = text.rstrip("\n") # remove trailing newline(s)
return text return text
def generate_output(handle): def make_post(args):
id = None
acct = None
if len(args) > 1:
id = args[1]
acct = args[3]
handle = args[0]
db = MySQLdb.connect( db = MySQLdb.connect(
host = cfg['db_host'], host = cfg['db_host'],
user=cfg['db_user'], user=cfg['db_user'],
passwd=cfg['db_pass'], passwd=cfg['db_pass'],
db=cfg['db_name'] db=cfg['db_name']
) )
# print("Generating post for {}".format(handle)) print("Generating post for {}".format(handle))
dc = db.cursor(MySQLdb.cursors.DictCursor) dc = db.cursor(MySQLdb.cursors.DictCursor)
c = db.cursor() c = db.cursor()
dc.execute(""" dc.execute("""
@ -63,6 +69,12 @@ def generate_output(handle):
""", (handle,)) """, (handle,))
bot = dc.fetchone() bot = dc.fetchone()
client = Mastodon(
client_id = bot['client_id'],
client_secret = bot['client_secret'],
access_token = bot['secret'],
api_base_url = "https://{}".format(handle.split("@")[2])
)
# by default, only select posts that don't have CWs. # by default, only select posts that don't have CWs.
# if learn_from_cw, then also select posts with CWs # if learn_from_cw, then also select posts with CWs
@ -80,7 +92,7 @@ def generate_output(handle):
# 4. join the list into a string separated by newlines # 4. join the list into a string separated by newlines
posts = "\n".join(list(sum(c.fetchall(), ()))) posts = "\n".join(list(sum(c.fetchall(), ())))
if len(posts) == 0: if len(posts) == 0:
print("{} - No posts to learn from.".format(handle)) print("No posts to learn from.")
return return
if bot['fake_mentions'] == 'never': if bot['fake_mentions'] == 'never':
@ -116,60 +128,26 @@ def generate_output(handle):
# also format handles without instances, e.g. @user instead of @user@instan.ce # also format handles without instances, e.g. @user instead of @user@instan.ce
post = re.sub(r"(?<!\S)@(\w+)", r"@{}\1".format(zws), post) post = re.sub(r"(?<!\S)@(\w+)", r"@{}\1".format(zws), post)
return bot, post print(post)
visibility = bot['post_privacy'] if len(args) == 1 else args[2]
visibilities = ['public', 'unlisted', 'private']
if visibilities.index(visibility) < visibilities.index(bot['post_privacy']):
# if post_privacy is set to a more restricted level than the visibility of the post we're replying to, use the user's setting
visibility = bot['post_privacy']
if acct is not None:
post = "{} {}".format(acct, post)
# ensure post isn't longer than bot['length']
def make_post(args): post = post[:bot['length']]
id = None # send toot!!
acct = None try:
if len(args) > 1: client.status_post(post, id, visibility = visibility, spoiler_text = bot['content_warning'])
id = args[1] except MastodonUnauthorizedError:
acct = args[3] # user has revoked the token given to the bot
handle = args[0] # this needs to be dealt with properly later on, but for now, we'll just disable the bot
c.execute("UPDATE bots SET enabled = FALSE WHERE handle = %s", (handle,))
# print("Generating post for {}".format(handle))
bot, post = generate_output(handle)
client = Mastodon(
client_id = bot['client_id'],
client_secret = bot['client_secret'],
access_token = bot['secret'],
api_base_url = "https://{}".format(handle.split("@")[2])
)
db = MySQLdb.connect(
host = cfg['db_host'],
user=cfg['db_user'],
passwd=cfg['db_pass'],
db=cfg['db_name']
)
c = db.cursor()
# print(post)
visibility = bot['post_privacy'] if len(args) == 1 else args[2]
visibilities = ['public', 'unlisted', 'private']
if visibilities.index(visibility) < visibilities.index(bot['post_privacy']):
# if post_privacy is set to a more restricted level than the visibility of the post we're replying to, use the user's setting
visibility = bot['post_privacy']
if acct is not None:
post = "{} {}".format(acct, post)
# ensure post isn't longer than bot['length']
# TODO: ehhhhhhhhh
post = post[:bot['length']]
# send toot!!
try:
client.status_post(post, id, visibility = visibility, spoiler_text = bot['content_warning'])
except MastodonUnauthorizedError:
# user has revoked the token given to the bot
# this needs to be dealt with properly later on, but for now, we'll just disable the bot
c.execute("UPDATE bots SET enabled = FALSE WHERE handle = %s", (handle,))
except:
print("Failed to create post for {}".format(handle))
if id == None: if id == None:
# this wasn't a reply, it was a regular post, so update the last post date # this wasn't a reply, it was a regular post, so update the last post date
c.execute("UPDATE bots SET last_post = CURRENT_TIMESTAMP() WHERE handle = %s", (handle,)) c.execute("UPDATE bots SET last_post = CURRENT_TIMESTAMP() WHERE handle = %s", (handle,))
db.commit() db.commit()
c.close()

View file

@ -81,7 +81,6 @@ def bot_accounts_add(mysql, cfg):
username = client.account_verify_credentials()['username'] username = client.account_verify_credentials()['username']
if username != session['username']: if username != session['username']:
error = "Please authenticate as {}.".format(session['username']) error = "Please authenticate as {}.".format(session['username'])
print("Auth error - {} is not {}".format(session['username'], username))
return render_template("bot/accounts_add.html", error = error) return render_template("bot/accounts_add.html", error = error)
except: except:
session['step'] = 1 session['step'] = 1

View file

@ -19,7 +19,7 @@ def scrape_posts(account):
) )
handle = account[0] handle = account[0]
outbox = account[1] outbox = account[1]
# print("Scraping {}".format(handle)) print("Scraping {}".format(handle))
c = db.cursor() c = db.cursor()
last_post = 0 last_post = 0
c.execute("SELECT COUNT(*) FROM `posts` WHERE `fedi_id` = %s", (handle,)) c.execute("SELECT COUNT(*) FROM `posts` WHERE `fedi_id` = %s", (handle,))
@ -80,15 +80,9 @@ def scrape_posts(account):
if not done: if not done:
if pleroma: if pleroma:
if 'next' in j: r = requests.get(j['next'], timeout = 10)
r = requests.get(j['next'], timeout = 10)
else:
done = True
else: else:
if 'prev' in j: r = requests.get(j['prev'], timeout = 10)
r = requests.get(j['prev'], timeout = 10)
else:
done = True
if r.status_code == 429: if r.status_code == 429:
# we are now being ratelimited, move on to the next user # we are now being ratelimited, move on to the next user
@ -100,7 +94,7 @@ def scrape_posts(account):
db.commit() db.commit()
db.commit() db.commit()
# print("Finished scraping {}".format(handle)) print("Finished scraping {}".format(handle))
print("Establishing DB connection") print("Establishing DB connection")
db = MySQLdb.connect( db = MySQLdb.connect(

View file

@ -2,55 +2,33 @@
import MySQLdb import MySQLdb
from mastodon import Mastodon from mastodon import Mastodon
from multiprocessing import Pool from multiprocessing import Pool
import requests
import json import json
import functions import functions
cfg = json.load(open('config.json')) cfg = json.load(open('config.json'))
def update_icon(bot): def update_icon(bot):
try: db = MySQLdb.connect(
db = MySQLdb.connect( host = cfg['db_host'],
host = cfg['db_host'], user=cfg['db_user'],
user=cfg['db_user'], passwd=cfg['db_pass'],
passwd=cfg['db_pass'], db=cfg['db_name'],
db=cfg['db_name'], use_unicode=True,
use_unicode=True, charset="utf8mb4"
charset="utf8mb4" )
)
except:
print("Failed to connect to database.")
return
url = "https://{}".format(bot['handle'].split("@")[2])
try:
r = requests.head(url, timeout=10, allow_redirects = True)
if r.status_code != 200:
raise
except:
print("{} is down.".format(url))
return
print("Updating cached icon for {}".format(bot['handle']))
client = Mastodon( client = Mastodon(
client_id = bot['client_id'], client_id = bot['client_id'],
client_secret = bot['client_secret'], client_secret = bot['client_secret'],
access_token = bot['secret'], access_token = bot['secret'],
api_base_url = url api_base_url = "https://{}".format(bot['handle'].split("@")[2])
) )
avatar = client.account_verify_credentials()['avatar']
c = db.cursor() c = db.cursor()
try:
avatar = client.account_verify_credentials()['avatar']
except:
c.execute("UPDATE bots SET icon_update_time = CURRENT_TIMESTAMP() WHERE handle = %s", (bot['handle'],))
db.commit()
c.close()
return
c.execute("UPDATE bots SET icon = %s, icon_update_time = CURRENT_TIMESTAMP() WHERE handle = %s", (avatar, bot['handle'])) c.execute("UPDATE bots SET icon = %s, icon_update_time = CURRENT_TIMESTAMP() WHERE handle = %s", (avatar, bot['handle']))
db.commit() db.commit()
c.close()
print("Establishing DB connection") print("Establishing DB connection")
db = MySQLdb.connect( db = MySQLdb.connect(
@ -70,7 +48,6 @@ db.commit()
print("Generating posts") print("Generating posts")
cursor.execute("SELECT handle FROM bots WHERE enabled = TRUE AND TIMESTAMPDIFF(MINUTE, last_post, CURRENT_TIMESTAMP()) >= post_frequency") cursor.execute("SELECT handle FROM bots WHERE enabled = TRUE AND TIMESTAMPDIFF(MINUTE, last_post, CURRENT_TIMESTAMP()) >= post_frequency")
# cursor.execute("SELECT handle FROM bots WHERE enabled = TRUE")
bots = cursor.fetchall() bots = cursor.fetchall()
with Pool(cfg['service_threads']) as p: with Pool(cfg['service_threads']) as p:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,44 +0,0 @@
var chatlog = [];
function sendMessage() {
let id = window.location.href.split("/").slice(-1)[0]
message = document.getElementById("chatbox-input-box").value
document.getElementById("chatbox-input-box").value = ''
document.getElementById("chatbox-input-box").disabled = true;
chatlog.push(["user", message])
renderChatlog();
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
message = this.responseText.replace("\n", "<br>");
} else {
message = "Encountered an error while trying to get a response.";
}
chatlog.push(["bot", message]);
renderChatlog();
document.getElementById("chatbox-input-box").disabled = false;
}
};
xhttp.open("GET", `/bot/chat/${id}/message`, true);
xhttp.send();
return false;
}
function renderChatlog() {
let chatbox = document.getElementById("chatbox");
let out = "";
if (chatlog.length > 50) {
chatlog.shift(); //only keep the 50 most recent messages to avoid slowdown
}
chatlog.forEach(function(item, i) {
if (item[0] == "user") {
out += `<div class="message-container user"><div class="message user">${item[1]}</div></div>`;
} else {
out += `<div class="message-container bot"><div class="bot-icon"></div><div class="message bot">${item[1]}</div></div>`;
}
})
chatbox.innerHTML = out;
chatbox.scrollTop = chatbox.scrollHeight;
}

View file

@ -217,29 +217,21 @@ form .row {
height: 90vh; height: 90vh;
background-color: #3d4353; background-color: #3d4353;
padding: 10px; padding: 10px;
overflow-y: scroll;
} }
#chatbox-input, #chatbox-input input{ #chatbox-input, #chatbox-input input{
width: 100%; width: 100%;
} }
#chatbox, #chatbox-input {
max-width: 600px;
margin: 0 auto;
}
#chatbox-input {
display: block;
}
.message { .message {
display: inline-block; display: inline-block;
padding: 5px; padding: 5px;
min-height: 30px; min-height: 30px;
max-width: 60%; max-width: 60%;
margin-bottom: 5px;
} }
.message-container.user { .message-container.user {
text-align: right; text-align: right;
} }
.message-container .bot-icon { .message-container .bot-icon {
background: center / contain url("https://lynnesbian.space/img/bune.png") no-repeat;
height: 30px; height: 30px;
width: 30px; width: 30px;
display: inline-block; display: inline-block;

View file

@ -4,17 +4,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>FediBooks</title> <title>FediBooks</title>
{% include 'imports.html' %} {% include 'imports.html' %}
<style>
.bot-icon {
background: center / contain url("{{icon}}") no-repeat;
}
</style>
<script>
window.onload = function() {
document.getElementById("chatbox-input-box").focus();
document.getElementById("chatbox-input").onsubmit = sendMessage;
}
</script>
</head> </head>
<body> <body>
@ -22,7 +11,7 @@
<h1 class="thin centred">Chat</h1> <h1 class="thin centred">Chat</h1>
<p class="centred">Talking to {{ bot }}</p> <p class="centred">Talking to {{ bot }}</p>
<p class="centred" style="margin: 20px 0;"> <p class="centred" style="margin: 20px 0;">
<a class="button btn-primary" href="/" role="button"><i class="fas fa-home"></i> Home</a> <a class="button btn-primary" href="/bot/accounts/add" role="button"><i class="fas fa-home"></i> Home</a>
</p> </p>
</div> </div>
@ -34,11 +23,17 @@
<div class="container"> <div class="container">
<div id="chatbox"> <div id="chatbox">
<div class="message-container user">
<div class="message user">Henlo</div>
</div>
<div class="message-container bot">
<div class="bot-icon"></div>
<div class="message bot">Henlo human uwu<br>How are you</div>
</div>
</div> </div>
<form id="chatbox-input"> <form id="chatbox-input">
<input id="chatbox-input-box" autocomplete="off" required name="message" placeholder="Press enter to send"> <input name="message" placeholder="Press enter to send">
</form> </form>
</div> </div>

View file

@ -26,12 +26,12 @@
{% elif session['step'] == 3 %} {% elif session['step'] == 3 %}
<p>You now need to give your bot access to the {{ session['instance'] }} account you have created for it. If you have not yet created an account on {{ session['instance'] }} for your bot to use, please do so now.</p> <p>You now need to give your bot access to the {{ session['instance'] }} account you have created for it. If you have not yet created an account on {{ session['instance'] }} for your bot to use, please do so now.</p>
<p>In another tab, sign in to the {{ session['instance'] }} account you want your bot to use. Once that's done, click next to begin the authorisation process.</p> <p>Sign in to the {{ session['instance'] }} account you want your bot to use, then click next to begin the authorisation process.</p>
{% elif session['step'] == 4 %} {% elif session['step'] == 4 %}
<h2 class="thin centred">Congratulations!</h2> <h2 class="thin centred">Congratulations!</h2>
<p>FediBooks has successfully authenticated with your instance, and your bot is ready to be configured. Click finish to return to the bot management screen.</p> <p>FediBooks has successfully authenticated with your instance, and your bot is ready to be configured. Click finish to return to the bot management screen.</p>
<p><strong>Important:</strong> To get your bot working, you need to add at least one account for it to learn from. You can do so by clicking the <i class="fas fa-users"></i> button. To configure settings such as posting frequency and content warnings, click the <i class="fas fa-cog"></i> button.</p> <p>To get your bot working, you need to add at least one account for it to learn from. You can do so by clicking the <i class="fas fa-users"></i> button. To configure settings such as posting frequency and content warnings, click the <i class="fas fa-cog"></i> button.</p>
{% else %} {% else %}
<h2 class="thin centred">Error</h2> <h2 class="thin centred">Error</h2>

View file

@ -2,7 +2,6 @@
<div class='subtle small'> <div class='subtle small'>
<p>FediBooks is beta software. It might behave unexpectedly. You can learn more about FediBooks <a href="/about">here</a>. <br> <p>FediBooks is beta software. It might behave unexpectedly. You can learn more about FediBooks <a href="/about">here</a>. <br>
Website design and FediBooks software by <a href='https://fedi.lynnesbian.space/@LynnearSoftware'>Lynne</a>. This site uses <a href="https://fontawesome.com">Font Awesome</a>. <br> Website design and FediBooks software by <a href='https://fedi.lynnesbian.space/@LynnearSoftware'>Lynne</a>. This site uses <a href="https://fontawesome.com">Font Awesome</a>. <br>
Some of FediBooks' functionality requires JavaScript to be enabled, although the core functions such as bot configuration do not. <br>
FediBooks uses a cookie to keep you logged in. Deleting this cookie will log you out, and your bots will still work. You can also sign out <a href="/do/signout">here</a>. <br> FediBooks uses a cookie to keep you logged in. Deleting this cookie will log you out, and your bots will still work. You can also sign out <a href="/do/signout">here</a>. <br>
Source code is available <a href="https://github.com/Lynnesbian/Fedibooks">here</a> under the AGPLv3 license.</p> Source code is available <a href="https://github.com/Lynnesbian/Fedibooks">here</a> under the AGPLv3 license.</p>
</div> </div>

View file

@ -2,4 +2,3 @@
<link rel="stylesheet" href="https://kit-free.fontawesome.com/releases/latest/css/free.min.css"> <link rel="stylesheet" href="https://kit-free.fontawesome.com/releases/latest/css/free.min.css">
<link rel='stylesheet' type='text/css' href="{{ url_for('static', filename='style.css') }}" /> <link rel='stylesheet' type='text/css' href="{{ url_for('static', filename='style.css') }}" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,700&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,700&display=swap">
<script src="{{ url_for('static', filename='script.js') }}"></script>

View file

@ -159,20 +159,8 @@ def bot_toggle(id):
@app.route("/bot/chat/<id>") @app.route("/bot/chat/<id>")
def bot_chat(id): def bot_chat(id):
# return render_template("coming_soon.html") return render_template("coming_soon.html")
if bot_check(id): # return render_template("/bot/chat.html", bot = id)
c = mysql.connection.cursor()
c.execute("SELECT icon FROM `bots` WHERE handle = %s", (id,))
icon = c.fetchone()[0]
if icon is None:
icon = "/img/bot_generic.png"
return render_template("/bot/chat.html", bot = id, icon = icon)
@app.route("/bot/chat/<id>/message")
def bot_chat_message(id):
if bot_check(id):
_, message = functions.generate_output(id)
return message
@app.route("/bot/blacklist/<id>") @app.route("/bot/blacklist/<id>")
def bot_blacklist(id): def bot_blacklist(id):
@ -270,12 +258,9 @@ def push(id):
'privkey': int(bot[0].rstrip("\0")), 'privkey': int(bot[0].rstrip("\0")),
'auth': bot[1] 'auth': bot[1]
} }
try: push_object = client.push_subscription_decrypt_push(request.data, params, request.headers['Encryption'], request.headers['Crypto-Key'])
push_object = client.push_subscription_decrypt_push(request.data, params, request.headers['Encryption'], request.headers['Crypto-Key']) notification = client.notifications(id = push_object['notification_id'])
notification = client.notifications(id = push_object['notification_id']) me = client.account_verify_credentials()['id']
me = client.account_verify_credentials()['id']
except:
return "Push failed - do we still have access to {}?".format(id)
# first, check how many times the bot has posted in this thread. # first, check how many times the bot has posted in this thread.
# if it's over 15, don't reply. # if it's over 15, don't reply.