diff --git a/webapp/alembic/versions/2a2f32ac91d6_added_titan_tokens.py b/webapp/alembic/versions/2a2f32ac91d6_added_titan_tokens.py new file mode 100644 index 0000000..57b8232 --- /dev/null +++ b/webapp/alembic/versions/2a2f32ac91d6_added_titan_tokens.py @@ -0,0 +1,160 @@ +"""Added Titan Tokens + +Revision ID: 2a2f32ac91d6 +Revises: 6fe130518448 +Create Date: 2017-08-13 22:44:15.996936 + +""" + +# revision identifiers, used by Alembic. +revision = '2a2f32ac91d6' +down_revision = '6fe130518448' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('titan_tokens', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.String(length=255), nullable=False), + sa.Column('tokens', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('token_transactions', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.String(length=255), nullable=False), + sa.Column('timestamp', sa.TIMESTAMP(), nullable=False), + sa.Column('action', sa.String(length=255), nullable=False), + sa.Column('net_tokens', sa.Integer(), nullable=False), + sa.Column('start_tokens', sa.Integer(), nullable=False), + sa.Column('end_tokens', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.alter_column(u'cosmetics', 'css', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False) + op.alter_column(u'guild_members', 'active', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'guild_members', 'banned', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False, + existing_server_default=sa.text(u"'0'")) + op.alter_column(u'guilds', 'bracket_links', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'guilds', 'channels', + existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + type_=sa.Text(length=4294967295), + existing_nullable=False) + op.alter_column(u'guilds', 'chat_links', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'guilds', 'emojis', + existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + type_=sa.Text(length=4294967295), + existing_nullable=False) + op.alter_column(u'guilds', 'roles', + existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + type_=sa.Text(length=4294967295), + existing_nullable=False) + op.alter_column(u'guilds', 'unauth_users', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'guilds', 'visitor_view', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False) + op.alter_column(u'guilds', 'webhooks', + existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + type_=sa.Text(length=4294967295), + existing_nullable=False) + op.alter_column(u'unauthenticated_users', 'revoked', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Boolean(), + existing_nullable=False, + existing_server_default=sa.text(u"'0'")) + op.alter_column(u'user_css', 'css', + existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + type_=sa.Text(length=4294967295), + existing_nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column(u'user_css', 'css', + existing_type=sa.Text(length=4294967295), + type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + existing_nullable=True) + op.alter_column(u'unauthenticated_users', 'revoked', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False, + existing_server_default=sa.text(u"'0'")) + op.alter_column(u'guilds', 'webhooks', + existing_type=sa.Text(length=4294967295), + type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + existing_nullable=False) + op.alter_column(u'guilds', 'visitor_view', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False) + op.alter_column(u'guilds', 'unauth_users', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'guilds', 'roles', + existing_type=sa.Text(length=4294967295), + type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + existing_nullable=False) + op.alter_column(u'guilds', 'emojis', + existing_type=sa.Text(length=4294967295), + type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + existing_nullable=False) + op.alter_column(u'guilds', 'chat_links', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'guilds', 'channels', + existing_type=sa.Text(length=4294967295), + type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'), + existing_nullable=False) + op.alter_column(u'guilds', 'bracket_links', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'guild_members', 'banned', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False, + existing_server_default=sa.text(u"'0'")) + op.alter_column(u'guild_members', 'active', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False, + existing_server_default=sa.text(u"'1'")) + op.alter_column(u'cosmetics', 'css', + existing_type=sa.Boolean(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False) + op.drop_table('token_transactions') + op.drop_table('titan_tokens') + # ### end Alembic commands ### diff --git a/webapp/config.example.py b/webapp/config.example.py index 786e614..4c8cab7 100644 --- a/webapp/config.example.py +++ b/webapp/config.example.py @@ -4,6 +4,10 @@ config = { 'client-id': "Your app client id", 'client-secret': "Your discord client secret", 'bot-token': "Discord bot token", + + # Rest API in https://developer.paypal.com/developer/applications + 'paypal-client-id': "Paypal client id", + 'paypal-client-secret': "Paypal client secret", 'app-location': "/var/www/Titan/webapp/", 'app-secret': "Type something random here, go wild.", diff --git a/webapp/titanembeds/blueprints/admin/admin.py b/webapp/titanembeds/blueprints/admin/admin.py index 7622742..22712c9 100644 --- a/webapp/titanembeds/blueprints/admin/admin.py +++ b/webapp/titanembeds/blueprints/admin/admin.py @@ -1,6 +1,6 @@ from flask import Blueprint, url_for, redirect, session, render_template, abort, request, jsonify from functools import wraps -from titanembeds.database import db, get_administrators_list, Cosmetics, Guilds, UnauthenticatedUsers, UnauthenticatedBans +from titanembeds.database import db, get_administrators_list, Cosmetics, Guilds, UnauthenticatedUsers, UnauthenticatedBans, TitanTokens, TokenTransactions, get_titan_token, set_titan_token from titanembeds.oauth import generate_guild_icon_url import datetime @@ -172,3 +172,52 @@ def update_administrate_guild(guild_id): def guilds(): guilds = db.session.query(Guilds).all() return render_template("admin_guilds.html.j2", servers=guilds, icon_generate=generate_guild_icon_url) + +@admin.route("/tokens", methods=["GET"]) +@is_admin +def manage_titan_tokens(): + tokeners = db.session.query(TitanTokens).all() + donators = [] + for usr in tokeners: + row = { + "user_id": usr.user_id, + "tokens": usr.tokens, + "transactions": [] + } + transact = db.session.query(TokenTransactions).filter(TokenTransactions.user_id == usr.user_id).all() + for tr in transact: + row["transactions"].append({ + "id": tr.id, + "user_id": tr.user_id, + "timestamp": tr.timestamp, + "action": tr.action, + "net_tokens": tr.net_tokens, + "start_tokens": tr.start_tokens, + "end_tokens": tr.end_tokens + }) + donators.append(row) + return render_template("admin_token_transactions.html.j2", donators=donators) + +@admin.route("/tokens", methods=["POST"]) +@is_admin +def post_titan_tokens(): + user_id = request.form.get("user_id", None) + amount = request.form.get("amount", None, type=int) + if not user_id or not amount: + abort(400) + if get_titan_token(user_id) != -1: + abort(409) + set_titan_token(user_id, amount, "NEW VIA ADMIN") + return ('', 204) + +@admin.route("/tokens", methods=["PATCH"]) +@is_admin +def patch_titan_tokens(): + user_id = request.form.get("user_id", None) + amount = request.form.get("amount", None, type=int) + if not user_id or not amount: + abort(400) + if get_titan_token(user_id) == -1: + abort(409) + set_titan_token(user_id, amount, "MODIFY VIA ADMIN") + return ('', 204) \ No newline at end of file diff --git a/webapp/titanembeds/blueprints/user/user.py b/webapp/titanembeds/blueprints/user/user.py index 5851a6e..eac354b 100644 --- a/webapp/titanembeds/blueprints/user/user.py +++ b/webapp/titanembeds/blueprints/user/user.py @@ -1,10 +1,12 @@ from flask import Blueprint, request, redirect, jsonify, abort, session, url_for, render_template +from flask import current_app as app from config import config from titanembeds.decorators import discord_users_only -from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, Cosmetics, UserCSS +from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, Cosmetics, UserCSS, set_titan_token, get_titan_token from titanembeds.oauth import authorize_url, token_url, make_authenticated_session, get_current_authenticated_user, get_user_managed_servers, check_user_can_administrate_guild, check_user_permission, generate_avatar_url, generate_guild_icon_url, generate_bot_invite_url import time import datetime +import paypalrestsdk user = Blueprint("user", __name__) @@ -70,7 +72,10 @@ def dashboard(): css_list = None if cosmetics and cosmetics.css: css_list = db.session.query(UserCSS).filter(UserCSS.user_id == session['user_id']).all() - return render_template("dashboard.html.j2", servers=guilds, icon_generate=generate_guild_icon_url, cosmetics=cosmetics, css_list=css_list) + tokens = get_titan_token(session["user_id"]) + if tokens == -1: + tokens = 0 + return render_template("dashboard.html.j2", servers=guilds, icon_generate=generate_guild_icon_url, cosmetics=cosmetics, css_list=css_list, tokens=tokens) @user.route("/custom_css/new", methods=["GET"]) @discord_users_only() @@ -322,3 +327,69 @@ def revoke_unauthenticated_user(): abort(409) db_user.revokeUser() return ('', 204) + +@user.route('/donate', methods=["GET"]) +@discord_users_only() +def donate_get(): + return render_template('donate.html.j2') + +def get_paypal_api(): + return paypalrestsdk.Api({ + 'mode': 'sandbox' if app.config["DEBUG"] else 'live', + 'client_id': config["paypal-client-id"], + 'client_secret': config["paypal-client-secret"]}) + +@user.route('/donate', methods=['POST']) +@discord_users_only() +def donate_post(): + donation_amount = request.form.get('amount') + if not donation_amount: + abort(402) + + donation_amount = "{0:.2f}".format(float(donation_amount)) + payer = {"payment_method": "paypal"} + items = [{"name": "TitanEmbeds Donation", + "price": donation_amount, + "currency": "USD", + "quantity": "1"}] + amount = {"total": donation_amount, + "currency": "USD"} + description = "Donate and support TitanEmbeds development." + redirect_urls = {"return_url": url_for('user.donate_confirm', success="true", _external=True), + "cancel_url": url_for('index', _external=True)} + payment = paypalrestsdk.Payment({"intent": "sale", + "payer": payer, + "redirect_urls": redirect_urls, + "transactions": [{"item_list": {"items": + items}, + "amount": amount, + "description": + description}]}, api=get_paypal_api()) + if payment.create(): + for link in payment.links: + if link['method'] == "REDIRECT": + return redirect(link["href"]) + return redirect(url_for('index')) + +@user.route("/donate/confirm") +@discord_users_only() +def donate_confirm(): + if not request.args.get('success'): + return redirect(url_for('index')) + payment = paypalrestsdk.Payment.find(request.args.get('paymentId'), api=get_paypal_api()) + if payment.execute({"payer_id": request.args.get('PayerID')}): + trans_id = str(payment.transactions[0]["related_resources"][0]["sale"]["id"]) + amount = float(payment.transactions[0]["amount"]["total"]) + tokens = int(amount * 100) + action = "PAYPAL {}".format(trans_id) + set_titan_token(session["user_id"], tokens, action) + return redirect(url_for('user.donate_thanks', transaction=trans_id)) + else: + return redirect(url_for('index')) + +@user.route("/donate/thanks") +@discord_users_only() +def donate_thanks(): + tokens = get_titan_token(session["user_id"]) + transaction = request.args.get("transaction") + return render_template("donate_thanks.html.j2", tokens=tokens, transaction=transaction) \ No newline at end of file diff --git a/webapp/titanembeds/database/__init__.py b/webapp/titanembeds/database/__init__.py index 7a2d9b3..e1ea632 100644 --- a/webapp/titanembeds/database/__init__.py +++ b/webapp/titanembeds/database/__init__.py @@ -11,4 +11,25 @@ from keyvalue_properties import KeyValueProperties, set_keyvalproperty, get_keyv from messages import Messages, get_channel_messages from cosmetics import Cosmetics from user_css import UserCSS -from administrators import Administrators, get_administrators_list \ No newline at end of file +from administrators import Administrators, get_administrators_list +from titan_tokens import TitanTokens, get_titan_token +from token_transactions import TokenTransactions + +def set_titan_token(user_id, amt_change, action): + token_count = get_titan_token(user_id) + if token_count >= 0: + token_usr = db.session.query(TitanTokens).filter(TitanTokens.user_id == user_id).first() + else: + token_count = 0 + token_usr = TitanTokens(user_id, 0) + db.session.add(token_usr) + db.session.commit() + new_token_count = token_count + amt_change + if new_token_count < 0: + return False + transact = TokenTransactions(user_id, action, amt_change, token_count, new_token_count) + db.session.add(transact) + token_usr.tokens = new_token_count + db.session.add(token_usr) + db.session.commit() + return True \ No newline at end of file diff --git a/webapp/titanembeds/database/titan_tokens.py b/webapp/titanembeds/database/titan_tokens.py new file mode 100644 index 0000000..3ea2bff --- /dev/null +++ b/webapp/titanembeds/database/titan_tokens.py @@ -0,0 +1,18 @@ +from titanembeds.database import db + +class TitanTokens(db.Model): + __tablename__ = "titan_tokens" + id = db.Column(db.Integer, primary_key=True) # Auto increment id + user_id = db.Column(db.String(255), nullable=False) # Discord user id of user + tokens = db.Column(db.Integer, nullable=False, default=0) # Token amount + + def __init__(self, user_id, tokens): + self.user_id = user_id + self.tokens = tokens + +def get_titan_token(user_id): + q = db.session.query(TitanTokens).filter(TitanTokens.user_id == user_id).first() + if q: + return q.tokens + else: + return -1 \ No newline at end of file diff --git a/webapp/titanembeds/database/token_transactions.py b/webapp/titanembeds/database/token_transactions.py new file mode 100644 index 0000000..7f184f5 --- /dev/null +++ b/webapp/titanembeds/database/token_transactions.py @@ -0,0 +1,21 @@ +from titanembeds.database import db +import datetime +import time + +class TokenTransactions(db.Model): + __tablename__ = "token_transactions" + id = db.Column(db.Integer, primary_key=True) # Auto increment id + user_id = db.Column(db.String(255), nullable=False) # Discord user id of user + timestamp = db.Column(db.TIMESTAMP, nullable=False) # The timestamp of when the action took place + action = db.Column(db.String(255), nullable=False) # Very short description of the action + net_tokens = db.Column(db.Integer, nullable=False) # Net change of the token amount + start_tokens = db.Column(db.Integer, nullable=False) # Token amount before transaction + end_tokens = db.Column(db.Integer, nullable=False) # Tokens after transaction + + def __init__(self, user_id, action, net_tokens, start_tokens, end_tokens): + self.user_id = user_id + self.timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S') + self.action = action + self.net_tokens = net_tokens + self.start_tokens = start_tokens + self.end_tokens = end_tokens diff --git a/webapp/titanembeds/static/js/admin_token_transactions.js b/webapp/titanembeds/static/js/admin_token_transactions.js new file mode 100644 index 0000000..c44c46d --- /dev/null +++ b/webapp/titanembeds/static/js/admin_token_transactions.js @@ -0,0 +1,56 @@ +/* global $, Materialize, location */ + +function postForm(user_id, amount) { + var funct = $.ajax({ + dataType: "json", + method: "POST", + data: {"user_id": user_id, "amount": amount} + }); + return funct.promise(); +} + +function patchForm(user_id, amount) { + var funct = $.ajax({ + dataType: "json", + method: "PATCH", + data: {"user_id": user_id, "amount": amount} + }); + return funct.promise(); +} + +$(function() { + $("#new_submit").click(function () { + var user_id = $("#new_user_id").val(); + var user_token = $("#new_user_token").val(); + if (user_id.length < 1 || user_token.length < 1) { + Materialize.toast("The user ID or balance field can't be blank!", 2000); + return; + } + var formPost = postForm(user_id, user_token); + formPost.done(function (data) { + location.reload(); + }); + formPost.fail(function (data) { + if (data.status == 409) { + Materialize.toast('This user id already exists!', 10000); + } else { + Materialize.toast('Oh no! Something has failed submitting a new entry!', 10000); + } + }); + }); +}); + +function submit_modify_user(user_id) { + var amount = $("#input_"+user_id).val(); + var formPatch = patchForm(user_id, amount); + formPatch.done(function (data) { + location.reload(); + }); + formPatch.fail(function (data) { + if (data.status == 409) { + Materialize.toast('This user id does not exists!', 10000); + } else { + Materialize.toast('Oh no! Something has failed changing the css toggle!', 10000); + } + }); +} \ No newline at end of file diff --git a/webapp/titanembeds/static/js/donate.js b/webapp/titanembeds/static/js/donate.js new file mode 100644 index 0000000..4e170b7 --- /dev/null +++ b/webapp/titanembeds/static/js/donate.js @@ -0,0 +1,19 @@ +/* global $ */ +(function () { + $('#token-slider').on('input', function(){ + var slider_value = $("#token-slider").val(); + var multiplier = 100; + + $("#money-display").text(slider_value); + $("#token-display").text(slider_value * multiplier); + }); + + $("#donate-btn").click(function () { + var slider_value = $("#token-slider").val(); + var form = $('
'); + $(document.body).append(form); + form.submit(); + }); +})(); \ No newline at end of file diff --git a/webapp/titanembeds/templates/admin_index.html.j2 b/webapp/titanembeds/templates/admin_index.html.j2 index 2785249..55f76db 100644 --- a/webapp/titanembeds/templates/admin_index.html.j2 +++ b/webapp/titanembeds/templates/admin_index.html.j2 @@ -20,6 +20,13 @@ Manage +New Entry
+User ID | +Starting Balance | +Submit | +
---|---|---|
+
+
+
+ |
+
+
+
+
+ |
+ + Submit + | +
View Transactions and Modify User Tokens
+Modify Amount | +Submit | +
---|---|
+
+
+
+ (Place a subtract sign in the front to remove tokens. Otherwise, it will add the amount) + |
+ + Submit + | +
Trans # | +Timestamp | +Action | +Change | +Starting Bal | +Ending Bal | +
---|---|---|---|---|---|
{{ trans.id }} | +{{ trans.timestamp }} | +{{ trans.action }}. | +{{ trans.net_tokens }} | +{{ trans.start_tokens }} | +{{ trans.end_tokens }} | +
Select a server to configure Titan Embeds.
*List missing some servers? It's because you must have either Manage Server, Kick Members, or Ban Members permissions to modify embed settings.
@@ -61,4 +60,17 @@ {% endfor %}Would you like to support the Titan Embeds project?
+You currently have {{ tokens }} Titan Tokens.
+ Donate!! +Contributing to the Titan project has never been so easy! Donate to support our project development and hosting.
+ +Currently if you donate, we cannot give much back in return, yet. However, we do have some donatator features up our sleeves and will be implemented.
+For now, you will receive Titan Tokens™ (to be spent on donator features) and a supporter role on our support server.
++ +
+$5 for 500 tokens!
+ Donate +You now have {{ tokens }} tokens!
+Please visit our support server and contact a True Titan (Administrators Role) to claim your Supporter role, if you haven't done so already. Mention the transaction ID of {{ transaction }}.
+ Support Server +Have a nice day!
+Add Titan to your discord server to create your own personalized chat embed!
Start here! diff --git a/webapp/titanembeds/templates/patreon_banner.html.j2 b/webapp/titanembeds/templates/patreon_banner.html.j2 deleted file mode 100644 index 7935920..0000000 --- a/webapp/titanembeds/templates/patreon_banner.html.j2 +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file