diff --git a/htmltests/embedpagetest.html b/htmltests/embedpagetest.html new file mode 100644 index 0000000..0f30d27 --- /dev/null +++ b/htmltests/embedpagetest.html @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + Embed - Titan Embeds for Discord + + + +
+
+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 adfsadfsdfas

+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/htmltests/titan frontend test.html b/htmltests/titan frontend test.html new file mode 100644 index 0000000..bf47628 --- /dev/null +++ b/htmltests/titan frontend test.html @@ -0,0 +1,113 @@ + + + + + + + + + + + + + Index - Titan Embeds for Discord + + +
+ + +
+

Embed Discord chats like a
true Titan

+

Add Titan to your discord server to create your own personalized chat embed!

+ Start here! +

+
+ +
+
+
+ + + + + + + diff --git a/titanembeds/app.py b/titanembeds/app.py index e4c3573..48de811 100644 --- a/titanembeds/app.py +++ b/titanembeds/app.py @@ -12,6 +12,7 @@ os.chdir(config['app-location']) app = Flask(__name__, static_folder="static") app.config['SQLALCHEMY_DATABASE_URI'] = config['database-uri'] app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no need this on for now. +app.config['RATELIMIT_HEADERS_ENABLED'] = True app.secret_key = config['app-secret'] db.init_app(app) diff --git a/titanembeds/blueprints/api/api.py b/titanembeds/blueprints/api/api.py index 77ae52d..fcc1508 100644 --- a/titanembeds/blueprints/api/api.py +++ b/titanembeds/blueprints/api/api.py @@ -1,6 +1,6 @@ from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers from titanembeds.decorators import valid_session_required, discord_users_only -from titanembeds.utils import get_client_ipaddr, discord_api, rate_limiter, channel_ratelimit_key, guild_ratelimit_key, cache, make_guildchannels_cache_key +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, cache, make_guildchannels_cache_key from titanembeds.oauth import user_has_permission, generate_avatar_url from flask import Blueprint, abort, jsonify, session, request from sqlalchemy import and_ @@ -49,16 +49,6 @@ def checkUserBanned(guild_id, ip_address=None): return True return banned -def check_guild_existance(guild_id): - dbGuild = Guilds.query.filter_by(guild_id=guild_id).first() - if not dbGuild: - return False - guilds = discord_api.get_all_guilds() - for guild in guilds: - if guild_id == guild['id']: - return True - return False - def update_user_status(guild_id, username, user_key=None): if user_unauthenticated(): ip_address = get_client_ipaddr() @@ -88,7 +78,7 @@ def update_user_status(guild_id, username, user_key=None): } if status['banned'] or status['revoked']: return status - dbUser = db.session.query(AuthenticatedUsers).filter(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == status['user_id']).first() + dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == status['user_id'])).first() dbUser.bumpTimestamp() return status @@ -144,17 +134,14 @@ def get_online_embed_users(guild_id): client_id = user.client_id u = discord_api.get_guild_member(guild_id, client_id)['content']['user'] meta = { + 'id': u['id'], 'username': u['username'], 'discriminator': u['discriminator'], - 'avatar': generate_avatar_url(u['id'], u['avatar']), + 'avatar_url': generate_avatar_url(u['id'], u['avatar']), } users['authenticated'].append(meta) return users -def guild_query_unauth_users_bool(guild_id): - dbGuild = Guilds.query.filter_by(guild_id=guild_id).first() - return dbGuild.unauth_users - @api.route("/fetch", methods=["GET"]) @valid_session_required(api=True) @rate_limiter.limit("2500/hour") @@ -174,7 +161,7 @@ def fetch(): messages = discord_api.get_channel_messages(channel_id, after_snowflake) status_code = messages['code'] response = jsonify(messages=messages.get('content', messages), status=status) - resonse.status_code = status_code + response.status_code = status_code return response @api.route("/post", methods=["POST"]) @@ -194,7 +181,7 @@ def post(): status_code = 401 else: message = discord_api.create_message(channel_id, content) - status_code = messages['code'] + status_code = message['code'] response = jsonify(message=message.get('content', message), status=status) response.status_code = status_code return response @@ -255,7 +242,7 @@ def create_authenticated_user(): if not check_guild_existance(guild_id): abort(404) if not checkUserBanned(guild_id): - db_user = db.session.query(AuthenticatedUsers).filter(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == session['user_id']).first() + db_user = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == session['user_id'])).first() if not db_user: db_user = AuthenticatedUsers(guild_id, session['user_id']) db.session.add(db_user) @@ -263,7 +250,7 @@ def create_authenticated_user(): if not check_user_in_guild(guild_id): discord_api.add_guild_member(guild_id, session['user_id'], session['user_keys']['access_token']) status = update_user_status(guild_id, session['username']) - return jsonify(error=False) + return jsonify(status=status) else: status = {'banned': True} response = jsonify(status=status) diff --git a/titanembeds/blueprints/embed/embed.py b/titanembeds/blueprints/embed/embed.py index 2c1d31a..60362f9 100644 --- a/titanembeds/blueprints/embed/embed.py +++ b/titanembeds/blueprints/embed/embed.py @@ -1,7 +1,40 @@ -from flask import Blueprint +from flask import Blueprint, render_template, abort, redirect, url_for, session +from titanembeds.utils import check_guild_existance, discord_api, guild_query_unauth_users_bool +from titanembeds.oauth import generate_guild_icon_url, generate_avatar_url, check_user_can_administrate_guild +import random embed = Blueprint("embed", __name__) -@embed.route("/") +def get_logingreeting(): + greetings = [ + "Let's get to know each other! My name is Titan, what's yours?", + "Hello and welcome!", + "What brings you here today?", + "....what do you expect this text to say?", + "Aha! ..made you look!", + "Initiating launch sequence...", + "Captain, what's your option?", + "Alright, here's the usual~", + ] + return random.choice(greetings) + +@embed.route("/") def guild_embed(guild_id): - return guild_id + print guild_id + if check_guild_existance(guild_id): + guild = discord_api.get_guild(guild_id)['content'] + return render_template("embed.html.j2", + login_greeting=get_logingreeting(), + guild_id=guild_id, guild=guild, + generate_guild_icon=generate_guild_icon_url, + unauth_enabled=guild_query_unauth_users_bool(guild_id) + ) + abort(404) + +@embed.route("/signin_complete") +def signin_complete(): + return render_template("signin_complete.html.j2") + +@embed.route("/login_discord") +def login_discord(): + return redirect(url_for("user.login_authenticated", redirect=url_for("embed.signin_complete", _external=True))) \ No newline at end of file diff --git a/titanembeds/blueprints/user/user.py b/titanembeds/blueprints/user/user.py index 49720ac..2ee6953 100644 --- a/titanembeds/blueprints/user/user.py +++ b/titanembeds/blueprints/user/user.py @@ -39,7 +39,7 @@ def callback(): session['avatar'] = generate_avatar_url(user['id'], user['avatar']) if session["redirect"]: redir = session["redirect"] - session.pop('redirect', None) + session['redirect'] = None return redirect(redir) return redirect(url_for("user.dashboard")) diff --git a/titanembeds/decorators.py b/titanembeds/decorators.py index ceb967b..b2908e9 100644 --- a/titanembeds/decorators.py +++ b/titanembeds/decorators.py @@ -7,7 +7,7 @@ def valid_session_required(api=False): def decorated_function(*args, **kwargs): if 'unauthenticated' not in session or 'user_id' not in session or 'username' not in session: if api: - return jsonify(error=True, message="Unauthenticated session"), 403 + return jsonify(error=True, message="Unauthenticated session"), 401 redirect(url_for('user.logout')) if session['unauthenticated'] and 'user_keys' not in session: session['user_keys'] = {} @@ -21,7 +21,7 @@ def discord_users_only(api=False): def decorated_function(*args, **kwargs): if 'unauthenticated' not in session or session['unauthenticated']: if api: - return jsonify(error=True, message="Not logged in as a discord user"), 403 + return jsonify(error=True, message="Not logged in as a discord user"), 401 return redirect(url_for("user.login_authenticated")) return f(*args, **kwargs) return decorated_function diff --git a/titanembeds/static/css/embedstyle.css b/titanembeds/static/css/embedstyle.css new file mode 100644 index 0000000..7c25399 --- /dev/null +++ b/titanembeds/static/css/embedstyle.css @@ -0,0 +1,130 @@ +html { +background-color: #455a64; +color: white; +} + +main { +min-height: calc(100vh - 80px); +} + +footer { +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; +} + +nav .brand-logo { +font-size: 1.5rem; +} + +@media only screen and (min-width: 993px) { +.container { + width: 85%; +} +} + +.side-nav { +color: white; +background-color: #607d8b; +} + +.side-nav .userView .name { +font-size: 20px; +} + +.side-nav li>a { +color: #eceff1; +} + +.side-nav .subheader { +color: #cfd8dc; +font-variant: small-caps; +} + +.divider { +background-color: #90a4ae; +} + +.channel-hash { +font-size: 95%; +color: #b0bec5; +} + +.membercircle { +margin-top: 5px; +height: 40px; +} + +.membername { +position: absolute; +padding-left: 10px; +} + +.chatcontent { +padding-left: 1%; +padding-top: 1%; +padding-bottom: 40px; +} + +@media only screen and (min-width: 601px) { +nav a.button-collapse { + display: block; +} +} + +.chatusername { +font-weight: bold; +color: #eceff1; +} + +.chattimestamp { +font-size: 10px; +color: #90a4ae; +} + +.footercontainer { +width: 100%; +position: relative; +margin: 10px; +} + +.currentuserchip { +display: inline-block; +position: relative; +top: -6px; +padding: 6px; +padding-right: 9px; +background-color: #455a64; +} + +.currentuserimage { +width: 30px; +} + +.currentusername { +position: relative; +top: 7px; +left: 5px; +} + +.input-field { +position: relative; +top: -19px; +} + +.left { +float: left; +} + +.modal { +background-color: #546e7a; +} \ No newline at end of file diff --git a/titanembeds/static/js/embed.js b/titanembeds/static/js/embed.js new file mode 100644 index 0000000..7ea8bfb --- /dev/null +++ b/titanembeds/static/js/embed.js @@ -0,0 +1,216 @@ +/* global $ */ +/* global Materialize */ +/* global Mustache */ +/* global guild_id */ + +var logintimer; // timer to keep track of user inactivity after hitting login + +function resize_messagebox() { + var namebox_width = $("#nameplate").outerWidth(true); + var screen_width = $(document).width(); + $("#messageboxouter").width(screen_width - namebox_width - 40); +} + +function query_guild() { + var funct = $.ajax({ + dataType: "json", + url: "/api/query_guild", + data: {"guild_id": guild_id} + }); + return funct.promise(); +} + +function create_authenticated_user() { + var funct = $.ajax({ + method: "POST", + dataType: "json", + url: "/api/create_authenticated_user", + data: {"guild_id": guild_id} + }); + return funct.promise(); +} + +function create_unauthenticated_user(username) { + var funct = $.ajax({ + method: "POST", + dataType: "json", + url: "/api/create_unauthenticated_user", + data: {"username": username, "guild_id": guild_id} + }); + return funct.promise(); +} + +function fetch(channel_id, after=null) { + var funct = $.ajax({ + method: "GET", + dataType: "json", + url: "/api/fetch", + data: {"channel_id": channel_id, "after": after} + }); + return funct.promise(); +} + +$(function(){ + resize_messagebox(); + + $("#loginmodal").modal({ + dismissible: false, // 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 + startingTop: '4%', // Starting top style attribute + endingTop: '10%', // Ending top style attribute + } + ); + + var guild = query_guild(); + guild.fail(function() { + $('#loginmodal').modal('open'); + }); + + guild.done(function(data) { + initialize_embed(data); + //$('#loginmodal').modal('open'); + }); +}); + +function lock_login_fields() { + $("#loginProgress").show(); + $("#discordlogin_btn").attr("disabled",true); + $("#custom_username_field").prop("disabled",true); + logintimer = setTimeout(function() { + unlock_login_fields(); + }, 60000); +} + +function unlock_login_fields() { + $("#loginProgress").hide(); + $("#discordlogin_btn").attr("disabled",false); + $("#custom_username_field").prop("disabled",false); + clearTimeout(logintimer); +} + +function initialize_embed(guildobj) { + $('#loginmodal').modal('close'); + unlock_login_fields(); + if (guildobj === undefined) { + var guild = query_guild(); + guild.done(function(data) { + prepare_guild(data); + }); + } else { + prepare_guild(guildobj); + } +} + +function prepare_guild(guildobj) { + console.log(guildobj); + fill_channels(guildobj.channels); + fill_discord_members(guildobj.discordmembers); + fill_authenticated_users(guildobj.embedmembers.authenticated); + fill_unauthenticated_users(guildobj.embedmembers.unauthenticated); +} + +function fill_channels(channels) { + var template = $('#mustache_channellistings').html(); + Mustache.parse(template); + $("#channels-list").empty(); + for (var i = 0; i < channels.length; i++) { + var chan = channels[i]; + var rendered = Mustache.render(template, {"channelid": chan.id, "channelname": chan.name}); + $("#channels-list").append(rendered); + } +} + +function fill_discord_members(discordmembers) { + var template = $('#mustache_authedusers').html(); + Mustache.parse(template); + $("#discord-members").empty(); + for (var i = 0; i < discordmembers.length; i++) { + var member = discordmembers[i]; + var rendered = Mustache.render(template, {"id": member.id, "username": member.username, "avatar": member.avatar_url}); + $("#discord-members").append(rendered); + } +} + +function fill_authenticated_users(users) { + var template = $('#mustache_authedusers').html(); + Mustache.parse(template); + $("#embed-discord-members").empty(); + for (var i = 0; i < users.length; i++) { + var member = users[i]; + var rendered = Mustache.render(template, {"id": member.id, "username": member.username, "avatar": member.avatar_url}); + $("#embed-discord-members").append(rendered); + } +} + +function fill_unauthenticated_users(users) { + var template = $('#mustache_unauthedusers').html(); + Mustache.parse(template); + $("#embed-unauth-users").empty(); + for (var i = 0; i < users.length; i++) { + var member = users[i]; + var rendered = Mustache.render(template, {"username": member.username, "discriminator": member.discriminator}); + $("#embed-unauth-users").append(rendered); + } +} + +function wait_for_discord_login() { + _wait_for_discord_login(0); +} + +function _wait_for_discord_login(index) { + setTimeout(function() { + var usr = create_authenticated_user(); + usr.done(function(data) { + initialize_embed(); + return; + }); + usr.fail(function(data) { + if (data.status == 403) { + Materialize.toast('Authentication error! You have been banned.', 10000); + } else if (index < 10) { + _wait_for_discord_login(index + 1); + } + }); + }, 5000); +} + +$("#discordlogin_btn").click(function() { + lock_login_fields(); + wait_for_discord_login(); +}); + +$("#custom_username_field").keyup(function(event){ + if(event.keyCode == 13 && $(this).val().length >= 2 && $(this).val().length <= 32) { + lock_login_fields(); + var usr = create_unauthenticated_user($(this).val()); + usr.done(function(data) { + initialize_embed(); + }); + usr.fail(function(data) { + if (data.status == 403) { + Materialize.toast('Authentication error! You have been banned.', 10000); + } + }) + } +}); + +$(window).resize(function() { + resize_messagebox(); +}); + +$('#guild-btn').sideNav({ + menuWidth: 300, // Default is 300 + edge: 'left', // Choose the horizontal origin + closeOnClick: true, // Closes side-nav on clicks, useful for Angular/Meteor + draggable: true // Choose whether you can drag to open on touch screens +} +); + +$('#members-btn').sideNav({ + menuWidth: 300, // Default is 300 + edge: 'right', // Choose the horizontal origin + draggable: true // Choose whether you can drag to open on touch screens +} +); \ No newline at end of file diff --git a/titanembeds/templates/embed.html.j2 b/titanembeds/templates/embed.html.j2 new file mode 100644 index 0000000..6b9c2d4 --- /dev/null +++ b/titanembeds/templates/embed.html.j2 @@ -0,0 +1,135 @@ + + + + + + + + + + + + + {{ guild['name'] }} - Embed - Titan Embeds for Discord + + + +
+
+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 Hello everyone!

+

3:30PM EndenDragon#69420 adfsadfsdfas

+
+
+ + + + + + + +
+
+
+
+
EndenDragon#4151
+
+
+
+
+ + + + + + + {% raw %} + + + + + + + + {% endraw %} + + + + + + + \ No newline at end of file diff --git a/titanembeds/templates/signin_complete.html.j2 b/titanembeds/templates/signin_complete.html.j2 new file mode 100644 index 0000000..b10e323 --- /dev/null +++ b/titanembeds/templates/signin_complete.html.j2 @@ -0,0 +1,12 @@ + + + + Sign in completed - Titan Embeds + + +

Sign in complete! You may now close the window.

+ + + \ No newline at end of file diff --git a/titanembeds/utils.py b/titanembeds/utils.py index 94a0296..6f285df 100644 --- a/titanembeds/utils.py +++ b/titanembeds/utils.py @@ -1,3 +1,4 @@ +from titanembeds.database import db, Guilds from titanembeds.discordrest import DiscordREST from flask import request, session from flask.ext.cache import Cache @@ -51,5 +52,18 @@ def guild_ratelimit_key(): guild_id = request.args.get('guild_id', "0") return (sess + guild_id).encode('utf-8') +def check_guild_existance(guild_id): + dbGuild = Guilds.query.filter_by(guild_id=guild_id).first() + if not dbGuild: + return False + guilds = discord_api.get_all_guilds() + for guild in guilds: + if guild_id == guild['id']: + return True + return False -rate_limiter = Limiter(key_func=get_client_ipaddr) # Default limit by ip address +def guild_query_unauth_users_bool(guild_id): + dbGuild = db.session.query(Guilds).filter(Guilds.guild_id==guild_id).first() + return dbGuild.unauth_users + +rate_limiter = Limiter(key_func=get_client_ipaddr) # Default limit by ip address \ No newline at end of file