reddit/app/app.py

444 lines
9.7 KiB
Python
Executable File

from flask import Flask, send_file, render_template, request
from urllib.parse import urlparse
import delete_posts
import config
import json
import re
import sqlite3
import subprocess
import time
app = Flask(__name__)
@app.route('/')
def index():
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
select = """
SELECT
count(*)
FROM
subreddit
"""
count = cursor.execute(select).fetchone()[0]
if count == 0:
return admin()
return front_page()
@app.route('/admin', methods=['GET', 'POST', 'DELETE'])
def admin():
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
if request.method == 'DELETE':
type = request.args.get("type")
if type == "sub":
delete = """
DELETE FROM
subreddit
WHERE
subreddit = ?
"""
elif type == "block":
delete = """
DELETE FROM
block
WHERE
name = ?
"""
else:
connection.close()
return ""
binds = [request.args.get("name")]
cursor.execute(delete, binds)
connection.commit()
connection.close()
return ""
elif request.method == 'POST':
type = request.form.get("type")
if type == "sub":
upsert = """
INSERT INTO
subreddit (subreddit, minimum_score, fetch_by, fetch_max)
VALUES
(?, ?, ?, ?)
ON CONFLICT
(subreddit)
DO UPDATE SET
minimum_score=excluded.minimum_score,
fetch_by=excluded.fetch_by,
fetch_max=excluded.fetch_max
"""
binds = [
request.form.get("name"),
int(request.form.get("score")),
request.form.get("by"),
int(request.form.get("max"))
]
elif type == "block":
upsert = """
INSERT OR IGNORE INTO
block (name)
VALUES
(?)
"""
binds = [request.form.get("name")]
else:
connection.close()
return ""
cursor.execute(upsert, binds)
connection.commit()
delete_posts.run()
sidebar_links = get_sidebar_links(cursor)
select = """
SELECT
subreddit,
minimum_score,
fetch_by,
fetch_max
FROM
subreddit
"""
subreddits = cursor.execute(select).fetchall()
select = """
SELECT
name
FROM
block
"""
rows = cursor.execute(select).fetchall()
blocks = [row[0] for row in rows]
connection.close()
return render_template('admin.html', sidebar_links=sidebar_links, subreddits=subreddits, blocks=blocks)
@app.route('/r/all')
def front_page():
title = "/r/all"
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
sidebar_links = get_sidebar_links(cursor)
sort = get_config("sort") or "created_utc asc"
select = f"""
SELECT
post
FROM
post
WHERE
hidden = ? AND
saved = ?
ORDER BY
{sort}
LIMIT ?
"""
binds = [False, False, config.posts_per_page_load]
posts = get_posts_from_select(cursor, select, binds)
connection.close()
return render_template(
'index.html',
title=title,
posts=posts,
sidebar_links=sidebar_links,
sort=sort.split())
@app.route('/r/other')
def other_page():
title = "/r/other"
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
sidebar_links = get_sidebar_links(cursor)
sort = get_config("sort") or "created_utc asc"
select = f"""
SELECT
post
FROM
post
WHERE
hidden = ? AND
saved = ? AND
subreddit IN
(
SELECT
subreddit
FROM
(
SELECT
subreddit,
count(*) count
FROM
post
WHERE
hidden = ? AND
saved = ?
GROUP BY
subreddit
) t
WHERE
count <= ?
)
ORDER BY
{sort}
LIMIT ?
"""
binds = [False, False, False, False, config.other_posts_cutoff, config.posts_per_page_load]
posts = get_posts_from_select(cursor, select, binds)
connection.close()
return render_template(
'index.html',
title=title,
posts=posts,
sidebar_links=sidebar_links,
sort=sort.split())
@app.route('/r/saved')
def get_saved():
title = "/r/saved"
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
sidebar_links = get_sidebar_links(cursor)
sort = get_config("sort") or "created_utc desc"
select = f"""
SELECT
post
FROM
post
WHERE
saved = ?
ORDER BY
{sort}
LIMIT ?
"""
binds = [True, config.posts_per_page_load]
posts = get_posts_from_select(cursor, select, binds)
connection.close()
return render_template(
'index.html',
title=title,
posts=posts,
sidebar_links=sidebar_links,
saved=True,
sort=sort.split())
@app.route('/r/<path:subreddit>')
def get_subreddit(subreddit):
title = f"/r/{subreddit}"
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
sidebar_links = get_sidebar_links(cursor)
sort = get_config("sort") or "created_utc asc"
select = f"""
SELECT
post
FROM
post
WHERE
subreddit = ? AND
hidden = ? AND
saved = ?
ORDER BY
{sort}
LIMIT ?
"""
binds = [subreddit, False, False, config.posts_per_page_load]
posts = get_posts_from_select(cursor, select, binds)
connection.close()
return render_template(
'index.html',
title=title,
posts=posts,
sidebar_links=sidebar_links,
sort=sort.split())
@app.route('/file/<path:filename>')
def serve_file(filename):
try:
return send_file('{0}/{1}'.format(config.media_dir, filename), as_attachment=False)
except FileNotFoundError:
return "File not found", 404
@app.route('/hide/<path:permalink>')
def hide_post(permalink):
if permalink[0] != "/":
permalink = "/" + permalink
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
update = """
UPDATE
post
SET
hidden = ?
WHERE
permalink = ? AND
saved = ?
"""
binds = [True, permalink, False]
cursor.execute(update,binds)
connection.commit()
connection.close()
return ""
@app.route('/save/<path:permalink>')
def save_post(permalink):
if permalink[0] != "/":
permalink = "/" + permalink
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
action = request.args.get("action")
update = """
UPDATE
post
SET
saved = ?
WHERE
permalink = ?
"""
binds = [(action == "save"), permalink]
cursor.execute(update,binds)
connection.commit()
connection.close()
return ""
@app.route('/config', methods=['POST'])
def save_config():
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
key = request.args.get("key")
value = request.args.get("value")
insert = f"""
INSERT INTO
config (key, value)
VALUES
(?, ?)
ON CONFLICT
(key)
DO UPDATE SET
value=excluded.value
"""
binds = [key, value]
cursor.execute(insert, binds)
connection.commit()
connection.close()
return ""
def get_config(key):
connection = sqlite3.connect(config.db_file)
cursor = connection.cursor()
select = """
SELECT
value
FROM
config
WHERE
key = ?
"""
binds = [key]
result = cursor.execute(select, binds).fetchone()
if result is None:
return None
return result[0]
def get_sidebar_links(cursor):
select = """
SELECT
subreddit
FROM
(
SELECT
subreddit,
count(*) count
FROM
post
WHERE
hidden = ?
GROUP BY
subreddit
ORDER BY
count desc
) t
WHERE
count > ?
"""
binds = [False, config.other_posts_cutoff]
results = cursor.execute(select, binds).fetchall()
links = [f"/r/{sub[0]}" for sub in results]
links.insert(0, "/r/all")
links.append("/r/other")
links.append("/r/saved")
links.append("/admin")
return links
def get_posts_from_select(cursor, select, binds):
results = cursor.execute(select, binds).fetchall()
posts = [json.loads(post[0]) for post in results]
add_media_html_to_posts(posts)
add_subreddits_to_posts(posts)
add_age_to_posts(posts)
reformat_body(posts)
return posts
def add_media_html_to_posts(posts):
for post_index, post in enumerate(posts):
media_html = []
for media_index, media in enumerate(post["media_urls"]):
filename = urlparse(media).path
if filename[0]=='/':
filename = filename[1:]
html = get_media_html(filename, True if (post_index < 3 and media_index == 0) else False)
media_html.append(html)
post["media_html"] = media_html
def add_subreddits_to_posts(posts):
# todo, remove after 30 days once subreddit is naturally a part of the post data
for post in posts:
if "subreddit" not in post:
m = re.search(r"\/r\/([a-zA-Z0-9_]+)\/.*", post["permalink"])
post["subreddit"] = m.group(1)
def add_age_to_posts(posts):
seconds = time.time()
for post in posts:
diff = seconds - int(post["created_utc"])
if diff < 3600:
post["age"] = str(int(diff//60))+'m'
elif diff < (3600 * 24):
post["age"] = str(int(diff//3600))+'h'
else:
post["age"] = str(int(diff//(3600*24)))+'d'
def reformat_body(posts):
for post in posts:
if "body" in post and post["body"] is not None:
post["body"] = post["body"].rstrip().replace("\n", "<br>")
post["body"] = re.sub(r"\[(.*?)\]\((.*?)\)", r'<b><a href="\2" style="white-space: nowrap;" class="no-style-link">\1</a></b>', post["body"])
def get_media_html(file, priority=False):
if file.endswith('.jpg') or file.endswith('.jpeg') or file.endswith('.png') or file.endswith('.gif'):
return '<img class="invertible" src="/file/{0}" {1}>'.format(file, 'fetchpriority="high" loading="eager"' if priority else '')
if file.find("_AUDIO_")>0:
return '<audio src="/file/{0}" hidden></audio>'.format(file)
if file.endswith('.mp4'):
return f"""
<video src="/file/{file}" type="video/mp4" onloadeddata="initializeControls(this)"></video>
<span class="controls button-wrapper">
<button name="volume-button" onclick="toggleVolume(this)">
<svg viewBox="0 0 24 24">
<use xlink:href="#icon-audio"></use>
</svg>
</button>
<input name="scrub" type="range" value=0 oninput="seekContent(this)">
<button name="play" onclick="toggleContent(this)">
<svg viewBox="0 0 24 24">
<use xlink:href="#icon-play"></use>
</svg>
</button>
</span>
"""
if file.endswith('.webm'):
return '<video src="/file/{0}" type="video/webm" controls></video>'.format(file)
return file
if __name__ == '__main__':
subprocess.run(["python3", "make_db.py"])
app.run(host='0.0.0.0', port=8000)