Implement server webhook icons as cosmetics & fixes

This commit is contained in:
Jeremy Zhang 2017-09-13 05:55:51 +00:00
parent d9edf7e8ef
commit c23b71d050
14 changed files with 186 additions and 18 deletions

View File

@ -8,6 +8,7 @@ class Guilds(Base):
unauth_users = db.Column(db.Boolean()) # If allowed unauth users unauth_users = db.Column(db.Boolean()) # If allowed unauth users
visitor_view = db.Column(db.Boolean()) # If users are automatically "signed in" and can view chat visitor_view = db.Column(db.Boolean()) # If users are automatically "signed in" and can view chat
webhook_messages = db.Column(db.Boolean()) # Use webhooks to send messages instead of the bot webhook_messages = db.Column(db.Boolean()) # Use webhooks to send messages instead of the bot
webhook_icon = db.Column(db.String(255), default=None) # Webhook icon url, None if unset
chat_links = db.Column(db.Boolean()) # If users can post links chat_links = db.Column(db.Boolean()) # If users can post links
bracket_links = db.Column(db.Boolean()) # If appending brackets to links to prevent embed bracket_links = db.Column(db.Boolean()) # If appending brackets to links to prevent embed
mentions_limit = db.Column(db.Integer) # If there is a limit on the number of mentions in a msg mentions_limit = db.Column(db.Integer) # If there is a limit on the number of mentions in a msg
@ -25,6 +26,7 @@ class Guilds(Base):
self.unauth_users = True # defaults to true self.unauth_users = True # defaults to true
self.visitor_view = False self.visitor_view = False
self.webhook_messages = False self.webhook_messages = False
self.webhook_icon = None
self.chat_links = True self.chat_links = True
self.bracket_links = True self.bracket_links = True
self.mentions_limit = -1 # -1 = unlimited mentions self.mentions_limit = -1 # -1 = unlimited mentions

View File

@ -0,0 +1,30 @@
"""Added webhook icon column to guilds and cosmetics
Revision ID: 39815dfbcccb
Revises: d1b89c41bf16
Create Date: 2017-09-13 04:31:35.532918
"""
# revision identifiers, used by Alembic.
revision = '39815dfbcccb'
down_revision = 'd1b89c41bf16'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('cosmetics', sa.Column('webhook_icon', sa.Boolean(), server_default=sa.text('false'), nullable=False))
op.add_column('guilds', sa.Column('webhook_icon', sa.String(length=255), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('guilds', 'webhook_icon')
op.drop_column('cosmetics', 'webhook_icon')
# ### end Alembic commands ###

View File

@ -36,7 +36,8 @@ def cosmetics_post():
if not user_id: if not user_id:
abort(400) abort(400)
css = request.form.get("css", None) css = request.form.get("css", None)
css_limit = request.form.get("css_limit", None) css_limit = int(request.form.get("css_limit", 0))
webhook_icon = request.form.get("webhook_icon", None)
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first() entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first()
if entry: if entry:
abort(409) abort(409)
@ -46,6 +47,9 @@ def cosmetics_post():
user.css = css user.css = css
if css_limit is not None: if css_limit is not None:
user.css_limit = css_limit user.css_limit = css_limit
if webhook_icon is not None:
webhook_icon = webhook_icon.lower() == "true"
user.webhook_icon = webhook_icon
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
return ('', 204) return ('', 204)
@ -71,6 +75,7 @@ def cosmetics_patch():
abort(400) abort(400)
css = request.form.get("css", None) css = request.form.get("css", None)
css_limit = request.form.get("css_limit", None) css_limit = request.form.get("css_limit", None)
webhook_icon = request.form.get("webhook_icon", None)
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first() entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first()
if not entry: if not entry:
abort(409) abort(409)
@ -79,6 +84,9 @@ def cosmetics_patch():
entry.css = css entry.css = css
if css_limit is not None: if css_limit is not None:
entry.css_limit = css_limit entry.css_limit = css_limit
if webhook_icon:
webhook_icon = webhook_icon.lower() == "true"
entry.webhook_icon = webhook_icon
db.session.commit() db.session.commit()
return ('', 204) return ('', 204)
def prepare_guild_members_list(members, bans): def prepare_guild_members_list(members, bans):
@ -129,6 +137,7 @@ def administrate_guild(guild_id):
abort(500) abort(500)
return return
session["redirect"] = None session["redirect"] = None
cosmetics = db.session.query(Cosmetics).filter(Cosmetics.user_id == session['user_id']).first()
permissions=[] permissions=[]
permissions.append("Manage Embed Settings") permissions.append("Manage Embed Settings")
permissions.append("Ban Members") permissions.append("Ban Members")
@ -146,9 +155,10 @@ def administrate_guild(guild_id):
"bracket_links": db_guild.bracket_links, "bracket_links": db_guild.bracket_links,
"mentions_limit": db_guild.mentions_limit, "mentions_limit": db_guild.mentions_limit,
"icon": db_guild.icon, "icon": db_guild.icon,
"discordio": db_guild.discordio if db_guild.discordio != None else "" "discordio": db_guild.discordio if db_guild.discordio != None else "",
"webhook_icon": db_guild.webhook_icon if db_guild.webhook_icon != None else "",
} }
return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions) return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics)
@admin.route("/administrate_guild/<guild_id>", methods=["POST"]) @admin.route("/administrate_guild/<guild_id>", methods=["POST"])
@is_admin @is_admin
@ -161,9 +171,14 @@ def update_administrate_guild(guild_id):
db_guild.bracket_links = request.form.get("bracket_links", db_guild.bracket_links) in ["true", True] db_guild.bracket_links = request.form.get("bracket_links", db_guild.bracket_links) in ["true", True]
db_guild.mentions_limit = request.form.get("mentions_limit", db_guild.mentions_limit) db_guild.mentions_limit = request.form.get("mentions_limit", db_guild.mentions_limit)
discordio = request.form.get("discordio", db_guild.discordio) discordio = request.form.get("discordio", db_guild.discordio)
if discordio and discordio.strip() == "": if discordio != None and discordio.strip() == "":
discordio = None discordio = None
db_guild.discordio = discordio db_guild.discordio = discordio
webhook_icon = request.form.get("webhook_icon", db_guild.webhook_icon)
if webhook_icon != None and webhook_icon.strip() == "":
webhook_icon = None
db_guild.webhook_icon = webhook_icon
print(webhook_icon)
db.session.commit() db.session.commit()
return jsonify( return jsonify(
id=db_guild.id, id=db_guild.id,
@ -175,6 +190,7 @@ def update_administrate_guild(guild_id):
bracket_links=db_guild.bracket_links, bracket_links=db_guild.bracket_links,
mentions_limit=db_guild.mentions_limit, mentions_limit=db_guild.mentions_limit,
discordio=db_guild.discordio, discordio=db_guild.discordio,
webhook_icon=db_guild.webhook_icon,
) )
@admin.route("/guilds") @admin.route("/guilds")

View File

@ -246,6 +246,11 @@ def post():
if (session['unauthenticated']): if (session['unauthenticated']):
username = session["username"] + "#" + str(session["user_id"]) username = session["username"] + "#" + str(session["user_id"])
avatar = url_for('static', filename='img/titanembeds_round.png', _external=True) avatar = url_for('static', filename='img/titanembeds_round.png', _external=True)
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
if dbguild:
icon = dbguild.webhook_icon
if icon:
avatar = icon
else: else:
username = session["username"] username = session["username"]
if dbUser: if dbUser:

View File

@ -196,6 +196,7 @@ def administrate_guild(guild_id):
permissions.append("Ban Members") permissions.append("Ban Members")
if check_user_permission(guild_id, 1): if check_user_permission(guild_id, 1):
permissions.append("Kick Members") permissions.append("Kick Members")
cosmetics = db.session.query(Cosmetics).filter(Cosmetics.user_id == session['user_id']).first()
all_members = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id).order_by(UnauthenticatedUsers.last_timestamp).all() all_members = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id).order_by(UnauthenticatedUsers.last_timestamp).all()
all_bans = db.session.query(UnauthenticatedBans).filter(UnauthenticatedBans.guild_id == guild_id).all() all_bans = db.session.query(UnauthenticatedBans).filter(UnauthenticatedBans.guild_id == guild_id).all()
users = prepare_guild_members_list(all_members, all_bans) users = prepare_guild_members_list(all_members, all_bans)
@ -209,9 +210,10 @@ def administrate_guild(guild_id):
"bracket_links": db_guild.bracket_links, "bracket_links": db_guild.bracket_links,
"mentions_limit": db_guild.mentions_limit, "mentions_limit": db_guild.mentions_limit,
"icon": db_guild.icon, "icon": db_guild.icon,
"discordio": db_guild.discordio if db_guild.discordio != None else "" "discordio": db_guild.discordio if db_guild.discordio != None else "",
"webhook_icon": db_guild.webhook_icon if db_guild.webhook_icon != None else "",
} }
return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions) return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics)
@user.route("/administrate_guild/<guild_id>", methods=["POST"]) @user.route("/administrate_guild/<guild_id>", methods=["POST"])
@discord_users_only() @discord_users_only()
@ -229,9 +231,15 @@ def update_administrate_guild(guild_id):
db_guild.mentions_limit = request.form.get("mentions_limit", db_guild.mentions_limit) db_guild.mentions_limit = request.form.get("mentions_limit", db_guild.mentions_limit)
discordio = request.form.get("discordio", db_guild.discordio) discordio = request.form.get("discordio", db_guild.discordio)
if discordio and discordio.strip() == "": if discordio != None and discordio.strip() == "":
discordio = None discordio = None
db_guild.discordio = discordio db_guild.discordio = discordio
webhook_icon = request.form.get("webhook_icon", db_guild.webhook_icon)
if webhook_icon != None and webhook_icon.strip() == "":
webhook_icon = None
db_guild.webhook_icon = webhook_icon
db.session.commit() db.session.commit()
return jsonify( return jsonify(
id=db_guild.id, id=db_guild.id,
@ -243,6 +251,7 @@ def update_administrate_guild(guild_id):
bracket_links=db_guild.bracket_links, bracket_links=db_guild.bracket_links,
mentions_limit=db_guild.mentions_limit, mentions_limit=db_guild.mentions_limit,
discordio=db_guild.discordio, discordio=db_guild.discordio,
webhook_icon=webhook_icon,
) )
@user.route("/add-bot/<guild_id>") @user.route("/add-bot/<guild_id>")
@ -358,7 +367,8 @@ def revoke_unauthenticated_user():
@user.route('/donate', methods=["GET"]) @user.route('/donate', methods=["GET"])
@discord_users_only() @discord_users_only()
def donate_get(): def donate_get():
return render_template('donate.html.j2') cosmetics = db.session.query(Cosmetics).filter(Cosmetics.user_id == session["user_id"]).first()
return render_template('donate.html.j2', cosmetics=cosmetics)
def get_paypal_api(): def get_paypal_api():
return paypalrestsdk.Api({ return paypalrestsdk.Api({
@ -430,20 +440,28 @@ def donate_patch():
if amount <= 0: if amount <= 0:
abort(400) abort(400)
subtract_amt = 0 subtract_amt = 0
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == session["user_id"]).first()
if item == "custom_css_slots": if item == "custom_css_slots":
subtract_amt = 100 subtract_amt = 100
if item == "webhook_icon":
subtract_amt = 300
if entry is not None and entry.webhook_icon:
abort(400)
amt_change = -1 * subtract_amt * amount amt_change = -1 * subtract_amt * amount
subtract = set_titan_token(session["user_id"], amt_change, "BUY " + item + " x" + str(amount)) subtract = set_titan_token(session["user_id"], amt_change, "BUY " + item + " x" + str(amount))
if not subtract: if not subtract:
return ('', 402) return ('', 402)
session["tokens"] += amt_change session["tokens"] += amt_change
if item == "custom_css_slots": if item == "custom_css_slots":
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == session["user_id"]).first()
if not entry: if not entry:
entry = Cosmetics(session["user_id"]) entry = Cosmetics(session["user_id"])
entry.css = True entry.css = True
entry.css_limit = 0 entry.css_limit = 0
entry.css_limit += amount entry.css_limit += amount
db.session.add(entry) if item == "webhook_icon":
db.session.commit() if not entry:
entry = Cosmetics(session["user_id"])
entry.webhook_icon = True
db.session.add(entry)
db.session.commit()
return ('', 204) return ('', 204)

View File

@ -6,6 +6,7 @@ class Cosmetics(db.Model):
user_id = db.Column(db.String(255), nullable=False) # Discord user id of user of cosmetics user_id = db.Column(db.String(255), nullable=False) # Discord user id of user of cosmetics
css = db.Column(db.Boolean(), nullable=False) # If they can create/edit custom CSS css = db.Column(db.Boolean(), nullable=False) # If they can create/edit custom CSS
css_limit = db.Column(db.Integer, nullable=False, server_default="0") # Custom CSS Limit css_limit = db.Column(db.Integer, nullable=False, server_default="0") # Custom CSS Limit
webhook_icon = db.Column(db.Boolean(), nullable=False, server_default=db.false()) # If they can set the webhook icon for all guilds
def __init__(self, user_id, **kwargs): def __init__(self, user_id, **kwargs):
self.user_id = user_id self.user_id = user_id
@ -19,3 +20,8 @@ class Cosmetics(db.Model):
self.css_limit = kwargs["css_limit"] self.css_limit = kwargs["css_limit"]
else: else:
self.css_limit = 0 self.css_limit = 0
if "webhook_icon" in kwargs:
self.webhook_icon = kwargs["webhook_icon"]
else:
self.webhook_icon = False

View File

@ -8,6 +8,7 @@ class Guilds(db.Model):
unauth_users = db.Column(db.Boolean(), nullable=False, default=1) # If allowed unauth users unauth_users = db.Column(db.Boolean(), nullable=False, default=1) # If allowed unauth users
visitor_view = db.Column(db.Boolean(), nullable=False, default=0) # If users are automatically "signed in" and can view chat visitor_view = db.Column(db.Boolean(), nullable=False, default=0) # If users are automatically "signed in" and can view chat
webhook_messages = db.Column(db.Boolean(), nullable=False, default=0) # Use webhooks to send messages instead of the bot webhook_messages = db.Column(db.Boolean(), nullable=False, default=0) # Use webhooks to send messages instead of the bot
webhook_icon = db.Column(db.String(255), default=None) # Webhook icon url, None if unset
chat_links = db.Column(db.Boolean(), nullable=False, default=1) # If users can post links chat_links = db.Column(db.Boolean(), nullable=False, default=1) # If users can post links
bracket_links = db.Column(db.Boolean(), nullable=False, default=1) # If appending brackets to links to prevent embed bracket_links = db.Column(db.Boolean(), nullable=False, default=1) # If appending brackets to links to prevent embed
mentions_limit = db.Column(db.Integer, nullable=False, default=11) # If there is a limit on the number of mentions in a msg mentions_limit = db.Column(db.Integer, nullable=False, default=11) # If there is a limit on the number of mentions in a msg
@ -25,6 +26,7 @@ class Guilds(db.Model):
self.unauth_users = True # defaults to true self.unauth_users = True # defaults to true
self.visitor_view = False self.visitor_view = False
self.webhook_messages = False self.webhook_messages = False
self.webhook_icon = None
self.chat_links = True self.chat_links = True
self.bracket_links = True self.bracket_links = True
self.mentions_limit = -1 # -1 = unlimited mentions self.mentions_limit = -1 # -1 = unlimited mentions

View File

@ -1,10 +1,13 @@
/* global $, Materialize, location */ /* global $, Materialize, location */
function postForm(user_id, css) { function postForm(user_id, css, css_limit, webhook_icon) {
if (css_limit == "") {
css_limit = 0;
}
var funct = $.ajax({ var funct = $.ajax({
dataType: "json", dataType: "json",
method: "POST", method: "POST",
data: {"user_id": user_id, "css": css} data: {"user_id": user_id, "css": css, "css_limit": css_limit, "webhook_icon": webhook_icon}
}); });
return funct.promise(); return funct.promise();
} }
@ -36,7 +39,9 @@ $(function() {
return; return;
} }
var css_checked = $("#new_css_switch").is(':checked'); var css_checked = $("#new_css_switch").is(':checked');
var formPost = postForm(user_id, css_checked); var css_limit = $("#new_css_limit").val();
var webhook_icon_checked = $("#new_webhook_icon_switch").is(':checked');
var formPost = postForm(user_id, css_checked, css_limit, webhook_icon_checked);
formPost.done(function (data) { formPost.done(function (data) {
location.reload(); location.reload();
}); });
@ -95,3 +100,18 @@ function update_css_limit(user_id, value) {
} }
}); });
} }
function update_webhook_icon_switch(user_id, element) {
var webhook_checked = $(element).is(':checked');
var formPatch = patchForm(user_id, {"webhook_icon": webhook_checked});
formPatch.done(function (data) {
Materialize.toast('Webhook Icon updated!', 10000);
});
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 webhook icon toggle!', 10000);
}
});
}

View File

@ -65,6 +65,17 @@ $("#discordio").keyup(function(event){
} }
}); });
$("#webhook_icon").keyup(function(event){
if(event.keyCode == 13){
var pathname = window.location.pathname;
var value = $("#webhook_icon").val()
var payload = {"webhook_icon": value}
$.post(pathname, payload, function(data) {
Materialize.toast('Updated Webhook Icon setting!', 2000)
});
}
});
function initiate_ban(guild_id, user_id) { function initiate_ban(guild_id, user_id) {
var reason = prompt("Please enter your reason for ban"); var reason = prompt("Please enter your reason for ban");
var payload = { var payload = {

View File

@ -46,4 +46,21 @@
} }
}); });
}); });
$("#buy-webhook-guest-user-avatar-btn").click(function () {
var formPatch = patchForm("webhook_icon", 1);
formPatch.done(function (data) {
alert("Successfully bought webhook guest user avatar perk!");
location.reload();
});
formPatch.fail(function (data) {
if (data.status == 400) {
Materialize.toast('Item already purchased!', 10000);
} else if (data.status == 402) {
Materialize.toast('Insufficient token funds!', 10000);
} else {
Materialize.toast('Purchasing webhook guest user avatar perk failed!', 10000);
}
});
});
})(); })();

View File

@ -14,6 +14,7 @@
<th>User ID</th> <th>User ID</th>
<th>CSS</th> <th>CSS</th>
<th>CSS Limit</th> <th>CSS Limit</th>
<th>Webhook Icon</th>
<th>Submit</th> <th>Submit</th>
</tr> </tr>
</thead> </thead>
@ -24,6 +25,16 @@
<input id="new_user_id" placeholder="User ID"> <input id="new_user_id" placeholder="User ID">
</div> </div>
</td> </td>
<td>
<div class="switch">
<label>
Off
<input type="checkbox" id="new_css_switch">
<span class="lever"></span>
On
</label>
</div>
</td>
<td> <td>
<div class="input-field inline"> <div class="input-field inline">
<input id="new_css_limit" placeholder="CSS Limit" type="number"> <input id="new_css_limit" placeholder="CSS Limit" type="number">
@ -33,7 +44,7 @@
<div class="switch"> <div class="switch">
<label> <label>
Off Off
<input type="checkbox" id="new_css_switch"> <input type="checkbox" id="new_webhook_icon_switch">
<span class="lever"></span> <span class="lever"></span>
On On
</label> </label>
@ -57,6 +68,7 @@
<th>User ID</th> <th>User ID</th>
<th>CSS</th> <th>CSS</th>
<th>CSS Limit</th> <th>CSS Limit</th>
<th>Webhook Icon</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -79,6 +91,16 @@
<input placeholder="CSS Limit" type="number" value="{{ cosmetic.css_limit }}" onchange="update_css_limit('{{ cosmetic.user_id }}', $(this).val())"> <input placeholder="CSS Limit" type="number" value="{{ cosmetic.css_limit }}" onchange="update_css_limit('{{ cosmetic.user_id }}', $(this).val())">
</div> </div>
</td> </td>
<td>
<div class="switch">
<label>
Off
<input type="checkbox" {% if cosmetic.webhook_icon %}checked{% endif %} onchange="update_webhook_icon_switch('{{ cosmetic.user_id }}', this)">
<span class="lever"></span>
On
</label>
</div>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -112,6 +112,16 @@
<p>Because we are a partner with <a href="https://discord.io" target="_blank">Discord.io</a>, we enable you to enter your custom discord.io link and replace the discord.gg link on the embed!</p> <p>Because we are a partner with <a href="https://discord.io" target="_blank">Discord.io</a>, we enable you to enter your custom discord.io link and replace the discord.gg link on the embed!</p>
<p>(Leave blank if none - enter to submit)</p> <p>(Leave blank if none - enter to submit)</p>
<input id="discordio" value="{{ guild['discordio'] }}"> <input id="discordio" value="{{ guild['discordio'] }}">
<br>
<p class="flow-text">Webhook Guest User Avatar URL</p>
<p>If enabled Webhook Messages, you may set it so that the guest users may have a custom avatar instead of the Titan logo. Source must be the permanent URL location to the image file (try imgur and get direct link to image).</p>
<p>(Leave blank if none - enter to submit)</p>
{% if not cosmetics.webhook_icon %}
<p class="red lighten-4"><strong>Your user account does not have access to change webhook avatar url. Please visit the Titan Tokens shop to activate this cosmetic item.</strong></p>
{% endif %}
<input id="webhook_icon" value="{{ guild['webhook_icon'] }}" {% if not cosmetics.webhook_icon %}disabled{% endif %}>
</div> </div>
</div> </div>
</div> </div>

View File

@ -32,13 +32,14 @@
<hr> <hr>
{% if cosmetics is none %} {% if (not cosmetics) or (cosmetics and not cosmetics.css) %}
<div class="row"> <div class="row">
<div class="col s12"> <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>Cosmetics!</h4> <h4>User Defined CSS!</h4>
<p class="flow-text">Would you like to have <strong>cosmetics</strong> such as <em>custom CSS</em> for your embed?</p> <p class="flow-text">Would you like to have <strong>user defined css</strong> (e.g. custom css) for your embed?</p>
<a class="waves-effect waves-light btn" href="https://discord.io/Titan">Talk to us!</a> <a class="waves-effect waves-light btn" href="https://discord.io/Titan">Talk to us!</a>
<p>The first slot is on the house. Increasing the slot count requires the user to spend Titan Tokens. However, we do offer unlimited predefined CSS Color Variables at no charge.</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -40,6 +40,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>Webhook Guest User Avatar <strong>[300 tokens]</strong></h4>
<p class="flow-text">Tired of the bland Titan logo for your guests avatars in your servers? Enables your account to be able to set webhook icons for guests for all servers you can manage.</p>
<p>(Note: Webhook Messages has to be enabled & Titan needs server permissions to create webhooks)</p>
<a class="waves-effect waves-light btn" id="buy-webhook-guest-user-avatar-btn" {% if cosmetics.webhook_icon %}disabled{% endif %}>{% if cosmetics.webhook_icon %}Already Purchased{% else %}Buy{% endif %}</a>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}
{% block script %} {% block script %}