From 143d08f2f74c6e1a3bf878e5839a0c441c0d191b Mon Sep 17 00:00:00 2001 From: lza_menace Date: Wed, 31 Aug 2022 23:02:31 -0700 Subject: [PATCH] moving stuff around, cleaning up, adding mod area --- suchwow/app.py | 204 +------------------------- suchwow/cli.py | 95 ++++++++++++ suchwow/config.py | 1 + suchwow/filters.py | 9 ++ suchwow/models.py | 11 ++ suchwow/routes/auth.py | 4 +- suchwow/routes/comment.py | 2 +- suchwow/routes/main.py | 50 +++++++ suchwow/routes/mod.py | 106 +++++++++++++ suchwow/routes/post.py | 24 +-- suchwow/templates/base.html | 4 + suchwow/templates/comment/create.html | 19 --- suchwow/templates/leaderboard.html | 2 +- suchwow/templates/mod/bans.html | 57 +++++++ suchwow/templates/mod/main.html | 72 +++++++++ suchwow/templates/mod/manage.html | 48 ++++++ suchwow/templates/mod/posts.html | 65 ++++++++ suchwow/templates/navbar.html | 10 +- suchwow/templates/post/create.html | 60 ++++++-- suchwow/utils/decorators.py | 2 +- 20 files changed, 591 insertions(+), 254 deletions(-) create mode 100644 suchwow/cli.py create mode 100644 suchwow/routes/main.py create mode 100644 suchwow/routes/mod.py delete mode 100644 suchwow/templates/comment/create.html create mode 100644 suchwow/templates/mod/bans.html create mode 100644 suchwow/templates/mod/main.html create mode 100644 suchwow/templates/mod/manage.html create mode 100644 suchwow/templates/mod/posts.html diff --git a/suchwow/app.py b/suchwow/app.py index b9bae69..3a3201f 100644 --- a/suchwow/app.py +++ b/suchwow/app.py @@ -1,22 +1,8 @@ -import json -import click -import arrow -from math import ceil -from datetime import datetime, timedelta -from random import choice -from os import makedirs, path, remove -from flask import Flask, request, session, redirect -from flask import render_template, flash, url_for +from flask import Flask from flask_session import Session -from suchwow import config -from suchwow.models import Post, Profile, Comment, Notification, db, Moderator, Ban -from suchwow.routes import auth, comment, post, profile, leaderboard, api -from suchwow.utils.decorators import login_required, moderator_required -from suchwow.utils.helpers import post_webhook, get_latest_tipped_posts -from suchwow.utils.helpers import get_top_posters, get_top_posts -from suchwow.reddit import make_post -from suchwow.discord import post_discord_webhook -from suchwow import wownero, filters + +from suchwow.routes import auth, comment, post, profile, leaderboard, api, mod, main +from suchwow import filters, cli app = Flask(__name__) @@ -30,186 +16,10 @@ app.register_blueprint(profile.bp) app.register_blueprint(comment.bp) app.register_blueprint(leaderboard.bp) app.register_blueprint(api.bp) +app.register_blueprint(mod.bp) +app.register_blueprint(main.bp) app.register_blueprint(filters.bp) - -@app.route("/") -def index(): - itp = 15 - page = request.args.get("page", 1) - submitter = request.args.get("submitter", None) - content = request.args.get("content", None) - - if content == 'latest_tipped': - posts = get_latest_tipped_posts() - return render_template( - "index.html", - posts=posts[0:30], - title="Latest Tipped Memes" - ) - - try: - page = int(page) - except: - flash("Wow, wtf hackerman. Cool it.", "is-danger") - page = 1 - - posts = Post.select().where(Post.approved==True).order_by(Post.timestamp.desc()) - if submitter: - posts = posts.where(Post.submitter==submitter) - - paginated_posts = posts.paginate(page, itp) - total_pages = ceil(posts.count() / itp) - return render_template( - "index.html", - posts=paginated_posts, - page=page, - total_pages=total_pages, - title="Latest Memes" - ) - - -@app.route("/mod") -@moderator_required -def mod_queue(): - posts = Post.select().where(Post.approved==False).order_by(Post.timestamp.asc()) - return render_template("index.html", posts=posts) - -@app.route("/about") -def about(): - mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username)) - return render_template("about.html", mods=mods) - -@app.cli.command("init") -def init(): - # create subdirs - for i in ["uploads", "db", "wallet"]: - makedirs(f"{config.DATA_FOLDER}/{i}", exist_ok=True) - - # init db - db.create_tables([Post, Profile, Comment, Notification, Moderator, Ban]) - -@app.cli.command("post_reddit") -@click.argument('last_hours') -def post_reddit(last_hours): - posts = Post.select().where( - Post.approved==True, - Post.to_reddit==False - ).order_by(Post.timestamp.asc()) - for p in posts: - if p.hours_elapsed() < int(last_hours): - if not p.to_reddit: - _p = make_post(p) - if _p: - p.to_reddit = True - p.save() - return - -@app.cli.command("create_accounts") -def create_accounts(): - wallet = wownero.Wallet() - for post in Post.select(): - if post.account_index not in wallet.accounts(): - account = wallet.new_account() - print(f"Created account {account}") - -@app.cli.command("payout_users") -def payout_users(): - wallet = wownero.Wallet() - _fa = wownero.from_atomic - _aw = wownero.as_wownero - for post in Post.select(): - try: - submitter = Profile.get(username=post.submitter) - balances = wallet.balances(post.account_index) - url = url_for('post.read', id=post.id, _external=True) - if balances[1] > 0.05: - print(f"Post #{post.id} has {balances[1]} funds unlocked and ready to send. Sweeping all funds to user's address ({submitter.address}).") - sweep = wallet.sweep_all(account=post.account_index, dest_address=submitter.address) - print(sweep) - if "tx_hash_list" in sweep: - amount = 0 - for amt in sweep["amount_list"]: - amount += int(amt) - except Exception as e: - print(f"Failed because: {e}") - -@app.cli.command("add_admin") -@click.argument("username") -def add_admin(username): - if not Moderator.filter(username=username): - m = Moderator(username=username) - m.save() - print(f"Added {username}") - post_webhook(f"Moderator `{username}` added :ship_it_parrot: by console :black_flag:") - else: - print("That moderator already exists") - -@app.cli.command("remove_admin") -@click.argument("username") -def remove_admin(username): - m = Moderator.filter(username=username).first() - if m: - m.delete_instance() - print(f"Deleted {username}") - post_webhook(f"Moderator `{username}` removed :excuseme: by console :black_flag:") - else: - print("That moderator doesn't exist") - -@app.cli.command("ban_user") -@click.argument("username") -@click.argument("reason", nargs=-1) -def ban_user(username, reason): - u = Profile.filter(username=username).first() - b = Ban.filter(user=u).first() - if b: - print("User already banned") - return False - if u: - b = Ban(user=u, reason=' '.join(reason)) - b.save() - print(f"Banned {username}") - return - else: - print("That user doesn't exist") - return - -@app.cli.command("unban_user") -@click.argument("username") -def ban_user(username): - u = Profile.filter(username=username).first() - if not u: - print("That user doesn't exist") - return False - b = Ban.filter(user=u).first() - if b: - b.delete_instance() - print(f"Unbanned {username}") - return True - else: - print("That user isn't banned") - return False - -@app.cli.command("show") -@click.argument("post_id") -def post_id(post_id): - p = Post.filter(id=post_id).first() - if p: - print(p.show()) - else: - print("That post doesn't exist") - -@app.cli.command("load_cache") -def load_cache(): - app.logger.info('loading top posters into cache') - get_top_posters() - app.logger.info('done') - app.logger.info('loading latest tipped into cache') - get_latest_tipped_posts() - app.logger.info('done') - for i in [1, 3, 7, 30, 9999]: - app.logger.info(f'loading top posts last {i} days into cache') - get_top_posts(i) - app.logger.info('done') +app.register_blueprint(cli.bp) if __name__ == "__main__": diff --git a/suchwow/cli.py b/suchwow/cli.py new file mode 100644 index 0000000..b1ce203 --- /dev/null +++ b/suchwow/cli.py @@ -0,0 +1,95 @@ +from os import makedirs + +import click +from flask import Blueprint, url_for, current_app + +from suchwow.models import Post, Profile, Comment, Notification, db, Moderator, Ban +from suchwow.utils.helpers import get_latest_tipped_posts +from suchwow.utils.helpers import get_top_posters, get_top_posts +from suchwow.reddit import make_post +from suchwow import wownero +from suchwow import config + +bp = Blueprint('cli', 'cli', cli_group=None) + + +@bp.cli.command("init") +def init(): + # create subdirs + for i in ["uploads", "db", "wallet"]: + makedirs(f"{config.DATA_FOLDER}/{i}", exist_ok=True) + + # init db + db.create_tables([Post, Profile, Comment, Notification, Moderator, Ban]) + + +@bp.cli.command("post_reddit") +@click.argument('last_hours') +def post_reddit(last_hours): + posts = Post.select().where( + Post.approved==True, + Post.to_reddit==False + ).order_by(Post.timestamp.asc()) + for p in posts: + if p.hours_elapsed() < int(last_hours): + if not p.to_reddit: + _p = make_post(p) + if _p: + p.to_reddit = True + p.save() + return + + +@bp.cli.command("create_accounts") +def create_accounts(): + wallet = wownero.Wallet() + for post in Post.select(): + if post.account_index not in wallet.accounts(): + account = wallet.new_account() + print(f"Created account {account}") + + +@bp.cli.command("payout_users") +def payout_users(): + wallet = wownero.Wallet() + _fa = wownero.from_atomic + _aw = wownero.as_wownero + for post in Post.select(): + try: + submitter = Profile.get(username=post.submitter) + balances = wallet.balances(post.account_index) + url = url_for('post.read', id=post.id, _external=True) + if balances[1] > 0.05: + print(f"Post #{post.id} has {balances[1]} funds unlocked and ready to send. Sweeping all funds to user's address ({submitter.address}).") + sweep = wallet.sweep_all(account=post.account_index, dest_address=submitter.address) + print(sweep) + if "tx_hash_list" in sweep: + amount = 0 + for amt in sweep["amount_list"]: + amount += int(amt) + except Exception as e: + print(f"Failed because: {e}") + + +@bp.cli.command("show") +@click.argument("post_id") +def post_id(post_id): + p = Post.filter(id=post_id).first() + if p: + print(p.show()) + else: + print("That post doesn't exist") + + +@bp.cli.command("load_cache") +def load_cache(): + current_app.logger.info('loading top posters into cache') + get_top_posters() + current_app.logger.info('done') + current_app.logger.info('loading latest tipped into cache') + get_latest_tipped_posts() + current_app.logger.info('done') + for i in [1, 3, 7, 30, 9999]: + current_app.logger.info(f'loading top posts last {i} days into cache') + get_top_posts(i) + current_app.logger.info('done') \ No newline at end of file diff --git a/suchwow/config.py b/suchwow/config.py index 7860537..60abbd0 100644 --- a/suchwow/config.py +++ b/suchwow/config.py @@ -14,6 +14,7 @@ OIDC_REDIRECT_URL = getenv('OIDC_REDIRECT_URL', 'http://localhost:5000/auth') SECRET_KEY = getenv('SECRET_KEY', 'yyyyyyyyyyyyy') # whatever you want it to be DATA_FOLDER = getenv('DATA_FOLDER', '/path/to/uploads') # some stable storage path SERVER_NAME = getenv('SERVER_NAME', 'localhost') # name of your DNS resolvable site (.com) +SUPER_ADMIN = getenv('SUPER_ADMIN', 'lza_menace') # top dawg you cannot delete WALLET_HOST = getenv('WALLET_HOST', 'localhost') # WALLET_PORT = int(getenv('WALLET_PORT', 8888)) # WALLET_PROTO = getenv('WALLET_PROTO', 'http') # diff --git a/suchwow/filters.py b/suchwow/filters.py index da6fa09..c370cf0 100644 --- a/suchwow/filters.py +++ b/suchwow/filters.py @@ -1,6 +1,8 @@ from flask import Blueprint, current_app from arrow import get as arrow_get +from suchwow.models import Moderator + bp = Blueprint('filters', 'filters') @@ -13,3 +15,10 @@ def shorten_address(a): @bp.app_template_filter('humanize') def humanize(d): return arrow_get(d).humanize() + +@bp.app_template_filter('is_moderator') +def is_moderator(s): + m = Moderator.select().where(Moderator.username == s) + if m: + return True + return False \ No newline at end of file diff --git a/suchwow/models.py b/suchwow/models.py index 92b95a3..295eaa7 100644 --- a/suchwow/models.py +++ b/suchwow/models.py @@ -1,4 +1,5 @@ from peewee import * +from random import choice from os import path from datetime import datetime from PIL import Image @@ -8,6 +9,16 @@ from suchwow import config db = SqliteDatabase(f"{config.DATA_FOLDER}/sqlite.db") +ban_reasons = [ + 'you smell bad', + 'didnt pass the vibe check, homie', + 'your memes are bad and you should feel bad', + 'i just dont like you' +] + +def get_ban_reason(): + return choice(ban_reasons) + class Post(Model): id = AutoField() title = CharField() diff --git a/suchwow/routes/auth.py b/suchwow/routes/auth.py index adaed4f..78e0b0f 100644 --- a/suchwow/routes/auth.py +++ b/suchwow/routes/auth.py @@ -21,7 +21,7 @@ def login(): @bp.route("/logout") def logout(): session["auth"] = None - return redirect(url_for("index")) + return redirect(url_for("main.index")) @bp.route("/auth/") def auth(): @@ -66,4 +66,4 @@ def auth(): # user can now visit /secret session["auth"] = user_profile - return redirect(url_for("index")) \ No newline at end of file + return redirect(url_for("main.index")) \ No newline at end of file diff --git a/suchwow/routes/comment.py b/suchwow/routes/comment.py index 97a252a..ae85d65 100644 --- a/suchwow/routes/comment.py +++ b/suchwow/routes/comment.py @@ -11,7 +11,7 @@ bp = Blueprint("comment", "comment") def create(post_id): if not Post.filter(id=post_id): flash("WTF, that post doesn't exist. Stop it, hackerman.", "is-danger") - return redirect(url_for("index")) + return redirect(url_for("main.index")) if request.method == "POST": comment_text = request.form.get("comment") diff --git a/suchwow/routes/main.py b/suchwow/routes/main.py new file mode 100644 index 0000000..e15f67c --- /dev/null +++ b/suchwow/routes/main.py @@ -0,0 +1,50 @@ +from math import ceil + +from flask import Blueprint, request, render_template, flash + +from suchwow.models import Post, Profile, Moderator +from suchwow.utils.helpers import get_latest_tipped_posts + +bp = Blueprint('main', 'main') + + +@bp.route("/") +def index(): + itp = 15 + page = request.args.get("page", 1) + submitter = request.args.get("submitter", None) + content = request.args.get("content", None) + + if content == 'latest_tipped': + posts = get_latest_tipped_posts() + return render_template( + "index.html", + posts=posts[0:30], + title="Latest Tipped Memes" + ) + + try: + page = int(page) + except: + flash("Wow, wtf hackerman. Cool it.", "is-danger") + page = 1 + + posts = Post.select().where(Post.approved==True).order_by(Post.timestamp.desc()) + if submitter: + posts = posts.where(Post.submitter==submitter) + + paginated_posts = posts.paginate(page, itp) + total_pages = ceil(posts.count() / itp) + return render_template( + "index.html", + posts=paginated_posts, + page=page, + total_pages=total_pages, + title="Latest Memes" + ) + + +@bp.route("/about") +def about(): + mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username)) + return render_template("about.html", mods=mods) \ No newline at end of file diff --git a/suchwow/routes/mod.py b/suchwow/routes/mod.py new file mode 100644 index 0000000..31bf95b --- /dev/null +++ b/suchwow/routes/mod.py @@ -0,0 +1,106 @@ +from flask import Blueprint, render_template, redirect, url_for, flash, request + +from suchwow.models import Post, Profile, Moderator, Ban, get_ban_reason +from suchwow.utils.decorators import moderator_required +from suchwow.utils.helpers import get_session_user +from suchwow import config + + +bp = Blueprint("mod", "mod") + +@bp.route('/mods') +@moderator_required +def main(): + live_posts = Post.select().where(Post.approved == True).count() + pending_posts = Post.select().where(Post.approved == False).count() + active_posters = Profile.select().join(Post, on=Post.submitter == Profile.username).distinct().count() + mods = Moderator.select().count() + return render_template( + 'mod/main.html', + live_posts=live_posts, + pending_posts=pending_posts, + active_posters=active_posters, + mods=mods + ) + + +@bp.route('/mods/pending') +@moderator_required +def pending_posts(): + posts = Post.select().where(Post.approved == False).order_by(Post.timestamp.asc()) + if not posts: + flash('no posts pending', 'is-warning') + return redirect(url_for('mod.main')) + return render_template('mod/posts.html', posts=posts) + + +@bp.route('/mods/manage', methods=['GET', 'POST']) +@moderator_required +def manage_mods(): + to_delete = request.args.get('delete') + if to_delete: + m = Moderator.select().where(Moderator.username == to_delete).first() + if not m: + flash('No moderator exists with that name', 'is-danger') + elif m.username == get_session_user(): + flash('Cannot remove yourself.', 'is-danger') + elif m.username == config.SUPER_ADMIN: + flash('Cannot delete super admin you son-of-a-bitch.', 'is-danger') + else: + m.delete_instance() + flash(f'Removed {to_delete} from mods!', 'is-success') + return redirect(url_for('mod.manage_mods')) + if request.method == 'POST': + to_add = request.form.get('username', None) + if to_add: + u = Profile.select().where(Profile.username == to_add).first() + if not u: + flash('That user does not appear to exist (no profile setup yet)', 'is-danger') + elif Moderator.select().where(Moderator.username == to_add).first(): + flash(f'{to_add} is already a mod, ya dingus.', 'is-warning') + else: + m = Moderator(username=to_add) + m.save() + flash(f'Added {to_add} to mods!', 'is-success') + mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username)) + return render_template('mod/manage.html', mods=mods) + + +@bp.route('/mods/bans', methods=['GET', 'POST']) +@moderator_required +def manage_bans(): + to_delete = request.args.get('delete') + if to_delete: + ban = Ban.select().join(Profile).where(Profile.username == to_delete).first() + if not ban: + flash('No ban exists for that user', 'is-danger') + elif ban.user == get_session_user(): + flash('Cannot ban yourself.', 'is-danger') + elif ban.user == config.SUPER_ADMIN: + flash('Cannot ban super admin you son-of-a-bitch.', 'is-danger') + else: + ban.delete_instance() + flash(f'Unbanned {to_delete}!', 'is-success') + return redirect(url_for('mod.manage_bans')) + if request.method == 'POST': + to_add = request.form.get('username', None) + if to_add: + u = Profile.select().where(Profile.username == to_add).first() + if not u: + flash('That user does not appear to exist (no profile setup yet)', 'is-danger') + elif Ban.select().join(Profile).where(Profile.username == to_add).first(): + flash(f'{to_add} is already banned, ya dingus.', 'is-warning') + elif to_add == config.SUPER_ADMIN: + flash('Cannot ban the super admin you son-of-a-bitch.', 'is-danger') + else: + reason = request.form.get('reason') + if not reason: + reason = get_ban_reason() + ban = Ban(user=u, reason=reason) + ban.save() + flash(f'Banned {to_add}!', 'is-success') + bans = Ban.select() + return render_template('mod/bans.html', bans=bans) + +# audit trail of activity +# view wallet rpc logs \ No newline at end of file diff --git a/suchwow/routes/post.py b/suchwow/routes/post.py index 3540549..8a0e1f7 100644 --- a/suchwow/routes/post.py +++ b/suchwow/routes/post.py @@ -48,7 +48,7 @@ def read(id): ) else: flash("No meme there, brah", "is-warning") - return redirect(url_for("index")) + return redirect(url_for("main.index")) @bp.route("/post/create", methods=["GET", "POST"]) @login_required @@ -107,7 +107,7 @@ def create(): url = url_for('post.read', id=post.id, _external=True) post_webhook(f"New post :doge2: [{post.id}]({url}) by `{submitter}` :neckbeard:") flash("New post created and pending approval!", "is-success") - return redirect(url_for("index")) + return redirect(url_for("main.index")) return render_template("post/create.html") @bp.route("/post//approve") @@ -122,22 +122,12 @@ def approve(id): post_webhook(f"Post [{post.id}]({url}) approved :white_check_mark: by `{get_session_user()}` :fieri_parrot:") flash("Approved", "is-success") if current_app.config["DEBUG"] is False: - # _r = None - # _d = None - # if not post.to_reddit: - # _r = make_post(post) - # if not post.to_discord: - # _d = post_discord_webhook(post) - # if _r and _d: - # post_webhook(f"Post [{post.id}]({url}) submitted :dab_parrot: to Reddit and Discord.") - # else: - # post_webhook(f"Post [{post.id}]({url}) failed :this-is-fine-fire: to post to socials...Reddit: {_r} - Discord: {_d}") post_discord_webhook(post) post_webhook(f"Post [{post.id}]({url}) submitted :dab_parrot: to Discord.") - return redirect(url_for("mod_queue")) + return redirect(url_for("mod.pending_posts")) else: flash("You can't approve this", "is-success") - return redirect(url_for("index")) + return redirect(url_for("main.index")) @bp.route("/post//delete") @login_required @@ -155,15 +145,15 @@ def delete(id): post_webhook(f"Post {post.id} deleted :dumpsterfire: by `{user}` :godmode:") flash("Deleted that shit, brah!", "is-success") if is_mod: - return redirect(url_for("mod_queue")) + return redirect(url_for("mod.pending_posts")) else: - return redirect(url_for("index")) + return redirect(url_for("main.index")) else: flash("You can't delete a meme you don't own, brah", "is-warning") return redirect(url_for("post.read", id=post.id)) else: flash("No meme there, brah", "is-warning") - return redirect(url_for("index")) + return redirect(url_for("main.index")) @bp.route("/uploads/") def uploaded_file(filename): diff --git a/suchwow/templates/base.html b/suchwow/templates/base.html index 102f997..81a7a8f 100644 --- a/suchwow/templates/base.html +++ b/suchwow/templates/base.html @@ -70,13 +70,17 @@ {% block content %} {% endblock %} +
+ + {% block footer %} +{% endblock %} {% endblock %} + diff --git a/suchwow/utils/decorators.py b/suchwow/utils/decorators.py index 7d41d3b..ad26031 100644 --- a/suchwow/utils/decorators.py +++ b/suchwow/utils/decorators.py @@ -21,7 +21,7 @@ def moderator_required(f): return f(*args, **kwargs) else: flash("You are not a moderator", "is-warning") - return redirect(url_for("index")) + return redirect(url_for("main.index")) return decorated_function def profile_required(f):