Compare commits

..

No commits in common. 'migrate-new-models' and 'master' have entirely different histories.

1
.gitignore vendored

@ -7,4 +7,3 @@ __pycache__
*sql *sql
flask_session flask_session
.env .env
.env.dev

@ -1,82 +0,0 @@
#!/usr/bin/env python3
# export suchwow data for the purpose of importing into new model definitions
import pickle
from suchwow.models import Post, Moderator, Profile, Ban, AuditEvent
from suchwow import wownero
wallet = wownero.Wallet()
if not wallet.connected:
print('Wallet not connected')
exit()
all_posts = Post.select().order_by(Post.timestamp.asc())
all_mods = Moderator.select()
all_profiles = Profile.select()
all_bans = Ban.select()
all_audits = AuditEvent.select()
all_data = {
'posts': list(),
'moderators': list(),
'profiles': list(),
'bans': list(),
'auditevents': list()
}
for post in all_posts:
post_data = {
'id': post.id,
'title': post.title,
'text': post.text,
'submitter': post.submitter,
'image_name': post.image_name,
'readonly': post.readonly,
'hidden': post.hidden,
'account_index': post.account_index,
'address_index': post.address_index,
'timestamp': post.timestamp,
'reddit_url': post.reddit_url,
'to_reddit': post.to_reddit,
'to_discord': post.to_discord,
'approved': post.approved,
'txes': wallet.make_wallet_rpc('get_transfers', {
'account_index': post.account_index,
'subaddr_indices': [],
'in': True,
'out': True
})
}
txes = 0
all_data['posts'].append(post_data)
if 'in' in post_data['txes']:
txes = len(post_data['txes']['in'])
print(f'Exporting post {post.id}. Found {txes} txes')
for mod in all_mods:
all_data['moderators'].append(mod.username)
for profile in all_profiles:
all_data['profiles'].append({
'username': profile.username,
'address': profile.address
})
for ban in all_bans:
all_data['bans'].append({
'username': ban.user.username,
'reason': ban.reason,
'timestamp': ban.timestamp
})
for event in all_audits:
all_data['auditevents'].append({
'username': event.user.username,
'timestamp': event.timestamp,
'action': event.action
})
with open('data/migrate_data.pkl', 'wb') as f:
f.write(pickle.dumps(all_data))

@ -1,107 +0,0 @@
#!/usr/bin/env python3
# import pickled suchwow data for the purpose of importing into new model definitions
import pickle
from datetime import datetime
from suchwow._models import User, Post, AuditEvent, TipSent, TipReceived, Vote
from suchwow import wownero
wallet = wownero.Wallet()
all_data = dict()
if not wallet.connected:
print('Wallet not running and connected')
exit()
with open('data/migrate_data.pkl', 'rb') as f:
all_data = pickle.load(f)
# first import users from old profiles
for user in all_data['profiles']:
if not User.select().where(User.username == user['username']).first():
u = User(
username=user['username'],
address=user['address'],
)
u.save()
print(f'Added user {u.username}')
for post in all_data['posts']:
if not Post.select().where(Post.id == post['id']).first():
user = User.get(username=post['submitter'])
account_idx = 0
address_idx, address = wallet.new_address(account_idx)
print(f'Saving post {post["id"]} for user {user.username} (account {account_idx}, address_idx {address_idx}, {address}')
Post.create(
id=post['id'],
title=post['title'],
text=post['text'],
user=user,
image_name=post['image_name'],
account_index=account_idx,
address_index=address_idx,
address=address,
timestamp=post['timestamp'],
approved=post['approved']
)
if 'in' in post['txes']:
p = Post.get(post['id'])
for tx in post['txes']['in']:
amount = sum(tx['amounts'])
received = TipReceived.select().where(
TipReceived.txid == tx['txid'],
TipReceived.amount == amount,
TipReceived.post == p
).first()
if not received:
TipReceived.create(
post=p,
timestamp=datetime.utcfromtimestamp(tx['timestamp']),
txid=tx['txid'],
amount=amount,
fee=tx['fee']
)
print(f'Saving received tip txid {tx["txid"]}')
if 'out' in post['txes']:
p = Post.get(post['id'])
for tx in post['txes']['out']:
if not TipSent.select().where(TipSent.txid == tx['txid']).first():
TipSent.create(
from_user=p.user,
to_user=p.user,
txid=tx['txid'],
timestamp=datetime.utcfromtimestamp(tx['timestamp']),
amount=tx['amount'],
fee=tx['fee']
)
print(f'Saving sent tip txid {tx["txid"]}')
for mod in all_data['moderators']:
u = User.get(User.username == mod)
if not u.moderator:
u.moderator = True
u.save()
print(f'Updated {u.username} as moderator')
for ban in all_data['bans']:
u = User.get(User.username == ban['username'])
if not u.banned:
u.banned = True
u.ban_reason = ban['reason']
u.ban_timestamp = ban['timestamp']
u.save()
print(f'Banned {u.username} ({u.ban_reason})')
for event in all_data['auditevents']:
if not AuditEvent.select().where(AuditEvent.timestamp == event['timestamp']):
u = User.get(User.username == event['username'])
AuditEvent.create(
user=u,
action=event['action'],
timestamp=event['timestamp']
)
print(f'Saved audit event ({u.username} -> {event["action"]} @ {event["timestamp"]}')

@ -9,4 +9,3 @@ qrcode
Pillow Pillow
arrow arrow
python-dotenv python-dotenv
lorem

@ -21,7 +21,7 @@ fi
# run rpc process # run rpc process
docker run --restart=always -d --name suchwow-wallet-rpc \ docker run --restart=always -d --name suchwow-wallet-rpc \
-v $WALLET_PATH:/root \ -v $WALLET_PATH:/root \
-p $WALLET_PORT:8888 \ -p 8888:8888 \
lalanza808/wownero:latest \ lalanza808/wownero:latest \
wownero-wallet-rpc \ wownero-wallet-rpc \
--daemon-address $DAEMON_URI \ --daemon-address $DAEMON_URI \

@ -1,223 +0,0 @@
from random import choice
from os import path
from datetime import datetime
from peewee import *
from PIL import Image
from suchwow import wownero
from suchwow import config
db = SqliteDatabase(f"{config.DATA_FOLDER}/suchwow_db.sqlite")
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 User(Model):
id = AutoField()
username = CharField()
address = CharField(null=True)
moderator = BooleanField(default=False)
banned = BooleanField(default=False)
ban_reason = TextField(null=True)
ban_timestamp = DateField(null=True)
login_timestamp = DateTimeField(null=True)
def get_wow_received(self):
tips = TipReceived.select().join(Post).where(Post.user == self)
return sum([tip.amount for tip in tips])
def get_wow_sent(self):
tips = TipSent.select().where(TipSent.from_user == self)
return sum([tip.amount + tip.fee for tip in tips])
def get_post_count(self):
posts = Post.select().where(Post.user == self)
return posts.count()
def get_post_addresses(self):
posts = Post.select().where(Post.user == self)
return [i.address_index for i in posts]
class Meta:
database = db
class Post(Model):
id = AutoField()
title = CharField()
text = CharField(null=True)
user = ForeignKeyField(User)
image_name = CharField()
account_index = IntegerField()
address_index = IntegerField(unique=True)
address = CharField(unique=True)
timestamp = DateTimeField(default=datetime.utcnow)
approved = BooleanField(default=False)
approved_by = ForeignKeyField(User, null=True)
def get_random(self):
all_posts = Post.select().where(Post.approved == True)
if all_posts:
return choice([i.id for i in all_posts])
else:
return None
def get_previous(self):
prev = Post.select().where(Post.id == self.id - 1).first()
if prev and prev.approved:
return prev.id
else:
return None
def get_next(self):
next = Post.select().where(Post.id == self.id + 1).first()
if next and next.approved:
return next.id
else:
return None
def get_image_path(self, thumbnail=False):
save_path_base = path.join(config.DATA_FOLDER, "uploads")
if thumbnail:
save_path = path.join(save_path_base, self.thumbnail)
else:
save_path = path.join(save_path_base, self.image_name)
return save_path
def save_thumbnail(self):
try:
image = Image.open(self.get_image_path())
image.thumbnail((200,200), Image.ANTIALIAS)
image.save(self.get_image_path(True), format=image.format, quality=90)
image.close()
return True
except:
return False
def strip_exif(self):
try:
image = Image.open(self.get_image_path())
data = image.getdata()
image_without_exif = Image.new(image.mode, image.size)
image_without_exif.putdata(data)
image_without_exif.save(self.get_image_path())
image_without_exif.close()
image.close()
except:
return False
def resize_image(self):
try:
with Image.open(self.get_image_path()) as img:
img.thumbnail((1800,1800))
img.save(self.get_image_path())
except:
return False
@property
def resized(self):
s = path.splitext(self.image_name)
return s[0] + '.resized' + s[1]
@property
def thumbnail(self):
s = path.splitext(self.image_name)
return s[0] + '.thumbnail' + s[1]
def get_wow_received(self):
tips = TipReceived.select().where(TipReceived.post == self)
return sum(tip.amount for tip in tips)
def hours_elapsed(self):
now = datetime.utcnow()
diff = now - self.timestamp
return diff.total_seconds() / 60 / 60
def show(self):
return {
'id': self.id,
'title': self.title,
'text': self.text,
'user': self.user.username,
'image_name': self.image_name,
'image_path': self.get_image_path(),
'thumbnail_name': self.thumbnail,
'thumbnail_path': self.get_image_path(True),
'account_index': self.account_index,
'address_index': self.address_index,
'address': self.address,
'timestamp': self.timestamp,
'approved': self.approved,
'approved_by': self.approved_by,
'received_wow': self.get_wow_received(),
'hours_elapsed': self.hours_elapsed(),
'user_tips_received': wownero.from_atomic(self.user.get_wow_received()),
'user_tips_sent': wownero.from_atomic(self.user.get_wow_sent())
}
class Meta:
database = db
class SocialPost(Model):
id = AutoField()
post = ForeignKeyField(Post)
service = CharField()
class Meta:
database = db
class Vote(Model):
id = AutoField()
post = ForeignKeyField(Post)
upvote = BooleanField()
timestamp = DateTimeField(default=datetime.now)
class Meta:
database = db
class AuditEvent(Model):
id = AutoField()
user = ForeignKeyField(User)
timestamp = DateTimeField(default=datetime.now)
action = CharField()
class Meta:
database = db
class TipReceived(Model):
id = AutoField()
post = ForeignKeyField(Post)
txid = CharField()
timestamp = DateTimeField()
amount = IntegerField()
fee = IntegerField()
class Meta:
database = db
class TipSent(Model):
id = AutoField()
from_user = ForeignKeyField(User)
to_user = ForeignKeyField(User)
txid = CharField()
timestamp = DateTimeField()
amount = IntegerField()
fee = IntegerField()
class Meta:
database = db

@ -1,12 +1,12 @@
from os import makedirs, getenv from os import makedirs
from random import choice
from datetime import datetime
import lorem, click import click
from flask import Blueprint from flask import Blueprint, url_for, current_app
from suchwow._models import db, User, Post, AuditEvent, TipSent, TipReceived, Vote from suchwow.models import Post, Profile, Comment, Notification, db, Moderator, Ban, AuditEvent
from suchwow.models import Post as OldPost 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 wownero
from suchwow import config from suchwow import config
@ -20,203 +20,76 @@ def init():
makedirs(f"{config.DATA_FOLDER}/{i}", exist_ok=True) makedirs(f"{config.DATA_FOLDER}/{i}", exist_ok=True)
# init db # init db
db.create_tables([User, Post, AuditEvent, TipSent, TipReceived, Vote]) db.create_tables([Post, Profile, Comment, Notification, Moderator, Ban, AuditEvent])
@bp.cli.command('generate_data')
def generate_data(): @bp.cli.command("post_reddit")
if getenv('FLASK_DEBUG', 0) == '1': @click.argument('last_hours')
users = ['lza_menace', 'wowario', 'jwinterm', 'dsc', 'asymptotically'] def post_reddit(last_hours):
for user in users: posts = Post.select().where(
moderator = False Post.approved==True,
if not User.select().where(User.username == user): Post.to_reddit==False
if user == 'lza_menace': ).order_by(Post.timestamp.asc())
moderator = True for p in posts:
User.create( if p.hours_elapsed() < int(last_hours):
username=user, if not p.to_reddit:
moderator=moderator _p = make_post(p)
) if _p:
print(f'Created user {user}') p.to_reddit = True
p.save()
for i in range(1, 5): return
@bp.cli.command("create_accounts")
def create_accounts():
wallet = wownero.Wallet() wallet = wownero.Wallet()
address_idx, address = wallet.new_address(config.WALLET_ACCOUNT) for post in Post.select():
wallet.store() if post.account_index not in wallet.accounts():
Post.create( account = wallet.new_account()
title=lorem.sentence(), print(f"Created account {account}")
text=lorem.sentence(),
user=choice(list(User.select())),
image_name='test.jpg', @bp.cli.command("payout_users")
account_index=config.WALLET_ACCOUNT,
address_index=address_idx,
address=address
)
@bp.cli.command('process_tips')
def process_tips():
w = wownero.Wallet()
for user in User.select().order_by(User.id.desc()):
user_posts = Post.select().where(Post.user == user)
if user_posts.count() == 0:
continue
addresses = [i.address_index for i in user_posts]
txes = w.transfers(config.WALLET_ACCOUNT, addresses)
if txes:
for tx in txes['in']:
if tx['unlock_time'] > 0:
print('someone added a lock time to this tx. skipping for now.')
continue
if tx['confirmations'] < 5:
continue
_tx = TipReceived.select().where(TipReceived.txid == tx['txid']).first()
if not _tx:
post = Post.select().where(Post.address == tx['address']).first()
if not post:
print('No post exists with that address. Not sure wat do.')
else:
TipReceived.create(
post=post,
txid=tx['txid'],
timestamp=datetime.utcfromtimestamp(tx['timestamp']),
amount=sum(tx['amounts']),
fee=tx['fee']
)
print('Saved tip {} ({} WOW) received for post {} by {}'.format(
tx['txid'], wownero.from_atomic(sum(tx['amounts'])),
post.id, post.user.username
))
@bp.cli.command('payout_users')
def payout_users(): def payout_users():
wallet = wownero.Wallet() wallet = wownero.Wallet()
balances = wallet.balances() _fa = wownero.from_atomic
print('Wallet balances are {} locked, {} unlocked'.format( _aw = wownero.as_wownero
wownero.from_atomic(balances[0]), wownero.from_atomic(balances[1]) for post in Post.select():
)) try:
for user in User.select().join(Post, on=Post.user).distinct().order_by(User.id.asc()): submitter = Profile.get(username=post.submitter)
rcvd = user.get_wow_received() balances = wallet.balances(post.account_index)
sent = user.get_wow_sent() url = url_for('post.read', id=post.id, _external=True)
if rcvd == 0: if balances[1] > 0.05:
continue print(f"Post #{post.id} has {balances[1]} funds unlocked and ready to send. Sweeping all funds to user's address ({submitter.address}).")
to_send = rcvd - sent sweep = wallet.sweep_all(account=post.account_index, dest_address=submitter.address)
if to_send >= wownero.to_atomic(.5): print(sweep)
print('{} has received {} atomic WOW but sent {} atomic WOW. Sending {} atomic WOW'.format( if "tx_hash_list" in sweep:
user.username, wownero.from_atomic(rcvd), amount = 0
wownero.from_atomic(sent), wownero.from_atomic(to_send) for amt in sweep["amount_list"]:
)) amount += int(amt)
if balances[1] >= to_send: except Exception as e:
tx_data = { print(f"Failed because: {e}")
'account_index': config.WALLET_ACCOUNT,
'destinations': [{
'address': user.address, @bp.cli.command("show")
'amount': to_send @click.argument("post_id")
}], def post_id(post_id):
'subaddress_indices': user.get_post_addresses(),
'priority': 0,
'unlock_time': 0,
'get_tx_key': True,
'do_not_relay': True,
'ring_size': 22
}
transfer = wallet.make_wallet_rpc('transfer', tx_data)
print(transfer)
if 'code' in transfer:
return
tx_data['destinations'][0]['amount'] = to_send - transfer['fee']
tx_data['do_not_relay'] = False
transfer = wallet.make_wallet_rpc('transfer', tx_data)
print(tx_data)
print(transfer)
if 'code' in transfer:
return
TipSent.create(
from_user=user,
to_user=user,
txid=transfer['tx_hash'],
timestamp=datetime.utcnow(),
amount=transfer['amount'],
fee=transfer['fee'],
)
print(f'Sent tip of {wownero.from_atomic(transfer["amount"])} WOW to {user} in tx_hash {transfer["tx_hash"]}')
wallet.make_wallet_rpc('store')
@bp.cli.command('fix_image')
@click.argument('post_id')
def fix_image(post_id):
p = Post.filter(id=post_id).first() p = Post.filter(id=post_id).first()
if p: if p:
p.strip_exif() print(p.show())
p.resize_image()
else: else:
print("That post doesn't exist") print("That post doesn't exist")
@bp.cli.command('rescan')
def rescan():
wallet = wownero.Wallet()
wallet.make_wallet_rpc('rescan_blockchain')
@bp.cli.command('save')
def rescan():
wallet = wownero.Wallet()
wallet.make_wallet_rpc('store')
print('Saved wallet.')
@bp.cli.command("create_accounts") @bp.cli.command("load_cache")
def create_accounts(): def load_cache():
wallet = wownero.Wallet() current_app.logger.info('loading top posters into cache')
highest_account = OldPost.select().order_by(OldPost.timestamp.desc()).first().account_index get_top_posters()
print(f'Highest post account index is {highest_account} but highest wallet account is {wallet.accounts()[-1]}. Generating new accounts!') current_app.logger.info('done')
while wallet.accounts()[-1] < highest_account: current_app.logger.info('loading latest tipped into cache')
account = wallet.new_account() get_latest_tipped_posts()
print(f"Created account {account}") current_app.logger.info('done')
wallet.make_wallet_rpc('store') 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)
# @bp.cli.command("payout_users") current_app.logger.info('done')
# 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')

@ -21,7 +21,6 @@ WALLET_PROTO = getenv('WALLET_PROTO', 'http') #
WALLET_RPC_USER = getenv('WALLET_RPC_USER', 'suchwow') # WALLET_RPC_USER = getenv('WALLET_RPC_USER', 'suchwow') #
WALLET_RPC_PASS = getenv('WALLET_RPC_PASS', 'suchwow') # WALLET_RPC_PASS = getenv('WALLET_RPC_PASS', 'suchwow') #
WALLET_PASS = getenv('WALLET_PASS', 'zzzzzzz') # You specify all these wallet details in .env 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 # Optional for posting to Reddit
PRAW_CLIENT_SECRET = getenv('PRAW_CLIENT_SECRET', None) PRAW_CLIENT_SECRET = getenv('PRAW_CLIENT_SECRET', None)

@ -2,8 +2,6 @@ from flask import Blueprint
from arrow import get as arrow_get from arrow import get as arrow_get
from suchwow.models import Moderator from suchwow.models import Moderator
from suchwow.wownero import from_atomic as _from_atomic
from suchwow.wownero import as_wownero
bp = Blueprint('filters', 'filters') bp = Blueprint('filters', 'filters')
@ -18,10 +16,6 @@ def shorten_address(a):
def humanize(d): def humanize(d):
return arrow_get(d).humanize() 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') @bp.app_template_filter('is_moderator')
def is_moderator(s): def is_moderator(s):
m = Moderator.select().where(Moderator.username == s) m = Moderator.select().where(Moderator.username == s)

@ -1,6 +1,7 @@
from flask import jsonify, Blueprint, url_for, request, abort from flask import jsonify, Blueprint, url_for, request, abort
from suchwow._models import Post from suchwow.models import Post
from suchwow import wownero
bp = Blueprint("api", "api") bp = Blueprint("api", "api")
@ -21,17 +22,18 @@ def api_list():
limit = 30 limit = 30
all_posts = [] all_posts = []
posts = Post.select().where( posts = Post.select().where(Post.approved==True).order_by(Post.timestamp.desc()).limit(limit).offset(offset)
Post.approved == True
).order_by(
Post.timestamp.desc()
).limit(limit).offset(offset)
for post in posts: for post in posts:
wallet = wownero.Wallet()
if wallet.connected:
address = wallet.get_address(account=post.account_index)
else:
address = ''
payload = { payload = {
'image': url_for('post.uploaded_file', filename=post.image_name, _external=True), 'image': url_for('post.uploaded_file', filename=post.image_name, _external=True),
'submitter': post.user.username, 'submitter': post.submitter,
'address': post.address, 'address': address,
'title': post.title, 'title': post.title,
'text': post.text, 'text': post.text,
'href': url_for('post.read', id=post.id, _external=True), 'href': url_for('post.read', id=post.id, _external=True),

@ -1,32 +1,17 @@
from datetime import datetime, timedelta
import peewee
from flask import render_template, Blueprint, request from flask import render_template, Blueprint, request
from suchwow._models import Post, TipReceived, User from suchwow.utils.helpers import get_top_posters, get_top_posts
bp = Blueprint("leaderboard", "leaderboard") bp = Blueprint("leaderboard", "leaderboard")
@bp.route("/leaderboards/top_posters") @bp.route("/leaderboards/top_posters")
def top_posters(): def top_posters():
tips_received = peewee.fn.SUM(TipReceived.amount) top_posters = get_top_posters()
top_posters = User.select(
User, tips_received
).join(
Post, peewee.JOIN.LEFT_OUTER, on=Post.user
).join(
TipReceived, peewee.JOIN.LEFT_OUTER
).group_by(User.username).order_by(
tips_received.desc()
).limit(30)
return render_template("leaderboard.html", posters=top_posters) return render_template("leaderboard.html", posters=top_posters)
@bp.route("/leaderboards/top_posts") @bp.route("/leaderboards/top_posts")
def top_posts(): def top_posts():
tips_received = peewee.fn.SUM(TipReceived.amount)
days = request.args.get('days', 1) days = request.args.get('days', 1)
try: try:
days = int(days) days = int(days)
@ -36,19 +21,5 @@ def top_posts():
if days not in [1, 3, 7, 30, 9999]: if days not in [1, 3, 7, 30, 9999]:
days = 7 days = 7
new_date = datetime.utcnow() - timedelta(hours=(days * 24)) posts = get_top_posts(days)
posts = Post.select(Post, tips_received).join( return render_template("post/top.html", posts=posts, days=days)
TipReceived
).where(
TipReceived.timestamp >= new_date
).group_by(
Post.id
).order_by(
tips_received.desc()
).limit(30)
return render_template(
"index.html",
posts=posts,
days=days,
title=f'Top Posts Last {days} Days'
)

@ -2,8 +2,8 @@ from math import ceil
from flask import Blueprint, request, render_template, flash from flask import Blueprint, request, render_template, flash
from suchwow._models import Post, User, TipReceived from suchwow.models import Post, Profile, Moderator
from suchwow.wownero import from_atomic from suchwow.utils.helpers import get_latest_tipped_posts
bp = Blueprint('main', 'main') bp = Blueprint('main', 'main')
@ -16,10 +16,10 @@ def index():
content = request.args.get("content", None) content = request.args.get("content", None)
if content == 'latest_tipped': if content == 'latest_tipped':
posts = Post.select().join(TipReceived).distinct().order_by(TipReceived.timestamp.desc()).limit(30) posts = get_latest_tipped_posts()
return render_template( return render_template(
"index.html", "index.html",
posts=posts, posts=posts[0:30],
title="Latest Tipped Memes" title="Latest Tipped Memes"
) )
@ -29,13 +29,9 @@ def index():
flash("Wow, wtf hackerman. Cool it.", "is-danger") flash("Wow, wtf hackerman. Cool it.", "is-danger")
page = 1 page = 1
posts = Post.select().where(Post.approved == True).order_by(Post.timestamp.desc()) posts = Post.select().where(Post.approved==True).order_by(Post.timestamp.desc())
if submitter: if submitter:
user = User.select().where(User.username == submitter) posts = posts.where(Post.submitter==submitter)
if not user:
flash('That user does not exist!', 'is-warning')
else:
posts = posts.where(Post.user == user)
paginated_posts = posts.paginate(page, itp) paginated_posts = posts.paginate(page, itp)
total_pages = ceil(posts.count() / itp) total_pages = ceil(posts.count() / itp)
@ -50,11 +46,5 @@ def index():
@bp.route("/about") @bp.route("/about")
def about(): def about():
mods = User.select().where(User.moderator == True) mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username))
return render_template( return render_template("about.html", mods=mods)
"about.html",
mods=mods,
meme_count=Post.select().where(Post.approved == True).count(),
wow_received=round(from_atomic(sum([i.amount for i in TipReceived.select()])), 2),
memer_count=User.select().count(),
)

@ -1,6 +1,6 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request from flask import Blueprint, render_template, redirect, url_for, flash, request
from suchwow._models import AuditEvent, Post, User, get_ban_reason from suchwow.models import AuditEvent, Post, Profile, Moderator, Ban, get_ban_reason
from suchwow.utils.decorators import moderator_required from suchwow.utils.decorators import moderator_required
from suchwow.utils.helpers import get_session_user, audit_event from suchwow.utils.helpers import get_session_user, audit_event
from suchwow import config from suchwow import config
@ -13,8 +13,8 @@ bp = Blueprint("mod", "mod")
def main(): def main():
live_posts = Post.select().where(Post.approved == True).count() live_posts = Post.select().where(Post.approved == True).count()
pending_posts = Post.select().where(Post.approved == False).count() pending_posts = Post.select().where(Post.approved == False).count()
active_posters = User.select().join(Post, on=Post.user).distinct().count() active_posters = Profile.select().join(Post, on=Post.submitter == Profile.username).distinct().count()
mods = User.select().where(User.moderator == True).count() mods = Moderator.select().count()
return render_template( return render_template(
'mod/main.html', 'mod/main.html',
live_posts=live_posts, live_posts=live_posts,
@ -37,76 +37,73 @@ def pending_posts():
@bp.route('/mods/manage', methods=['GET', 'POST']) @bp.route('/mods/manage', methods=['GET', 'POST'])
@moderator_required @moderator_required
def manage_mods(): def manage_mods():
to_remove = request.args.get('delete') to_delete = request.args.get('delete')
if to_remove: if to_delete:
u = User.select().where(User.username == to_remove).first() m = Moderator.select().where(Moderator.username == to_delete).first()
if not u.moderator: if not m:
flash('That user is not a moderator', 'is-danger') flash('No moderator exists with that name', 'is-danger')
elif u.username == get_session_user(): elif m.username == get_session_user():
flash('Cannot remove yourself.', 'is-danger') flash('Cannot remove yourself.', 'is-danger')
elif u.username == config.SUPER_ADMIN: elif m.username == config.SUPER_ADMIN:
flash('Cannot delete super admin you son-of-a-bitch.', 'is-danger') flash('Cannot delete super admin you son-of-a-bitch.', 'is-danger')
else: else:
u.moderator = False m.delete_instance()
u.save() audit_event(f'Deleted {to_delete} from mods')
audit_event(f'Removed {to_remove} from mods') flash(f'Removed {to_delete} from mods!', 'is-success')
flash(f'Removed {to_remove} from mods!', 'is-success')
return redirect(url_for('mod.manage_mods')) return redirect(url_for('mod.manage_mods'))
if request.method == 'POST': if request.method == 'POST':
to_add = request.form.get('username', None) to_add = request.form.get('username', None)
if to_add: if to_add:
u = User.select().where(User.username == to_add).first() u = Profile.select().where(Profile.username == to_add).first()
if not u: if not u:
flash('That user does not appear to exist', 'is-danger') flash('That user does not appear to exist (no profile setup yet)', 'is-danger')
elif u.moderator: elif Moderator.select().where(Moderator.username == to_add).first():
flash(f'{to_add} is already a mod, ya dingus.', 'is-warning') flash(f'{to_add} is already a mod, ya dingus.', 'is-warning')
else: else:
u.moderator = True m = Moderator(username=to_add)
u.save() m.save()
audit_event(f'Added {to_add} to mods') audit_event(f'Added {to_add} to mods')
flash(f'Added {to_add} to mods!', 'is-success') flash(f'Added {to_add} to mods!', 'is-success')
mods = User.select().where(User.moderator == True) mods = Profile.select().join(Moderator, on=(Profile.username == Moderator.username))
return render_template('mod/manage.html', mods=mods) return render_template('mod/manage.html', mods=mods)
@bp.route('/mods/bans', methods=['GET', 'POST']) @bp.route('/mods/bans', methods=['GET', 'POST'])
@moderator_required @moderator_required
def manage_bans(): def manage_bans():
to_unban = request.args.get('delete') to_delete = request.args.get('delete')
if to_unban: if to_delete:
u = User.select().where(User.username == to_unban).first() ban = Ban.select().join(Profile).where(Profile.username == to_delete).first()
if not u.banned: if not ban:
flash('No ban exists for that user', 'is-danger') flash('No ban exists for that user', 'is-danger')
elif u.username == get_session_user(): elif ban.user == get_session_user():
flash('Cannot ban yourself.', 'is-danger') flash('Cannot ban yourself.', 'is-danger')
elif u.username == config.SUPER_ADMIN: elif ban.user == config.SUPER_ADMIN:
flash('Cannot ban super admin you son-of-a-bitch.', 'is-danger') flash('Cannot ban super admin you son-of-a-bitch.', 'is-danger')
else: else:
u.banned = False ban.delete_instance()
u.save() audit_event(f'Removed ban on {to_delete}')
audit_event(f'Removed ban on {to_unban}') flash(f'Unbanned {to_delete}!', 'is-success')
flash(f'Unbanned {to_unban}!', 'is-success')
return redirect(url_for('mod.manage_bans')) return redirect(url_for('mod.manage_bans'))
if request.method == 'POST': if request.method == 'POST':
to_ban = request.form.get('username', None) to_add = request.form.get('username', None)
if to_ban: if to_add:
u = User.select().where(User.username == to_ban).first() u = Profile.select().where(Profile.username == to_add).first()
if not u: if not u:
flash('That user does not appear to exist', 'is-danger') flash('That user does not appear to exist (no profile setup yet)', 'is-danger')
elif u.banned: elif Ban.select().join(Profile).where(Profile.username == to_add).first():
flash(f'{to_ban} is already banned, ya dingus.', 'is-warning') flash(f'{to_add} is already banned, ya dingus.', 'is-warning')
elif u.username == config.SUPER_ADMIN: elif to_add == config.SUPER_ADMIN:
flash('Cannot ban the super admin you son-of-a-bitch.', 'is-danger') flash('Cannot ban the super admin you son-of-a-bitch.', 'is-danger')
else: else:
reason = request.form.get('reason') reason = request.form.get('reason')
if not reason: if not reason:
reason = get_ban_reason() reason = get_ban_reason()
u.banned = True ban = Ban(user=u, reason=reason)
u.ban_reason = reason ban.save()
u.save() audit_event(f'Banned {to_add} ({reason})')
audit_event(f'Banned {to_ban} ({reason})') flash(f'Banned {to_add}!', 'is-success')
flash(f'Banned {to_ban}!', 'is-success') bans = Ban.select()
bans = User.select().where(User.banned == True)
return render_template('mod/bans.html', bans=bans) return render_template('mod/bans.html', bans=bans)

@ -8,10 +8,10 @@ from flask import render_template, Blueprint, request, flash
from flask import send_from_directory, redirect, url_for, current_app from flask import send_from_directory, redirect, url_for, current_app
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from suchwow import wownero, config from suchwow import wownero
from suchwow._models import User, Post, TipReceived from suchwow.models import Post, Profile, Comment, Ban
from suchwow.utils.decorators import login_required, address_required, moderator_required from suchwow.utils.decorators import login_required, profile_required, moderator_required
from suchwow.utils.helpers import allowed_file, get_session_user from suchwow.utils.helpers import allowed_file, is_moderator, get_session_user
from suchwow.utils.helpers import audit_event from suchwow.utils.helpers import audit_event
from suchwow.discord import post_discord_webhook from suchwow.discord import post_discord_webhook
@ -22,21 +22,28 @@ bp = Blueprint("post", "post")
def read(id): def read(id):
_address_qr = BytesIO() _address_qr = BytesIO()
qr_code = None qr_code = None
post = Post.select().where(Post.id == id).first() if Post.filter(id=id):
if post: wallet = wownero.Wallet()
post = Post.get(id=id)
if not post.approved: if not post.approved:
if not User.select().where(User.username == get_session_user()).first().moderator: if not is_moderator(get_session_user()):
flash("That post has not been approved.", "is-warning") flash("That post has not been approved.", "is-warning")
return redirect("/") return redirect("/")
qr_uri = f'wownero:{post.address}?tx_description=suchwow%20post%20{post.id}' if wallet.connected:
qrcode_make(qr_uri).save(_address_qr) 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() qr_code = b64encode(_address_qr.getvalue()).decode()
tips = TipReceived.select().where(TipReceived.post == post).order_by(TipReceived.timestamp.desc()) else:
address = "?"
transfers = "?"
return render_template( return render_template(
"post/read.html", "post/read.html",
post=post, post=post,
qr_code=qr_code, address=address,
tips=tips transfers=transfers,
qr_code=qr_code
) )
else: else:
flash("No meme there, brah", "is-warning") flash("No meme there, brah", "is-warning")
@ -44,11 +51,13 @@ def read(id):
@bp.route("/post/create", methods=["GET", "POST"]) @bp.route("/post/create", methods=["GET", "POST"])
@login_required @login_required
@address_required @profile_required
def create(): def create():
u = User.select().where(User.username == get_session_user()).first() submitter = get_session_user()
if u.banned: u = Profile.filter(username=submitter)
flash(f"You can't post: {u.ban_reason}", "is-danger") banned = Ban.filter(user=u).first()
if banned:
flash(f"You can't post: {banned.reason}", "is-danger")
return redirect("/") return redirect("/")
if request.method == "POST": if request.method == "POST":
post_title = request.form.get("title") post_title = request.form.get("title")
@ -73,25 +82,27 @@ def create():
save_path_base = path.join(current_app.config["DATA_FOLDER"], "uploads") save_path_base = path.join(current_app.config["DATA_FOLDER"], "uploads")
save_path = path.join(save_path_base, filename) save_path = path.join(save_path_base, filename)
file.save(save_path) file.save(save_path)
wallet_found = False try:
while wallet_found is False:
wallet = wownero.Wallet() wallet = wownero.Wallet()
address_idx, address = wallet.new_address(config.WALLET_ACCOUNT) account_index = wallet.new_account()
in_use = Post.select().where(Post.address == address).first() in_use = Post.select().where(Post.account_index == account_index).first()
if in_use: if in_use:
continue flash("Suchwow wallet is fucked up! Try again later.", "is-danger")
wallet_found = True return redirect(request.url)
except:
flash("Suchwow wallet is fucked up! Try again later.", "is-danger")
return redirect(request.url)
post = Post( post = Post(
title=post_title, title=post_title,
text=request.form.get("text", ""), text=request.form.get("text", ""),
user=u, submitter=submitter,
image_name=filename, image_name=filename,
account_index=config.WALLET_ACCOUNT, account_index=account_index,
address_index=address_idx, address_index=0
address=address
) )
post.save() post.save()
post.save_thumbnail() post.save_thumbnail()
url = url_for('post.read', id=post.id, _external=True)
audit_event(f'Created new post {post.id}') audit_event(f'Created new post {post.id}')
flash("New post created and pending approval!", "is-success") flash("New post created and pending approval!", "is-success")
return redirect(url_for("main.index")) return redirect(url_for("main.index"))
@ -100,7 +111,8 @@ def create():
@bp.route("/post/<id>/approve") @bp.route("/post/<id>/approve")
@moderator_required @moderator_required
def approve(id): def approve(id):
post = Post.select().where(Post.id == id).first() post = Post.get(id=id)
url = url_for('post.read', id=post.id, _external=True)
if post: if post:
if not post.approved: if not post.approved:
post.approved = True post.approved = True
@ -117,10 +129,12 @@ def approve(id):
@bp.route("/post/<id>/delete") @bp.route("/post/<id>/delete")
@login_required @login_required
def delete(id): def delete(id):
post = Post.select().where(Post.id == id).first() filtered = Post.filter(id=id)
user = User.select().where(User.username == get_session_user()).first() user = get_session_user()
if post: is_mod = is_moderator(user)
if user == post.user or user.moderator: if filtered:
post = filtered.first()
if user == post.submitter or is_mod:
save_path_base = path.join(current_app.config["DATA_FOLDER"], "uploads") save_path_base = path.join(current_app.config["DATA_FOLDER"], "uploads")
save_path = path.join(save_path_base, post.image_name) save_path = path.join(save_path_base, post.image_name)
try: try:
@ -130,7 +144,7 @@ def delete(id):
audit_event(f'Deleted post {post.id}') audit_event(f'Deleted post {post.id}')
post.delete_instance() post.delete_instance()
flash("Deleted that shit, brah!", "is-success") flash("Deleted that shit, brah!", "is-success")
if user.moderator: if is_mod:
return redirect(url_for("mod.pending_posts")) return redirect(url_for("mod.pending_posts"))
else: else:
return redirect(url_for("main.index")) return redirect(url_for("main.index"))

@ -1,8 +1,7 @@
from flask import render_template, Blueprint, flash, request, redirect, flash from flask import render_template, Blueprint, flash
from flask import request, redirect, url_for, session
from suchwow._models import User from suchwow.models import Profile
from suchwow.utils.decorators import login_required from suchwow.utils.decorators import login_required
from suchwow.utils.helpers import get_session_user
bp = Blueprint("profile", "profile") bp = Blueprint("profile", "profile")
@ -10,18 +9,27 @@ bp = Blueprint("profile", "profile")
@bp.route("/profile/edit", methods=["GET", "POST"]) @bp.route("/profile/edit", methods=["GET", "POST"])
@login_required @login_required
def edit(): def edit():
user = User.select().where(User.username == get_session_user()).first() un = session["auth"]["preferred_username"]
profile_exists = Profile.filter(username=un)
if request.method == "POST": if request.method == "POST":
address = request.form.get("address") address = request.form.get("address")
if len(address) in [97, 108]: if len(address) in [97, 108]:
if user: if profile_exists:
user.address = address profile = Profile.get(username=un)
user.save() profile.address = address
profile.save()
else: else:
User.create(username=get_session_user()) profile = Profile(
flash('Wallet address saved!', 'is-success') username=un,
address=address
)
profile.save()
return redirect(request.args.get("redirect", "/")) return redirect(request.args.get("redirect", "/"))
else: else:
flash("WTF bro, that's not a valid Wownero address", "is-warning") flash("WTF bro, that's not a valid Wownero address", "is-warning")
return redirect(request.url) return redirect(request.url)
return render_template("profile/edit.html", profile=user) if profile_exists:
profile = Profile.get(username=un)
else:
profile = None
return render_template("profile/edit.html", profile=profile)

File diff suppressed because one or more lines are too long

@ -2,27 +2,6 @@
{% block content %} {% block content %}
<nav class="level" style="margin: 3em 0">
<div class="level-item has-text-centered">
<div>
<p class="heading">Memes</p>
<p class="title">{{ meme_count }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="heading">Tips</p>
<p class="title">{{ wow_received }} WOW</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="heading">Memers</p>
<p class="title">{{ memer_count }}</p>
</div>
</div>
</nav>
<div class="container" style="text-align:center;"> <div class="container" style="text-align:center;">
<div class="about content"> <div class="about content">
<h1>About</h1> <h1>About</h1>

@ -47,7 +47,6 @@
<meta name="msapplication-TileColor" content="#da532c"> <meta name="msapplication-TileColor" content="#da532c">
<meta name="keywords" content="wownero, wow, memes, monero, xmr, cryptocurrency"> <meta name="keywords" content="wownero, wow, memes, monero, xmr, cryptocurrency">
<link href="/static/css/bulma.min.css" rel="stylesheet"> <link href="/static/css/bulma.min.css" rel="stylesheet">
<link href="/static/css/font-awesome.all.min.css" rel="stylesheet">
<link href="/static/css/custom.css" rel="stylesheet"> <link href="/static/css/custom.css" rel="stylesheet">
<title>SuchWow!</title> <title>SuchWow!</title>
</head> </head>

@ -16,18 +16,19 @@
{% if posts %} {% if posts %}
{% for row in posts | batch(4) %} {% for row in posts | batch(4) %}
<div class="columns"> <div class="columns">
{% for post in row %} {% for p in row %}
{% set post = p.show() %}
<div class="column"> <div class="column">
<div class="card"> <div class="card">
<div class="card-image"> <div class="card-image">
<a href="{{ url_for('post.read', id=post.id) }}"> <a href="{{ url_for('post.read', id=post.id) }}">
{% if post.get_image_path().endswith('mp4') %} {% if p.get_image_path().endswith('mp4') %}
<video style="max-height: 100vh!important;" controls> <video style="max-height: 100vh!important;" controls>
<source src="{{ url_for('post.uploaded_file', filename=post.image_name) }}" type="video/mp4"> <source src="{{ url_for('post.uploaded_file', filename=p.image_name) }}" type="video/mp4">
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>
{% else %} {% else %}
<img alt="SuchWow #{{ post.id }} - {{ post.title }} by {{ post.user.username }}" src="{{ url_for('post.uploaded_file', filename=post.thumbnail) }}" /> <img alt="SuchWow #{{ post.id }} - {{ post.title }} by {{ post.submitter }}" src="{{ url_for('post.uploaded_file', filename=post.thumbnail_name) }}" />
{% endif %} {% endif %}
</a> </a>
</div> </div>
@ -37,12 +38,13 @@
<p class="title is-4"> <p class="title is-4">
<a href="{{ url_for('post.read', id=post.id) }}">{{ post.title }}</a> <a href="{{ url_for('post.read', id=post.id) }}">{{ post.title }}</a>
</p> </p>
<p class="subtitle is-6"><a href="/?submitter={{ post.user.username }}">{{ post.user.username }}</a></p> <p class="subtitle is-6"><a href="/?submitter={{ post.submitter }}">{{ post.submitter }}</a></p>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
{{ post.text | truncate(60) }} {{ post.text | truncate(60) }}
<p><strong>{{ post.get_wow_received() | from_atomic }} WOW received</strong></p> <p><strong>{{ post.received_wow }} WOW received</strong></p>
<time datetime="2016-1-1">{{ post.timestamp.year }}-{{ post.timestamp.month }}-{{ post.timestamp.day }} {{ post.timestamp.hour }}:{{ post.timestamp.minute }} UTC</time> <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> <p>({{ post.timestamp | humanize }})</p>
</div> </div>

@ -13,11 +13,11 @@
<th>Post Count</th> <th>Post Count</th>
<th>Amount</th> <th>Amount</th>
</tr> </tr>
{% for poster in posters %} {% for poster, data in posters.items() | sort(attribute='1.amount', reverse=True) %}
<tr> <tr>
<td><a href="/?submitter={{ poster.username }}">{{ poster.username }}</a></td> <td><a href="/?submitter={{ poster }}">{{ poster }}</a></td>
<td>{{ poster.get_post_count() }}</td> <td>{{ data["posts"] | length }}</td>
<td>{{ poster.get_wow_received() | from_atomic }} WOW</td> <td>{{ data["amount"] }} WOW</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

@ -8,11 +8,11 @@
{% for ban in bans %} {% for ban in bans %}
<article class="message" style="width: 30%; margin: 2em auto;"> <article class="message" style="width: 30%; margin: 2em auto;">
<div class="message-header"> <div class="message-header">
<p>{{ ban.username }}</p> <p>{{ ban.user.username }}</p>
<a href="?delete={{ ban.username }}" class="delete"></a> <a href="?delete={{ ban.user.username }}" class="delete"></a>
</div> </div>
<div class="message-body"> <div class="message-body">
{{ ban.ban_reason }} {{ ban.reason }}
</div> </div>
</article> </article>
{% endfor %} {% endfor %}

@ -12,30 +12,11 @@
<li class="is-active"><a href="#" aria-current="page">Post {{ post.id }}</a></li> <li class="is-active"><a href="#" aria-current="page">Post {{ post.id }}</a></li>
</ul> </ul>
</nav> </nav>
<div class="tabs is-fullwidth"> {% if post.hidden %}
<ul> <h2>You cannot see this post</h2>
<li> {% else %}
<a {% if post.get_previous() %}href="{{ url_for('post.read', id=post.get_previous()) }}"{% else %}class="disabled"{% endif %}>
<span class="icon"><i class="fas fa-angle-left" aria-hidden="true"></i></span>
<span>Previous</span>
</a>
</li>
<li>
<a {% if post.get_random() %}href="{{ url_for('post.read', id=post.get_random()) }}{% endif %}">
<span class="icon"><i class="fas fa-question" aria-hidden="true"></i></span>
<span>Random</span>
</a>
</li>
<li>
<a {% if post.get_next() %}href="{{ url_for('post.read', id=post.get_next()) }}"{% else %}class="disabled"{% endif %}>
<span>Next</span>
<span class="icon"><i class="fas fa-angle-right" aria-hidden="true"></i></span>
</a>
</li>
</ul>
</div>
<!-- Post Info --> <!-- Post Info -->
<section class="section" style="padding: 0;"> <section class="section">
<div class="content"> <div class="content">
<h1>{{ post.title }}</h1> <h1>{{ post.title }}</h1>
<p>{{ post.text }}</p> <p>{{ post.text }}</p>
@ -43,49 +24,68 @@
<a href="{{ url_for('post.approve', id=post.id) }}" class="button is-success">Approve</a> <a href="{{ url_for('post.approve', id=post.id) }}" class="button is-success">Approve</a>
<a href="{{ url_for('post.delete', id=post.id) }}" class="button is-danger">Reject</a> <a href="{{ url_for('post.delete', id=post.id) }}" class="button is-danger">Reject</a>
{% endif %} {% endif %}
<p class="mt-2">Submitted by <i><u><a href="/?submitter={{ post.user.username }}">{{ post.user.username }}</a></u></i> at <i>{{ post.timestamp }}</i></p> <p class="mt-2">Submitted by <i><u><a href="/?submitter={{ post.submitter }}">{{ post.submitter }}</a></u></i> at <i>{{ post.timestamp }}</i></p>
<div style="max-width: 80vh; margin: 0 auto;"> <!-- <img src="{{ url_for('post.uploaded_file', filename=post.image_name) }}" width=600/ style="border-radius:4px;"> -->
{% if post.get_image_path().endswith('mp4') %} {% if post.get_image_path().endswith('mp4') %}
<video style="max-height: 100vh!important;" controls> <video style="max-height: 100vh!important;" controls>
<source src="{{ url_for('post.uploaded_file', filename=post.image_name) }}" type="video/mp4"> <source src="{{ url_for('post.uploaded_file', filename=post.image_name) }}" type="video/mp4">
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>
{% else %} {% else %}
<a href="{{ url_for('post.uploaded_file', filename=post.image_name, _external=True) }}"> <img alt="SuchWow #{{ post.id }} - {{ post.title }} by {{ post.submitter }}" src="{{ url_for('post.uploaded_file', filename=post.image_name) }}" />
<img alt="SuchWow #{{ post.id }} - {{ post.title }} by {{ post.user.username }}" src="{{ url_for('post.uploaded_file', filename=post.image_name) }}" />
</a>
{% endif %} {% endif %}
</div> </div>
</div>
</section> </section>
<!-- Payments --> <!-- Payments -->
<section class="section content"> <section class="section content">
<h3>Payments</h3> <h3>Payments</h3>
<p style="word-break:break-all;">Vote for this post by sending WOW to the following address:<br><i>{{ post.address }}</i></p> <p style="word-break:break-all;">Vote for this post by sending WOW to the following address:<br><i>{{ address }}</i></p>
{% if qr_code %} {% if qr_code %}
<img src="data:image/png;base64,{{ qr_code }}" width=180 class="center"><br /><br /> <img src="data:image/png;base64,{{ qr_code }}" width=180 class="center"><br /><br />
{% endif %} {% endif %}
<div class="columns"> <div class="columns">
<div class="column content"> <div class="column content">
<h4>{{ tips | sum(attribute='amount') | from_atomic }} WOW Received</h4> <h4>WOW Received</h4>
{% if tips %} {% if transfers.in %}
<ul> <ul>
{% for tip in tips %} {% for transfer in transfers.in %}
<li class="is-small" style="font-size:.8em;"> {% if transfer.amount > 0 %}
{{ tip.amount | from_atomic }} WOW <li>
(<a href="https://explore.wownero.com/tx/{{ tip.txid }}" target="_blank">{{ tip.txid | shorten_address }}</a>) {{ transfer.amount / 100000000000 }} WOW
- {{ tip.timestamp | humanize }} (<a href="https://wownero.club/transaction/{{ transfer.txid }}" target="_blank">{{ transfer.txid | shorten_address }}</a>)
- {{ transfer.timestamp | humanize }}
</li> </li>
{% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
{% else %} {% else %}
<p>No WOW received yet. Show this post some love!</p> <p>No WOW received yet. Show this post some love!</p>
{% endif %} {% endif %}
</div> </div>
<div class="column content">
<h4>WOW Sent</h4>
{% if transfers.out %}
<ul>
{% for transfer in transfers.out %}
<li>
{{ transfer.amount / 100000000000 }} WOW
(<a href="https://wownero.club/transaction/{{ transfer.txid }}" target="_blank">{{ transfer.txid | shorten_address }}</a>)
- {{ transfer.timestamp | humanize }}
</li>
{% endfor %}
</ul>
{% else %}
<p>No payouts yet.</p>
{% endif %}
</div>
</div> </div>
</section> </section>
{% endif %}
{% if config.DEBUG %} {% if config.DEBUG %}
{{ post.show() }} {{ post.show() }}
{% endif %} {% endif %}

@ -0,0 +1,62 @@
{% extends 'base.html' %}
{% block content %}
<div class="container" style="text-align:center;">
<h1 class="title">Top Memes Last {{ days }} Days</h1>
<section class="section">
{% if posts %}
{% for row in posts | sort(attribute='received_wow', reverse=True) | batch(4) %}
<div class="columns">
{% if loop.index < 15 %}
{% for post in row %}
<div class="column">
<div class="card">
<div class="card-image">
<a href="{{ url_for('post.read', id=post.id) }}">
<img src="{{ url_for('post.uploaded_file', filename=post.thumbnail_name) }}" alt="Placeholder image">
</a>
</div>
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-4">
<a href="{{ url_for('post.read', id=post.id) }}">{{ post.title }}</a>
</p>
<p class="subtitle is-6"><a href="/?submitter={{ post.submitter }}">{{ post.submitter }}</a></p>
</div>
</div>
<div class="content">
{{ post.text | truncate(60) }}
<p><strong>{{ post.received_wow }} WOW received</strong></p>
<time datetime="2016-1-1">{{ post.timestamp.year }}-{{ post.timestamp.month }}-{{ post.timestamp.day }} {{ post.timestamp.hour }}:{{ post.timestamp.minute }} UTC</time>
</div>
</div>
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endfor %}
{% else %}
<p>No posts yet!</p>
{% endif %}
</section>
{% if total_pages %}
<nav class="pagination is-centered pb-4" role="navigation" aria-label="pagination">
<ul class="pagination-list">
{% for p in range(1, total_pages + 1) %}
<a href="{% if request.args.submitter %}/?submitter={{ request.args.submitter }}&{% else %}/?{% endif %}page={{ p }}" class="pagination-link {% if p == page %}current-page-btn{% endif %}">{{ p }}</a>
{% endfor %}
</ul>
</nav>
{% endif %}
</div>
{% endblock %}

@ -6,21 +6,17 @@
<div class="edit"> <div class="edit">
<h1>Edit Profile</h1> <h1>Edit Profile</h1>
<p>You need to setup your profile before you can submit memes. As of now this only consists of a payout address so we know where to send Wownero if someone sends funds for your post.</p> <p>You need to setup your profile before you can submit memes. As of now this only consists of a payout address so we know where to send Wownero if someone sends funds for your post.</p>
<form method=post enctype=multipart/form-data class="mt-4"> <form method=post enctype=multipart/form-data class="form-horizontal">
<div class="field"> <div class="form-group">
<label class="label">Payout Address</label> <label class="sr-only" for="address">Payout Address</label>
<div class="control"> <input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="address" placeholder="Wownero address for payouts" name="address">
<input class="input" type="text" placeholder="Wownero address for payouts" name="address">
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div> </div>
<div class="form-group">
<button type="submit" class="btn btn-success">Submit</button>
</div> </div>
</form> </form>
{% if profile %} {% if profile %}
<p style="word-break:break-all;" class="mt-4">Existing Address:<br>{{ profile.address }}</p> <p style="word-break:break-all;">Existing Address:<br>{{ profile.address }}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>

@ -1,6 +1,6 @@
from flask import session, redirect, url_for, flash from flask import session, redirect, url_for, flash
from functools import wraps from functools import wraps
from suchwow._models import User from suchwow.models import Profile, Moderator
def login_required(f): def login_required(f):
@ -16,19 +16,19 @@ def moderator_required(f):
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if "auth" not in session or not session["auth"]: if "auth" not in session or not session["auth"]:
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
u = User.select().where(User.username == session["auth"]["preferred_username"]).first() m = Moderator.filter(username=session["auth"]["preferred_username"])
if u.moderator: if m:
return f(*args, **kwargs) return f(*args, **kwargs)
else: else:
flash("You are not a moderator", "is-warning") flash("You are not a moderator", "is-warning")
return redirect(url_for("main.index")) return redirect(url_for("main.index"))
return decorated_function return decorated_function
def address_required(f): def profile_required(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
u = User.select().where(User.username == session["auth"]["preferred_username"]).first() un = session["auth"]["preferred_username"]
if not u.address: if not Profile.filter(username=un):
url = "{}?redirect={}".format( url = "{}?redirect={}".format(
url_for("profile.edit"), url_for("profile.edit"),
url_for("post.create") url_for("post.create")

@ -4,7 +4,8 @@ from datetime import datetime, timedelta
from flask import session from flask import session
from suchwow._models import AuditEvent, User from suchwow.models import Moderator, Post, AuditEvent, Profile
from suchwow.wownero import Wallet, from_atomic
from suchwow import config from suchwow import config
@ -12,12 +13,19 @@ def allowed_file(filename):
return "." in filename and \ return "." in filename and \
filename.rsplit(".", 1)[1].lower() in config.ALLOWED_EXTENSIONS filename.rsplit(".", 1)[1].lower() in config.ALLOWED_EXTENSIONS
def get_current_user(): def is_moderator(username):
u = User.select().where(User.username == get_session_user()).first() m = Moderator.filter(username=username)
return u if m:
return True
else:
return False
def get_profile():
p = Profile.filter(username=get_session_user()).first()
return p
def audit_event(event): def audit_event(event):
e = AuditEvent(user=get_current_user(), action=event) e = AuditEvent(user=get_profile(), action=event)
e.save() e.save()
def get_session_user(): def get_session_user():
@ -25,3 +33,116 @@ def get_session_user():
return None return None
return session["auth"]["preferred_username"].strip() return session["auth"]["preferred_username"].strip()
def get_latest_tipped_posts():
key_name = 'latest_tips'
posts = []
tipped_posts = rw_cache(key_name)
if not tipped_posts:
w = Wallet()
data = {}
for acc in w.accounts():
txes = w.transfers(acc)
if 'in' in txes:
for tx in txes['in']:
p = Post.select().where(
Post.account_index==acc
).first()
if p:
data[tx['timestamp']] = p
dates = sorted(data, reverse=True)
for d in dates:
if not data[d] in posts:
posts.append(data[d])
tipped_posts = rw_cache(key_name, posts)
return tipped_posts
def get_top_posters():
top_posters = {}
posts = rw_cache('top_posters')
if not posts:
posts = Post.select().where(Post.approved==True)
for post in posts:
transfers = []
incoming = Wallet().incoming_transfers(post.account_index)
if "transfers" in incoming:
for xfer in incoming["transfers"]:
transfers.append(from_atomic(xfer["amount"]))
total = sum(transfers)
if post.submitter not in top_posters:
top_posters[post.submitter] = {"amount": 0, "posts": []}
top_posters[post.submitter]["amount"] += float(total)
top_posters[post.submitter]["posts"].append(post)
rw_cache('top_posters', top_posters)
else:
top_posters = posts
return top_posters
def get_top_posts(days=1):
top_posts = []
try:
days = int(days)
except:
days = 1
# stupid magic number bcuz fuck it
if days not in [1, 3, 7, 30, 9999]:
days = 7
hours = 24 * days
diff = datetime.now() - timedelta(hours=hours)
key_name = f'top_posts_{str(hours)}'
posts = rw_cache(key_name)
if not posts:
posts = Post.select().where(
Post.approved==True,
Post.timestamp > diff
).order_by(
Post.timestamp.desc()
)
for post in posts:
p = post.show()
if isinstance(p['received_wow'], float):
top_posts.append(p)
posts = rw_cache(key_name, top_posts)
return posts
# Use hacky filesystem cache since i dont feel like shipping redis
def rw_cache(key_name, data=None, diff_seconds=3600):
pickle_file = path.join(config.DATA_FOLDER, f'{key_name}.pkl')
try:
if path.isfile(pickle_file):
mtime_ts = path.getmtime(pickle_file)
mtime = datetime.fromtimestamp(mtime_ts)
now = datetime.now()
diff = now - mtime
# If pickled data file is less than an hour old, load it and render page
# Otherwise, determine balances, build json, store pickled data, and render page
if diff.seconds < diff_seconds:
print(f'unpickling {key_name}')
with open(pickle_file, 'rb') as f:
pickled_data = pickle.load(f)
return pickled_data
else:
if data:
print(f'pickling {key_name}')
with open(pickle_file, 'wb') as f:
f.write(pickle.dumps(data))
return data
else:
return None
else:
if data:
print(f'pickling {key_name}')
with open(pickle_file, 'wb') as f:
f.write(pickle.dumps(data))
return data
else:
return None
except:
return None

@ -47,12 +47,22 @@ class Wallet(object):
def height(self): def height(self):
return self.make_wallet_rpc('get_height', {}) return self.make_wallet_rpc('get_height', {})
def spend_key(self):
return self.make_wallet_rpc('query_key', {'key_type': 'spend_key'})['key']
def view_key(self):
return self.make_wallet_rpc('query_key', {'key_type': 'view_key'})['key']
def seed(self):
return self.make_wallet_rpc('query_key', {'key_type': 'mnemonic'})['key']
def accounts(self): def accounts(self):
_accounts = self.make_wallet_rpc('get_accounts') _accounts = self.make_wallet_rpc('get_accounts')
return [i['account_index'] for i in _accounts['subaddress_accounts']] return [i['account_index'] for i in _accounts['subaddress_accounts']]
def new_account(self, label=None): def new_account(self, label=None):
_account = self.make_wallet_rpc('create_account', {'label': label}) _account = self.make_wallet_rpc('create_account', {'label': label})
self.store()
return _account['account_index'] return _account['account_index']
def addresses(self, account, addr_indices=None): def addresses(self, account, addr_indices=None):
@ -67,33 +77,34 @@ class Wallet(object):
addresses[_addr['address_index']] = _addr['address'] addresses[_addr['address_index']] = _addr['address']
return addresses return addresses
def get_address(self, account, address_indices=[]): def get_address(self, account):
qdata = {'account_index': account, 'address_index': address_indices} qdata = {'account_index': account}
_addresses = self.make_wallet_rpc('get_address', qdata) _addresses = self.make_wallet_rpc('get_address', qdata)
return _addresses if 'address' in _addresses:
return _addresses['address']
else:
return None
def new_address(self, account, label=None): def new_address(self, account, label=None):
data = {'account_index': account, 'label': label} data = {'account_index': account, 'label': label}
_address = self.make_wallet_rpc('create_address', data) _address = self.make_wallet_rpc('create_address', data)
self.store()
return (_address['address_index'], _address['address']) return (_address['address_index'], _address['address'])
def transfers(self, account_index=0, address_indices=[], _in=True, _out=True): def transfers(self, account, address_indices=[]):
data = { data = {
'account_index': account_index, 'account_index': account,
'subaddr_indices': address_indices, 'subaddr_indices': address_indices,
'in': _in, 'in': True,
'out': _out 'out': True
} }
_transfers = self.make_wallet_rpc('get_transfers', data) _transfers = self.make_wallet_rpc('get_transfers', data)
return _transfers return _transfers
def balances(self, address_indices=[]): def balances(self, account):
data = { data = {'account_index': account}
'account_index': config.WALLET_ACCOUNT,
'address_indices': address_indices
}
_balance = self.make_wallet_rpc('get_balance', data) _balance = self.make_wallet_rpc('get_balance', data)
return (_balance['balance'], _balance['unlocked_balance']) return (from_atomic(_balance['balance']), from_atomic(_balance['unlocked_balance']))
def transfer(self, dest_address, amount, priority, account): def transfer(self, dest_address, amount, priority, account):
data = { data = {
@ -111,15 +122,26 @@ class Wallet(object):
self.store() self.store()
return transfer return transfer
def incoming_transfers(self, transfer_type='all'): def sweep_all(self, account, dest_address):
data = {
'address': dest_address,
'account_index': account,
}
sweep = self.make_wallet_rpc('sweep_all', data)
self.store()
return sweep
def incoming_transfers(self, account, transfer_type='all', verbose=True):
data = { data = {
'transfer_type': transfer_type, 'transfer_type': transfer_type,
'account_index': config.WALLET_ACCOUNT 'account_index': account,
'verbose': verbose
} }
transfers = self.make_wallet_rpc('incoming_transfers', data) transfers = self.make_wallet_rpc('incoming_transfers', data)
return transfers return transfers
def to_atomic(amount): def to_atomic(amount):
if not isinstance(amount, (Decimal, float) + six.integer_types): if not isinstance(amount, (Decimal, float) + six.integer_types):
raise ValueError("Amount '{}' doesn't have numeric type. Only Decimal, int, long and " raise ValueError("Amount '{}' doesn't have numeric type. Only Decimal, int, long and "

@ -1,7 +0,0 @@
#!/bin/bash
rsync -avzP australia:/opt/suchwow/data/uploads/ data/uploads/
rsync -avzP australia:/opt/suchwow/data/sqlite.db data/sqlite.db
rsync -avzP australia:/opt/suchwow/data/migrate_data.pkl data/migrate_data.pkl
.venv/bin/python3 import.py
Loading…
Cancel
Save