mirror of
https://github.com/Lynnesbian/FediBooks/
synced 2024-11-25 16:48:58 +00:00
Compare commits
24 commits
00369be990
...
1c67016fa6
Author | SHA1 | Date | |
---|---|---|---|
1c67016fa6 | |||
dc7787d296 | |||
fe01416134 | |||
8cab227531 | |||
4b6e563f76 | |||
07670c4a74 | |||
f1a4811094 | |||
570c62a779 | |||
39178dff9c | |||
d8dc54f802 | |||
e9cdbf7de2 | |||
3d0cdbc5e5 | |||
d48eb9264f | |||
5f089f4040 | |||
60728c0b35 | |||
170a666496 | |||
898f2f7aae | |||
841098cc18 | |||
34622230b0 | |||
11197eb7e7 | |||
9830eeda6b | |||
6cfa9ef35f | |||
fa60a6569d | |||
d06f89ed3f |
13 changed files with 201 additions and 76 deletions
|
@ -27,8 +27,7 @@ If this doesn't work, try using ``pip`` instead. If it still doesn't work, you m
|
|||
```
|
||||
CREATE DATABASE `fedibooks`;
|
||||
CREATE USER 'myuser' IDENTIFIED BY 'mypassword';
|
||||
GRANT USAGE ON *.* TO 'myuser'@localhost IDENTIFIED BY 'mypassword';
|
||||
GRANT ALL privileges ON `fedibooks`.* TO 'myuser'@localhost;
|
||||
GRANT ALL PRIVILEGES ON `fedibooks`.* TO 'myuser';
|
||||
FLUSH PRIVILEGES;
|
||||
exit
|
||||
```
|
||||
|
|
|
@ -34,24 +34,18 @@ def extract_post(post):
|
|||
text = text.rstrip("\n") # remove trailing newline(s)
|
||||
return text
|
||||
|
||||
def make_post(args):
|
||||
id = None
|
||||
acct = None
|
||||
if len(args) > 1:
|
||||
id = args[1]
|
||||
acct = args[3]
|
||||
handle = args[0]
|
||||
def generate_output(handle):
|
||||
db = MySQLdb.connect(
|
||||
host = cfg['db_host'],
|
||||
user=cfg['db_user'],
|
||||
passwd=cfg['db_pass'],
|
||||
db=cfg['db_name']
|
||||
)
|
||||
print("Generating post for {}".format(handle))
|
||||
# print("Generating post for {}".format(handle))
|
||||
dc = db.cursor(MySQLdb.cursors.DictCursor)
|
||||
c = db.cursor()
|
||||
dc.execute("""
|
||||
SELECT
|
||||
SELECT
|
||||
learn_from_cw,
|
||||
length,
|
||||
fake_mentions,
|
||||
|
@ -64,17 +58,11 @@ def make_post(args):
|
|||
FROM
|
||||
bots, credentials
|
||||
WHERE
|
||||
bots.handle = %s
|
||||
bots.handle = %s
|
||||
AND bots.credentials_id = credentials.id
|
||||
""", (handle,))
|
||||
|
||||
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.
|
||||
# if learn_from_cw, then also select posts with CWs
|
||||
|
@ -92,7 +80,7 @@ def make_post(args):
|
|||
# 4. join the list into a string separated by newlines
|
||||
posts = "\n".join(list(sum(c.fetchall(), ())))
|
||||
if len(posts) == 0:
|
||||
print("No posts to learn from.")
|
||||
print("{} - No posts to learn from.".format(handle))
|
||||
return
|
||||
|
||||
if bot['fake_mentions'] == 'never':
|
||||
|
@ -124,30 +112,64 @@ def make_post(args):
|
|||
if bot['fake_mentions_full']:
|
||||
post = re.sub(r"@(\w+)@([\w.]+)", r"@{}\1@{}\2".format(zws, zws), post)
|
||||
else:
|
||||
post = re.sub(r"@(\w+)@([\w.]+)", r"@{}\1".format(zws), post)
|
||||
post = re.sub(r"@(\w+)@([\w.]+)", r"@{}\1".format(zws), post)
|
||||
# also format handles without instances, e.g. @user instead of @user@instan.ce
|
||||
post = re.sub(r"(?<!\S)@(\w+)", r"@{}\1".format(zws), 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)
|
||||
return bot, post
|
||||
|
||||
# ensure post isn't longer than bot['length']
|
||||
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,))
|
||||
|
||||
def make_post(args):
|
||||
id = None
|
||||
acct = None
|
||||
if len(args) > 1:
|
||||
id = args[1]
|
||||
acct = args[3]
|
||||
handle = args[0]
|
||||
|
||||
# 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:
|
||||
# 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,))
|
||||
db.commit()
|
||||
c.close()
|
||||
|
|
|
@ -47,7 +47,7 @@ def bot_accounts_add(mysql, cfg):
|
|||
else:
|
||||
session['instance_type'] = "Mastodon"
|
||||
session['step'] += 1
|
||||
|
||||
|
||||
else:
|
||||
error = "Unsupported instance type. Misskey support is planned."
|
||||
return render_template("bot/accounts_add.html", error = error)
|
||||
|
@ -81,12 +81,13 @@ def bot_accounts_add(mysql, cfg):
|
|||
username = client.account_verify_credentials()['username']
|
||||
if username != 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)
|
||||
except:
|
||||
session['step'] = 1
|
||||
error = "Authentication failed."
|
||||
return render_template("bot/accounts_add.html", error = error)
|
||||
|
||||
|
||||
# 1. download host-meta to find webfinger URL
|
||||
r = requests.get("https://{}/.well-known/host-meta".format(session['instance']), timeout=10)
|
||||
if r.status_code != 200:
|
||||
|
|
|
@ -19,7 +19,7 @@ def scrape_posts(account):
|
|||
)
|
||||
handle = account[0]
|
||||
outbox = account[1]
|
||||
print("Scraping {}".format(handle))
|
||||
# print("Scraping {}".format(handle))
|
||||
c = db.cursor()
|
||||
last_post = 0
|
||||
c.execute("SELECT COUNT(*) FROM `posts` WHERE `fedi_id` = %s", (handle,))
|
||||
|
@ -80,9 +80,15 @@ def scrape_posts(account):
|
|||
|
||||
if not done:
|
||||
if pleroma:
|
||||
r = requests.get(j['next'], timeout = 10)
|
||||
if 'next' in j:
|
||||
r = requests.get(j['next'], timeout = 10)
|
||||
else:
|
||||
done = True
|
||||
else:
|
||||
r = requests.get(j['prev'], timeout = 10)
|
||||
if 'prev' in j:
|
||||
r = requests.get(j['prev'], timeout = 10)
|
||||
else:
|
||||
done = True
|
||||
|
||||
if r.status_code == 429:
|
||||
# we are now being ratelimited, move on to the next user
|
||||
|
@ -94,7 +100,7 @@ def scrape_posts(account):
|
|||
db.commit()
|
||||
|
||||
db.commit()
|
||||
print("Finished scraping {}".format(handle))
|
||||
# print("Finished scraping {}".format(handle))
|
||||
|
||||
print("Establishing DB connection")
|
||||
db = MySQLdb.connect(
|
||||
|
|
|
@ -2,33 +2,55 @@
|
|||
import MySQLdb
|
||||
from mastodon import Mastodon
|
||||
from multiprocessing import Pool
|
||||
import requests
|
||||
import json
|
||||
import functions
|
||||
|
||||
cfg = json.load(open('config.json'))
|
||||
|
||||
def update_icon(bot):
|
||||
db = MySQLdb.connect(
|
||||
host = cfg['db_host'],
|
||||
user=cfg['db_user'],
|
||||
passwd=cfg['db_pass'],
|
||||
db=cfg['db_name'],
|
||||
use_unicode=True,
|
||||
charset="utf8mb4"
|
||||
)
|
||||
try:
|
||||
db = MySQLdb.connect(
|
||||
host = cfg['db_host'],
|
||||
user=cfg['db_user'],
|
||||
passwd=cfg['db_pass'],
|
||||
db=cfg['db_name'],
|
||||
use_unicode=True,
|
||||
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_id = bot['client_id'],
|
||||
client_secret = bot['client_secret'],
|
||||
access_token = bot['secret'],
|
||||
api_base_url = "https://{}".format(bot['handle'].split("@")[2])
|
||||
api_base_url = url
|
||||
)
|
||||
|
||||
avatar = client.account_verify_credentials()['avatar']
|
||||
|
||||
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']))
|
||||
db.commit()
|
||||
c.close()
|
||||
|
||||
print("Establishing DB connection")
|
||||
db = MySQLdb.connect(
|
||||
|
@ -48,6 +70,7 @@ db.commit()
|
|||
|
||||
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")
|
||||
bots = cursor.fetchall()
|
||||
|
||||
with Pool(cfg['service_threads']) as p:
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 8.9 KiB |
44
app/static/script.js
Normal file
44
app/static/script.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
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;
|
||||
}
|
|
@ -217,21 +217,29 @@ form .row {
|
|||
height: 90vh;
|
||||
background-color: #3d4353;
|
||||
padding: 10px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#chatbox-input, #chatbox-input input{
|
||||
width: 100%;
|
||||
}
|
||||
#chatbox, #chatbox-input {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#chatbox-input {
|
||||
display: block;
|
||||
}
|
||||
.message {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
min-height: 30px;
|
||||
max-width: 60%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.message-container.user {
|
||||
text-align: right;
|
||||
}
|
||||
.message-container .bot-icon {
|
||||
background: center / contain url("https://lynnesbian.space/img/bune.png") no-repeat;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
|
|
|
@ -4,6 +4,17 @@
|
|||
<meta charset="UTF-8">
|
||||
<title>FediBooks</title>
|
||||
{% 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>
|
||||
|
||||
<body>
|
||||
|
@ -11,7 +22,7 @@
|
|||
<h1 class="thin centred">Chat</h1>
|
||||
<p class="centred">Talking to {{ bot }}</p>
|
||||
<p class="centred" style="margin: 20px 0;">
|
||||
<a class="button btn-primary" href="/bot/accounts/add" role="button"><i class="fas fa-home"></i> Home</a>
|
||||
<a class="button btn-primary" href="/" role="button"><i class="fas fa-home"></i> Home</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -23,17 +34,11 @@
|
|||
|
||||
<div class="container">
|
||||
<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>
|
||||
|
||||
<form id="chatbox-input">
|
||||
<input name="message" placeholder="Press enter to send">
|
||||
<input id="chatbox-input-box" autocomplete="off" required name="message" placeholder="Press enter to send">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
|
||||
{% include 'error.html' %}
|
||||
|
||||
|
||||
<div class="container centred">
|
||||
<form method="POST">
|
||||
{% if session['step'] == 1 %}
|
||||
|
@ -26,12 +26,12 @@
|
|||
|
||||
{% 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>Sign in to the {{ session['instance'] }} account you want your bot to use, then click next to begin the authorisation process.</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>
|
||||
|
||||
{% elif session['step'] == 4 %}
|
||||
<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>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><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>
|
||||
|
||||
{% else %}
|
||||
<h2 class="thin centred">Error</h2>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<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>
|
||||
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>
|
||||
Source code is available <a href="https://github.com/Lynnesbian/Fedibooks">here</a> under the AGPLv3 license.</p>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<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" 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>
|
||||
|
|
31
app/webui.py
31
app/webui.py
|
@ -76,7 +76,7 @@ def render_delete():
|
|||
# should never happen ;)
|
||||
session['error'] = "An unknown error occurred."
|
||||
return redirect(url_for("render_delete"), 303)
|
||||
|
||||
|
||||
if bcrypt.checkpw(pw_hashed, data['password']):
|
||||
# passwords match, delete the account
|
||||
session['error'] = "succ ess"
|
||||
|
@ -106,7 +106,7 @@ def render_delete():
|
|||
|
||||
c.close()
|
||||
mysql.connection.commit()
|
||||
|
||||
|
||||
# TODO: show a "deletion successful" message or something
|
||||
return redirect(url_for("do_signout"), 303)
|
||||
|
||||
|
@ -159,8 +159,20 @@ def bot_toggle(id):
|
|||
|
||||
@app.route("/bot/chat/<id>")
|
||||
def bot_chat(id):
|
||||
return render_template("coming_soon.html")
|
||||
# return render_template("/bot/chat.html", bot = id)
|
||||
# return render_template("coming_soon.html")
|
||||
if bot_check(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>")
|
||||
def bot_blacklist(id):
|
||||
|
@ -258,9 +270,12 @@ def push(id):
|
|||
'privkey': int(bot[0].rstrip("\0")),
|
||||
'auth': bot[1]
|
||||
}
|
||||
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'])
|
||||
me = client.account_verify_credentials()['id']
|
||||
try:
|
||||
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'])
|
||||
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.
|
||||
# if it's over 15, don't reply.
|
||||
|
@ -329,7 +344,7 @@ def do_login():
|
|||
if data == None:
|
||||
session['error'] = "Incorrect login information."
|
||||
return redirect(url_for("show_login_page"), 303)
|
||||
|
||||
|
||||
if bcrypt.checkpw(pw_hashed, data['password']):
|
||||
session['user_id'] = data['id']
|
||||
return redirect(url_for("render_home"))
|
||||
|
|
Loading…
Reference in a new issue