moving stuff around, cleaning up, adding mod area
parent
0cab370e12
commit
143d08f2f7
@ -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')
|
@ -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)
|
@ -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
|
@ -1,19 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="container" style="width:40%;">
|
|
||||||
<div class="submit">
|
|
||||||
<h1>Leave a Comment</h1>
|
|
||||||
<form method=post enctype=multipart/form-data class="form-horizontal">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="sr-only" for="inlineFormInput">Text</label>
|
|
||||||
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="inlineFormInput" placeholder="Comment text (max 300 chars)" name="comment">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<button type="submit" class="btn btn-success">Submit</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,57 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container" style="text-align:center;">
|
||||||
|
<h1 class="title">Manage Mods</h1>
|
||||||
|
<nav class="breadcrumb is-centered" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="{{ url_for('mod.main') }}">Mods</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<section class="section">
|
||||||
|
{% for ban in bans %}
|
||||||
|
<article class="message" style="width: 30%; margin: 2em auto;">
|
||||||
|
<div class="message-header">
|
||||||
|
<p>{{ ban.user.username }}</p>
|
||||||
|
<a href="?delete={{ ban.user.username }}" class="delete"></a>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{{ ban.reason }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
<div class="container" style="text-align:left; width: 30%;">
|
||||||
|
<form method="post">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Ban User</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" placeholder="Username" name="username">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Reason</label>
|
||||||
|
<div class="control">
|
||||||
|
<textarea class="textarea" placeholder="Reason for ban" name="reason"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link">Submit</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link is-light">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock %}
|
@ -0,0 +1,72 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container content" style="padding-top: 4em;">
|
||||||
|
|
||||||
|
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Live Memes</p>
|
||||||
|
<p class="title">{{ live_posts }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Pending Memes</p>
|
||||||
|
<p class="title">{{ pending_posts }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Active Posters</p>
|
||||||
|
<p class="title">{{ active_posters }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Mods</p>
|
||||||
|
<p class="title">{{ mods }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="tabs is-toggle is-fullwidth">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('mod.pending_posts') }}">
|
||||||
|
<span class="icon is-small"></span>
|
||||||
|
<span>Manage Queue</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('mod.manage_bans') }}">
|
||||||
|
<span class="icon is-small"></span>
|
||||||
|
<span>Manage Bans</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('mod.manage_mods') }}">
|
||||||
|
<span class="icon is-small"></span>
|
||||||
|
<span>Manage Mods</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a>
|
||||||
|
<span class="icon is-small"></span>
|
||||||
|
<span>View Logs</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<figure>
|
||||||
|
<img src="https://suchwow.xyz/uploads/pg1VwHJWeKT5dWXy-wowcomfysemifinal.gif">
|
||||||
|
</figure>
|
||||||
|
<div style="padding-bottom:10em;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock %}
|
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container" style="text-align:center;">
|
||||||
|
<h1 class="title">Manage Mods</h1>
|
||||||
|
<nav class="breadcrumb is-centered" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="{{ url_for('mod.main') }}">Mods</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<section class="section">
|
||||||
|
{% for mod in mods %}
|
||||||
|
<div class="block">
|
||||||
|
<span class="tag is-large">
|
||||||
|
{{ mod.username }}
|
||||||
|
<a href="?delete={{ mod.username }}" class="delete"></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
<div class="container" style="text-align:left; width: 30%;">
|
||||||
|
<form method="post">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Add New Mod</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" placeholder="Username" name="username">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link">Submit</button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link is-light">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock %}
|
@ -0,0 +1,65 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container" style="text-align:center;">
|
||||||
|
|
||||||
|
<h1 class="title">{% if title %}{{ title }}{% else %}Pending Posts{% endif %}</h1>
|
||||||
|
<nav class="breadcrumb is-centered" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="{{ url_for('mod.main') }}">Mods</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<section class="section">
|
||||||
|
{% if posts %}
|
||||||
|
{% for row in posts | batch(4) %}
|
||||||
|
<div class="columns">
|
||||||
|
{% for p in row %}
|
||||||
|
{% set post = p.show() %}
|
||||||
|
<div class="column">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-image">
|
||||||
|
{% if p.get_image_path().endswith('mp4') %}
|
||||||
|
<video style="max-height: 100vh!important;" controls>
|
||||||
|
<source src="{{ url_for('post.uploaded_file', filename=p.image_name) }}" type="video/mp4">
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
{% else %}
|
||||||
|
<img alt="SuchWow #{{ post.id }} - {{ post.title }} by {{ post.submitter }}" src="{{ url_for('post.uploaded_file', filename=post.thumbnail_name) }}" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media-content">
|
||||||
|
<p class="title is-4">
|
||||||
|
{{ post.title }}
|
||||||
|
</p>
|
||||||
|
<p class="subtitle is-6"><a href="/?submitter={{ post.submitter }}">{{ post.submitter }}</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{{ post.text | truncate(60) }}
|
||||||
|
<time datetime="2016-1-1">{{ post.timestamp.year }}-{{ post.timestamp.month }}-{{ post.timestamp.day }} {{ post.timestamp.hour }}:{{ post.timestamp.minute }} UTC</time>
|
||||||
|
<p>({{ post.timestamp | humanize }})</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="card-footer">
|
||||||
|
<a href="{{ url_for('post.approve', id=post.id) }}" class="card-footer-item" style="color:green;"><strong>Approve</strong></a>
|
||||||
|
<a href="{{ url_for('post.delete', id=post.id) }}" class="card-footer-item" style="color:red;"><strong>Deny</strong></a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p>No posts pending!</p>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}{% endblock %}
|
Loading…
Reference in New Issue