diff --git a/requirements.txt b/requirements.txt index 6194b69..2848a12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ asyncio_extras kombu redis aioredis -Flask-Babel \ No newline at end of file +Flask-Babel +patreon \ No newline at end of file diff --git a/webapp/alembic/versions/16b4fdbbe155_added_patreon_table.py b/webapp/alembic/versions/16b4fdbbe155_added_patreon_table.py new file mode 100644 index 0000000..db8d896 --- /dev/null +++ b/webapp/alembic/versions/16b4fdbbe155_added_patreon_table.py @@ -0,0 +1,33 @@ +"""Added patreon table + +Revision ID: 16b4fdbbe155 +Revises: 7d6484faaccd +Create Date: 2017-11-21 03:16:42.629612 + +""" + +# revision identifiers, used by Alembic. +revision = '16b4fdbbe155' +down_revision = '7d6484faaccd' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('patreon', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.String(length=255), nullable=False), + sa.Column('total_synced', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('patreon') + # ### end Alembic commands ### diff --git a/webapp/config.example.py b/webapp/config.example.py index dcd9a04..57e5f5c 100644 --- a/webapp/config.example.py +++ b/webapp/config.example.py @@ -12,6 +12,10 @@ config = { # V2 reCAPTCHA from https://www.google.com/recaptcha/admin 'recaptcha-site-key': "reCAPTCHA v2 Site Key", 'recaptcha-secret-key': "reCAPTCHA v2 Secret Key", + + # Patreon + 'patreon-client-id': "Patreon client id", + 'patreon-client-secret': "Patreon client secret", 'app-location': "/var/www/Titan/webapp/", 'app-secret': "Type something random here, go wild.", diff --git a/webapp/titanembeds/blueprints/user/user.py b/webapp/titanembeds/blueprints/user/user.py index fb99c9f..48c9da1 100644 --- a/webapp/titanembeds/blueprints/user/user.py +++ b/webapp/titanembeds/blueprints/user/user.py @@ -3,12 +3,13 @@ from flask import current_app as app from flask_socketio import emit from config import config from titanembeds.decorators import discord_users_only -from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, Cosmetics, UserCSS, set_titan_token, get_titan_token +from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, Cosmetics, UserCSS, Patreon, 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 import json +import patreon user = Blueprint("user", __name__) @@ -472,4 +473,91 @@ def donate_patch(): entry.guest_icon = True db.session.add(entry) db.session.commit() - return ('', 204) \ No newline at end of file + return ('', 204) + +@user.route("/patreon") +@discord_users_only() +def patreon_landing(): + return render_template("patreon.html.j2", pclient_id=config["patreon-client-id"], state="initial") + +@user.route("/patreon/callback") +@discord_users_only() +def patreon_callback(): + patreon_oauth_client = patreon.OAuth(config["patreon-client-id"], config["patreon-client-secret"]) + tokens = patreon_oauth_client.get_tokens(request.args.get("code"), url_for("user.patreon_callback", _external=True)) + if "error" in tokens: + if "patreon" in session: + del session["patreon"] + return redirect(url_for("user.patreon_landing")) + session["patreon"] = tokens + return redirect(url_for("user.patreon_sync_get")) + +def format_patreon_user(user): + pledges = [] + for pledge in user.relationship('pledges'): + pledges.append({ + "id": pledge.id(), + "attributes": pledge.attributes(), + }) + usrobj = { + "id": user.id(), + "attributes": user.attributes(), + "pledges": pledges, + "titan": { + "eligible_tokens": 0, + "total_cents_synced": 0, + "total_cents_pledged": 0, + }, + } + if usrobj["pledges"]: + usrobj["titan"]["total_cents_pledged"] = usrobj["pledges"][0]["total_historical_amount_cents"] + dbpatreon = db.session.query(Patreon).filter(Patreon.user_id == user.id()).first() + if dbpatreon: + usrobj["titan"]["total_cents_synced"] = dbpatreon.total_synced + usrobj["titan"]["eligible_tokens"] = usrobj["titan"]["total_cents_pledged"] - usrobj["titan"]["total_cents_synced"] + return usrobj + +@user.route("/patreon/sync", methods=["GET"]) +@discord_users_only() +def patreon_sync_get(): + if "patreon" not in session: + return redirect(url_for("user.patreon_landing")) + api_client = patreon.API(session["patreon"]["access_token"]) + user_response = api_client.fetch_user(None, { + 'pledge': ["amount_cents", "total_historical_amount_cents", "declined_since", "created_at", "pledge_cap_cents", "patron_pays_fees", "outstanding_payment_amount_cents"] + }) + user = user_response.data() + if not (user): + del session["patreon"] + return redirect(url_for("user.patreon_landing")) + return render_template("patreon.html.j2", state="prepare", user=format_patreon_user(user)) + +@user.route("/patreon/sync", methods=["POST"]) +@discord_users_only() +def patreon_sync_post(): + if "patreon" not in session: + abort(401) + api_client = patreon.API(session["patreon"]["access_token"]) + user_response = api_client.fetch_user(None, { + 'pledge': ["amount_cents", "total_historical_amount_cents", "declined_since", "created_at", "pledge_cap_cents", "patron_pays_fees", "outstanding_payment_amount_cents"] + }) + user = user_response.data() + if not (user): + abort(403) + usr = format_patreon_user(user) + if usr["titan"]["eligible_tokens"] <= 0: + return ('', 402) + dbpatreon = db.session.query(Patreon).filter(Patreon.user_id == usr["id"]).first() + if not dbpatreon: + dbpatreon = Patreon(usr["id"]) + dbpatreon.total_synced = usr["titan"]["total_cents_pledged"] + db.session.add(dbpatreon) + db.session.commit() + set_titan_token(session["user_id"], usr["titan"]["eligible_tokens"], "PATREON {} [{}]".format(usr["attributes"]["full_name"], usr["id"])) + session["tokens"] = get_titan_token(session["user_id"]) + return ('', 204) + +@user.route("/patreon/thanks") +@discord_users_only() +def patreon_thanks(): + return render_template("patreon.html.j2", state="thanks") \ No newline at end of file diff --git a/webapp/titanembeds/database/__init__.py b/webapp/titanembeds/database/__init__.py index e0e39ba..01880af 100644 --- a/webapp/titanembeds/database/__init__.py +++ b/webapp/titanembeds/database/__init__.py @@ -14,6 +14,7 @@ from .user_css import UserCSS from .administrators import Administrators, get_administrators_list from .titan_tokens import TitanTokens, get_titan_token from .token_transactions import TokenTransactions +from .patreon import Patreon def set_titan_token(user_id, amt_change, action): token_count = get_titan_token(user_id) diff --git a/webapp/titanembeds/database/patreon.py b/webapp/titanembeds/database/patreon.py new file mode 100644 index 0000000..64b61ab --- /dev/null +++ b/webapp/titanembeds/database/patreon.py @@ -0,0 +1,14 @@ +from titanembeds.database import db + +class Patreon(db.Model): + __tablename__ = "patreon" + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.String(255), nullable=False) # User ID from patreon + total_synced = db.Column(db.Integer, nullable=False) # Total cents synced on our end + + def __init__(self, user_id, total_synced=0): + self.user_id = user_id + self.total_synced = total_synced + + def __repr__(self): + return ''.format(self.id, self.user_id, self.total_synced) \ No newline at end of file diff --git a/webapp/titanembeds/static/js/patreon.js b/webapp/titanembeds/static/js/patreon.js new file mode 100644 index 0000000..ea1663f --- /dev/null +++ b/webapp/titanembeds/static/js/patreon.js @@ -0,0 +1,20 @@ +/* global $, Materialize */ +(function () { + function post() { + var funct = $.ajax({ + dataType: "json", + method: "POST", + }); + return funct.promise(); + } + + $("#syncbtn").click(function () { + var formPost = post(); + formPost.done(function (data) { + window.location.href = "thanks"; + }); + formPost.fail(function (data) { + Materialize.toast('Failed to sync Patreon....', 10000); + }); + }); +})(); \ No newline at end of file diff --git a/webapp/titanembeds/templates/donate.html.j2 b/webapp/titanembeds/templates/donate.html.j2 index 8097318..8765165 100644 --- a/webapp/titanembeds/templates/donate.html.j2 +++ b/webapp/titanembeds/templates/donate.html.j2 @@ -6,12 +6,7 @@

Contributing to the Titan project has never been so easy! Donate to support our project development and hosting.

- -
+

The Name-Your-Price Tool

Donate to receive Titan Tokens™ (to be spent on donator features below) and a supporter role on our support server.

@@ -22,6 +17,16 @@
+
+
+
+

Patreon

+

Would you like to donate monthly instead of one-time? How about using a different payment method other than PayPal? Patreon, is the answer!

+ Visit Patreon
+ Sync Pledges +
+
+

diff --git a/webapp/titanembeds/templates/patreon.html.j2 b/webapp/titanembeds/templates/patreon.html.j2 new file mode 100644 index 0000000..c3f7a87 --- /dev/null +++ b/webapp/titanembeds/templates/patreon.html.j2 @@ -0,0 +1,87 @@ +{% extends 'site_layout.html.j2' %} +{% set title="Patreon Portal" %} + +{% block content %} +

Ready to sync your Patreon with Titan Embeds?

+

Keeping track of Titan Tokens from your patreon pledges has never been so easy!

+
+
+ {% if state %} + + {% if state == "initial" %} +
+ +
Once you are ready to sync your Patreon pledges with Titan, hit the button to begin!
+
+ Authenticate w/ Patreon! +
+
+ {% endif %} + + {% if state == "prepare" %} +
+ +
Just making sure this is correct...
+

If any of these information is incorrect, you may be using an incorrect account for either services before syncing.

+
+
+
+

Discord

+ +

{{ session["username"] }}#{{ session["discriminator"] }}

+
+
+

Patreon

+ +

{{ user["attributes"]["full_name"] }}

+

{{ user["attributes"]["email"] }}

+
+
+

Notices:

+
    + {% if user["attributes"]["discord_id"] is none %} +
  • You have not linked a Discord account with your Patreon. Proceed with caution as the account you're trying to sync may be different.
  • + {% elif user["attributes"]["discord_id"] != session["user_id"] %} +
  • Patreon has reported that you've linked a different Discord account to their services. Proceed with caution as the account you're trying to sync may be different.
  • + {% endif %} +
  • Titan Embeds will only sync pledges that have been successfully paid. If you have pledged more than what it currently shows here, please wait till the payment goes through at the beginning of next month.
  • +
+
+
+
+

{{ user["titan"]["eligible_tokens"] }} tokens avaliable for syncing.
Out of {{ "$%.2f"|format(user["titan"]["total_cents_pledged"]/100) }} dollar total pledged, {{ "$%.2f"|format(user["titan"]["total_cents_synced"]/100) }} has been claimed from the Patreon account already.

+ Sync +

*If there are any discrepancies, please contact us!

+
+
+ {% endif %} + + {% if state == "thanks" %} +
+ +
Thanks for syncing your Patreon!
+

You now have {{ session["tokens"] }} Titan Tokens!

+
+ Return to Store +
+
+ {% endif %} + + {% endif %} +
+
+{% endblock %} +{% block additional_head_elements %} + +{% endblock %} +{% block script %} + +{% endblock %} \ No newline at end of file