diff --git a/README.md b/README.md index 4a2fa0a..da8666a 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,14 @@ Post your best neroChans. Get tipped. No middle-men. p2p tips. No passwords. Wallet signing authentication. + + +## Dev Stuff + +Stagenet txes for testing/validation: + +| recipient | tx_id | tx_key | +| --- | --- | --- | +| 78TanhCTvw4V8HkY3vD49A5EiyeGCzCHQUm59sByukTcffZPf3QHoK8PDg8WpMUc6VGwqxTu65HvwCUfB2jZutb6NKpjArk | 077b8654dd95fdfbd6d97808e2a9ad37cf767fb2f9da4cb0e1e6427c8587f6ee | be64bd151bd01cb4f8572a3c9731d0dff726079213e9f7017957799edc46630b | +| 78TanhCTvw4V8HkY3vD49A5EiyeGCzCHQUm59sByukTcffZPf3QHoK8PDg8WpMUc6VGwqxTu65HvwCUfB2jZutb6NKpjArk | 46fd71389ed54f195d359b84897bb89b37bb8da0bbe72ef22b552c8786346805 | b683e96770c76a1a23253873ad8a2ebb1832e14d90a05fb49a9e6e22e73d630a | +| | | | \ No newline at end of file diff --git a/env-example b/env-example index 5f44586..b58d175 100644 --- a/env-example +++ b/env-example @@ -8,5 +8,4 @@ XMR_WALLET_NETWORK=stagenet SITE_NAME=nerochan SECRET_KEY=xxxxxxxxxxxxxxxxxxx -STATS_TOKEN=xxxxxxxxxxxxxxxxxxxx SERVER_NAME=127.0.0.1:5000 diff --git a/example_feather.png b/example_feather.png new file mode 100644 index 0000000..7516cfd Binary files /dev/null and b/example_feather.png differ diff --git a/nerochan/cli.py b/nerochan/cli.py index bdd8e60..537241d 100644 --- a/nerochan/cli.py +++ b/nerochan/cli.py @@ -1,10 +1,13 @@ import click import lorem +from datetime import datetime, timedelta, timezone + from os import path, makedirs from urllib.request import urlopen -from nerochan.models import User, Artwork +from nerochan.helpers import make_wallet_rpc, daemon +from nerochan.models import User, Artwork, Transaction def cli(app): @@ -14,6 +17,30 @@ def cli(app): from nerochan.models import db model = peewee.Model.__subclasses__() db.create_tables(model) + + @app.cli.command('verify_tips') + def verify_tips(): + txes = Transaction.select().where(Transaction.verified == False) + for tx in txes: + data = { + 'txid': tx.tx_id, + 'tx_key': tx.tx_key, + 'address': tx.to_address + } + try: + res = make_wallet_rpc('check_tx_key', data) + if res['in_pool'] is False: + txdata = daemon.transactions([tx.tx_id])[0] + d = txdata.timestamp.astimezone(timezone.utc) + tx.atomic_xmr = res['received'] + tx.tx_date = d + tx.verified = True + tx.save() + click.echo(f'[+] Found valid tip {tx.tx_id}') + except Exception as e: + # delete if it fails for over 8 hours + if tx.create_date <= datetime.utcnow() - timedelta(hours=8): + pass @app.cli.command('generate_data') def generate_data(): @@ -74,7 +101,7 @@ def cli(app): click.echo(f'[+] Downloaded {art}') if not Artwork.select().where(Artwork.image == bn).first(): artwork = Artwork( - creator=_user, + user=_user, image=bn, approved=True, title=lorem.sentence(), diff --git a/nerochan/config.py b/nerochan/config.py index 58bc451..6a498d1 100644 --- a/nerochan/config.py +++ b/nerochan/config.py @@ -17,6 +17,8 @@ XMR_WALLET_RPC_USER = getenv('XMR_WALLET_RPC_USER') XMR_WALLET_RPC_PASS = getenv('XMR_WALLET_RPC_PASS') XMR_WALLET_RPC_PORT = getenv('XMR_WALLET_RPC_PORT', 8000) XMR_WALLET_NETWORK = getenv('XMR_WALLET_NETWORK') +XMR_DAEMON_URI = getenv('XMR_DAEMON_URI') +XMR_DAEMON_HOST, XMR_DAEMON_PORT = XMR_DAEMON_URI.split('://')[1].split(':') REDIS_HOST = getenv('REDIS_HOST', 'localhost') REDIS_PORT = getenv('REDIS_PORT', 6379) DATA_PATH = getenv('DATA_PATH', f'{getcwd()}/data') diff --git a/nerochan/filters.py b/nerochan/filters.py index ec03734..d675af3 100644 --- a/nerochan/filters.py +++ b/nerochan/filters.py @@ -1,8 +1,10 @@ from datetime import datetime import arrow -from monero import numbers -from flask import Blueprint, current_app +from monero.numbers import from_atomic +from flask import Blueprint + +from nerochan import config bp = Blueprint('filters', 'filters') @@ -18,8 +20,15 @@ def from_ts(v): @bp.app_template_filter('xmr_block_explorer') def xmr_block_explorer(v): - return f'https://www.exploremonero.com/transaction/{v}' - -@bp.app_template_filter('from_atomic') -def from_atomic(amt): - return numbers.from_atomic(amt) + if config.XMR_WALLET_NETWORK == 'stagenet': + return f'https://stagenet.xmrchain.net/tx/{v}' + else: + return f'https://www.exploremonero.com/transaction/{v}' + +@bp.app_template_filter('atomic') +def atomic(amt): + return float(from_atomic(amt)) + +@bp.app_template_filter('shorten') +def shorten(s): + return s[:4] + '...' + s[-5:] \ No newline at end of file diff --git a/nerochan/forms.py b/nerochan/forms.py index e8f3984..eef07bd 100644 --- a/nerochan/forms.py +++ b/nerochan/forms.py @@ -21,27 +21,19 @@ def is_valid_xmr_address(form, field): class UserRegistration(FlaskForm): - handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'form-control', 'type': 'text'}) - wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'form-control', 'type': 'text'}) + handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'u-full-width', 'type': 'text'}) + wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'u-full-width', 'type': 'text'}) class UserLogin(FlaskForm): - handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'form-control', 'type': 'text'}) + handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'u-full-width', 'type': 'text'}) class UserChallenge(FlaskForm): - signature = StringField('Signature:', validators=[DataRequired()], render_kw={'placeholder': 'signed data', 'class': 'form-control', 'type': 'text'}) + signature = StringField('Signature:', validators=[DataRequired()], render_kw={'placeholder': 'signed data', 'class': 'u-full-width', 'type': 'text'}) -class ConfirmPlatformSubscription(FlaskForm): - tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={'placeholder': 'TX ID', 'class': 'form-control', 'type': 'text'}) - tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={'placeholder': 'TX Key', 'class': 'form-control', 'type': 'text'}) +class ConfirmTip(FlaskForm): + tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={'placeholder': 'TX ID', 'class': 'u-full-width', 'type': 'text'}) + tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={'placeholder': 'TX Key', 'class': 'u-full-width', 'type': 'text'}) - -class ConfirmCreatorSubscription(ConfirmPlatformSubscription): - wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'form-control', 'type': 'text'}) - - -class CreateSubscription(FlaskForm): - price_xmr = FloatField('Price (XMR):', validators=[DataRequired()], render_kw={'placeholder': '.5', 'class': 'form-control', 'type': 'text'}) - number_days = FloatField('Length (Days)', validators=[DataRequired()], render_kw={'placeholder': '30', 'class': 'form-control', 'type': 'text'}) diff --git a/nerochan/helpers.py b/nerochan/helpers.py index f90b4bf..8ebfc1b 100644 --- a/nerochan/helpers.py +++ b/nerochan/helpers.py @@ -1,19 +1,34 @@ -import peewee as pw -import playhouse.postgres_ext as pwpg from monero.wallet import Wallet +from monero.daemon import Daemon from nerochan import config +daemon = Daemon( + host=config.XMR_DAEMON_HOST, + port=config.XMR_DAEMON_PORT, + timeout=3 +) + +wallet = Wallet( + port=config.XMR_WALLET_RPC_PORT, + user=config.XMR_WALLET_RPC_USER, + password=config.XMR_WALLET_RPC_PASS, + timeout=3 +) + def make_wallet_rpc(method, data={}): try: - w = Wallet( - port=config.XMR_WALLET_RPC_PORT, - user=config.XMR_WALLET_RPC_USER, - password=config.XMR_WALLET_RPC_PASS, - timeout=3 - ) + w = wallet res = w._backend.raw_request(method, data) return res except Exception as e: - raise e \ No newline at end of file + raise e + +def make_daemon_rpc(method, data={}): + try: + d = daemon + res = d._backend.raw_request(method, data) + return res + except Exception as e: + raise e diff --git a/nerochan/models.py b/nerochan/models.py index e5f2926..89c2eaa 100644 --- a/nerochan/models.py +++ b/nerochan/models.py @@ -58,7 +58,7 @@ class User(pw.Model): class Profile(pw.Model): """ Profile model is for users to provide metadata about - themselves; Creators for their fans or even just the general public. + themselves; artists for their fans or even just the general public. Links to social media, contact info, portfolio sites, etc should go in here. """ @@ -77,10 +77,10 @@ class Profile(pw.Model): class Artwork(pw.Model): """ - Artwork model is any uploaded content from a creator. + Artwork model is any uploaded content from a user. """ id = pw.AutoField() - creator = pw.ForeignKeyField(User) + user = pw.ForeignKeyField(User) image = pw.CharField() upload_date = pw.DateTimeField(default=datetime.utcnow) last_edit_date = pw.DateTimeField(default=datetime.utcnow) @@ -135,9 +135,13 @@ class Transaction(pw.Model): """ id = pw.AutoField() tx_id = pw.CharField(unique=True) - atomic_xmr = pw.BigIntegerField() + tx_key = pw.CharField(unique=True) + atomic_xmr = pw.BigIntegerField(null=True) to_address = pw.CharField() - content = pw.ForeignKeyField(Artwork) + artwork = pw.ForeignKeyField(Artwork) + verified = pw.BooleanField(default=False) + create_date = pw.DateTimeField(default=datetime.utcnow) + tx_date = pw.DateTimeField(null=True) class Meta: database = db diff --git a/nerochan/routes/artwork.py b/nerochan/routes/artwork.py index a29f8de..25f2012 100644 --- a/nerochan/routes/artwork.py +++ b/nerochan/routes/artwork.py @@ -1,7 +1,9 @@ from flask import Blueprint, render_template, flash, redirect, url_for from flask_login import login_required -from nerochan.models import Artwork, User +from nerochan.helpers import make_wallet_rpc +from nerochan.forms import ConfirmTip +from nerochan.models import Artwork, Transaction bp = Blueprint('artwork', 'artwork', url_prefix='/artwork') @@ -10,13 +12,43 @@ bp = Blueprint('artwork', 'artwork', url_prefix='/artwork') def list(): return 'show all artwork' -@bp.route('/') +@bp.route('/', methods=['GET', 'POST']) def show(id): + form = ConfirmTip() artwork = Artwork.get_or_none(id) if not artwork: flash('That artwork does not exist.', 'warning') return redirect(url_for('main.index')) - return render_template('artwork/show.html', artwork=artwork) + if form.validate_on_submit(): + # Create a tx object to verify later + try: + tx = Transaction( + tx_id=form.tx_id.data, + tx_key=form.tx_key.data, + to_address=artwork.user.wallet_address, + artwork=artwork.id + ) + tx.save() + except Exception as e: + pass + return redirect(url_for('artwork.show', id=artwork.id)) + txes_pending = Transaction.select().where( + Transaction.artwork == artwork, + Transaction.verified == False + ).count() + txes = Transaction.select().where( + Transaction.artwork == artwork, + Transaction.verified == True + ).order_by(Transaction.tx_date.desc()) + total = sum([i.atomic_xmr for i in txes]) + return render_template( + 'artwork/show.html', + artwork=artwork, + txes_pending=txes_pending, + txes=txes, + total=total, + form=form + ) @bp.route('/new', methods=['GET', 'POST']) @login_required diff --git a/nerochan/static/css/main.css b/nerochan/static/css/main.css index b544830..785b3b6 100644 --- a/nerochan/static/css/main.css +++ b/nerochan/static/css/main.css @@ -8,6 +8,12 @@ a, a:visited { color: white; } +.container-wide { + max-width: 90%; + margin-left: 3em; + margin-right: 3em; +} + .artworkLink img { border-radius: 2px; margin: 1em; @@ -93,7 +99,7 @@ input[type="text"] { margin-right: 35px; text-decoration: none; line-height: 6.5rem; - color: #222; + color: #fff; transition: all .2s ease; } diff --git a/nerochan/templates/artwork/show.html b/nerochan/templates/artwork/show.html index eee1564..d3b272e 100644 --- a/nerochan/templates/artwork/show.html +++ b/nerochan/templates/artwork/show.html @@ -7,7 +7,7 @@

{{ artwork.title }}

- Posted by {{ artwork.creator.handle }} - {{ artwork.upload_date | humanize }} + posted by {{ artwork.user.handle }} - {{ artwork.upload_date | humanize }}

{{ artwork.description }}

@@ -15,46 +15,52 @@
Send a Tip
-

{{ artwork.creator.wallet_address }}

-
+

{{ artwork.user.wallet_address }}

+ + {{ form.csrf_token }}
- - + {{ form.tx_id.label }} + {{ form.tx_id }}
- - + {{ form.tx_key.label }} + {{ form.tx_key }}
- - - - - - - - - - - - - - - - - - - - -
TXIDXMRDate
e599...5429.053 days ago
681a...e264.2528 days ago
+ {% if txes_pending %} +
{{ txes_pending }} tips pending
+ {% endif %} + {% if txes %} + + + + + + + + + + {% for tx in txes %} + + + + + + {% endfor %} + +
TXIDXMRDate
{{ tx.tx_id | shorten }}{{ tx.atomic_xmr | atomic }}{{ tx.tx_date | humanize }}
+

Total Received: {{ total | atomic }} XMR

+ {% else %} +

No tips confirmed yet...

+ {% endif %}
diff --git a/nerochan/templates/index.html b/nerochan/templates/index.html index 051ba3e..7d500a4 100644 --- a/nerochan/templates/index.html +++ b/nerochan/templates/index.html @@ -4,7 +4,8 @@
-

Latest Artworks

+

artworks

+ {% if feed['artwork'] %} {%- for _artwork in feed['artwork'] | batch(4) %} {%- for artwork in _artwork %} @@ -13,12 +14,19 @@ {%- endfor %} {%- endfor %}

...view all

+ {% else %} +

There's nothing here yet...

+ {% endif %}
-

Latest Artists

+

artists

+ {% if feed['users'] %} {% for user in feed['users'] %}

{{ user.handle }}

{% endfor %} + {% else %} +

There's nothing here yet...

+ {% endif %}