diff --git a/suchwow/cli.py b/suchwow/cli.py index 955b28f..289680c 100644 --- a/suchwow/cli.py +++ b/suchwow/cli.py @@ -23,73 +23,73 @@ def init(): db.create_tables([User, Post, AuditEvent, TipSent, TipReceived, Vote]) -@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 +# @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 60abbd0..2bb427e 100644 --- a/suchwow/config.py +++ b/suchwow/config.py @@ -21,6 +21,7 @@ WALLET_PROTO = getenv('WALLET_PROTO', 'http') # WALLET_RPC_USER = getenv('WALLET_RPC_USER', 'suchwow') # WALLET_RPC_PASS = getenv('WALLET_RPC_PASS', 'suchwow') # WALLET_PASS = getenv('WALLET_PASS', 'zzzzzzz') # You specify all these wallet details in .env +WALLET_ACCOUNT = getenv('WALLET_ACCOUNT', 0) # Optional for posting to Reddit PRAW_CLIENT_SECRET = getenv('PRAW_CLIENT_SECRET', None) diff --git a/suchwow/filters.py b/suchwow/filters.py index 60edc4c..9e148d5 100644 --- a/suchwow/filters.py +++ b/suchwow/filters.py @@ -2,6 +2,8 @@ from flask import Blueprint from arrow import get as arrow_get from suchwow.models import Moderator +from suchwow.wownero import from_atomic as _from_atomic +from suchwow.wownero import as_wownero bp = Blueprint('filters', 'filters') @@ -16,6 +18,10 @@ def shorten_address(a): def humanize(d): return arrow_get(d).humanize() +@bp.app_template_filter('from_atomic') +def from_atomic(a): + return as_wownero(_from_atomic(a)) + @bp.app_template_filter('is_moderator') def is_moderator(s): m = Moderator.select().where(Moderator.username == s) diff --git a/suchwow/routes/main.py b/suchwow/routes/main.py index e15f67c..e3b3834 100644 --- a/suchwow/routes/main.py +++ b/suchwow/routes/main.py @@ -2,7 +2,7 @@ from math import ceil from flask import Blueprint, request, render_template, flash -from suchwow.models import Post, Profile, Moderator +from suchwow._models import Post, User from suchwow.utils.helpers import get_latest_tipped_posts bp = Blueprint('main', 'main') @@ -31,7 +31,11 @@ def index(): posts = Post.select().where(Post.approved==True).order_by(Post.timestamp.desc()) if submitter: - posts = posts.where(Post.submitter==submitter) + user = Post.select().where(Post.username == submitter).first() + if not user: + flash('That user does not exist!', 'is-warning') + else: + posts = posts.where(Post.user == user) paginated_posts = posts.paginate(page, itp) total_pages = ceil(posts.count() / itp) @@ -46,5 +50,5 @@ def index(): @bp.route("/about") def about(): - mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username)) + mods = User.select().where(User.moderator == True) 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 index 85d84aa..1b0a065 100644 --- a/suchwow/routes/mod.py +++ b/suchwow/routes/mod.py @@ -1,6 +1,6 @@ from flask import Blueprint, render_template, redirect, url_for, flash, request -from suchwow.models import AuditEvent, Post, Profile, Moderator, Ban, get_ban_reason +from suchwow._models import AuditEvent, Post, User, get_ban_reason from suchwow.utils.decorators import moderator_required from suchwow.utils.helpers import get_session_user, audit_event from suchwow import config @@ -13,8 +13,8 @@ bp = Blueprint("mod", "mod") 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() + active_posters = User.select().join(Post, on=Post.user).distinct().count() + mods = User.select().where(User.moderator == True).count() return render_template( 'mod/main.html', live_posts=live_posts, @@ -37,73 +37,76 @@ def pending_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(): + to_remove = request.args.get('delete') + if to_remove: + u = User.select().where(User.username == to_remove).first() + if not u.moderator: + flash('That user is not a moderator', 'is-danger') + elif u.username == get_session_user(): flash('Cannot remove yourself.', 'is-danger') - elif m.username == config.SUPER_ADMIN: + elif u.username == config.SUPER_ADMIN: flash('Cannot delete super admin you son-of-a-bitch.', 'is-danger') else: - m.delete_instance() - audit_event(f'Deleted {to_delete} from mods') - flash(f'Removed {to_delete} from mods!', 'is-success') + u.moderator = False + u.save() + audit_event(f'Removed {to_remove} from mods') + flash(f'Removed {to_remove} 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() + u = User.select().where(User.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('That user does not appear to exist', 'is-danger') + elif u.moderator: flash(f'{to_add} is already a mod, ya dingus.', 'is-warning') else: - m = Moderator(username=to_add) - m.save() + u.moderator = True + u.save() audit_event(f'Added {to_add} to mods') flash(f'Added {to_add} to mods!', 'is-success') - mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username)) + mods = User.select().where(User.moderator == True) 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: + to_unban = request.args.get('delete') + if to_unban: + u = User.select().where(User.username == to_unban).first() + if not u.banned: flash('No ban exists for that user', 'is-danger') - elif ban.user == get_session_user(): + elif u.username == get_session_user(): flash('Cannot ban yourself.', 'is-danger') - elif ban.user == config.SUPER_ADMIN: + elif u.username == config.SUPER_ADMIN: flash('Cannot ban super admin you son-of-a-bitch.', 'is-danger') else: - ban.delete_instance() - audit_event(f'Removed ban on {to_delete}') - flash(f'Unbanned {to_delete}!', 'is-success') + u.banned = False + u.save() + audit_event(f'Removed ban on {to_unban}') + flash(f'Unbanned {to_unban}!', '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() + to_ban = request.form.get('username', None) + if to_ban: + u = User.select().where(User.username == to_ban).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('That user does not appear to exist', 'is-danger') + elif u.banned: + flash(f'{to_ban} is already banned, ya dingus.', 'is-warning') + elif u.username == 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() - audit_event(f'Banned {to_add} ({reason})') - flash(f'Banned {to_add}!', 'is-success') - bans = Ban.select() + u.banned = True + u.ban_reason = reason + u.save() + audit_event(f'Banned {to_ban} ({reason})') + flash(f'Banned {to_ban}!', 'is-success') + bans = User.select().where(User.banned == True) return render_template('mod/bans.html', bans=bans) diff --git a/suchwow/routes/post.py b/suchwow/routes/post.py index 0ada6d3..9f52907 100644 --- a/suchwow/routes/post.py +++ b/suchwow/routes/post.py @@ -8,9 +8,9 @@ from flask import render_template, Blueprint, request, flash from flask import send_from_directory, redirect, url_for, current_app from werkzeug.utils import secure_filename -from suchwow import wownero -from suchwow.models import Post, Profile, Comment, Ban -from suchwow.utils.decorators import login_required, profile_required, moderator_required +from suchwow import wownero, config +from suchwow._models import User, Post, TipReceived +from suchwow.utils.decorators import login_required, address_required, moderator_required from suchwow.utils.helpers import allowed_file, is_moderator, get_session_user from suchwow.utils.helpers import audit_event from suchwow.discord import post_discord_webhook @@ -22,28 +22,21 @@ bp = Blueprint("post", "post") def read(id): _address_qr = BytesIO() qr_code = None - if Post.filter(id=id): - wallet = wownero.Wallet() - post = Post.get(id=id) + post = Post.select().where(Post.id == id).first() + if post: if not post.approved: if not is_moderator(get_session_user()): flash("That post has not been approved.", "is-warning") return redirect("/") - if wallet.connected: - address = wallet.get_address(account=post.account_index) - transfers = wallet.transfers(account=post.account_index) - qr_uri = f'wownero:{address}?tx_description=suchwow%20post%20{post.id}' - address_qr = qrcode_make(qr_uri).save(_address_qr) - qr_code = b64encode(_address_qr.getvalue()).decode() - else: - address = "?" - transfers = "?" + qr_uri = f'wownero:{post.address}?tx_description=suchwow%20post%20{post.id}' + qrcode_make(qr_uri).save(_address_qr) + qr_code = b64encode(_address_qr.getvalue()).decode() + tips = TipReceived.select().where(TipReceived.post == post).order_by(TipReceived.timestamp.desc()) return render_template( "post/read.html", post=post, - address=address, - transfers=transfers, - qr_code=qr_code + qr_code=qr_code, + tips=tips ) else: flash("No meme there, brah", "is-warning") @@ -51,13 +44,11 @@ def read(id): @bp.route("/post/create", methods=["GET", "POST"]) @login_required -@profile_required +@address_required def create(): - submitter = get_session_user() - u = Profile.filter(username=submitter) - banned = Ban.filter(user=u).first() - if banned: - flash(f"You can't post: {banned.reason}", "is-danger") + u = User.select().where(User.username == get_session_user()).first() + if u.banned: + flash(f"You can't post: {u.ban_reason}", "is-danger") return redirect("/") if request.method == "POST": post_title = request.form.get("title") @@ -82,37 +73,34 @@ def create(): save_path_base = path.join(current_app.config["DATA_FOLDER"], "uploads") save_path = path.join(save_path_base, filename) file.save(save_path) - try: + wallet_found = False + while wallet_found is False: wallet = wownero.Wallet() - account_index = wallet.new_account() - in_use = Post.select().where(Post.account_index == account_index).first() + address_idx, address = wallet.new_address(config.WALLET_ACCOUNT) + in_use = Post.select().where(Post.address == address).first() if in_use: - flash("Suchwow wallet is fucked up! Try again later.", "is-danger") - return redirect(request.url) - except: - flash("Suchwow wallet is fucked up! Try again later.", "is-danger") - return redirect(request.url) - post = Post( - title=post_title, - text=request.form.get("text", ""), - submitter=submitter, - image_name=filename, - account_index=account_index, - address_index=0 - ) - post.save() - post.save_thumbnail() - url = url_for('post.read', id=post.id, _external=True) - audit_event(f'Created new post {post.id}') - flash("New post created and pending approval!", "is-success") - return redirect(url_for("main.index")) + continue + wallet_found = True + post = Post( + title=post_title, + text=request.form.get("text", ""), + user=u, + image_name=filename, + account_index=config.WALLET_ACCOUNT, + address_index=address_idx, + address=address + ) + post.save() + post.save_thumbnail() + audit_event(f'Created new post {post.id}') + flash("New post created and pending approval!", "is-success") + return redirect(url_for("main.index")) return render_template("post/create.html") @bp.route("/post//approve") @moderator_required def approve(id): - post = Post.get(id=id) - url = url_for('post.read', id=post.id, _external=True) + post = Post.select().where(Post.id == id).first() if post: if not post.approved: post.approved = True @@ -129,12 +117,10 @@ def approve(id): @bp.route("/post//delete") @login_required def delete(id): - filtered = Post.filter(id=id) - user = get_session_user() - is_mod = is_moderator(user) - if filtered: - post = filtered.first() - if user == post.submitter or is_mod: + post = Post.select().where(Post.id == id).first() + user = User.select().where(User.username == get_session_user()).first() + if post: + if user == post.user or user.moderator: save_path_base = path.join(current_app.config["DATA_FOLDER"], "uploads") save_path = path.join(save_path_base, post.image_name) try: @@ -144,7 +130,7 @@ def delete(id): audit_event(f'Deleted post {post.id}') post.delete_instance() flash("Deleted that shit, brah!", "is-success") - if is_mod: + if user.moderator: return redirect(url_for("mod.pending_posts")) else: return redirect(url_for("main.index")) diff --git a/suchwow/templates/index.html b/suchwow/templates/index.html index 656557d..cc4757e 100644 --- a/suchwow/templates/index.html +++ b/suchwow/templates/index.html @@ -28,7 +28,7 @@ Your browser does not support the video tag. {% else %} - SuchWow #{{ post.id }} - {{ post.title }} by {{ post.submitter }} + SuchWow #{{ post.id }} - {{ post.title }} by {{ post.user.username }} {% endif %} diff --git a/suchwow/templates/mod/bans.html b/suchwow/templates/mod/bans.html index bdf4780..ee86f24 100644 --- a/suchwow/templates/mod/bans.html +++ b/suchwow/templates/mod/bans.html @@ -8,11 +8,11 @@ {% for ban in bans %}
-

{{ ban.user.username }}

- +

{{ ban.username }}

+
- {{ ban.reason }} + {{ ban.ban_reason }}
{% endfor %} diff --git a/suchwow/templates/post/read.html b/suchwow/templates/post/read.html index 53613b5..805e167 100644 --- a/suchwow/templates/post/read.html +++ b/suchwow/templates/post/read.html @@ -12,9 +12,6 @@
  • Post {{ post.id }}
  • - {% if post.hidden %} -

    You cannot see this post

    - {% else %}
    @@ -24,15 +21,14 @@ Approve Reject {% endif %} -

    Submitted by {{ post.submitter }} at {{ post.timestamp }}

    - +

    Submitted by {{ post.user.username }} at {{ post.timestamp }}

    {% if post.get_image_path().endswith('mp4') %} {% else %} - SuchWow #{{ post.id }} - {{ post.title }} by {{ post.submitter }} + SuchWow #{{ post.id }} - {{ post.title }} by {{ post.user.username }} {% endif %}
    @@ -40,52 +36,30 @@

    Payments

    -

    Vote for this post by sending WOW to the following address:
    {{ address }}

    +

    Vote for this post by sending WOW to the following address:
    {{ post.address }}

    {% if qr_code %}

    {% endif %}
    -

    WOW Received

    - {% if transfers.in %} +

    {{ tips | sum(attribute='amount') | from_atomic }} WOW Received

    + {% if tips %}
      - {% for transfer in transfers.in %} - {% if transfer.amount > 0 %} -
    • - {{ transfer.amount / 100000000000 }} WOW - ({{ transfer.txid | shorten_address }}) - - {{ transfer.timestamp | humanize }} + {% for tip in tips %} +
    • + {{ tip.amount | from_atomic }} WOW + ({{ tip.txid | shorten_address }}) + - {{ tip.timestamp | humanize }}
    • - {% endif %} {% endfor %}
    {% else %}

    No WOW received yet. Show this post some love!

    {% endif %}
    -
    -

    WOW Sent

    - {% if transfers.out %} -
      - {% for transfer in transfers.out %} -
    • - {{ transfer.amount / 100000000000 }} WOW - ({{ transfer.txid | shorten_address }}) - - {{ transfer.timestamp | humanize }} -
    • - {% endfor %} -
    - {% else %} -

    No payouts yet.

    - {% endif %} -
    -
    - - {% endif %} - {% if config.DEBUG %} {{ post.show() }} {% endif %} diff --git a/suchwow/utils/decorators.py b/suchwow/utils/decorators.py index ad26031..1032acd 100644 --- a/suchwow/utils/decorators.py +++ b/suchwow/utils/decorators.py @@ -1,6 +1,6 @@ from flask import session, redirect, url_for, flash from functools import wraps -from suchwow.models import Profile, Moderator +from suchwow._models import User def login_required(f): @@ -16,19 +16,19 @@ def moderator_required(f): def decorated_function(*args, **kwargs): if "auth" not in session or not session["auth"]: return redirect(url_for("auth.login")) - m = Moderator.filter(username=session["auth"]["preferred_username"]) - if m: + u = User.select().where(User.username == session["auth"]["preferred_username"]).first() + if u.moderator: return f(*args, **kwargs) else: flash("You are not a moderator", "is-warning") return redirect(url_for("main.index")) return decorated_function -def profile_required(f): +def address_required(f): @wraps(f) def decorated_function(*args, **kwargs): - un = session["auth"]["preferred_username"] - if not Profile.filter(username=un): + u = User.select().where(User.username == session["auth"]["preferred_username"]).first() + if not u.address: url = "{}?redirect={}".format( url_for("profile.edit"), url_for("post.create") diff --git a/suchwow/utils/helpers.py b/suchwow/utils/helpers.py index feb1338..82df455 100644 --- a/suchwow/utils/helpers.py +++ b/suchwow/utils/helpers.py @@ -4,7 +4,8 @@ from datetime import datetime, timedelta from flask import session -from suchwow.models import Moderator, Post, AuditEvent, Profile +from suchwow._models import AuditEvent, User +from suchwow.models import Moderator, Post, Profile from suchwow.wownero import Wallet, from_atomic from suchwow import config @@ -20,12 +21,12 @@ def is_moderator(username): else: return False -def get_profile(): - p = Profile.filter(username=get_session_user()).first() - return p +def get_current_user(): + u = User.select().where(User.username == get_session_user()).first() + return u def audit_event(event): - e = AuditEvent(user=get_profile(), action=event) + e = AuditEvent(user=get_current_user(), action=event) e.save() def get_session_user():