From 30092fde01827c3d1ad470f4e8347c37693f38fe Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Fri, 9 Jun 2017 04:22:33 +0000 Subject: [PATCH 001/135] Implemented Visitor View -- kinda betaish still --- discordbot/titanembeds/database/guilds.py | 2 + ...2e_added_visitor_view_column_to_guilds_.py | 28 +++++ webapp/titanembeds/blueprints/api/api.py | 60 +++++++-- webapp/titanembeds/blueprints/embed/embed.py | 3 +- webapp/titanembeds/blueprints/user/user.py | 2 + webapp/titanembeds/database/guilds.py | 2 + webapp/titanembeds/static/css/embedstyle.css | 114 ++++++++++++------ .../static/js/administrate_guild.js | 9 ++ webapp/titanembeds/static/js/embed.js | 62 +++++++++- .../templates/administrate_guild.html.j2 | 13 ++ webapp/titanembeds/templates/embed.html.j2 | 6 +- webapp/titanembeds/utils.py | 4 + 12 files changed, 245 insertions(+), 60 deletions(-) create mode 100644 webapp/alembic/versions/b1124468bb2e_added_visitor_view_column_to_guilds_.py diff --git a/discordbot/titanembeds/database/guilds.py b/discordbot/titanembeds/database/guilds.py index a73ccb0..54dfe69 100644 --- a/discordbot/titanembeds/database/guilds.py +++ b/discordbot/titanembeds/database/guilds.py @@ -6,6 +6,7 @@ class Guilds(Base): guild_id = db.Column(db.String(255)) # Discord guild id name = db.Column(db.String(255)) # Name 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 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 mentions_limit = db.Column(db.Integer) # If there is a limit on the number of mentions in a msg @@ -20,6 +21,7 @@ class Guilds(Base): self.guild_id = guild_id self.name = name self.unauth_users = True # defaults to true + self.visitor_view = False self.chat_links = True self.bracket_links = True self.mentions_limit = -1 # -1 = unlimited mentions diff --git a/webapp/alembic/versions/b1124468bb2e_added_visitor_view_column_to_guilds_.py b/webapp/alembic/versions/b1124468bb2e_added_visitor_view_column_to_guilds_.py new file mode 100644 index 0000000..f0f4d69 --- /dev/null +++ b/webapp/alembic/versions/b1124468bb2e_added_visitor_view_column_to_guilds_.py @@ -0,0 +1,28 @@ +"""Added visitor view column to guilds table + +Revision ID: b1124468bb2e +Revises: 95ab6a63135d +Create Date: 2017-06-08 06:31:28.953304 + +""" + +# revision identifiers, used by Alembic. +revision = 'b1124468bb2e' +down_revision = '95ab6a63135d' +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('guilds', sa.Column('visitor_view', sa.Boolean(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('guilds', 'visitor_view') + # ### end Alembic commands ### diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 847a613..4ba5aba 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -1,6 +1,6 @@ from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties, GuildMembers, Messages, get_channel_messages, list_all_guild_members from titanembeds.decorators import valid_session_required, discord_users_only -from titanembeds.utils import check_guild_existance, guild_query_unauth_users_bool, get_client_ipaddr, discord_api, rate_limiter, channel_ratelimit_key, guild_ratelimit_key +from titanembeds.utils import check_guild_existance, guild_accepts_visitors, guild_query_unauth_users_bool, get_client_ipaddr, discord_api, rate_limiter, channel_ratelimit_key, guild_ratelimit_key from titanembeds.oauth import user_has_permission, generate_avatar_url, check_user_can_administrate_guild from flask import Blueprint, abort, jsonify, session, request from sqlalchemy import and_ @@ -162,8 +162,8 @@ def get_dbguild_channels(guild_id): q = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first() return json.loads(q.channels) -def get_guild_channels(guild_id): - if user_unauthenticated(): +def get_guild_channels(guild_id, force_everyone=False): + if user_unauthenticated() or force_everyone: member_roles = [guild_id] #equivilant to @everyone role else: member_roles = get_member_roles(guild_id, session['user_id']) @@ -177,7 +177,7 @@ def get_guild_channels(guild_id): for channel in guild_channels: if channel['type'] == "text": result = {"channel": channel, "read": False, "write": False, "mention_everyone": False} - if guild_owner == session['user_id']: + if guild_owner == session.get("user_id"): result["read"] = True result["write"] = True result["mention_everyone"] = True @@ -219,7 +219,7 @@ def get_guild_channels(guild_id): # member specific for overwrite in channel["permission_overwrites"]: - if overwrite["type"] == "member" and overwrite["id"] == session["user_id"]: + if overwrite["type"] == "member" and overwrite["id"] == session.get("user_id"): channel_perm = (channel_perm & ~overwrite['deny']) | overwrite['allow'] break @@ -240,8 +240,8 @@ def get_guild_channels(guild_id): result_channels.append(result) return sorted(result_channels, key=lambda k: k['channel']['position']) -def filter_guild_channel(guild_id, channel_id): - channels = get_guild_channels(guild_id) +def filter_guild_channel(guild_id, channel_id, force_everyone=False): + channels = get_guild_channels(guild_id, force_everyone) for chan in channels: if chan["channel"]["id"] == channel_id: return chan @@ -327,6 +327,25 @@ def fetch(): response.status_code = status_code return response +@api.route("/fetch_visitor", methods=["GET"]) +@rate_limiter.limit("2 per 2 second", key_func = channel_ratelimit_key) +def fetch_visitor(): + guild_id = request.args.get("guild_id") + channel_id = request.args.get('channel_id') + after_snowflake = request.args.get('after', None, type=int) + if not guild_accepts_visitors(guild_id): + abort(403) + messages = {} + chan = filter_guild_channel(guild_id, channel_id, True) + if not chan.get("read"): + status_code = 401 + else: + messages = get_channel_messages(channel_id, after_snowflake) + status_code = 200 + response = jsonify(messages=messages) + response.status_code = status_code + return response + @api.route("/post", methods=["POST"]) @valid_session_required(api=True) @rate_limiter.limit("1 per 10 second", key_func = channel_ratelimit_key) @@ -393,21 +412,36 @@ def create_unauthenticated_user(): response.status_code = 403 return response +def process_query_guild(guild_id, visitor=False): + widget = discord_api.get_widget(guild_id) + channels = get_guild_channels(guild_id, visitor) + discordmembers = get_online_discord_users(guild_id, widget) + embedmembers = get_online_embed_users(guild_id) + emojis = get_guild_emojis(guild_id) + if visitor: + for channel in channels: + channel["write"] = False + return jsonify(channels=channels, discordmembers=discordmembers, embedmembers=embedmembers, emojis=emojis, instant_invite=widget.get("instant_invite")) + @api.route("/query_guild", methods=["GET"]) @valid_session_required(api=True) def query_guild(): guild_id = request.args.get('guild_id') if check_guild_existance(guild_id): if check_user_in_guild(guild_id): - widget = discord_api.get_widget(guild_id) - channels = get_guild_channels(guild_id) - discordmembers = get_online_discord_users(guild_id, widget) - embedmembers = get_online_embed_users(guild_id) - emojis = get_guild_emojis(guild_id) - return jsonify(channels=channels, discordmembers=discordmembers, embedmembers=embedmembers, emojis=emojis, instant_invite=widget.get("instant_invite")) + return process_query_guild(guild_id) abort(403) abort(404) +@api.route("/query_guild_visitor", methods=["GET"]) +def query_guild_visitor(): + guild_id = request.args.get('guild_id') + if check_guild_existance(guild_id): + if not guild_accepts_visitors(guild_id): + abort(403) + return process_query_guild(guild_id, True) + abort(404) + @api.route("/create_authenticated_user", methods=["POST"]) @discord_users_only(api=True) def create_authenticated_user(): diff --git a/webapp/titanembeds/blueprints/embed/embed.py b/webapp/titanembeds/blueprints/embed/embed.py index 8d3f2dd..12560d2 100644 --- a/webapp/titanembeds/blueprints/embed/embed.py +++ b/webapp/titanembeds/blueprints/embed/embed.py @@ -1,5 +1,5 @@ from flask import Blueprint, render_template, abort, redirect, url_for, session, request -from titanembeds.utils import check_guild_existance, guild_query_unauth_users_bool +from titanembeds.utils import check_guild_existance, guild_query_unauth_users_bool, guild_accepts_visitors from titanembeds.oauth import generate_guild_icon_url, generate_avatar_url from titanembeds.database import db, Guilds, UserCSS from config import config @@ -42,6 +42,7 @@ def guild_embed(guild_id): guild_id=guild_id, guild=guild_dict, generate_guild_icon=generate_guild_icon_url, unauth_enabled=guild_query_unauth_users_bool(guild_id), + visitors_enabled=guild_accepts_visitors(guild_id), client_id=config['client-id'], css=get_custom_css() ) diff --git a/webapp/titanembeds/blueprints/user/user.py b/webapp/titanembeds/blueprints/user/user.py index 558a3bb..21ed408 100644 --- a/webapp/titanembeds/blueprints/user/user.py +++ b/webapp/titanembeds/blueprints/user/user.py @@ -175,6 +175,7 @@ def administrate_guild(guild_id): "id": db_guild.guild_id, "name": db_guild.name, "unauth_users": db_guild.unauth_users, + "visitor_view": db_guild.visitor_view, "chat_links": db_guild.chat_links, "bracket_links": db_guild.bracket_links, "mentions_limit": db_guild.mentions_limit, @@ -192,6 +193,7 @@ def update_administrate_guild(guild_id): if not db_guild: abort(400) db_guild.unauth_users = request.form.get("unauth_users", db_guild.unauth_users) in ["true", True] + db_guild.visitor_view = request.form.get("visitor_view", db_guild.visitor_view) in ["true", True] db_guild.chat_links = request.form.get("chat_links", db_guild.chat_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) diff --git a/webapp/titanembeds/database/guilds.py b/webapp/titanembeds/database/guilds.py index 934bf6a..db086e9 100644 --- a/webapp/titanembeds/database/guilds.py +++ b/webapp/titanembeds/database/guilds.py @@ -6,6 +6,7 @@ class Guilds(db.Model): guild_id = db.Column(db.String(255), nullable=False) # Discord guild id name = db.Column(db.String(255), nullable=False) # Name 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 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 mentions_limit = db.Column(db.Integer, nullable=False, default=11) # If there is a limit on the number of mentions in a msg @@ -20,6 +21,7 @@ class Guilds(db.Model): self.guild_id = guild_id self.name = name self.unauth_users = True # defaults to true + self.visitor_view = False self.chat_links = True self.bracket_links = True self.mentions_limit = -1 # -1 = unlimited mentions diff --git a/webapp/titanembeds/static/css/embedstyle.css b/webapp/titanembeds/static/css/embedstyle.css index 85d2382..21ede8a 100644 --- a/webapp/titanembeds/static/css/embedstyle.css +++ b/webapp/titanembeds/static/css/embedstyle.css @@ -233,6 +233,14 @@ a { #nameplate { cursor: pointer; } + +#visitor_mode_message { + margin-right: auto; + margin-left: auto; + display: block; + width: 305px; +} + @font-face { font-family: Whitney; font-style: light; @@ -261,53 +269,79 @@ a { src: url("../fonts/whitney_bold.woff") format("woff") } * { -font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif; + font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif; } #footercontainer { -border-radius:20px; -border: 1px solid rgb(99,99,99); -margin-left:-0px!important; -padding-left:-4px!important; + border-radius: 20px; + border: 1px solid rgb(99, 99, 99); + margin-left: -0px!important; + padding-left: -4px!important; } #nameplate { -background:transparent!important; -margin-left:10px + background: transparent!important; + margin-left: 10px } #chatcontent > p > span.chatusername, #curuser_discrim, #curuser_name { -display:block + display: block } #curuser_discrim { -font-size:50%; + font-size: 50%; } #curuser_discrim, #curuser_name { -margin-top:-2px + margin-top: -2px } #currentuserimage { -margin-top:4px; -margin-right:4px + margin-top: 4px; + margin-right: 4px +} +#chatcontent > p { + display: table; +} +#chatcontent > p > span { + display: table-row +} +#chatcontent > p > span.chatusername { + display: table-header-group +} +#chatcontent > p > span.chatmessage { + display: table-footer-group; + display: inline-block!important; + color: rgb(195, 196, 197) +} +::-webkit-input-placeholder { + color: rgb(99, 99, 99) +} +:-moz-placeholder { + color: rgb(99, 99, 99) +} +::-moz-placeholder { + color: rgb(99, 99, 99) +} +:-ms-input-placeholder { + color: rgb(99, 99, 99) +} +::-ms-input-placeholder { + color: rgb(99, 99, 99) } -#chatcontent > p { display: table; } -#chatcontent > p > span { display: table-row } -#chatcontent > p > span.chatusername { display: table-header-group } -#chatcontent > p > span.chatmessage { display: table-footer-group;display:inline-block!important;color:rgb(195,196,197) } -::-webkit-input-placeholder { color:rgb(99,99,99) } -:-moz-placeholder { color:rgb(99,99,99) } -::-moz-placeholder { color:rgb(99,99,99) } -:-ms-input-placeholder { color:rgb(99,99,99) } -::-ms-input-placeholder { color:rgb(99,99,99) } body > div.navbar-fixed > nav > div { -background:#263238 - background: -webkit-linear-gradient(#263238, #37474f, #455a64); /* For Safari 5.1 to 6.0 */ - background: -o-linear-gradient(#263238, #37474f, #455a64); /* For Opera 11.1 to 12.0 */ - background: -moz-linear-gradient(#263238, #37474f, #455a64); /* For Firefox 3.6 to 15 */ - background: linear-gradient(#263238, #37474f, #455a64); /* Standard syntax */ + background: #263238 background: -webkit-linear-gradient(#263238, #37474f, #455a64); + /* For Safari 5.1 to 6.0 */ + + background: -o-linear-gradient(#263238, #37474f, #455a64); + /* For Opera 11.1 to 12.0 */ + + background: -moz-linear-gradient(#263238, #37474f, #455a64); + /* For Firefox 3.6 to 15 */ + + background: linear-gradient(#263238, #37474f, #455a64); + /* Standard syntax */ } div.divider { -margin-left:10px; -margin-right:10px; + margin-left: 10px; + margin-right: 10px; } #discord-members > li > a.subheader, #members-nav > li:nth-child(1) > a, @@ -316,21 +350,21 @@ margin-right:10px; #members-nav > li:nth-child(4) > a, #guest-members-count, #members-nav > li:nth-child(6) > a { -text-transform: uppercase; + text-transform: uppercase; +} +#members-btn > i { + visibility: hidden } -#members-btn > i { visibility:hidden } #members-btn { -visibility:visible; -background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyNiI+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGQ9Ik0xIDFoMjR2MjRIMVYxeiIvPgogICAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE2Ljc3MjA3NTQgMTQuNjc5MTc3OGMtLjIyNDkwOTctLjIyNTY4ODYtLjQ3NDE1NzMtLjQzNTE2ODEtLjc0MjA3NTQtLjYyOTE3NzguMzUtLjAzLjY4LS4wNS45Ny0uMDUgMi4zMyAwIDcgMS4xNyA3IDMuNVYyMGgtNHYtMWMwLTEuODgxNjkxNC0xLjQwNzcwNTYtMy4zMjMwMTQzLTMuMjI3OTI0Ni00LjMyMDgyMjJ6bS0xLjQzODU3MzUtOC4xNzU4NTM0QzE1LjgwOTgwODIgNi4xODUyNTE3MyAxNi4zODI3ODQ1IDYgMTcgNmMxLjY2IDAgMi45OSAxLjM0IDIuOTkgM3MtMS4zMyAzLTIuOTkgM2MtLjYxNzE5MTQgMC0xLjE5MDE0NzEtLjE4NTIzNzMtMS42NjY0NDIzLS41MDMyODcyQzE1Ljc1NzQ4MzIgMTAuNzYyMTQwOTUgMTYgOS45MDk1NTYgMTYgOS4wMDAwNjY5M2MwLS45MDk1Mjg5Ni0uMjQyNTM4MS0xLjc2MjE0ODgtLjY2NjQ5ODEtMi40OTY3NDI1M3pNMTAgMTNjMi4yMSAwIDQtMS43OSA0LTRzLTEuNzktNC00LTQtNCAxLjc5LTQgNCAxLjc5IDQgNCA0em0wIDJjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6Ii8+CiAgPC9nPgo8L3N2Zz4=); -background-repeat:no-repeat; -margin-top:18px + visibility: visible; + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyNiI+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGQ9Ik0xIDFoMjR2MjRIMVYxeiIvPgogICAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE2Ljc3MjA3NTQgMTQuNjc5MTc3OGMtLjIyNDkwOTctLjIyNTY4ODYtLjQ3NDE1NzMtLjQzNTE2ODEtLjc0MjA3NTQtLjYyOTE3NzguMzUtLjAzLjY4LS4wNS45Ny0uMDUgMi4zMyAwIDcgMS4xNyA3IDMuNVYyMGgtNHYtMWMwLTEuODgxNjkxNC0xLjQwNzcwNTYtMy4zMjMwMTQzLTMuMjI3OTI0Ni00LjMyMDgyMjJ6bS0xLjQzODU3MzUtOC4xNzU4NTM0QzE1LjgwOTgwODIgNi4xODUyNTE3MyAxNi4zODI3ODQ1IDYgMTcgNmMxLjY2IDAgMi45OSAxLjM0IDIuOTkgM3MtMS4zMyAzLTIuOTkgM2MtLjYxNzE5MTQgMC0xLjE5MDE0NzEtLjE4NTIzNzMtMS42NjY0NDIzLS41MDMyODcyQzE1Ljc1NzQ4MzIgMTAuNzYyMTQwOTUgMTYgOS45MDk1NTYgMTYgOS4wMDAwNjY5M2MwLS45MDk1Mjg5Ni0uMjQyNTM4MS0xLjc2MjE0ODgtLjY2NjQ5ODEtMi40OTY3NDI1M3pNMTAgMTNjMi4yMSAwIDQtMS43OSA0LTRzLTEuNzktNC00LTQtNCAxLjc5LTQgNCAxLjc5IDQgNCA0em0wIDJjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6Ii8+CiAgPC9nPgo8L3N2Zz4=); + background-repeat: no-repeat; + margin-top: 18px } .circle:hover { -border-radius:20px; -background: linear-gradient(to right, #f9f9f9 90%, #fff); + border-radius: 20px; + background: linear-gradient(to right, #f9f9f9 90%, #fff); } #channels-list > li:hover { --webkit-filter: brightness(150%); -} -#loginmodal { -} + -webkit-filter: brightness(150%); +} \ No newline at end of file diff --git a/webapp/titanembeds/static/js/administrate_guild.js b/webapp/titanembeds/static/js/administrate_guild.js index d689ceb..78c3b1b 100644 --- a/webapp/titanembeds/static/js/administrate_guild.js +++ b/webapp/titanembeds/static/js/administrate_guild.js @@ -7,6 +7,15 @@ $('#unauth_users').change(function() { }); }); +$('#visitor_view').change(function() { + var pathname = window.location.pathname; + var checked = $(this).is(':checked') + var payload = {"visitor_view": checked} + $.post(pathname, payload, function(data) { + Materialize.toast('Updated visitor mode setting!', 2000) + }); +}); + $('#chat_links').change(function() { var pathname = window.location.pathname; var checked = $(this).is(':checked') diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 47cf570..8b07f20 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -5,6 +5,7 @@ /* global bot_client_id */ /* global moment */ /* global localStorage */ +/* global visitors_enabled */ (function () { const theme_options = ["DiscordDark", "BetterTitan"]; // All the avaliable theming names @@ -21,6 +22,7 @@ var fetch_error_count = 0; // Number of errors fetch has encountered var priority_query_guild = false; // So you have selected a channel? Let's populate it. var current_username_discrim; // Current username/discrim pair, eg EndenDraogn#4151 + var visitor_mode = false; // Keep track of if using the visitor mode or authenticate mode function element_in_view(element, fullyInView) { var pageTop = $(window).scrollTop(); @@ -36,9 +38,13 @@ } function query_guild() { + var url = "/api/query_guild"; + if (visitor_mode) { + url = url += "_visitor"; + } var funct = $.ajax({ dataType: "json", - url: "/api/query_guild", + url: url, data: {"guild_id": guild_id} }); return funct.promise(); @@ -65,10 +71,14 @@ } function fetch(channel_id, after=null) { + var url = "/api/fetch"; + if (visitor_mode) { + url += "_visitor"; + } var funct = $.ajax({ method: "GET", dataType: "json", - url: "/api/fetch", + url: url, data: {"guild_id": guild_id,"channel_id": channel_id, "after": after} }); return funct.promise(); @@ -118,6 +128,10 @@ $("#userembedmodal").modal("open"); }); + $("#visitor_login_btn").click(function () { + $("#loginmodal").modal("open"); + }); + $( "#theme-selector" ).change(function() { var theme = $("#theme-selector option:selected").val(); changeTheme(theme); @@ -171,6 +185,20 @@ if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, " ")); } + + function setVisitorMode(enabled) { + if (!visitors_enabled) { + return; + } + visitor_mode = enabled; + if (visitor_mode) { + $("#visitor_mode_message").show(); + $("#messagebox").hide(); + } else { + $("#visitor_mode_message").hide(); + $("#messagebox").show(); + } + } function primeEmbed() { $("#focusmodal").modal("close"); @@ -181,8 +209,10 @@ $("#modal_invite_btn").attr("href", data.instant_invite); }); + var login_modal_dismissible = visitors_enabled; + $("#loginmodal").modal({ - dismissible: false, // Modal can be dismissed by clicking outside of the modal + dismissible: login_modal_dismissible, // Modal can be dismissed by clicking outside of the modal opacity: .5, // Opacity of modal background inDuration: 300, // Transition in duration outDuration: 200, // Transition out duration @@ -196,6 +226,16 @@ var guild = query_guild(); guild.fail(function() { unlock_login_fields(); + if (visitors_enabled) { + setVisitorMode(true); + var guild2 = query_guild(); + guild2.done(function(data) { + initialize_embed(data); + }); + guild2.fail(function() { + setVisitorMode(false); + }); + } }); guild.done(function(data) { @@ -375,12 +415,14 @@ setTimeout(function() { var usr = create_authenticated_user(); usr.done(function(data) { + setVisitorMode(false); initialize_embed(); return; }); usr.fail(function(data) { if (data.status == 403) { Materialize.toast('Authentication error! You have been banned.', 10000); + setVisitorMode(true); } else if (index < 10) { _wait_for_discord_login(index + 1); } @@ -524,9 +566,13 @@ } fet.done(function(data) { var status = data.status; - update_embed_userchip(status.authenticated, status.avatar, status.username, status.user_id, status.discriminator); + if (visitor_mode) { + update_embed_userchip(false, null, "Titan", "0001", null); + } else { + update_embed_userchip(status.authenticated, status.avatar, status.username, status.user_id, status.discriminator); + } last_message_id = fill_discord_messages(data.messages, jumpscroll); - if (status.manage_embed) { + if (!visitor_mode && status.manage_embed) { $("#administrate_link").show(); } else { $("#administrate_link").hide(); @@ -554,6 +600,10 @@ $('#loginmodal').modal('open'); Materialize.toast('Session expired! You have been logged out.', 10000); } + setVisitorMode(true); + if (visitor_mode) { + fetchtimeout = setTimeout(run_fetch_routine, 5000); + } }); fet.catch(function(data) { if (500 <= data.status && data.status < 600) { @@ -600,6 +650,7 @@ lock_login_fields(); var usr = create_unauthenticated_user($(this).val()); usr.done(function(data) { + setVisitorMode(false); initialize_embed(); }); usr.fail(function(data) { @@ -611,6 +662,7 @@ Materialize.toast('Illegal username provided! Only alphanumeric, spaces, dashes, and underscores allowed in usernames.', 10000); } unlock_login_fields(); + setVisitorMode(true); }); } } diff --git a/webapp/titanembeds/templates/administrate_guild.html.j2 b/webapp/titanembeds/templates/administrate_guild.html.j2 index 438a08d..bdc2868 100644 --- a/webapp/titanembeds/templates/administrate_guild.html.j2 +++ b/webapp/titanembeds/templates/administrate_guild.html.j2 @@ -48,6 +48,19 @@ Enable + +
+ +

Toggle Visitor Mode

+

Visitors are able to view the channels that @everyone has access to. However, they are not able to send messages until they login using the usual methods.

+
+ +

diff --git a/webapp/titanembeds/templates/embed.html.j2 b/webapp/titanembeds/templates/embed.html.j2 index 535e2e0..304b7d5 100644 --- a/webapp/titanembeds/templates/embed.html.j2 +++ b/webapp/titanembeds/templates/embed.html.j2 @@ -151,7 +151,10 @@
Titan#0001
-
+
+ + +
@@ -188,6 +191,7 @@ diff --git a/webapp/titanembeds/utils.py b/webapp/titanembeds/utils.py index df2ad51..68f822b 100644 --- a/webapp/titanembeds/utils.py +++ b/webapp/titanembeds/utils.py @@ -65,6 +65,10 @@ def check_guild_existance(guild_id): else: return True +def guild_accepts_visitors(guild_id): + dbGuild = Guilds.query.filter_by(guild_id=guild_id).first() + return dbGuild.visitor_view + def guild_query_unauth_users_bool(guild_id): dbGuild = db.session.query(Guilds).filter(Guilds.guild_id==guild_id).first() return dbGuild.unauth_users From a4ed38e59d9043c72ed51560237b4ace80bb1af7 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Fri, 9 Jun 2017 08:22:22 +0000 Subject: [PATCH 002/135] Emoji icons showing up in embed --- webapp/titanembeds/static/css/embedstyle.css | 8 ++++++++ webapp/titanembeds/static/js/embed.js | 18 +++++++++++++++++- webapp/titanembeds/templates/embed.html.j2 | 4 ++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/webapp/titanembeds/static/css/embedstyle.css b/webapp/titanembeds/static/css/embedstyle.css index 21ede8a..dbbf20c 100644 --- a/webapp/titanembeds/static/css/embedstyle.css +++ b/webapp/titanembeds/static/css/embedstyle.css @@ -241,6 +241,14 @@ a { width: 305px; } +.message_emoji { + height: 20px; +} + +.message_emoji:hover { + height: 30px; +} + @font-face { font-family: Whitney; font-style: light; diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 8b07f20..e7ef9cd 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -18,6 +18,7 @@ var last_message_id; // last message tracked var selected_channel = guild_id; // user selected channel, defaults to #general channel var guild_channels = {}; // all server channels used to highlight channels in messages + var emoji_store = {}; // all server emojis var times_fetched = 0; // kept track of how many times that it has fetched var fetch_error_count = 0; // Number of errors fetch has encountered var priority_query_guild = false; // So you have selected a channel? Let's populate it. @@ -275,6 +276,7 @@ } function prepare_guild(guildobj) { + emoji_store = guildobj.emojis; fill_channels(guildobj.channels); fill_discord_members(guildobj.discordmembers); fill_authenticated_users(guildobj.embedmembers.authenticated); @@ -519,6 +521,18 @@ } return message; } + + function parse_emoji_in_message(message) { + var template = $('#mustache_message_emoji').html(); + Mustache.parse(template); + for (var i = 0; i < emoji_store.length; i++) { + var emoji = emoji_store[i]; + var emoji_format = "<:" + emoji.name + ":" + emoji.id + ">"; + var rendered = Mustache.render(template, {"id": emoji.id, "name": emoji.name}).trim(); + message.content = message.content.replace(emoji_format, rendered); + } + return message; + } function fill_discord_messages(messages, jumpscroll) { if (messages.length == 0) { @@ -534,7 +548,9 @@ message = parse_message_time(message); message = parse_message_attachments(message); message = parse_channels_in_message(message); - var rendered = Mustache.render(template, {"id": message.id, "full_timestamp": message.formatted_timestamp, "time": message.formatted_time, "username": message.author.username, "discriminator": message.author.discriminator, "content": nl2br(escapeHtml(message.content))}); + message.content = escapeHtml(message.content); + message = parse_emoji_in_message(message); + var rendered = Mustache.render(template, {"id": message.id, "full_timestamp": message.formatted_timestamp, "time": message.formatted_time, "username": message.author.username, "discriminator": message.author.discriminator, "content": nl2br(message.content)}); $("#chatcontent").append(rendered); last = message.id; handle_last_message_mention(); diff --git a/webapp/titanembeds/templates/embed.html.j2 b/webapp/titanembeds/templates/embed.html.j2 index 304b7d5..484f1fd 100644 --- a/webapp/titanembeds/templates/embed.html.j2 +++ b/webapp/titanembeds/templates/embed.html.j2 @@ -186,6 +186,10 @@ + + {% endraw %} {% endraw %} From fbe41a9a3590955e323e8feb15c0d6c04e9f447c Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 10 Jun 2017 03:21:44 +0000 Subject: [PATCH 009/135] Change flask sqlalchemy characterset to utf8mb4 --- webapp/titanembeds/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/titanembeds/app.py b/webapp/titanembeds/app.py index 76b9a20..cab84af 100644 --- a/webapp/titanembeds/app.py +++ b/webapp/titanembeds/app.py @@ -11,7 +11,7 @@ import os os.chdir(config['app-location']) app = Flask(__name__, static_folder="static") -app.config['SQLALCHEMY_DATABASE_URI'] = config['database-uri'] +app.config['SQLALCHEMY_DATABASE_URI'] = config['database-uri'] + "?charset=utf8mb4" app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no need this on for now. app.config['RATELIMIT_HEADERS_ENABLED'] = True app.config['SQLALCHEMY_POOL_RECYCLE'] = 250 From 289fdc0eb54de61c67a909cc5348e42e1611d3a8 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 10 Jun 2017 03:43:23 +0000 Subject: [PATCH 010/135] Fix emoji not replacing or rendering, also fix visitor fetching 429 error --- webapp/titanembeds/blueprints/api/api.py | 9 ++------- webapp/titanembeds/static/js/embed.js | 15 +++++++++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 4ba5aba..106446c 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -100,17 +100,12 @@ def check_user_in_guild(guild_id): return dbUser is not None and not checkUserRevoke(guild_id) def parse_emoji(textToParse, guild_id): - emojis = [] - emojis = re.findall(":(.*?):", textToParse) guild_emojis = get_guild_emojis(guild_id) - newText = textToParse for gemoji in guild_emojis: emoji_name = gemoji["name"] emoji_id = gemoji["id"] - for usremote in emojis: - if usremote == emoji_name: - newText = newText.replace(":{}:".format(emoji_name), "<:{}:{}>".format(emoji_name, emoji_id)) - return newText + textToParse = textToParse.replace(":{}:".format(emoji_name), "<:{}:{}>".format(emoji_name, emoji_id)) + return textToParse def format_post_content(guild_id, message): diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 19802ec..42c1c73 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -37,6 +37,10 @@ return ((elementTop <= pageBottom) && (elementBottom >= pageTop)); } } + + String.prototype.replaceAll = function(target, replacement) { + return this.split(target).join(replacement); + }; function query_guild() { var url = "/api/query_guild"; @@ -533,7 +537,7 @@ var emoji = emoji_store[i]; var emoji_format = "<:" + emoji.name + ":" + emoji.id + ">"; var rendered = Mustache.render(template, {"id": emoji.id, "name": emoji.name}).trim(); - message.content = message.content.replace(emoji_format, rendered); + message.content = message.content.replaceAll(emoji_format, rendered); } return message; } @@ -621,9 +625,12 @@ $('#loginmodal').modal('open'); Materialize.toast('Session expired! You have been logged out.', 10000); } - setVisitorMode(true); - if (visitor_mode) { - fetchtimeout = setTimeout(run_fetch_routine, 5000); + + if (data.status != 429) { + setVisitorMode(true); + if (visitor_mode) { + fetchtimeout = setTimeout(run_fetch_routine, 5000); + } } }); fet.catch(function(data) { From 926d4d14a9f14fa4056eddb5a72e710ce552fd72 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Mon, 12 Jun 2017 04:31:34 +0000 Subject: [PATCH 011/135] Fixed mention color and cleaned up css --- webapp/titanembeds/static/css/embedstyle.css | 426 ++++++++++--------- webapp/titanembeds/static/js/embed.js | 5 +- webapp/titanembeds/templates/embed.html.j2 | 2 +- 3 files changed, 225 insertions(+), 208 deletions(-) diff --git a/webapp/titanembeds/static/css/embedstyle.css b/webapp/titanembeds/static/css/embedstyle.css index 068dfea..81cb55e 100644 --- a/webapp/titanembeds/static/css/embedstyle.css +++ b/webapp/titanembeds/static/css/embedstyle.css @@ -1,54 +1,120 @@ +@font-face { + font-family: Whitney; + font-style: light; + font-weight: 300; + src: url("/static/fonts/whitney_light.woff") format("woff"); +} + +@font-face { + font-family: Whitney; + font-style: normal; + font-weight: 500; + src: url("/static/fonts/whitney_normal.woff") format("woff"); +} + +@font-face { + font-family: Whitney; + font-style: medium; + font-weight: 600; + src: url("/static/fonts/whitney_medium.woff") format("woff"); +} + +@font-face { + font-family: Whitney; + font-style: bold; + font-weight: 700; + src: url("/static/fonts/whitney_bold.woff") format("woff"); +} + html { -background-color: #455a64; -color: white; + background-color: #455a64; + color: white; + font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif; } main { -min-height: calc(100vh - 80px); -overflow-x: hidden; + min-height: calc(100vh - 80px); + overflow-x: hidden; } footer { -position: fixed; -bottom: 0; -left: 0; -right: 0; -height: 50px; -background-color: #37474f; + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 50px; + background-color: #37474f; } nav { -background-color: #263238; -background: linear-gradient(rgba(38, 50, 56, 1), rgba(255,0,0,0)); -box-shadow: none; + background-color: #263238; + background: linear-gradient(rgba(38, 50, 56, 1), rgba(255,0,0,0)); + box-shadow: none; } nav .brand-logo { -font-size: 1.5rem; + font-size: 1.5rem; +} + +@media only screen and (min-width: 601px) { + nav a.button-collapse { + display: block; + } +} + +body > div.navbar-fixed > nav > div { + background: #263238 background: -webkit-linear-gradient(#263238, #37474f, #455a64); + /* For Safari 5.1 to 6.0 */ + + background: -o-linear-gradient(#263238, #37474f, #455a64); + /* For Opera 11.1 to 12.0 */ + + background: -moz-linear-gradient(#263238, #37474f, #455a64); + /* For Firefox 3.6 to 15 */ + + background: linear-gradient(#263238, #37474f, #455a64); + /* Standard syntax */ } @media only screen and (min-width: 993px) { -.container { - width: 85%; -} + .container { + width: 85%; + } } .side-nav { -color: white; -background-color: #607d8b; + color: white; + background-color: #607d8b; } .side-nav .userView .name { -font-size: 20px; + font-size: 20px; } .side-nav li>a { -color: #eceff1; + color: #eceff1; } .side-nav .subheader { -color: #cfd8dc; -font-variant: small-caps; + color: #cfd8dc; + font-variant: small-caps; +} + +.side-nav div.divider { + background-color: #90a4ae; + margin-left: 10px; + margin-right: 10px; +} + +#members-btn > i { + visibility: hidden; +} + +#members-btn { + visibility: visible; + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyNiI+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGQ9Ik0xIDFoMjR2MjRIMVYxeiIvPgogICAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE2Ljc3MjA3NTQgMTQuNjc5MTc3OGMtLjIyNDkwOTctLjIyNTY4ODYtLjQ3NDE1NzMtLjQzNTE2ODEtLjc0MjA3NTQtLjYyOTE3NzguMzUtLjAzLjY4LS4wNS45Ny0uMDUgMi4zMyAwIDcgMS4xNyA3IDMuNVYyMGgtNHYtMWMwLTEuODgxNjkxNC0xLjQwNzcwNTYtMy4zMjMwMTQzLTMuMjI3OTI0Ni00LjMyMDgyMjJ6bS0xLjQzODU3MzUtOC4xNzU4NTM0QzE1LjgwOTgwODIgNi4xODUyNTE3MyAxNi4zODI3ODQ1IDYgMTcgNmMxLjY2IDAgMi45OSAxLjM0IDIuOTkgM3MtMS4zMyAzLTIuOTkgM2MtLjYxNzE5MTQgMC0xLjE5MDE0NzEtLjE4NTIzNzMtMS42NjY0NDIzLS41MDMyODcyQzE1Ljc1NzQ4MzIgMTAuNzYyMTQwOTUgMTYgOS45MDk1NTYgMTYgOS4wMDAwNjY5M2MwLS45MDk1Mjg5Ni0uMjQyNTM4MS0xLjc2MjE0ODgtLjY2NjQ5ODEtMi40OTY3NDI1M3pNMTAgMTNjMi4yMSAwIDQtMS43OSA0LTRzLTEuNzktNC00LTQtNCAxLjc5LTQgNCAxLjc5IDQgNCA0em0wIDJjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6Ii8+CiAgPC9nPgo8L3N2Zz4=); + background-repeat: no-repeat; + margin-top: 18px } .role-title { @@ -57,71 +123,117 @@ font-variant: small-caps; font-size: 80% !important; } -.divider { -background-color: #90a4ae; -} - .channel-hash { -font-size: 95%; -color: #b0bec5; + font-size: 95%; + color: #b0bec5; } .membercircle { -margin-top: 5px; -height: 40px; + margin-top: 5px; + height: 40px; } .membername { -position: absolute; -padding-left: 10px; + position: absolute; + padding-left: 10px; } -.chatcontent { -padding-left: 1%; -padding-top: 1%; -padding-bottom: 40px; - -/* https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ */ -/* These are technically the same, but use both */ -overflow-wrap: break-word; -word-wrap: break-word; - --ms-word-break: break-all; -/* This is the dangerous one in WebKit, as it breaks things wherever */ -word-break: break-all; -/* Instead use this non-standard one: */ -word-break: break-word; - -/* Adds a hyphen where the word breaks, if supported (No Blink) */ --ms-hyphens: auto; --moz-hyphens: auto; --webkit-hyphens: auto; -hyphens: auto; +#chatcontent { + padding-left: 1%; + padding-top: 1%; + padding-bottom: 40px; + + /* https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ */ + /* These are technically the same, but use both */ + overflow-wrap: break-word; + word-wrap: break-word; + + -ms-word-break: break-all; + /* This is the dangerous one in WebKit, as it breaks things wherever */ + word-break: break-all; + /* Instead use this non-standard one: */ + word-break: break-word; + + /* Adds a hyphen where the word breaks, if supported (No Blink) */ + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; } -@media only screen and (min-width: 601px) { -nav a.button-collapse { +#curuser_discrim, +#curuser_name { display: block; } + +#chatcontent > p { + display: table; +} + +#chatcontent > p > span { + display: table-row +} + +::-webkit-input-placeholder { + color: rgb(99, 99, 99); +} + +:-moz-placeholder { + color: rgb(99, 99, 99); +} + +::-moz-placeholder { + color: rgb(99, 99, 99); +} + +:-ms-input-placeholder { + color: rgb(99, 99, 99); +} + +::-ms-input-placeholder { + color: rgb(99, 99, 99); +} + +#discord-members > li > a.subheader, +#members-nav > li:nth-child(1) > a, +#discord-members-count, +#embed-discord-members-count, +#members-nav > li:nth-child(4) > a, +#guest-members-count, +#members-nav > li:nth-child(6) > a { + text-transform: uppercase; +} + +.circle:hover { + border-radius: 20px; + background: linear-gradient(to right, #f9f9f9 90%, #fff); +} + +#channels-list > li:hover { + -webkit-filter: brightness(150%); } .chatusername { -font-weight: bold; -color: #eceff1; + font-weight: bold; + color: #eceff1; } .chattimestamp { -font-size: 10px; -color: #90a4ae; -margin-right: 3px; + font-size: 10px; + color: #90a4ae; + margin-right: 3px; } .footercontainer { -width: 100%; -position: relative; -margin: 10px; -white-space: nowrap; -overflow: hidden; + width: 100%; + position: relative; + margin: 10px; + white-space: nowrap; + overflow: hidden; + border-radius: 20px; + border: 1px solid rgb(99, 99, 99); + margin-left: -0px; + padding-left: -4px; } #messageboxouter { @@ -130,35 +242,49 @@ overflow: hidden; } .currentuserchip { -display: inline-block; -position: relative; -top: -6px; -padding: 6px; -padding-right: 9px; -background-color: #455a64; + display: inline-block; + position: relative; + top: -6px; + padding: 6px; + padding-right: 9px; + background-color: #455a64; } .currentuserimage { -width: 30px; + width: 30px; } .currentusername { -position: relative; -top: 7px; -left: 5px; + position: relative; + top: 7px; + left: 5px; +} + +#curuser_discrim { + font-size: 50%; +} + +#curuser_discrim, +#curuser_name { + margin-top: -2px; +} + +#currentuserimage { + margin-top: 4px; + margin-right: 4px; } .input-field { -position: relative; -top: -19px; + position: relative; + top: -19px; } .left { -float: left; + float: left; } .modal { -background-color: #546e7a; + background-color: #546e7a; } .betatag { @@ -225,13 +351,15 @@ a { display: -ms-flexbox; display: flex; -webkit-align-items: center; - -ms-flex-align: center; - align-items: center; + -ms-flex-align: center; + align-items: center; } } #nameplate { cursor: pointer; + background: transparent; + margin-left: 10px; } #visitor_mode_message { @@ -254,130 +382,20 @@ a { height: 30px; } -@font-face { - font-family: Whitney; - font-style: light; - font-weight: 300; - src: url("../fonts/whitney_light.woff") format("woff") +.chatusername { + display: table-header-group; +} +.chatmessage { + display: table-footer-group; + display: inline-block; + color: rgb(195, 196, 197); } -@font-face { - font-family: Whitney; - font-style: normal; - font-weight: 500; - src: url("../fonts/whitney_normal.woff") format("woff") +p.mentioned { + font-weight: bold; + font-style: italic; } -@font-face { - font-family: Whitney; - font-style: medium; - font-weight: 600; - src: url("../fonts/whitney_medium.woff") format("woff") -} - -@font-face { - font-family: Whitney; - font-style: bold; - font-weight: 700; - src: url("../fonts/whitney_bold.woff") format("woff") -} -* { - font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif; -} -#footercontainer { - border-radius: 20px; - border: 1px solid rgb(99, 99, 99); - margin-left: -0px!important; - padding-left: -4px!important; -} -#nameplate { - background: transparent!important; - margin-left: 10px -} -#chatcontent > p > span.chatusername, -#curuser_discrim, -#curuser_name { - display: block -} -#curuser_discrim { - font-size: 50%; -} -#curuser_discrim, -#curuser_name { - margin-top: -2px -} -#currentuserimage { - margin-top: 4px; - margin-right: 4px -} -#chatcontent > p { - display: table; -} -#chatcontent > p > span { - display: table-row -} -#chatcontent > p > span.chatusername { - display: table-header-group -} -#chatcontent > p > span.chatmessage { - display: table-footer-group; - display: inline-block!important; - color: rgb(195, 196, 197) -} -::-webkit-input-placeholder { - color: rgb(99, 99, 99) -} -:-moz-placeholder { - color: rgb(99, 99, 99) -} -::-moz-placeholder { - color: rgb(99, 99, 99) -} -:-ms-input-placeholder { - color: rgb(99, 99, 99) -} -::-ms-input-placeholder { - color: rgb(99, 99, 99) -} -body > div.navbar-fixed > nav > div { - background: #263238 background: -webkit-linear-gradient(#263238, #37474f, #455a64); - /* For Safari 5.1 to 6.0 */ - - background: -o-linear-gradient(#263238, #37474f, #455a64); - /* For Opera 11.1 to 12.0 */ - - background: -moz-linear-gradient(#263238, #37474f, #455a64); - /* For Firefox 3.6 to 15 */ - - background: linear-gradient(#263238, #37474f, #455a64); - /* Standard syntax */ -} -div.divider { - margin-left: 10px; - margin-right: 10px; -} -#discord-members > li > a.subheader, -#members-nav > li:nth-child(1) > a, -#discord-members-count, -#embed-discord-members-count, -#members-nav > li:nth-child(4) > a, -#guest-members-count, -#members-nav > li:nth-child(6) > a { - text-transform: uppercase; -} -#members-btn > i { - visibility: hidden -} -#members-btn { - visibility: visible; - background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyNiI+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGQ9Ik0xIDFoMjR2MjRIMVYxeiIvPgogICAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE2Ljc3MjA3NTQgMTQuNjc5MTc3OGMtLjIyNDkwOTctLjIyNTY4ODYtLjQ3NDE1NzMtLjQzNTE2ODEtLjc0MjA3NTQtLjYyOTE3NzguMzUtLjAzLjY4LS4wNS45Ny0uMDUgMi4zMyAwIDcgMS4xNyA3IDMuNVYyMGgtNHYtMWMwLTEuODgxNjkxNC0xLjQwNzcwNTYtMy4zMjMwMTQzLTMuMjI3OTI0Ni00LjMyMDgyMjJ6bS0xLjQzODU3MzUtOC4xNzU4NTM0QzE1LjgwOTgwODIgNi4xODUyNTE3MyAxNi4zODI3ODQ1IDYgMTcgNmMxLjY2IDAgMi45OSAxLjM0IDIuOTkgM3MtMS4zMyAzLTIuOTkgM2MtLjYxNzE5MTQgMC0xLjE5MDE0NzEtLjE4NTIzNzMtMS42NjY0NDIzLS41MDMyODcyQzE1Ljc1NzQ4MzIgMTAuNzYyMTQwOTUgMTYgOS45MDk1NTYgMTYgOS4wMDAwNjY5M2MwLS45MDk1Mjg5Ni0uMjQyNTM4MS0xLjc2MjE0ODgtLjY2NjQ5ODEtMi40OTY3NDI1M3pNMTAgMTNjMi4yMSAwIDQtMS43OSA0LTRzLTEuNzktNC00LTQtNCAxLjc5LTQgNCAxLjc5IDQgNCA0em0wIDJjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6Ii8+CiAgPC9nPgo8L3N2Zz4=); - background-repeat: no-repeat; - margin-top: 18px -} -.circle:hover { - border-radius: 20px; - background: linear-gradient(to right, #f9f9f9 90%, #fff); -} -#channels-list > li:hover { - -webkit-filter: brightness(150%); +p.mentioned span.chatmessage { + color: #ff5252; } \ No newline at end of file diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 42c1c73..551284c 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -501,9 +501,8 @@ var lastmsg = $("#chatcontent p:last-child"); var content = lastmsg.text().toLowerCase(); var username_discrim = current_username_discrim.toLowerCase(); - if (content.includes("@everyone") || content.includes("@" + username_discrim)) { - lastmsg.css( "color", "#ff5252" ); - lastmsg.css( "font-weight", "bold" ); + if (content.includes("@everyone") || content.includes("@here") || content.includes("@" + username_discrim)) { + lastmsg.addClass( "mentioned" ); } } diff --git a/webapp/titanembeds/templates/embed.html.j2 b/webapp/titanembeds/templates/embed.html.j2 index 4e49a53..bcee46d 100644 --- a/webapp/titanembeds/templates/embed.html.j2 +++ b/webapp/titanembeds/templates/embed.html.j2 @@ -180,7 +180,7 @@ + {% raw %} +{% endblock %} diff --git a/webapp/titanembeds/templates/admin_index.html.j2 b/webapp/titanembeds/templates/admin_index.html.j2 index 6c2b851..6f9d5ec 100644 --- a/webapp/titanembeds/templates/admin_index.html.j2 +++ b/webapp/titanembeds/templates/admin_index.html.j2 @@ -2,5 +2,16 @@ {% set title="Admin" %} {% block content %} +

Administrate Titan Embeds

+

Select an action.

+
+
+
+

Cosmetics

+

Give or revoke special cosmetics privilages for users.

+ Manage +
+
+
{% endblock %} From c7abae4b7abac7636584b2dd1124858fa47e6378 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 22 Jul 2017 22:09:35 +0000 Subject: [PATCH 035/135] List of all query params card and show the cards in guild administration page --- webapp/titanembeds/templates/about.html.j2 | 21 ++------ .../templates/administrate_guild.html.j2 | 5 ++ .../templates/card_commands.html.j2 | 18 +++++++ .../templates/card_queryparams.html.j2 | 50 +++++++++++++++++++ 4 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 webapp/titanembeds/templates/card_commands.html.j2 create mode 100644 webapp/titanembeds/templates/card_queryparams.html.j2 diff --git a/webapp/titanembeds/templates/about.html.j2 b/webapp/titanembeds/templates/about.html.j2 index baeaf0e..f34d2aa 100644 --- a/webapp/titanembeds/templates/about.html.j2 +++ b/webapp/titanembeds/templates/about.html.j2 @@ -40,24 +40,9 @@ used to replace Discord itself. (that's what the mobile apps are for!) It is used in conjunction for a quick and dirty Discord embed for websites. Some uses include forum shoutboxes, etc.

-

Commands

-
-
-
-
-
-

All commands start by mentioning the bot user, @Titan.

-

Guest User Moderation

-

All guest users are denoted by square brackets around their username in the Discord channel, when sending messages.

-
    -
  • ban <username-query>[#<discriminator>]
    Bans the user by the username. The username does not need to be the full string. The discriminator is optional.
    Eg: ban Titan#0001
  • -
  • kick <username-query>[#<discriminator>]
    Kicks the user by the username. The username does not need to be the full string. The discriminator is optional.
    Eg: kick Titan#0001
  • -
-
-
-
-
-
+{% include 'card_commands.html.j2' %} + +{% include 'card_queryparams.html.j2' %}

Chat with us!

diff --git a/webapp/titanembeds/templates/administrate_guild.html.j2 b/webapp/titanembeds/templates/administrate_guild.html.j2 index 547e35b..5e5e91c 100644 --- a/webapp/titanembeds/templates/administrate_guild.html.j2 +++ b/webapp/titanembeds/templates/administrate_guild.html.j2 @@ -173,6 +173,11 @@ {% endif %}
+

+{% include 'card_commands.html.j2' %} + +{% include 'card_queryparams.html.j2' %} + {% endblock %} {% block script %} diff --git a/webapp/titanembeds/templates/card_commands.html.j2 b/webapp/titanembeds/templates/card_commands.html.j2 new file mode 100644 index 0000000..30e4af9 --- /dev/null +++ b/webapp/titanembeds/templates/card_commands.html.j2 @@ -0,0 +1,18 @@ +

Commands

+
+
+
+
+
+

All commands start by mentioning the bot user, @Titan.

+

Guest User Moderation

+

All guest users are denoted by square brackets around their username in the Discord channel, when sending messages.

+
    +
  • ban <username-query>[#<discriminator>]
    Bans the user by the username. The username does not need to be the full string. The discriminator is optional.
    Eg: ban Titan#0001
  • +
  • kick <username-query>[#<discriminator>]
    Kicks the user by the username. The username does not need to be the full string. The discriminator is optional.
    Eg: kick Titan#0001
  • +
+
+
+
+
+
\ No newline at end of file diff --git a/webapp/titanembeds/templates/card_queryparams.html.j2 b/webapp/titanembeds/templates/card_queryparams.html.j2 new file mode 100644 index 0000000..d4e7bbd --- /dev/null +++ b/webapp/titanembeds/templates/card_queryparams.html.j2 @@ -0,0 +1,50 @@ +

Query Parameters

+
+
+
+
+
+

Use query parameters to customize your individual embeds out of this world!

+

Query parameters are in the format of key-value pairs. They are appended after your embed url such that it would look like so:
https://titanembeds.tk/embed/1234567890?css=1&theme=DiscordDark&forcefocus=1

+

Below is the reference of all the avaliable query parameters that may be used.

+
    +
  • + css=<integer>
    + Styles the embed's theme according to the unique custom CSS ID. Custom CSS may be managed from the user dashboard page.
    + Eg: css=1 +
  • +
  • + defaultchannel=<snowflake>
    + Instead of having your #general channel as the first channel your users see, you may change it. Enable Discord's Developer mode in the Appearances tab of the User Settings and copy the channel ID.
    + Eg: defaultchannel=1234567890 +
  • +
  • + forcefocus=<integer>
    + Embeds are activated once the frame is loaded. However, if you have more than one embeds on the page, the rate limits might bite your user's toes. Having this setting set means that the embed will not be loaded until the user mouseovers the frame.
    +
    + Avaliable Options: +
      +
    • 0 = disabled (default)
    • +
    • 1 = enabled
    • +
    +
    + Eg: forcefocus=1 +
  • +
  • + theme=<string>
    + Want your embed to use one of our premade themes? Look no further!
    +
    + Avaliable Options: +
      +
    • BetterTitan
    • +
    • DiscordDark
    • +
    +
    + Eg: theme=DiscordDark +
  • +
+
+
+
+
+
\ No newline at end of file From b6525bc00efec7b968b73cc0904187ce31d674af Mon Sep 17 00:00:00 2001 From: Jori van Ee Date: Thu, 27 Jul 2017 17:59:53 +0200 Subject: [PATCH 036/135] Update admin_cosmetics.html.j2 Added placeholder for user id field --- webapp/titanembeds/templates/admin_cosmetics.html.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/titanembeds/templates/admin_cosmetics.html.j2 b/webapp/titanembeds/templates/admin_cosmetics.html.j2 index 897ea62..96a68bd 100644 --- a/webapp/titanembeds/templates/admin_cosmetics.html.j2 +++ b/webapp/titanembeds/templates/admin_cosmetics.html.j2 @@ -20,7 +20,7 @@
- +
From 4a9c38aa47d43bfcba5fff97bae5f102f366142f Mon Sep 17 00:00:00 2001 From: JustMaffie Date: Thu, 27 Jul 2017 22:55:28 +0200 Subject: [PATCH 037/135] Admin guilds manage thing --- webapp/titanembeds/blueprints/admin/admin.py | 60 ++++++++++++++++++- .../templates/admin_guilds.html.j2 | 32 ++++++++++ .../titanembeds/templates/admin_index.html.j2 | 7 +++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 webapp/titanembeds/templates/admin_guilds.html.j2 diff --git a/webapp/titanembeds/blueprints/admin/admin.py b/webapp/titanembeds/blueprints/admin/admin.py index dafb2a4..3e20eac 100644 --- a/webapp/titanembeds/blueprints/admin/admin.py +++ b/webapp/titanembeds/blueprints/admin/admin.py @@ -1,6 +1,7 @@ from flask import Blueprint, url_for, redirect, session, render_template, abort, request from functools import wraps -from titanembeds.database import db, get_administrators_list, Cosmetics +from titanembeds.database import db, get_administrators_list, Cosmetics, Guilds +from titanembeds.oauth import generate_guild_icon_url admin = Blueprint("admin", __name__) @@ -73,4 +74,59 @@ def cosmetics_patch(): entry.css = css db.session.commit() return ('', 204) - \ No newline at end of file + +@admin.route("/administrate_guild/", methods=["GET"]) +@is_admin +def administrate_guild(guild_id): + db_guild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first() + session["redirect"] = None + permissions=[] + permissions.append("Manage Embed Settings") + permissions.append("Ban Members") + permissions.append("Kick Members") + 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() + users = prepare_guild_members_list(all_members, all_bans) + dbguild_dict = { + "id": db_guild.guild_id, + "name": db_guild.name, + "unauth_users": db_guild.unauth_users, + "visitor_view": db_guild.visitor_view, + "chat_links": db_guild.chat_links, + "bracket_links": db_guild.bracket_links, + "mentions_limit": db_guild.mentions_limit, + "icon": db_guild.icon, + "discordio": db_guild.discordio if db_guild.discordio != None else "" + } + return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions) + +@admin.route("/administrate_guild/", methods=["POST"]) +@is_admin +def update_administrate_guild(guild_id): + db_guild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first() + db_guild.unauth_users = request.form.get("unauth_users", db_guild.unauth_users) in ["true", True] + db_guild.visitor_view = request.form.get("visitor_view", db_guild.visitor_view) in ["true", True] + db_guild.chat_links = request.form.get("chat_links", db_guild.chat_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) + + discordio = request.form.get("discordio", db_guild.discordio) + if discordio and discordio.strip() == "": + discordio = None + db_guild.discordio = discordio + db.session.commit() + return jsonify( + id=db_guild.id, + guild_id=db_guild.guild_id, + unauth_users=db_guild.unauth_users, + chat_links=db_guild.chat_links, + bracket_links=db_guild.bracket_links, + mentions_limit=db_guild.mentions_limit, + discordio=db_guild.discordio, + ) + +@user.route("/guilds") +@is_admin +def guilds(): + guilds = db.session.query(Guilds).all() + return render_template("admin_guilds.html.j2", servers=guilds, icon_generate=generate_guild_icon_url) diff --git a/webapp/titanembeds/templates/admin_guilds.html.j2 b/webapp/titanembeds/templates/admin_guilds.html.j2 new file mode 100644 index 0000000..1a68e62 --- /dev/null +++ b/webapp/titanembeds/templates/admin_guilds.html.j2 @@ -0,0 +1,32 @@ +{% extends 'site_layout.html.j2' %} +{% set title="Admin" %} + +{% block content %} +

Manage Guilds (Admin)

+

Select a server to configure Titan Embeds.

+
+ {% for server in servers %} +
+
+
+
+ {% if server.icon %} + + {% else %} + No icon :( + {% endif %} +
+
+ +

{{ server.name }}

+
+ Modify +
+
+
+
+
+ {% endfor %} +
+ +{% endblock %} diff --git a/webapp/titanembeds/templates/admin_index.html.j2 b/webapp/titanembeds/templates/admin_index.html.j2 index 6f9d5ec..ad74096 100644 --- a/webapp/titanembeds/templates/admin_index.html.j2 +++ b/webapp/titanembeds/templates/admin_index.html.j2 @@ -13,5 +13,12 @@ Manage +
+
+

Guilds

+

Manage any guild in Titan, this will give you the normal dashboard.

+ Manage +
+
{% endblock %} From 33f881bff371602fa4910a0db5f043040a5a04cd Mon Sep 17 00:00:00 2001 From: JustMaffie Date: Thu, 27 Jul 2017 23:15:24 +0200 Subject: [PATCH 038/135] I've found some bugs in the admin guilds thing system, this push fixed it, these bugs are tested, investigated, solved and tested again --- webapp/titanembeds/blueprints/admin/admin.py | 48 +++++++++++++++++-- .../templates/admin_guilds.html.j2 | 4 +- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/webapp/titanembeds/blueprints/admin/admin.py b/webapp/titanembeds/blueprints/admin/admin.py index 3e20eac..c19d2f8 100644 --- a/webapp/titanembeds/blueprints/admin/admin.py +++ b/webapp/titanembeds/blueprints/admin/admin.py @@ -1,7 +1,8 @@ from flask import Blueprint, url_for, redirect, session, render_template, abort, request from functools import wraps -from titanembeds.database import db, get_administrators_list, Cosmetics, Guilds +from titanembeds.database import db, get_administrators_list, Cosmetics, Guilds, UnauthenticatedUsers, UnauthenticatedBans from titanembeds.oauth import generate_guild_icon_url +import datetime admin = Blueprint("admin", __name__) @@ -74,11 +75,53 @@ def cosmetics_patch(): entry.css = css db.session.commit() return ('', 204) +def prepare_guild_members_list(members, bans): + all_users = [] + ip_pool = [] + members = sorted(members, key=lambda k: datetime.datetime.strptime(str(k.last_timestamp), "%Y-%m-%d %H:%M:%S"), reverse=True) + for member in members: + user = { + "id": member.id, + "username": member.username, + "discrim": member.discriminator, + "ip": member.ip_address, + "last_visit": member.last_timestamp, + "kicked": member.revoked, + "banned": False, + "banned_timestamp": None, + "banned_by": None, + "banned_reason": None, + "ban_lifted_by": None, + "aliases": [], + } + for banned in bans: + if banned.ip_address == member.ip_address: + if banned.lifter_id is None: + user['banned'] = True + user["banned_timestamp"] = banned.timestamp + user['banned_by'] = banned.placer_id + user['banned_reason'] = banned.reason + user['ban_lifted_by'] = banned.lifter_id + continue + if user["ip"] not in ip_pool: + all_users.append(user) + ip_pool.append(user["ip"]) + else: + for usr in all_users: + if user["ip"] == usr["ip"]: + alias = user["username"]+"#"+str(user["discrim"]) + if len(usr["aliases"]) < 5 and alias not in usr["aliases"]: + usr["aliases"].append(alias) + continue + return all_users @admin.route("/administrate_guild/", methods=["GET"]) @is_admin def administrate_guild(guild_id): db_guild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first() + if not db_guild: + abort(500) + return session["redirect"] = None permissions=[] permissions.append("Manage Embed Settings") @@ -109,7 +152,6 @@ def update_administrate_guild(guild_id): db_guild.chat_links = request.form.get("chat_links", db_guild.chat_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) - discordio = request.form.get("discordio", db_guild.discordio) if discordio and discordio.strip() == "": discordio = None @@ -125,7 +167,7 @@ def update_administrate_guild(guild_id): discordio=db_guild.discordio, ) -@user.route("/guilds") +@admin.route("/guilds") @is_admin def guilds(): guilds = db.session.query(Guilds).all() diff --git a/webapp/titanembeds/templates/admin_guilds.html.j2 b/webapp/titanembeds/templates/admin_guilds.html.j2 index 1a68e62..af4c2c7 100644 --- a/webapp/titanembeds/templates/admin_guilds.html.j2 +++ b/webapp/titanembeds/templates/admin_guilds.html.j2 @@ -11,7 +11,7 @@
{% if server.icon %} - + {% else %} No icon :( {% endif %} @@ -20,7 +20,7 @@

{{ server.name }}


- Modify + Modify
From e6624e0185f3b1779b152a7ae4ce3e2013c5c87b Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Fri, 28 Jul 2017 05:30:59 +0000 Subject: [PATCH 039/135] hotfix if the membr has a role that does not exist somehow --- webapp/titanembeds/blueprints/api/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 4fe7ea9..04466dd 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -259,7 +259,9 @@ def get_online_discord_users(guild_id, embed): member["color"] = None if apimem: for roleid in reversed(apimem["roles"]): - role = guildroles_filtered[roleid] + role = guildroles_filtered.get(roleid) + if not role: + continue if role["color"] != 0: member["color"] = '{0:02x}'.format(role["color"]) #int to hex if role["hoist"]: From fc8bfb2ae152b183beed883515b1d0f9916038e6 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Fri, 28 Jul 2017 05:48:54 +0000 Subject: [PATCH 040/135] Remove Reddit Tech from partnerships --- .../static/img/partners/reddit_tech.png | Bin 19026 -> 0 bytes webapp/titanembeds/templates/about.html.j2 | 18 ------------------ 2 files changed, 18 deletions(-) delete mode 100644 webapp/titanembeds/static/img/partners/reddit_tech.png diff --git a/webapp/titanembeds/static/img/partners/reddit_tech.png b/webapp/titanembeds/static/img/partners/reddit_tech.png deleted file mode 100644 index 8e738d8d6a1002acbbcaa645758ce3db6035e042..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19026 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 - -
-
-
-
-
- -
-
-
Reddit Tech
-

Reddit Technology Discussions

-

Are you interested in technology? Want to discuss your PC build with others, or anything else tech related? Just want to hang out with fellow PCMasterRace people? Then Reddit Tech is the place for you!

- Discord Server -
-
-
-
-
{% endblock %} From 45df6b19fc0942e10a39bf9293b967e557ddac5b Mon Sep 17 00:00:00 2001 From: JustMaffie Date: Sat, 29 Jul 2017 23:52:24 +0200 Subject: [PATCH 041/135] Added a protection against crash chars that make discord crash, this is not available to be disabled, this only helps out your server. --- discordbot/titanembeds/bot.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/discordbot/titanembeds/bot.py b/discordbot/titanembeds/bot.py index a6eb05e..a6327b3 100644 --- a/discordbot/titanembeds/bot.py +++ b/discordbot/titanembeds/bot.py @@ -110,6 +110,17 @@ class Titan(discord.Client): print("Skipping indexing server due to no-init flag") async def on_message(self, message): + crashChar = 'ौौौौ' + if crashChar in message.content: + try: + await bot.delete_message(message) + await bot.send_message(message.channel, + "**I've delete a message posted by {} because it contained characters which crashes discord. I've also banned him.**".format( + message.author.name + "#" + message.author.discriminator + "(ID: " + message.author.id + ")")) + await message.server.ban(message.author, "Causing discord to crash because of weird characters.") + except: + pass + return await self.wait_until_dbonline() await self.database.push_message(message) From 111ffb7871451f60b6dc507f270fc05082b0e688 Mon Sep 17 00:00:00 2001 From: JustMaffie Date: Sat, 29 Jul 2017 23:57:59 +0200 Subject: [PATCH 042/135] Added the same feature as previous commit to the website --- webapp/titanembeds/blueprints/api/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 04466dd..62611f0 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -112,6 +112,10 @@ def parse_emoji(textToParse, guild_id): def format_post_content(guild_id, channel_id, message): illegal_post = False illegal_reasons = [] + crashChar = 'ौौौौ' + if crashChar in message: + illegal_post = True + illegal_reasons.append("This message contains a character that crashes a users discord client.") message = message.replace("<", "\<") message = message.replace(">", "\>") message = parse_emoji(message, guild_id) From 4066f629f3fdf2a179f9ea0230a5497b31f9a673 Mon Sep 17 00:00:00 2001 From: JustMaffie Date: Sun, 30 Jul 2017 01:04:03 +0200 Subject: [PATCH 043/135] Undo and fix previous commits --- webapp/titanembeds/blueprints/api/api.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 62611f0..04466dd 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -112,10 +112,6 @@ def parse_emoji(textToParse, guild_id): def format_post_content(guild_id, channel_id, message): illegal_post = False illegal_reasons = [] - crashChar = 'ौौौौ' - if crashChar in message: - illegal_post = True - illegal_reasons.append("This message contains a character that crashes a users discord client.") message = message.replace("<", "\<") message = message.replace(">", "\>") message = parse_emoji(message, guild_id) From eaf55ba027f490c258b6c4085183b7356707bc9d Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sun, 30 Jul 2017 23:10:35 +0000 Subject: [PATCH 044/135] Modify github links to TitanEmbeds organization --- CONTRIBUTING.md | 2 +- discordbot/requirements.txt | 2 +- webapp/titanembeds/discordrest.py | 2 +- webapp/titanembeds/templates/site_layout.html.j2 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d478bcc..216fe01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ For those who would like to run the codebase yourself, you may follow the instru 4. Add your SSH key from this link to github 5. At the top right corner, click on New Workspace (To create one for Titan) 6. Fill in the details, click on Python as the template environment -7. Set the `Clone from Git or Mercurial url` to `git@github.com:EndenDragon/Titan.git` This should pull titan to your workspace. +7. Set the `Clone from Git or Mercurial url` to `git@github.com:TitanEmbeds/Titan.git` This should pull titan to your workspace. 8. Right click `cloud9_install.sh` file at the left sidebar and click run. This will set everything up. 9. Afterwards, just edit the respective config.py files in the webapp/discordbot directories and you are ready to go! 10. Now you're ready to run Titan... webapp! To make the webapp to work, rightclick `run_c9.py` file and click run. Congratz! It will tell you the exact url where your stuff is running at. diff --git a/discordbot/requirements.txt b/discordbot/requirements.txt index a15e5e1..94ca12d 100644 --- a/discordbot/requirements.txt +++ b/discordbot/requirements.txt @@ -1,3 +1,3 @@ -https://github.com/EndenDragon/discord.py/archive/async.zip#egg=discord.py[voice] +https://github.com/TitanEmbeds/discord.py/archive/async.zip#egg=discord.py[voice] sqlalchemy asyncio_extras diff --git a/webapp/titanembeds/discordrest.py b/webapp/titanembeds/discordrest.py index 4739c41..3afc03b 100644 --- a/webapp/titanembeds/discordrest.py +++ b/webapp/titanembeds/discordrest.py @@ -17,7 +17,7 @@ class DiscordREST: def __init__(self, bot_token): self.global_redis_prefix = "discordapiratelimit/" self.bot_token = bot_token - self.user_agent = "TitanEmbeds (https://github.com/EndenDragon/Titan) Python/{} requests/{}".format(sys.version_info, requests.__version__) + self.user_agent = "TitanEmbeds (https://github.com/TitanEmbeds/Titan) Python/{} requests/{}".format(sys.version_info, requests.__version__) def init_discordrest(self): if not self._bucket_contains("global_limited"): diff --git a/webapp/titanembeds/templates/site_layout.html.j2 b/webapp/titanembeds/templates/site_layout.html.j2 index d221ca5..d89a40b 100644 --- a/webapp/titanembeds/templates/site_layout.html.j2 +++ b/webapp/titanembeds/templates/site_layout.html.j2 @@ -68,7 +68,7 @@ From 5da55cc66b2e08687ea37d18e8ffdb31393a63fc Mon Sep 17 00:00:00 2001 From: Jori van Ee Date: Tue, 1 Aug 2017 20:24:54 +0200 Subject: [PATCH 045/135] Partially implement Database Cleanup button in the admin Panel --- webapp/titanembeds/blueprints/api/api.py | 12 ++++++++++-- webapp/titanembeds/templates/admin_index.html.j2 | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 04466dd..c61e7c4 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -1,4 +1,4 @@ -from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties, GuildMembers, Messages, get_channel_messages, list_all_guild_members +from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties, GuildMembers, Messages, get_channel_messages, list_all_guild_members, get_administrators_list from titanembeds.decorators import valid_session_required, discord_users_only from titanembeds.utils import check_guild_existance, guild_accepts_visitors, guild_query_unauth_users_bool, get_client_ipaddr, discord_api, rate_limiter, channel_ratelimit_key, guild_ratelimit_key from titanembeds.oauth import user_has_permission, generate_avatar_url, check_user_can_administrate_guild @@ -499,9 +499,17 @@ def create_authenticated_user(): response.status_code = 403 return response +def canCleanupDB(): + canclean = False + if request.form.get("secret", None) == config['app-secret']: + canclean = True + if 'user_id' in session: + if session['user_id'] in get_administrators_list(): + canclean = True + return canclean @api.route("/cleanup-db", methods=["DELETE"]) def cleanup_keyval_db(): - if request.form.get("secret", None) == config["app-secret"]: + if canCleanupDB(): db.session.query(KeyValueProperties).filter(KeyValueProperties.expiration < datetime.datetime.now()).delete() db.session.commit() diff --git a/webapp/titanembeds/templates/admin_index.html.j2 b/webapp/titanembeds/templates/admin_index.html.j2 index ad74096..60a7ce1 100644 --- a/webapp/titanembeds/templates/admin_index.html.j2 +++ b/webapp/titanembeds/templates/admin_index.html.j2 @@ -20,5 +20,12 @@ Manage +
+
+

Cleanup DB

+

Clean up the database

+ SoonTM +
+
{% endblock %} From 2ace5f70405399b268e75c32b3e0dade705ebc86 Mon Sep 17 00:00:00 2001 From: JustMaffie Date: Wed, 2 Aug 2017 13:43:51 +0200 Subject: [PATCH 046/135] Implement nickname support Nicknames will be shown in both the embed and discord. --- discordbot/titanembeds/database/__init__.py | 6 +++++- webapp/titanembeds/blueprints/api/api.py | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/discordbot/titanembeds/database/__init__.py b/discordbot/titanembeds/database/__init__.py index d2bbf39..bca6b8b 100644 --- a/discordbot/titanembeds/database/__init__.py +++ b/discordbot/titanembeds/database/__init__.py @@ -69,6 +69,8 @@ class DatabaseInterface(object): def get_message_author(self, message): author = message.author + if author.nick is not None: + author.name = author.nick obj = { "username": author.name, "discriminator": author.discriminator, @@ -81,6 +83,8 @@ class DatabaseInterface(object): def get_message_mentions(self, mentions): ments = [] for author in mentions: + if author.nick: + author.name = author.nick ments.append({ "username": author.name, "discriminator": author.discriminator, @@ -264,7 +268,7 @@ class DatabaseInterface(object): dbmember.active = active dbmember.username = member.name dbmember.discriminator = member.discriminator - dbmember.nick = member.nick + dbmember.nickname = member.nick dbmember.avatar = member.avatar dbmember.roles = json.dumps(self.list_role_ids(member.roles)) session.commit() diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index c61e7c4..11a315e 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -109,7 +109,7 @@ def parse_emoji(textToParse, guild_id): return textToParse -def format_post_content(guild_id, channel_id, message): +def format_post_content(guild_id, channel_id, message, dbUser): illegal_post = False illegal_reasons = [] message = message.replace("<", "\<") @@ -140,7 +140,11 @@ def format_post_content(guild_id, channel_id, message): if (session['unauthenticated']): message = u"**[{}#{}]** {}".format(session['username'], session['user_id'], message) else: - message = u"**<{}#{}>** {}".format(session['username'], session['discriminator'], message) # I would like to do a @ mention, but i am worried about notif spam + username = session['username'] + if dbUser: + if dbUser.nickname: + username = dbUser.nickname + message = u"**<{}#{}>** {}".format(username, session['discriminator'], message) # I would like to do a @ mention, but i am worried about notify spam return (message, illegal_post, illegal_reasons) def format_everyone_mention(channel, content): @@ -364,11 +368,15 @@ def post(): guild_id = request.form.get("guild_id") channel_id = request.form.get('channel_id') content = request.form.get('content') - content, illegal_post, illegal_reasons = format_post_content(guild_id, channel_id, content) + if "user_id" in session: + dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session['user_id']).first() + else: + dbUser = None if user_unauthenticated(): key = session['user_keys'][guild_id] else: key = None + content, illegal_post, illegal_reasons = format_post_content(guild_id, channel_id, content, dbUser) status = update_user_status(guild_id, session['username'], key) message = {} if illegal_post: @@ -392,6 +400,9 @@ def post(): avatar = url_for('static', filename='img/titanembeds_round.png', _external=True) else: username = session["username"] + if dbUser: + if dbUser.nickname: + username = dbUser.nickname if content.startswith("(Titan Dev) "): content = content[12:] username = "(Titan Dev) " + username From b91b3821a4c78704f1a800b1c7bdd3559eb7fd41 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Wed, 2 Aug 2017 19:28:08 +0000 Subject: [PATCH 047/135] Fully implement database cleanup button, ajax side --- webapp/titanembeds/static/js/admin_index.js | 30 +++++++++++++++++++ .../titanembeds/templates/admin_index.html.j2 | 9 ++++-- 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 webapp/titanembeds/static/js/admin_index.js diff --git a/webapp/titanembeds/static/js/admin_index.js b/webapp/titanembeds/static/js/admin_index.js new file mode 100644 index 0000000..1a1f11c --- /dev/null +++ b/webapp/titanembeds/static/js/admin_index.js @@ -0,0 +1,30 @@ +/* global $ */ +/* global Materialize */ + +(function () { + function cleanup_database() { + var funct = $.ajax({ + method: "DELETE", + url: "/api/cleanup-db", + }); + return funct.promise(); + } + + $(function(){ + $("#db_cleanup_btn").click(run_cleanup_db); + }); + + function run_cleanup_db() { + $("#db_cleanup_btn").attr("disabled",true); + Materialize.toast('Please wait for the cleanup database task to finish...', 10000); + var cleanupdb = cleanup_database(); + cleanupdb.done(function () { + $("#db_cleanup_btn").attr("disabled",false); + Materialize.toast('Successfully cleaned up the database!', 10000); + }); + cleanupdb.fail(function () { + $("#db_cleanup_btn").attr("disabled",false); + Materialize.toast('Database cleanup failiure.', 10000); + }); + } +})(); \ 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 60a7ce1..2785249 100644 --- a/webapp/titanembeds/templates/admin_index.html.j2 +++ b/webapp/titanembeds/templates/admin_index.html.j2 @@ -22,10 +22,13 @@
-

Cleanup DB

-

Clean up the database

- SoonTM +

Run a Database Cleanup

+

Clears the keyval caches and purges the old messages. (Hit once, and wait a minute)

+ Run DB Cleanup Task
{% endblock %} +{% block script %} + +{% endblock %} \ No newline at end of file From 3b5707cd15157f33e31a460b5ad04241ac23c298 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Thu, 3 Aug 2017 22:26:45 +0000 Subject: [PATCH 048/135] Hotfix for webhooks not showing up in the embeds. Message author object sometimes gives User for webhooks. User objects does not have nicknames. Check if the user/author object has nick attribute. --- discordbot/titanembeds/database/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discordbot/titanembeds/database/__init__.py b/discordbot/titanembeds/database/__init__.py index bca6b8b..8b3b2c0 100644 --- a/discordbot/titanembeds/database/__init__.py +++ b/discordbot/titanembeds/database/__init__.py @@ -69,7 +69,7 @@ class DatabaseInterface(object): def get_message_author(self, message): author = message.author - if author.nick is not None: + if hasattr(author, 'nick') and author.nick: author.name = author.nick obj = { "username": author.name, From f3a1efb049e5fd5db7457ca34418b58755d27337 Mon Sep 17 00:00:00 2001 From: "Jeremy \"EndenDragon\" Zhang" Date: Thu, 3 Aug 2017 21:09:14 -0700 Subject: [PATCH 049/135] Set manage guilds page title It was left as Admin for some reason --- webapp/titanembeds/templates/admin_guilds.html.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/titanembeds/templates/admin_guilds.html.j2 b/webapp/titanembeds/templates/admin_guilds.html.j2 index af4c2c7..3434d13 100644 --- a/webapp/titanembeds/templates/admin_guilds.html.j2 +++ b/webapp/titanembeds/templates/admin_guilds.html.j2 @@ -1,5 +1,5 @@ {% extends 'site_layout.html.j2' %} -{% set title="Admin" %} +{% set title="Manage Guilds as Administrator" %} {% block content %}

Manage Guilds (Admin)

From 94ce92f641f4c7f11c6dfe2cb8b83e7316148c42 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Fri, 4 Aug 2017 19:42:45 +0000 Subject: [PATCH 050/135] Remove JS from the about page --- webapp/titanembeds/static/img/people/js.png | Bin 57530 -> 0 bytes webapp/titanembeds/templates/about.html.j2 | 15 --------------- 2 files changed, 15 deletions(-) delete mode 100644 webapp/titanembeds/static/img/people/js.png diff --git a/webapp/titanembeds/static/img/people/js.png b/webapp/titanembeds/static/img/people/js.png deleted file mode 100644 index 2235f9495425836af2d0b1e09443fd29583df56d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57530 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%0D`m@KwJdyvaQg; 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 diff --git a/webapp/titanembeds/templates/about.html.j2 b/webapp/titanembeds/templates/about.html.j2 index 7d7436c..d374d9d 100644 --- a/webapp/titanembeds/templates/about.html.j2 +++ b/webapp/titanembeds/templates/about.html.j2 @@ -97,21 +97,6 @@ etc.

-
-
-
-
- .JS -
-
-
.JS
-

Embed Theme Architect

-

.JS didn't make the designs in JavaScript, he helped out Titan with his CSS skillz.

-
-
-
-
-
From 38533c22a9fdcd17f60bdcc6a261df34af1f1f1e Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 5 Aug 2017 06:44:00 +0000 Subject: [PATCH 051/135] Whoops, jsonify isnt imported in the admin blueprint... such sneaky 500 error! --- webapp/titanembeds/blueprints/admin/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/titanembeds/blueprints/admin/admin.py b/webapp/titanembeds/blueprints/admin/admin.py index c19d2f8..7622742 100644 --- a/webapp/titanembeds/blueprints/admin/admin.py +++ b/webapp/titanembeds/blueprints/admin/admin.py @@ -1,4 +1,4 @@ -from flask import Blueprint, url_for, redirect, session, render_template, abort, request +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.oauth import generate_guild_icon_url From 5210de6884b09904117ab04eea9cdb5b62c04494 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 5 Aug 2017 18:01:20 +0000 Subject: [PATCH 052/135] Add webhook message instructions card panel --- .../static/img/webhook_comparison.png | Bin 0 -> 22505 bytes .../titanembeds/static/img/webhook_inuse.png | Bin 0 -> 59927 bytes webapp/titanembeds/templates/about.html.j2 | 2 ++ .../templates/administrate_guild.html.j2 | 2 ++ .../templates/card_webhookmsgs.html.j2 | 23 ++++++++++++++++++ 5 files changed, 27 insertions(+) create mode 100644 webapp/titanembeds/static/img/webhook_comparison.png create mode 100644 webapp/titanembeds/static/img/webhook_inuse.png create mode 100644 webapp/titanembeds/templates/card_webhookmsgs.html.j2 diff --git a/webapp/titanembeds/static/img/webhook_comparison.png b/webapp/titanembeds/static/img/webhook_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..332edcca08b8c6febe37379500a3110f32227954 GIT binary patch literal 22505 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 literal 0 HcmV?d00001 diff --git a/webapp/titanembeds/static/img/webhook_inuse.png b/webapp/titanembeds/static/img/webhook_inuse.png new file mode 100644 index 0000000000000000000000000000000000000000..cdc297ffa67d48073ddb2b346ae395047d6b47f9 GIT binary patch literal 59927 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 {% include 'card_queryparams.html.j2' %} +{% include 'card_webhookmsgs.html.j2' %} +

Chat with us!

diff --git a/webapp/titanembeds/templates/administrate_guild.html.j2 b/webapp/titanembeds/templates/administrate_guild.html.j2 index 5e5e91c..27578c2 100644 --- a/webapp/titanembeds/templates/administrate_guild.html.j2 +++ b/webapp/titanembeds/templates/administrate_guild.html.j2 @@ -178,6 +178,8 @@ {% include 'card_queryparams.html.j2' %} +{% include 'card_webhookmsgs.html.j2' %} + {% endblock %} {% block script %} diff --git a/webapp/titanembeds/templates/card_webhookmsgs.html.j2 b/webapp/titanembeds/templates/card_webhookmsgs.html.j2 new file mode 100644 index 0000000..e473bb8 --- /dev/null +++ b/webapp/titanembeds/templates/card_webhookmsgs.html.j2 @@ -0,0 +1,23 @@ +

NEW! Webhook Messages

+
+
+
+
+
+

Reading messages in Discord can be 20% more cooler!

+

A quick comparison between having webhook messages enabled vs disabled for a text channel.

+
    +
  • + Old vs new comparison of having webhook messages enabled/disabled for the channel +
  • +
+
+

If you want your messages to look more real, just add a webhook to the channel starting with a name of Titan or [Titan] (case insensitive).

+ Webhook configuration +

(Webhook Messages enabled for the #general channel)

+

The new message format should automatically take effect for the channel the webhook is set to send from.

+
+
+
+
+
\ No newline at end of file From 8fbe8e95d976e03ddb385768e969eeb3e7954f54 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 5 Aug 2017 18:06:05 +0000 Subject: [PATCH 053/135] Add webhook guest msg note to the commands card --- webapp/titanembeds/templates/card_commands.html.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/titanembeds/templates/card_commands.html.j2 b/webapp/titanembeds/templates/card_commands.html.j2 index 30e4af9..3aab508 100644 --- a/webapp/titanembeds/templates/card_commands.html.j2 +++ b/webapp/titanembeds/templates/card_commands.html.j2 @@ -6,7 +6,7 @@

All commands start by mentioning the bot user, @Titan.

Guest User Moderation

-

All guest users are denoted by square brackets around their username in the Discord channel, when sending messages.

+

All guest users are denoted by square brackets (or Titan's logo as avatar if enabled Webhook Messages) around their username in the Discord channel, when sending messages.

  • ban <username-query>[#<discriminator>]
    Bans the user by the username. The username does not need to be the full string. The discriminator is optional.
    Eg: ban Titan#0001
  • kick <username-query>[#<discriminator>]
    Kicks the user by the username. The username does not need to be the full string. The discriminator is optional.
    Eg: kick Titan#0001
  • From 162baec3c08a0528913065f30e8b7435b78d030e Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 5 Aug 2017 21:41:19 +0000 Subject: [PATCH 054/135] Refocus message box after posting message --- webapp/titanembeds/static/js/embed.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index b6d94ba..72d3959 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -763,6 +763,7 @@ }); funct.always(function() { $("#messagebox").attr('readonly', false); + $("#messagebox").focus(); }); } }); From ed327a4ad1f4b2e5b965d06926977349c16ec807 Mon Sep 17 00:00:00 2001 From: JustMaffie Date: Mon, 7 Aug 2017 01:53:51 +0200 Subject: [PATCH 055/135] Add everybot to about page partners section --- .../static/img/partners/everybot.jpg | Bin 0 -> 2207 bytes webapp/titanembeds/templates/about.html.j2 | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 webapp/titanembeds/static/img/partners/everybot.jpg diff --git a/webapp/titanembeds/static/img/partners/everybot.jpg b/webapp/titanembeds/static/img/partners/everybot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e598d33dd4f41aa9f39c2da34002ff97b78ed19 GIT binary patch literal 2207 zcmbW%c{tQt9{}+4o7LEtv1Vz)Fd<=NXV6%)O{n3vpnGk}GM6hQqJ(T&E_aHNCD9Dm zmh_+yLaG@eO4oMnS0?3^)ZB6J`@HY-ynns#`#sM&f1LAuzUO?NbDqO{&6@(^PIiuV z0D=I3_zm#h09zn{LZMMe0W=zo!3YQnV?~68goNcKrNppG3M$G<3W|z*)edOv-KVXp zsHkZ~&_1X~Bob9LOe~D`%?}t5^>>Rv7z{>ONLWrpL{1;4h|~X{gZCIn2!KS82#59o zm;?lufOt=U3jdr)=&u0(3)mjGz!dp;;WjAUGU`fFqFz1V1~TzYY)*NJ*TY zHA>3E54|rEtAF{%Z2?uA`c7#IdtJ@IKPm|$C?hK;ub__C&?M|98Xht-HZiq5Ot!Ok zaCAEA>E(UwIMpZM%=bS81_g)EevFR!DfWC^@|CM8*HY8cbMx{Gel9Hfgw*CMZx(<28}=VA3BC)4K)?~`T^9uViQnN82qaDqC28$}_KTF-r+--hYjfjveJ4iM zfU+*_AH^1wQ6o;Pb9ZTf$^JW7(*Gs<7wq4z2_OuI_?HKl09Ii0+0lCEbUl(tk6hJE zhC3XMUAK?ZBLic|wdbIMeh~DAjDzLjlSu*&N`wr@DvAV2V#5w;hINPeXB;a~Q zEFEA)$>nB$))nL*(fhEdq*n$SlNXA*{tN(Y2sLJ*`` zSy9T*!WfWcz0VFqpyG&N6EqC4vW~oJH+Kluc z5Y+r7>hsd&5}MnCk+WfH$7{oNrf;NpsTFuKO7R^uZYI;z_2v{S!Rh=-+&LK&byAj( z(OV425gS^L^R6wAR>|rxUb>Fi(4P6=uy$zQXV!MErjYX7_Iw^TQ;}YoYg)`?MIvowZ#CcD{?I?`A)?>%ARDh5k6 zw6I({6MA7K_-EWhTg8D+bam%Uhaa02#%zn8Ui`SHU-t+P1R4p-t~JgMWo*|PFv2U9 zb%&$Hz*f08XLUZ7`QCs3m4rbDxrOj4J1h7KxjTP5D3tDO#}T1CEz6O=+I3oLSi`dK zLzVTtr9w(7t9g%OrG=zPLD~*f2e@Ka6Q&im^s4&JFND{95?HvYT7WW|c$E0nu@*nM zSC($HDLfYF6?4iUR!}DFw*y^buEjxo%*pR2ea9aUPr6~n>)z!a6D(hLllJNM;WYbq zyN)o|2I8l~P5ZNJMPfBleZ98wxYli^;pR6(smjvmd^hcj@3ZD|60E&;0;ewgmSSWY z(^#?G;K0K7GSov(1I}e97#qWRP+H@kv*G|=X)r>(K zEuz;K&)OR8;OJH3tGU~!G=9GyJD4VFkF>|P3B|PYz~Rk7{`-#N0j}4_E+c78A}q!{ zyC+V1vvu9qnNTSg{s&cECW18Jd8QgNQB8RQ{jt1bwa=d${DLh)a|Nj*xZ7gw_0m5gq_ zZ1I4Tg{jL=>eX(=yx??XSKzOs^)~9V+Dw>>CB_Z$XqR@nh2_L#*nHMx#h#pKb8%O4 zw2IJ<5VN~(Xy%bbc~3Nc1Ixb)W?(|ws#%0XU;70NkrVW5YqsoqZ+5jcchIxrrpv=D z_MKKOGn-CP$4-$9+R5JDTP`*W>8B^V@K5gnuIJ1>M^YNnvWPGOnws*PfCm{1C<;DK zfFPoSD@ElU!Iy*_Dn%K11v4_>ly>hNcLZC)d$^L4UTe=uRL3?Y<-5Z;Y%;)l@O!=o z38}7gC{cVQjG~aa2t(IAw+rds@G7E_B)e$G#9ztnUv@{7-6E6~lLYx+Zz-}Y-3jW$PuHH~uGlFu+Ry literal 0 HcmV?d00001 diff --git a/webapp/titanembeds/templates/about.html.j2 b/webapp/titanembeds/templates/about.html.j2 index cc059eb..c23080e 100644 --- a/webapp/titanembeds/templates/about.html.j2 +++ b/webapp/titanembeds/templates/about.html.j2 @@ -153,4 +153,21 @@ etc.

+ {% endblock %} From 8d707a7e150001a95e97f74afe79cbbb2c296ee0 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Mon, 7 Aug 2017 03:08:26 +0000 Subject: [PATCH 056/135] Nickname support for the user sidebar --- webapp/titanembeds/static/js/embed.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 72d3959..a959f87 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -403,7 +403,11 @@ $("#discord-members").append(rendered_role); for (var j = 0; j < roleobj.members.length; j++) { var member = roleobj.members[j]; - var rendered_user = Mustache.render(template_user, {"id": member.id.toString() + "d", "username": member.username, "avatar": member.avatar_url}); + var member_name = member.nick; + if (!member_name) { + member_name = member.username; + } + var rendered_user = Mustache.render(template_user, {"id": member.id.toString() + "d", "username": member_name, "avatar": member.avatar_url}); $("#discord-members").append(rendered_user); $( "#discorduser-" + member.id.toString() + "d").click({"member_id": member.id.toString()}, function(event) { mention_member(event.data.member_id); From a1ec9403722c9cec405b3a80e04c3e31dc564bac Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Mon, 7 Aug 2017 03:55:33 +0000 Subject: [PATCH 057/135] Typo, close paragraph tag for everybot card --- webapp/titanembeds/templates/about.html.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/titanembeds/templates/about.html.j2 b/webapp/titanembeds/templates/about.html.j2 index c23080e..a56ab01 100644 --- a/webapp/titanembeds/templates/about.html.j2 +++ b/webapp/titanembeds/templates/about.html.j2 @@ -163,7 +163,7 @@ etc.

Everybot

Hey, thank you for reading this, Everybot is a Discord Bot created by JustMaffie, we aim to bring fun and moderation to Discord servers using our commands. - We also want to provide a nice community at our Discord Server. + We also want to provide a nice community at our Discord Server.

Discord Server
From 1b8da1a5c16d7035fb07b6f8bc0ca11407d3eb99 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Tue, 8 Aug 2017 02:31:42 +0000 Subject: [PATCH 058/135] Set sidenavs max width to leave room if embed width is too small. Closes #35 --- webapp/titanembeds/static/css/embedstyle.css | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/titanembeds/static/css/embedstyle.css b/webapp/titanembeds/static/css/embedstyle.css index 81cb55e..3b3f9e0 100644 --- a/webapp/titanembeds/static/css/embedstyle.css +++ b/webapp/titanembeds/static/css/embedstyle.css @@ -85,6 +85,7 @@ body > div.navbar-fixed > nav > div { .side-nav { color: white; background-color: #607d8b; + max-width: 95%; } .side-nav .userView .name { From 335d9f569e48b20a939c7d679c64bc972de601db Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Tue, 8 Aug 2017 03:14:17 +0000 Subject: [PATCH 059/135] Overriding builtin themes with user defined css. Users should take advantage of the url query parmeters to choose which theme to override at load. Closes #36. --- webapp/titanembeds/static/css/embedstyle.css | 4 +++ webapp/titanembeds/static/js/embed.js | 28 ++++++++++++++------ webapp/titanembeds/templates/embed.html.j2 | 4 +++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/webapp/titanembeds/static/css/embedstyle.css b/webapp/titanembeds/static/css/embedstyle.css index 3b3f9e0..c77fe6b 100644 --- a/webapp/titanembeds/static/css/embedstyle.css +++ b/webapp/titanembeds/static/css/embedstyle.css @@ -301,6 +301,10 @@ body > div.navbar-fixed > nav > div { font-size: 85%; } +.input-field label { + color: white; +} + a { color: #82b1ff; } diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index a959f87..e0c78d7 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -151,14 +151,20 @@ $("#loginmodal").modal("open"); }); - $( "#theme-selector" ).change(function() { + $( "#theme-selector" ).change(function () { var theme = $("#theme-selector option:selected").val(); - changeTheme(theme); + var keep_custom_css = $("#overwrite_theme_custom_css_checkbox").is(':checked'); + changeTheme(theme, keep_custom_css); + }); + + $("#overwrite_theme_custom_css_checkbox").change(function () { + var keep_custom_css = $("#overwrite_theme_custom_css_checkbox").is(':checked'); + changeTheme(null, keep_custom_css); }); var themeparam = getParameterByName('theme'); var localstore_theme = localStorage.getItem("theme"); - if ((getParameterByName("css") == null) && ((themeparam && $.inArray(themeparam, theme_options) != -1) || (localstore_theme))) { + if ((themeparam && $.inArray(themeparam, theme_options) != -1) || (localstore_theme)) { var theme; if (themeparam) { theme = themeparam; @@ -199,15 +205,21 @@ } }); - function changeTheme(theme) { + function changeTheme(theme=null, keep_custom_css=true) { if (theme == "") { $("#css-theme").attr("href", ""); $("#user-defined-css").text(user_def_css); localStorage.removeItem("theme"); - } else if ($.inArray(theme, theme_options) != -1) { - $("#user-defined-css").text(""); - $("#css-theme").attr("href", "/static/themes/" + theme + "/css/style.css"); - localStorage.setItem("theme", theme); + } else if ($.inArray(theme, theme_options) != -1 || theme == null) { + if (!keep_custom_css) { + $("#user-defined-css").text(""); + } else { + $("#user-defined-css").text(user_def_css); + } + if (theme) { + $("#css-theme").attr("href", "/static/themes/" + theme + "/css/style.css"); + localStorage.setItem("theme", theme); + } } } diff --git a/webapp/titanembeds/templates/embed.html.j2 b/webapp/titanembeds/templates/embed.html.j2 index 5c81b29..0ca7dd1 100644 --- a/webapp/titanembeds/templates/embed.html.j2 +++ b/webapp/titanembeds/templates/embed.html.j2 @@ -134,6 +134,10 @@ +

+ + +

From 146b2311cf7b4f7aaf0f6efa7a1b287492f0cbce Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Wed, 9 Aug 2017 01:26:11 +0000 Subject: [PATCH 060/135] Support for dynamic default channel --- webapp/titanembeds/static/js/embed.js | 27 +++++++++++-------- .../templates/card_queryparams.html.j2 | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index e0c78d7..d9801f9 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -18,7 +18,7 @@ var fetchtimeout; // fetch routine timer var currently_fetching; // fetch lock- if true, do not fetch var last_message_id; // last message tracked - var selected_channel = guild_id; // user selected channel, defaults to #general channel + var selected_channel = null; // user selected channel var guild_channels = {}; // all server channels used to highlight channels in messages var emoji_store = {}; // all server emojis var times_fetched = 0; // kept track of how many times that it has fetched @@ -315,7 +315,6 @@ for (var i = 0; i < guildchannels.length; i++) { if (guildchannels[i].channel.id == defaultChannel) { selected_channel = defaultChannel; - debugger; return; } } @@ -335,6 +334,7 @@ var template = $('#mustache_channellistings').html(); Mustache.parse(template); $("#channels-list").empty(); + var curr_default_channel = selected_channel; for (var i = 0; i < channels.length; i++) { var chan = channels[i]; guild_channels[chan.channel.id] = chan; @@ -344,18 +344,23 @@ $("#channel-" + chan.channel.id.toString()).click({"channel_id": chan.channel.id.toString()}, function(event) { select_channel(event.data.channel_id); }); - if (chan.channel.id == selected_channel) { - if (chan.write) { - $("#messagebox").prop('disabled', false); - $("#messagebox").prop('placeholder', "Enter message"); - } else { - $("#messagebox").prop('disabled', true); - $("#messagebox").prop('placeholder', "Messages is disabled in this channel."); - } - $("#channeltopic").text(chan.channel.topic); + if (!selected_channel && (!curr_default_channel || chan.channel.position < curr_default_channel.channel.position)) { + curr_default_channel = chan; } } } + if (typeof curr_default_channel == "object") { + selected_channel = curr_default_channel.channel.id; + } + var this_channel = guild_channels[selected_channel]; + if (this_channel.write) { + $("#messagebox").prop('disabled', false); + $("#messagebox").prop('placeholder', "Enter message"); + } else { + $("#messagebox").prop('disabled', true); + $("#messagebox").prop('placeholder', "Messages is disabled in this channel."); + } + $("#channeltopic").text(this_channel.channel.topic); $("#channel-"+selected_channel).parent().addClass("active"); } diff --git a/webapp/titanembeds/templates/card_queryparams.html.j2 b/webapp/titanembeds/templates/card_queryparams.html.j2 index d4e7bbd..7b26578 100644 --- a/webapp/titanembeds/templates/card_queryparams.html.j2 +++ b/webapp/titanembeds/templates/card_queryparams.html.j2 @@ -15,7 +15,7 @@
  • defaultchannel=<snowflake>
    - Instead of having your #general channel as the first channel your users see, you may change it. Enable Discord's Developer mode in the Appearances tab of the User Settings and copy the channel ID.
    + Instead of having the top channel as the first channel your users see, you may change it. Enable Discord's Developer mode in the Appearances tab of the User Settings and copy the channel ID.
    Eg: defaultchannel=1234567890
  • From 08a90c8a510e1d5de1ba3ac44a28f83a0d7f278d Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Wed, 9 Aug 2017 01:31:20 +0000 Subject: [PATCH 061/135] Handle 404 fetch if the channel id doesnt exist --- webapp/titanembeds/blueprints/api/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 11a315e..43918ff 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -333,6 +333,8 @@ def fetch(): session['user_keys'].pop(guild_id, None) else: chan = filter_guild_channel(guild_id, channel_id) + if not chan: + abort(404) if not chan.get("read"): status_code = 401 else: @@ -352,6 +354,8 @@ def fetch_visitor(): abort(403) messages = {} chan = filter_guild_channel(guild_id, channel_id, True) + if not chan: + abort(404) if not chan.get("read"): status_code = 401 else: From 0ece2b56e7c032570d1bd211b0b0434d3c2a56be Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Wed, 9 Aug 2017 02:07:13 +0000 Subject: [PATCH 062/135] Set default fallback if defaultchannel query parameter is locked for the user --- webapp/titanembeds/static/js/embed.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index d9801f9..53d50a7 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -314,6 +314,9 @@ } for (var i = 0; i < guildchannels.length; i++) { if (guildchannels[i].channel.id == defaultChannel) { + if (!guildchannels[i].read) { + return; + } selected_channel = defaultChannel; return; } From 26c0e7444fcb68a2ff08661d6eeb0e771450c797 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Wed, 9 Aug 2017 04:40:12 +0000 Subject: [PATCH 063/135] Fixes #30, member list hoisting, hopefully. --- webapp/titanembeds/blueprints/api/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 43918ff..c796a73 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -262,10 +262,14 @@ def get_online_discord_users(guild_id, embed): member["hoist-role"] = None member["color"] = None if apimem: - for roleid in reversed(apimem["roles"]): + mem_roles = [] + for roleid in apimem["roles"]: role = guildroles_filtered.get(roleid) if not role: continue + mem_roles.append(role) + mem_roles = sorted(mem_roles, key=lambda k: k['position']) + for role in mem_roles: if role["color"] != 0: member["color"] = '{0:02x}'.format(role["color"]) #int to hex if role["hoist"]: From b3d5a7e17180ad1d51734a5c6cb704e786b23ec8 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Wed, 9 Aug 2017 06:58:28 +0000 Subject: [PATCH 064/135] Escape user css in the dashboard. Should fix if brackets show up in the css code --- webapp/titanembeds/templates/usercss.html.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/titanembeds/templates/usercss.html.j2 b/webapp/titanembeds/templates/usercss.html.j2 index 165c4bf..625abed 100644 --- a/webapp/titanembeds/templates/usercss.html.j2 +++ b/webapp/titanembeds/templates/usercss.html.j2 @@ -34,7 +34,7 @@ will have CSS cosmetic privilages removed, if caught. Please don't, we check the

    Edit your CSS code here

    -
    {% if new %}/* Enter your CSS code here! */{% else %}{{ css.css }}{% endif %}
    +
    {% if new %}/* Enter your CSS code here! */{% else %}{{ css.css|e }}{% endif %}

    From 8f56bea4cc9f145fbb559586729dc2942d773a1c Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Thu, 10 Aug 2017 01:00:14 +0000 Subject: [PATCH 065/135] Resolves #37, basic Discord markdown parsing Adds support for bold, italics, underline, strikethrough, code, and blockcode (no Syntax highlighting yet) --- webapp/titanembeds/static/css/embedstyle.css | 19 +++++++++++++++++-- webapp/titanembeds/static/js/embed.js | 12 ++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/webapp/titanembeds/static/css/embedstyle.css b/webapp/titanembeds/static/css/embedstyle.css index c77fe6b..6348717 100644 --- a/webapp/titanembeds/static/css/embedstyle.css +++ b/webapp/titanembeds/static/css/embedstyle.css @@ -169,6 +169,7 @@ body > div.navbar-fixed > nav > div { #chatcontent > p { display: table; + width: 90%; } #chatcontent > p > span { @@ -391,8 +392,7 @@ a { display: table-header-group; } .chatmessage { - display: table-footer-group; - display: inline-block; + display: inline; color: rgb(195, 196, 197); } @@ -403,4 +403,19 @@ p.mentioned { p.mentioned span.chatmessage { color: #ff5252; +} + +.chatmessage code { + background-color: gray; + color: lightgray; + border-radius: 5px; + padding: 2px; +} + +.chatmessage code.blockcode { + width: 100%; + display: inline-block; + white-space: pre-wrap; + line-height: 15px; + padding: 5px; } \ No newline at end of file diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 53d50a7..23c3c0b 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -603,6 +603,16 @@ } return message; } + + function parse_message_markdown(text) { + text = text.replace(/\*\*(.*?)\*\*/g, "$1"); + text = text.replace(/\*(.*?)\*/g, "$1"); + text = text.replace(/__(.*?)__/g, "$1"); + text = text.replace(/~~(.*?)~~/g, "$1"); + text = text.replace(/\`\`\`([^]+)\`\`\`/g, "$1"); + text = text.replace(/\`(.*?)\`/g, "$1"); + return text; + } function fill_discord_messages(messages, jumpscroll) { if (messages.length == 0) { @@ -619,11 +629,13 @@ message = parse_message_attachments(message); message = parse_channels_in_message(message); message.content = escapeHtml(message.content); + message.content = parse_message_markdown(message.content); message = parse_emoji_in_message(message); var rendered = Mustache.render(template, {"id": message.id, "full_timestamp": message.formatted_timestamp, "time": message.formatted_time, "username": message.author.username, "discriminator": message.author.discriminator, "content": nl2br(message.content)}); $("#chatcontent").append(rendered); last = message.id; handle_last_message_mention(); + $("#chatcontent p:last-child").find(".blockcode").find("br").remove(); // Remove excessive breaks in codeblocks } $("html, body").animate({ scrollTop: $(document).height() }, "slow"); $('#chatcontent').linkify({ From 98d13e9febcba488d9609468dad96e0231f5ffe3 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Thu, 10 Aug 2017 01:11:18 +0000 Subject: [PATCH 066/135] We cant forget the secret way to make italics with underscores --- webapp/titanembeds/static/js/embed.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 23c3c0b..37c5331 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -608,6 +608,7 @@ text = text.replace(/\*\*(.*?)\*\*/g, "$1"); text = text.replace(/\*(.*?)\*/g, "$1"); text = text.replace(/__(.*?)__/g, "$1"); + text = text.replace(/_(.*?)_/g, "$1"); text = text.replace(/~~(.*?)~~/g, "$1"); text = text.replace(/\`\`\`([^]+)\`\`\`/g, "$1"); text = text.replace(/\`(.*?)\`/g, "$1"); From 77bde8f258a15116e4955a5d9bdd3d1f98dc3b94 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Thu, 10 Aug 2017 02:05:05 +0000 Subject: [PATCH 067/135] Better nickname support - Username field will not be overwritten with the nickname. - Mentions work again! - User chip will show nickname instead of username if present - Authenticated embed users sidebar nickname support --- discordbot/titanembeds/database/__init__.py | 2 -- webapp/titanembeds/blueprints/api/api.py | 12 ++++++--- webapp/titanembeds/database/__init__.py | 2 +- webapp/titanembeds/database/guild_members.py | 3 +++ webapp/titanembeds/database/messages.py | 13 ++++++--- webapp/titanembeds/static/js/embed.js | 28 +++++++++++++++----- 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/discordbot/titanembeds/database/__init__.py b/discordbot/titanembeds/database/__init__.py index 8b3b2c0..1911793 100644 --- a/discordbot/titanembeds/database/__init__.py +++ b/discordbot/titanembeds/database/__init__.py @@ -69,8 +69,6 @@ class DatabaseInterface(object): def get_message_author(self, message): author = message.author - if hasattr(author, 'nick') and author.nick: - author.name = author.nick obj = { "username": author.name, "discriminator": author.discriminator, diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index c796a73..1fa4133 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -1,4 +1,4 @@ -from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties, GuildMembers, Messages, get_channel_messages, list_all_guild_members, get_administrators_list +from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties, GuildMembers, Messages, get_channel_messages, list_all_guild_members, get_guild_member, get_administrators_list from titanembeds.decorators import valid_session_required, discord_users_only from titanembeds.utils import check_guild_existance, guild_accepts_visitors, guild_query_unauth_users_bool, get_client_ipaddr, discord_api, rate_limiter, channel_ratelimit_key, guild_ratelimit_key from titanembeds.oauth import user_has_permission, generate_avatar_url, check_user_can_administrate_guild @@ -60,6 +60,7 @@ def update_user_status(guild_id, username, user_key=None): 'manage_embed': False, 'ip_address': ip_address, 'username': username, + 'nickname': None, 'user_key': user_key, 'guild_id': guild_id, 'user_id': session['user_id'], @@ -81,6 +82,7 @@ def update_user_status(guild_id, username, user_key=None): 'avatar': session["avatar"], 'manage_embed': check_user_can_administrate_guild(guild_id), 'username': username, + 'nickname': None, 'discriminator': session['discriminator'], 'guild_id': guild_id, 'user_id': session['user_id'], @@ -89,6 +91,9 @@ def update_user_status(guild_id, username, user_key=None): } if status['banned'] or status['revoked']: return status + dbMember = get_guild_member(guild_id, status["user_id"]) + if dbMember: + status["nickname"] = dbMember.nickname dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == status['user_id'])).first() dbUser.bumpTimestamp() return status @@ -296,6 +301,7 @@ def get_online_embed_users(guild_id): meta = { 'id': usrdb.user_id, 'username': usrdb.username, + 'nickname': usrdb.nickname, 'discriminator': usrdb.discriminator, 'avatar_url': generate_avatar_url(usrdb.user_id, usrdb.avatar), } @@ -342,7 +348,7 @@ def fetch(): if not chan.get("read"): status_code = 401 else: - messages = get_channel_messages(channel_id, after_snowflake) + messages = get_channel_messages(guild_id, channel_id, after_snowflake) status_code = 200 response = jsonify(messages=messages, status=status) response.status_code = status_code @@ -363,7 +369,7 @@ def fetch_visitor(): if not chan.get("read"): status_code = 401 else: - messages = get_channel_messages(channel_id, after_snowflake) + messages = get_channel_messages(guild_id, channel_id, after_snowflake) status_code = 200 response = jsonify(messages=messages) response.status_code = status_code diff --git a/webapp/titanembeds/database/__init__.py b/webapp/titanembeds/database/__init__.py index 6e6bbf0..7a2d9b3 100644 --- a/webapp/titanembeds/database/__init__.py +++ b/webapp/titanembeds/database/__init__.py @@ -6,7 +6,7 @@ from guilds import Guilds from unauthenticated_users import UnauthenticatedUsers from unauthenticated_bans import UnauthenticatedBans from authenticated_users import AuthenticatedUsers -from guild_members import GuildMembers, list_all_guild_members +from guild_members import GuildMembers, list_all_guild_members, get_guild_member from keyvalue_properties import KeyValueProperties, set_keyvalproperty, get_keyvalproperty, getexpir_keyvalproperty, setexpir_keyvalproperty, ifexists_keyvalproperty, delete_keyvalproperty from messages import Messages, get_channel_messages from cosmetics import Cosmetics diff --git a/webapp/titanembeds/database/guild_members.py b/webapp/titanembeds/database/guild_members.py index 1ea147f..1f735a1 100644 --- a/webapp/titanembeds/database/guild_members.py +++ b/webapp/titanembeds/database/guild_members.py @@ -43,3 +43,6 @@ def list_all_guild_members(guild_id): "nickname": member.nickname, }) return memlist + +def get_guild_member(guild_id, member_id): + return db.session.query(GuildMembers).filter(GuildMembers.guild_id == guild_id, GuildMembers.user_id == member_id).first() diff --git a/webapp/titanembeds/database/messages.py b/webapp/titanembeds/database/messages.py index 0142a75..16f1f04 100644 --- a/webapp/titanembeds/database/messages.py +++ b/webapp/titanembeds/database/messages.py @@ -1,4 +1,4 @@ -from titanembeds.database import db +from titanembeds.database import db, get_guild_member from sqlalchemy import cast import json @@ -29,7 +29,7 @@ class Messages(db.Model): def __repr__(self): return ''.format(self.id, self.guild_id, self.guild_id, self.channel_id, self.message_id) -def get_channel_messages(channel_id, after_snowflake=None): +def get_channel_messages(guild_id, channel_id, after_snowflake=None): if not after_snowflake: q = db.session.query(Messages).filter(Messages.channel_id == channel_id).order_by(Messages.timestamp.desc()).limit(50) else: @@ -40,7 +40,7 @@ def get_channel_messages(channel_id, after_snowflake=None): if x.message_id in snowflakes: continue snowflakes.append(x.message_id) - msgs.append({ + message = { "attachments": json.loads(x.attachments), "timestamp": x.timestamp, "id": x.message_id, @@ -49,5 +49,10 @@ def get_channel_messages(channel_id, after_snowflake=None): "content": x.content, "channel_id": x.channel_id, "mentions": json.loads(x.mentions) - }) + } + member = get_guild_member(guild_id, message["author"]["id"]) + message["author"]["nickname"] = None + if member: + message["author"]["nickname"] = member.nickname + msgs.append(message) return msgs diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 37c5331..64e5de5 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -447,7 +447,11 @@ $("#embed-discord-members-count").html(users.length); for (var i = 0; i < users.length; i++) { var member = users[i]; - var rendered = Mustache.render(template, {"id": member.id.toString() + "a", "username": member.username, "avatar": member.avatar_url}); + var username = member.username; + if (member.nickname) { + username = member.nickname; + } + var rendered = Mustache.render(template, {"id": member.id.toString() + "a", "username": username, "avatar": member.avatar_url}); $("#embed-discord-members").append(rendered); $( "#discorduser-" + member.id.toString() + "a").click({"member_id": member.id.toString()}, function(event) { mention_member(event.data.member_id); @@ -632,7 +636,11 @@ message.content = escapeHtml(message.content); message.content = parse_message_markdown(message.content); message = parse_emoji_in_message(message); - var rendered = Mustache.render(template, {"id": message.id, "full_timestamp": message.formatted_timestamp, "time": message.formatted_time, "username": message.author.username, "discriminator": message.author.discriminator, "content": nl2br(message.content)}); + var username = message.author.username; + if (message.author.nickname) { + username = message.author.nickname; + } + var rendered = Mustache.render(template, {"id": message.id, "full_timestamp": message.formatted_timestamp, "time": message.formatted_time, "username": username, "discriminator": message.author.discriminator, "content": nl2br(message.content)}); $("#chatcontent").append(rendered); last = message.id; handle_last_message_mention(); @@ -667,9 +675,9 @@ fet.done(function(data) { var status = data.status; if (visitor_mode) { - update_embed_userchip(false, null, "Titan", "0001", null); + update_embed_userchip(false, null, "Titan", null, "0001", null); } else { - update_embed_userchip(status.authenticated, status.avatar, status.username, status.user_id, status.discriminator); + update_embed_userchip(status.authenticated, status.avatar, status.username, status.nickname, status.user_id, status.discriminator); } last_message_id = fill_discord_messages(data.messages, jumpscroll); if (!visitor_mode && status.manage_embed) { @@ -723,18 +731,24 @@ }); } - function update_embed_userchip(authenticated, avatar, username, userid, discrim=null) { + function update_embed_userchip(authenticated, avatar, username, nickname, userid, discrim=null) { if (authenticated) { $("#currentuserimage").show(); $("#currentuserimage").attr("src", avatar); $("#curuser_name").text(username); $("#curuser_discrim").text("#" + discrim); - current_username_discrim = username + "#" + discrim; + current_username_discrim = "#" + discrim; } else { $("#currentuserimage").hide(); $("#curuser_name").text(username); $("#curuser_discrim").text("#" + userid); - current_username_discrim = username + "#" + userid; + current_username_discrim = "#" + userid; + } + if (nickname) { + $("#curuser_name").text(nickname); + current_username_discrim = nickname + current_username_discrim; + } else { + current_username_discrim = username + current_username_discrim; } } From 68d8882d3e786399b98f740941c27882b1015d88 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Thu, 10 Aug 2017 02:56:45 +0000 Subject: [PATCH 068/135] Fix mentions with nicknames --- discordbot/titanembeds/database/__init__.py | 2 -- webapp/titanembeds/database/messages.py | 5 +++++ webapp/titanembeds/static/js/embed.js | 8 ++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/discordbot/titanembeds/database/__init__.py b/discordbot/titanembeds/database/__init__.py index 1911793..3c22c11 100644 --- a/discordbot/titanembeds/database/__init__.py +++ b/discordbot/titanembeds/database/__init__.py @@ -81,8 +81,6 @@ class DatabaseInterface(object): def get_message_mentions(self, mentions): ments = [] for author in mentions: - if author.nick: - author.name = author.nick ments.append({ "username": author.name, "discriminator": author.discriminator, diff --git a/webapp/titanembeds/database/messages.py b/webapp/titanembeds/database/messages.py index 16f1f04..fbe497f 100644 --- a/webapp/titanembeds/database/messages.py +++ b/webapp/titanembeds/database/messages.py @@ -54,5 +54,10 @@ def get_channel_messages(guild_id, channel_id, after_snowflake=None): message["author"]["nickname"] = None if member: message["author"]["nickname"] = member.nickname + for mention in message["mentions"]: + author = get_guild_member(guild_id, mention["id"]) + mention["nickname"] = None + if author: + mention["nickname"] = author.nickname msgs.append(message) return msgs diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 64e5de5..cc20b0b 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -510,8 +510,12 @@ var mentions = message.mentions; for (var i = 0; i < mentions.length; i++) { var mention = mentions[i]; - message.content = message.content.replace(new RegExp("<@" + mention.id + ">", 'g'), "@" + mention.username + "#" + mention.discriminator); - message.content = message.content.replace(new RegExp("<@!" + mention.id + ">", 'g'), "@" + mention.username + "#" + mention.discriminator); + var username = mention.username; + if (mention.nickname) { + username = mention.nickname; + } + message.content = message.content.replace(new RegExp("<@" + mention.id + ">", 'g'), "@" + username + "#" + mention.discriminator); + message.content = message.content.replace(new RegExp("<@!" + mention.id + ">", 'g'), "@" + username + "#" + mention.discriminator); message.content = message.content.replace("<@&" + guild_id + ">", "@everyone"); } return message; From a165722856edd0122d223018e402bf35aded8448 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Thu, 10 Aug 2017 21:27:08 +0000 Subject: [PATCH 069/135] Guest username changing support --- webapp/titanembeds/blueprints/api/api.py | 34 ++++++++++++++ webapp/titanembeds/static/js/embed.js | 53 ++++++++++++++++++++++ webapp/titanembeds/templates/embed.html.j2 | 10 ++++ 3 files changed, 97 insertions(+) diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 1fa4133..633148b 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -467,6 +467,40 @@ def create_unauthenticated_user(): response.status_code = 403 return response +@api.route("/change_unauthenticated_username", methods=["POST"]) +@rate_limiter.limit("1 per 15 minute", key_func=guild_ratelimit_key) +def change_unauthenticated_username(): + username = request.form['username'] + guild_id = request.form['guild_id'] + ip_address = get_client_ipaddr() + username = username.strip() + if len(username) < 2 or len(username) > 32: + abort(406) + if not all(x.isalnum() or x.isspace() or "-" == x or "_" == x for x in username): + abort(406) + if not check_guild_existance(guild_id): + abort(404) + if not guild_query_unauth_users_bool(guild_id): + abort(401) + if not checkUserBanned(guild_id, ip_address): + if 'user_keys' not in session or guild_id not in session['user_keys'] or not session['unauthenticated']: + abort(424) + session['username'] = username + if 'user_id' not in session or len(str(session["user_id"])) > 4: + session['user_id'] = random.randint(0,9999) + user = UnauthenticatedUsers(guild_id, username, session['user_id'], ip_address) + db.session.add(user) + db.session.commit() + key = user.user_key + session['user_keys'][guild_id] = key + status = update_user_status(guild_id, username, key) + return jsonify(status=status) + else: + status = {'banned': True} + response = jsonify(status=status) + response.status_code = 403 + return response + def process_query_guild(guild_id, visitor=False): widget = discord_api.get_widget(guild_id) channels = get_guild_channels(guild_id, visitor) diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index cc20b0b..34d0854 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -76,6 +76,16 @@ }); return funct.promise(); } + + function change_unauthenticated_username(username) { + var funct = $.ajax({ + method: "POST", + dataType: "json", + url: "/api/change_unauthenticated_username", + data: {"username": username, "guild_id": guild_id} + }); + return funct.promise(); + } function fetch(channel_id, after=null) { var url = "/api/fetch"; @@ -680,8 +690,10 @@ var status = data.status; if (visitor_mode) { update_embed_userchip(false, null, "Titan", null, "0001", null); + update_change_username_modal(); } else { update_embed_userchip(status.authenticated, status.avatar, status.username, status.nickname, status.user_id, status.discriminator); + update_change_username_modal(status.authenticated, status.username); } last_message_id = fill_discord_messages(data.messages, jumpscroll); if (!visitor_mode && status.manage_embed) { @@ -755,6 +767,19 @@ current_username_discrim = username + current_username_discrim; } } + + function update_change_username_modal(authenticated=false, username=null) { + if (!$("#change_username_field") || $("#change_username_field").is(":focus")) { + return; + } + if (authenticated || visitor_mode) { + $("#change_username_field").attr("disabled", true); + $("#change_username_field").val(""); + } else { + $("#change_username_field").attr("disabled", false); + $("#change_username_field").val(username); + } + } $("#discordlogin_btn").click(function() { lock_login_fields(); @@ -788,6 +813,34 @@ } } }); + + $("#change_username_field").keyup(function(event){ + if (event.keyCode == 13) { + $(this).blur(); + if (!(new RegExp(/^[a-z\d\-_\s]+$/i).test($(this).val()))) { + Materialize.toast('Illegal username provided! Only alphanumeric, spaces, dashes, and underscores allowed in usernames.', 10000); + return; + } + if(($(this).val().length >= 2 && $(this).val().length <= 32) && $("#curuser_name").text() != $(this).val()) { + var usr = change_unauthenticated_username($(this).val()); + usr.done(function(data) { + Materialize.toast('Username changed successfully!', 10000); + initialize_embed(); + }); + usr.fail(function(data) { + if (data.status == 429) { + Materialize.toast('Sorry! You are allowed to change your username once every 15 minutes.', 10000); + } else if (data.status == 403) { + Materialize.toast('Authentication error! You have been banned.', 10000); + } else if (data.status == 406) { + Materialize.toast('Illegal username provided! Only alphanumeric, spaces, dashes, and underscores allowed in usernames.', 10000); + } else { + Materialize.toast('Something unexpected happened! Error code of ' + data.status, 10000); + } + }); + } + } + }); $("#messagebox").keyup(function(event){ if ($(this).val().length == 1) { diff --git a/webapp/titanembeds/templates/embed.html.j2 b/webapp/titanembeds/templates/embed.html.j2 index 0ca7dd1..ce00a98 100644 --- a/webapp/titanembeds/templates/embed.html.j2 +++ b/webapp/titanembeds/templates/embed.html.j2 @@ -126,6 +126,16 @@ +
    + tag_faces +
    From d363b6679918bed0e792eb1eee2593eecd7570e4 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sun, 13 Aug 2017 11:19:43 +0000 Subject: [PATCH 072/135] Added a patreon banner link to the index and dashboard --- webapp/titanembeds/templates/dashboard.html.j2 | 1 + webapp/titanembeds/templates/index.html.j2 | 1 + webapp/titanembeds/templates/patreon_banner.html.j2 | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 webapp/titanembeds/templates/patreon_banner.html.j2 diff --git a/webapp/titanembeds/templates/dashboard.html.j2 b/webapp/titanembeds/templates/dashboard.html.j2 index c13e606..eb12152 100644 --- a/webapp/titanembeds/templates/dashboard.html.j2 +++ b/webapp/titanembeds/templates/dashboard.html.j2 @@ -2,6 +2,7 @@ {% set title="Dashboard" %} {% block content %} +{% include 'patreon_banner.html.j2' %}

    User Dashboard

    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.

    diff --git a/webapp/titanembeds/templates/index.html.j2 b/webapp/titanembeds/templates/index.html.j2 index 92b853a..43d2515 100644 --- a/webapp/titanembeds/templates/index.html.j2 +++ b/webapp/titanembeds/templates/index.html.j2 @@ -2,6 +2,7 @@ {% set title="Index" %} {% block content %} +{% include 'patreon_banner.html.j2' %}

    Embed Discord like a
    true Titan

    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 new file mode 100644 index 0000000..7935920 --- /dev/null +++ b/webapp/titanembeds/templates/patreon_banner.html.j2 @@ -0,0 +1,3 @@ + \ No newline at end of file From 8bc95f7b0ead7e67ef5435667007248cb8c51b51 Mon Sep 17 00:00:00 2001 From: "Jeremy \"EndenDragon\" Zhang" Date: Sun, 13 Aug 2017 18:10:03 -0700 Subject: [PATCH 073/135] Paypal (#41) * Implement Titan Tokens clientside * Titan Tokens can be modified in the admin panel --- .../2a2f32ac91d6_added_titan_tokens.py | 160 ++++++++++++++++++ webapp/config.example.py | 4 + webapp/titanembeds/blueprints/admin/admin.py | 51 +++++- webapp/titanembeds/blueprints/user/user.py | 75 +++++++- webapp/titanembeds/database/__init__.py | 23 ++- webapp/titanembeds/database/titan_tokens.py | 18 ++ .../database/token_transactions.py | 21 +++ .../static/js/admin_token_transactions.js | 56 ++++++ webapp/titanembeds/static/js/donate.js | 19 +++ .../titanembeds/templates/admin_index.html.j2 | 7 + .../admin_token_transactions.html.j2 | 105 ++++++++++++ .../titanembeds/templates/dashboard.html.j2 | 16 +- webapp/titanembeds/templates/donate.html.j2 | 51 ++++++ .../templates/donate_thanks.html.j2 | 18 ++ webapp/titanembeds/templates/index.html.j2 | 1 - .../templates/patreon_banner.html.j2 | 3 - 16 files changed, 618 insertions(+), 10 deletions(-) create mode 100644 webapp/alembic/versions/2a2f32ac91d6_added_titan_tokens.py create mode 100644 webapp/titanembeds/database/titan_tokens.py create mode 100644 webapp/titanembeds/database/token_transactions.py create mode 100644 webapp/titanembeds/static/js/admin_token_transactions.js create mode 100644 webapp/titanembeds/static/js/donate.js create mode 100644 webapp/titanembeds/templates/admin_token_transactions.html.j2 create mode 100644 webapp/titanembeds/templates/donate.html.j2 create mode 100644 webapp/titanembeds/templates/donate_thanks.html.j2 delete mode 100644 webapp/titanembeds/templates/patreon_banner.html.j2 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
    +
    +
    +

    Titan Tokens

    +

    View transactions and modify Titan Tokens per user.

    + Manage +
    +

    Run a Database Cleanup

    diff --git a/webapp/titanembeds/templates/admin_token_transactions.html.j2 b/webapp/titanembeds/templates/admin_token_transactions.html.j2 new file mode 100644 index 0000000..c81d779 --- /dev/null +++ b/webapp/titanembeds/templates/admin_token_transactions.html.j2 @@ -0,0 +1,105 @@ +{% extends 'site_layout.html.j2' %} +{% set title="Editing User Titan Tokens" %} + +{% block content %} +

    Administrating Titan Tokens

    + +
    +
    +
    +

    New Entry

    + + + + + + + + + + + + + + + +
    User IDStarting BalanceSubmit
    +
    + +
    +
    +
    + +
    +
    + Submit +
    +
    +
    +
    +
    +

    View Transactions and Modify User Tokens

    +
      + {% for don in donators %} +
    • +
      {{ don.user_id }}
      +
      + + + + + + + + + + + + + + +
      Modify AmountSubmit
      +
      + +
      +

      (Place a subtract sign in the front to remove tokens. Otherwise, it will add the amount)

      +
      + Submit +
      + +

      Balance: {{ don.tokens }} Tokens

      + + + + + + + + + + + + + {% for trans in don.transactions %} + + + + + + + + + {% endfor %} + +
      Trans #TimestampActionChangeStarting BalEnding Bal
      {{ trans.id }}{{ trans.timestamp }}{{ trans.action }}.{{ trans.net_tokens }}{{ trans.start_tokens }}{{ trans.end_tokens }}
      +
      +
    • + {% endfor %} +
    +
    +
    +
    +{% endblock %} +{% block script %} + +{% endblock %} diff --git a/webapp/titanembeds/templates/dashboard.html.j2 b/webapp/titanembeds/templates/dashboard.html.j2 index eb12152..70ec2df 100644 --- a/webapp/titanembeds/templates/dashboard.html.j2 +++ b/webapp/titanembeds/templates/dashboard.html.j2 @@ -2,7 +2,6 @@ {% set title="Dashboard" %} {% block content %} -{% include 'patreon_banner.html.j2' %}

    User Dashboard

    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 %}
    {% endif %} -{% endblock %} + +
    + +
    +
    +
    +

    Donations!

    +

    Would you like to support the Titan Embeds project?

    +

    You currently have {{ tokens }} Titan Tokens.

    + Donate!! +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/webapp/titanembeds/templates/donate.html.j2 b/webapp/titanembeds/templates/donate.html.j2 new file mode 100644 index 0000000..d3f2093 --- /dev/null +++ b/webapp/titanembeds/templates/donate.html.j2 @@ -0,0 +1,51 @@ +{% extends 'site_layout.html.j2' %} +{% set title="Donate" %} + +{% block content %} +

    Donate and Support Titan Embeds

    +

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

    + +
    +
    +
    +

    The Name-Your-Price Tool

    +

    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!

    + +
    +
    +
    +{% endblock %} +{% block script %} + +{% endblock %} + +{% block additional_head_elements %} + +{% endblock %} \ No newline at end of file diff --git a/webapp/titanembeds/templates/donate_thanks.html.j2 b/webapp/titanembeds/templates/donate_thanks.html.j2 new file mode 100644 index 0000000..b32cb62 --- /dev/null +++ b/webapp/titanembeds/templates/donate_thanks.html.j2 @@ -0,0 +1,18 @@ +{% extends 'site_layout.html.j2' %} +{% set title="Thanks for Donating" %} + +{% block content %} +

    Thank you for Donating and Supporting the Titan Embeds project!

    + +
    +
    +
    +

    You're officially one step closer to becoming a True Titan!

    +

    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!

    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/webapp/titanembeds/templates/index.html.j2 b/webapp/titanembeds/templates/index.html.j2 index 43d2515..92b853a 100644 --- a/webapp/titanembeds/templates/index.html.j2 +++ b/webapp/titanembeds/templates/index.html.j2 @@ -2,7 +2,6 @@ {% set title="Index" %} {% block content %} -{% include 'patreon_banner.html.j2' %}

    Embed Discord like a
    true Titan

    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 From 0d87e11e3e3c5500d1fe9ca88b9cef444cbbdca5 Mon Sep 17 00:00:00 2001 From: "Jeremy \"EndenDragon\" Zhang" Date: Sun, 13 Aug 2017 18:11:17 -0700 Subject: [PATCH 074/135] Add paypalrestsdk to requirements.txt --- webapp/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/requirements.txt b/webapp/requirements.txt index 402aba3..1c9a4e6 100644 --- a/webapp/requirements.txt +++ b/webapp/requirements.txt @@ -3,3 +3,4 @@ flask-sqlalchemy flask_limiter requests_oauthlib Flask-SSLify +paypalrestsdk From fc05f3dda2827c3c4ff192b9bb586ccff5061b00 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Mon, 14 Aug 2017 05:43:25 +0000 Subject: [PATCH 075/135] Token count in menu --- webapp/titanembeds/blueprints/user/user.py | 9 +++++---- webapp/titanembeds/templates/dashboard.html.j2 | 2 +- webapp/titanembeds/templates/site_layout.html.j2 | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/webapp/titanembeds/blueprints/user/user.py b/webapp/titanembeds/blueprints/user/user.py index eac354b..4024a22 100644 --- a/webapp/titanembeds/blueprints/user/user.py +++ b/webapp/titanembeds/blueprints/user/user.py @@ -41,6 +41,9 @@ def callback(): session['username'] = user['username'] session['discriminator'] = user['discriminator'] session['avatar'] = generate_avatar_url(user['id'], user['avatar']) + session["tokens"] = get_titan_token(session["user_id"]) + if session["tokens"] == -1: + session["tokens"] = 0 if session["redirect"]: redir = session["redirect"] session['redirect'] = None @@ -72,10 +75,7 @@ def dashboard(): css_list = None if cosmetics and cosmetics.css: css_list = db.session.query(UserCSS).filter(UserCSS.user_id == session['user_id']).all() - 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) + return render_template("dashboard.html.j2", servers=guilds, icon_generate=generate_guild_icon_url, cosmetics=cosmetics, css_list=css_list) @user.route("/custom_css/new", methods=["GET"]) @discord_users_only() @@ -383,6 +383,7 @@ def donate_confirm(): tokens = int(amount * 100) action = "PAYPAL {}".format(trans_id) set_titan_token(session["user_id"], tokens, action) + session["tokens"] = get_titan_token(session["user_id"]) return redirect(url_for('user.donate_thanks', transaction=trans_id)) else: return redirect(url_for('index')) diff --git a/webapp/titanembeds/templates/dashboard.html.j2 b/webapp/titanembeds/templates/dashboard.html.j2 index 70ec2df..4246208 100644 --- a/webapp/titanembeds/templates/dashboard.html.j2 +++ b/webapp/titanembeds/templates/dashboard.html.j2 @@ -68,7 +68,7 @@

    Donations!

    Would you like to support the Titan Embeds project?

    -

    You currently have {{ tokens }} Titan Tokens.

    +

    You currently have {{ session["tokens"] }} Titan Tokens.

    Donate!!
    diff --git a/webapp/titanembeds/templates/site_layout.html.j2 b/webapp/titanembeds/templates/site_layout.html.j2 index d89a40b..02e84fb 100644 --- a/webapp/titanembeds/templates/site_layout.html.j2 +++ b/webapp/titanembeds/templates/site_layout.html.j2 @@ -28,6 +28,8 @@ {% if session['unauthenticated'] is defined and not session['unauthenticated'] %}