Implement Patreon token syncer

This commit is contained in:
Jeremy Zhang 2017-11-22 02:41:43 +00:00
parent 2e71200843
commit 6d2d4dd19c
9 changed files with 262 additions and 9 deletions

View File

@ -10,4 +10,5 @@ asyncio_extras
kombu kombu
redis redis
aioredis aioredis
Flask-Babel Flask-Babel
patreon

View File

@ -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 ###

View File

@ -12,6 +12,10 @@ config = {
# V2 reCAPTCHA from https://www.google.com/recaptcha/admin # V2 reCAPTCHA from https://www.google.com/recaptcha/admin
'recaptcha-site-key': "reCAPTCHA v2 Site Key", 'recaptcha-site-key': "reCAPTCHA v2 Site Key",
'recaptcha-secret-key': "reCAPTCHA v2 Secret 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-location': "/var/www/Titan/webapp/",
'app-secret': "Type something random here, go wild.", 'app-secret': "Type something random here, go wild.",

View File

@ -3,12 +3,13 @@ from flask import current_app as app
from flask_socketio import emit from flask_socketio import emit
from config import config from config import config
from titanembeds.decorators import discord_users_only 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 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 time
import datetime import datetime
import paypalrestsdk import paypalrestsdk
import json import json
import patreon
user = Blueprint("user", __name__) user = Blueprint("user", __name__)
@ -472,4 +473,91 @@ def donate_patch():
entry.guest_icon = True entry.guest_icon = True
db.session.add(entry) db.session.add(entry)
db.session.commit() db.session.commit()
return ('', 204) 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")

View File

@ -14,6 +14,7 @@ from .user_css import UserCSS
from .administrators import Administrators, get_administrators_list from .administrators import Administrators, get_administrators_list
from .titan_tokens import TitanTokens, get_titan_token from .titan_tokens import TitanTokens, get_titan_token
from .token_transactions import TokenTransactions from .token_transactions import TokenTransactions
from .patreon import Patreon
def set_titan_token(user_id, amt_change, action): def set_titan_token(user_id, amt_change, action):
token_count = get_titan_token(user_id) token_count = get_titan_token(user_id)

View File

@ -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 '<Patreon {0} {1} {2}>'.format(self.id, self.user_id, self.total_synced)

View File

@ -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);
});
});
})();

View File

@ -6,12 +6,7 @@
<p class="flow-text">Contributing to the Titan project has never been so easy! Donate to support our project development and hosting.</p> <p class="flow-text">Contributing to the Titan project has never been so easy! Donate to support our project development and hosting.</p>
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12 m8">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<a id="patreonbtn" class="waves-effect waves-light btn btn-large center_content" href="https://www.patreon.com/TitanEmbeds" target="_blank">Donate monthly on our Patreon!</a>
</div>
</div>
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text"> <div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>The Name-Your-Price Tool</h4> <h4>The Name-Your-Price Tool</h4>
<p class="flow-text">Donate to receive <strong>Titan Tokens&trade;</strong> (to be spent on donator features below) and a <strong>supporter role</strong> on our support server.</p> <p class="flow-text">Donate to receive <strong>Titan Tokens&trade;</strong> (to be spent on donator features below) and a <strong>supporter role</strong> on our support server.</p>
@ -22,6 +17,16 @@
<a class="waves-effect waves-light btn" id="donate-btn">Donate</a> <a class="waves-effect waves-light btn" id="donate-btn">Donate</a>
</div> </div>
</div> </div>
<div class="col s12 m4">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>Patreon</h4>
<p>Would you like to donate monthly instead of one-time? How about using a different payment method other than PayPal? Patreon, is the answer!</p>
<a id="patreonbtn" class="waves-effect waves-light btn btn-large center_content" href="https://www.patreon.com/TitanEmbeds" target="_blank">Visit Patreon</a> <br>
<a id="patreonsyncbtn" class="waves-effect waves-light btn btn-large center_content" href="{{ url_for("user.patreon_landing") }}">Sync Pledges</a>
</div>
</div>
</div>
</div> </div>
<hr> <hr>
<div class="row"> <div class="row">

View File

@ -0,0 +1,87 @@
{% extends 'site_layout.html.j2' %}
{% set title="Patreon Portal" %}
{% block content %}
<h1>Ready to sync your Patreon with Titan Embeds?</h1>
<p class="flow-text">Keeping track of Titan Tokens from your patreon pledges has never been so easy!</p>
<div class="row">
<div class="col s12">
{% if state %}
{% if state == "initial" %}
<div class="card-panel indigo lighten-5 z-depth-3 hoverable">
<span class="black-text center-align">
<h5>Once you are ready to sync your Patreon pledges with Titan, hit the button to begin!</h5>
<br>
<a href="{{ "https://www.patreon.com/oauth2/authorize?response_type=code&client_id={}&redirect_uri={}".format(pclient_id, url_for("user.patreon_callback", _external=True))|e }}" class="waves-effect waves-light btn btn-large center_content">Authenticate w/ Patreon!</a>
</span>
</div>
{% endif %}
{% if state == "prepare" %}
<div class="card-panel indigo lighten-5 z-depth-3 hoverable">
<span class="black-text center-align">
<h5>Just making sure this is correct...</h5>
<p>If any of these information is incorrect, you may be using an incorrect account for either services before syncing.</p>
<br>
<div class="row">
<div class="col s12 m6">
<h4>Discord</h4>
<img src="{{ session["avatar"] }}" class="circle syncavatar">
<p class="flow-text">{{ session["username"] }}#{{ session["discriminator"] }}</p>
</div>
<div class="col s12 m6">
<h4>Patreon</h4>
<img src="{{ user["attributes"]["thumb_url"] }}" class="circle syncavatar">
<p class="flow-text">{{ user["attributes"]["full_name"] }}</p>
<p class="flow-text">{{ user["attributes"]["email"] }}</p>
</div>
<div class="col s12">
<p class="flow-text">Notices:</p>
<ul class="browser-default">
{% if user["attributes"]["discord_id"] is none %}
<li class="warning">You have not linked a Discord account with your Patreon. Proceed with caution as the account you're trying to sync may be different.</li>
{% elif user["attributes"]["discord_id"] != session["user_id"] %}
<li class="warning">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.</li>
{% endif %}
<li>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.</li>
</ul>
</div>
</div>
<br>
<p class="flow-text"><strong>{{ user["titan"]["eligible_tokens"] }} tokens avaliable for syncing.</strong><br>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.</p>
<a id="syncbtn" class="waves-effect waves-light btn btn-large center_content" {% if user["titan"]["eligible_tokens"] <= 0 %}disabled{% endif %}>Sync</a>
<p>*If there are any discrepancies, please <a href="https://discord.io/titan" target="_blank">contact us</a>!</p>
</span>
</div>
{% endif %}
{% if state == "thanks" %}
<div class="card-panel indigo lighten-5 z-depth-3 hoverable">
<span class="black-text center-align">
<h5>Thanks for syncing your Patreon!</h5>
<p>You now have {{ session["tokens"] }} Titan Tokens!</p>
<br>
<a href="{{ url_for("user.donate_get") }}" class="waves-effect waves-light btn btn-large center_content">Return to Store</a>
</span>
</div>
{% endif %}
{% endif %}
</div>
</div>
{% endblock %}
{% block additional_head_elements %}
<style>
.syncavatar {
max-width: 100px;
}
li.warning {
color: red;
}
</style>
{% endblock %}
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='js/patreon.js') }}"></script>
{% endblock %}