diff --git a/discordbot/titanembeds/socketio/socketiointerface.py b/discordbot/titanembeds/socketio/socketiointerface.py index e7d3366..a421cb6 100644 --- a/discordbot/titanembeds/socketio/socketiointerface.py +++ b/discordbot/titanembeds/socketio/socketiointerface.py @@ -23,4 +23,13 @@ class SocketIOInterface: "mentions": get_message_mentions(message.mentions), "attachments": message.attachments, } - await self.io.emit('MESSAGE_CREATE', data=msg, room=message.server.id, namespace='/gateway') \ No newline at end of file + nickname = None + if hasattr(message.author, 'nick') and message.author.nick: + nickname = message.author.nick + msg["author"]["nickname"] = nickname + for mention in msg["mentions"]: + mention["nickname"] = None + member = message.server.get_member(mention["id"]) + if member: + mention["nickname"] = member.nick + await self.io.emit('MESSAGE_CREATE', data=msg, room=str("CHANNEL_"+message.channel.id), namespace='/gateway') \ No newline at end of file diff --git a/webapp/titanembeds/blueprints/api/api.py b/webapp/titanembeds/blueprints/api/api.py index 633148b..39efe4e 100644 --- a/webapp/titanembeds/blueprints/api/api.py +++ b/webapp/titanembeds/blueprints/api/api.py @@ -2,11 +2,10 @@ from titanembeds.database import db, Guilds, UnauthenticatedUsers, Unauthenticat 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 -from titanembeds.database import get_administrators_list +from titanembeds.userbookkeeping import user_unauthenticated, checkUserRevoke, checkUserBanned, update_user_status, check_user_in_guild, get_guild_channels from flask import Blueprint, abort, jsonify, session, request, url_for from sqlalchemy import and_ import random -import requests import json import datetime import re @@ -14,97 +13,6 @@ from config import config api = Blueprint("api", __name__) -def user_unauthenticated(): - if 'unauthenticated' in session: - return session['unauthenticated'] - return True - -def checkUserRevoke(guild_id, user_key=None): - revoked = True #guilty until proven not revoked - if user_unauthenticated(): - dbUser = UnauthenticatedUsers.query.filter(and_(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key)).first() - revoked = dbUser.isRevoked() - else: - banned = checkUserBanned(guild_id) - if banned: - return revoked - dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first() - revoked = not dbUser or not dbUser.active - return revoked - -def checkUserBanned(guild_id, ip_address=None): - banned = True - if user_unauthenticated(): - dbUser = UnauthenticatedBans.query.filter(and_(UnauthenticatedBans.guild_id == guild_id, UnauthenticatedBans.ip_address == ip_address)).all() - if not dbUser: - banned = False - else: - for usr in dbUser: - if usr.lifter_id is not None: - banned = False - else: - banned = False - dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first() - if not dbUser: - banned = False - else: - banned = dbUser.banned - return banned - -def update_user_status(guild_id, username, user_key=None): - if user_unauthenticated(): - ip_address = get_client_ipaddr() - status = { - 'authenticated': False, - 'avatar': 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'], - 'banned': checkUserBanned(guild_id, ip_address), - 'revoked': checkUserRevoke(guild_id, user_key), - } - if status['banned'] or status['revoked']: - session['user_keys'].pop(guild_id, None) - return status - dbUser = UnauthenticatedUsers.query.filter(and_(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key)).first() - dbUser.bumpTimestamp() - if dbUser.username != username or dbUser.ip_address != ip_address: - dbUser.username = username - dbUser.ip_address = ip_address - db.session.commit() - else: - status = { - 'authenticated': True, - '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'], - 'banned': checkUserBanned(guild_id), - 'revoked': checkUserRevoke(guild_id) - } - 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 - -def check_user_in_guild(guild_id): - if user_unauthenticated(): - return guild_id in session['user_keys'] - else: - dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == session['user_id'])).first() - return dbUser is not None and not checkUserRevoke(guild_id) - def parse_emoji(textToParse, guild_id): guild_emojis = get_guild_emojis(guild_id) for gemoji in guild_emojis: @@ -160,92 +68,6 @@ def format_everyone_mention(channel, content): content = content.replace("@here", u"@\u200Bhere") return content -def get_member_roles(guild_id, user_id): - q = db.session.query(GuildMembers).filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == user_id).first() - return json.loads(q.roles) - -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, 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']) - if guild_id not in member_roles: - member_roles.append(guild_id) - dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first() - guild_channels = json.loads(dbguild.channels) - guild_roles = json.loads(dbguild.roles) - guild_owner = str(dbguild.owner_id) - result_channels = [] - for channel in guild_channels: - if channel['type'] == "text": - result = {"channel": channel, "read": False, "write": False, "mention_everyone": False} - if guild_owner == session.get("user_id"): - result["read"] = True - result["write"] = True - result["mention_everyone"] = True - result_channels.append(result) - continue - channel_perm = 0 - - # @everyone - for role in guild_roles: - if role["id"] == guild_id: - channel_perm |= role["permissions"] - continue - - # User Guild Roles - for m_role in member_roles: - for g_role in guild_roles: - if g_role["id"] == m_role: - channel_perm |= g_role["permissions"] - continue - - # If has server administrator permission - if user_has_permission(channel_perm, 3): - result["read"] = True - result["write"] = True - result["mention_everyone"] = True - result_channels.append(result) - continue - - denies = 0 - allows = 0 - - # channel specific - for overwrite in channel["permission_overwrites"]: - if overwrite["type"] == "role" and overwrite["id"] in member_roles: - denies |= overwrite["deny"] - allows |= overwrite["allow"] - - channel_perm = (channel_perm & ~denies) | allows - - # member specific - for overwrite in channel["permission_overwrites"]: - if overwrite["type"] == "member" and overwrite["id"] == session.get("user_id"): - channel_perm = (channel_perm & ~overwrite['deny']) | overwrite['allow'] - break - - result["read"] = user_has_permission(channel_perm, 10) - result["write"] = user_has_permission(channel_perm, 11) - result["mention_everyone"] = user_has_permission(channel_perm, 17) - - # If default channel, you can read - if channel["id"] == guild_id: - result["read"] = True - - # If you cant read channel, you cant write in it - if not user_has_permission(channel_perm, 10): - result["read"] = False - result["write"] = False - result["mention_everyone"] = False - - result_channels.append(result) - return sorted(result_channels, key=lambda k: k['channel']['position']) - def filter_guild_channel(guild_id, channel_id, force_everyone=False): channels = get_guild_channels(guild_id, force_everyone) for chan in channels: @@ -341,6 +163,7 @@ def fetch(): status_code = 403 if user_unauthenticated(): session['user_keys'].pop(guild_id, None) + session.modified = True else: chan = filter_guild_channel(guild_id, channel_id) if not chan: diff --git a/webapp/titanembeds/blueprints/gateway/gateway.py b/webapp/titanembeds/blueprints/gateway/gateway.py index cc3ebfc..d108ea1 100644 --- a/webapp/titanembeds/blueprints/gateway/gateway.py +++ b/webapp/titanembeds/blueprints/gateway/gateway.py @@ -1,22 +1,43 @@ -from titanembeds.utils import socketio +from titanembeds.utils import socketio, guild_accepts_visitors, get_client_ipaddr +from titanembeds.userbookkeeping import check_user_in_guild, get_guild_channels, update_user_status from flask_socketio import Namespace, emit, disconnect, join_room import functools from flask import request, session - -def authenticated_only(f): - @functools.wraps(f) - def wrapped(*args, **kwargs): - if False: - pass - #disconnect() - else: - return f(*args, **kwargs) - return wrapped +import time class Gateway(Namespace): def on_connect(self): emit('hello') def on_identify(self, data): - room = data["guild_id"] - join_room(room) \ No newline at end of file + guild_id = data["guild_id"] + if not guild_accepts_visitors(guild_id) and not check_user_in_guild(guild_id): + disconnect() + return + channels = [] + if guild_accepts_visitors(guild_id) and not check_user_in_guild(guild_id): + channels = get_guild_channels(guild_id, force_everyone=True) + else: + channels = get_guild_channels(guild_id) + join_room("GUILD_"+guild_id) + for chan in channels: + if chan["read"]: + join_room("CHANNEL_"+chan["channel"]["id"]) + if session.get("unauthenticated", True) and guild_id in session.get("user_keys", {}): + join_room("IP_"+get_client_ipaddr()) + elif not session.get("unauthenticated", True): + join_room("USER_"+session["user_id"]) + emit("identified") + + def on_heartbeat(self, data): + guild_id = data["guild_id"] + visitor_mode = data["visitor_mode"] + if not visitor_mode: + status = update_user_status(guild_id, session["username"], session["user_keys"][guild_id]) + if status["revoked"] or status["banned"]: + emit("revoke") + time.sleep(1000) + disconnect() + else: + if not guild_accepts_visitors(guild_id): + disconnect() \ No newline at end of file diff --git a/webapp/titanembeds/static/js/embed.js b/webapp/titanembeds/static/js/embed.js index 0078fcc..9e6a8d0 100644 --- a/webapp/titanembeds/static/js/embed.js +++ b/webapp/titanembeds/static/js/embed.js @@ -17,17 +17,13 @@ var has_already_been_focused = false; // keep track of if the embed has initially been focused. var has_already_been_initially_resized = false; // keep track if the embed initially been resized var logintimer; // timer to keep track of user inactivity after hitting login - 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 = 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 - 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 + var socket = null; // Socket.io object function element_in_view(element, fullyInView) { var pageTop = $(window).scrollTop(); @@ -249,6 +245,8 @@ } else { primeEmbed(); } + + setInterval(send_socket_heartbeat, 5000); }); function changeTheme(theme=null, keep_custom_css=true) { @@ -339,6 +337,9 @@ } function initialize_embed(guildobj) { + if (socket) { + socket.disconnect(); + } if (guildobj === undefined) { var guild = query_guild(); guild.done(function(data) { @@ -379,6 +380,7 @@ fill_unauthenticated_users(guildobj.embedmembers.unauthenticated); $("#instant-inv").attr("href", guildobj.instant_invite); run_fetch_routine(); + initiate_websockets(); } function fill_channels(channels) { @@ -559,8 +561,6 @@ last_message_id = null; $("#channels-list > li.active").removeClass("active"); $("#channel-"+selected_channel).parent().addClass("active"); - priority_query_guild = true; - clearTimeout(fetchtimeout); run_fetch_routine(); } } @@ -718,11 +718,6 @@ } function run_fetch_routine() { - if (currently_fetching) { - return; - } - currently_fetching = true; - times_fetched += 1; var channel_id = selected_channel; var fet; var jumpscroll; @@ -752,13 +747,11 @@ } var guild = query_guild(); guild.done(function(guildobj) { - priority_query_guild = false; fill_channels(guildobj.channels); fill_discord_members(guildobj.discordmembers); fill_authenticated_users(guildobj.embedmembers.authenticated); fill_unauthenticated_users(guildobj.embedmembers.unauthenticated); $("#instant-inv").attr("href", guildobj.instant_invite); - initiate_websockets(); }); }); fet.fail(function(data) { @@ -769,25 +762,9 @@ $('#loginmodal').modal('open'); Materialize.toast('Session expired! You have been logged out.', 10000); } - - if (data.status != 429) { - setVisitorMode(true); - if (visitor_mode) { - fetchtimeout = setTimeout(run_fetch_routine, 5000); - } - } - }); - fet.catch(function(data) { - if (500 <= data.status && data.status < 600) { - if (fetch_error_count % 5 == 0) { - Materialize.toast('Fetching messages error! EndenDragon probably broke something. Sorry =(', 10000); - } - fetch_error_count += 1; - fetchtimeout = setTimeout(run_fetch_routine, 10000); - } + setVisitorMode(true); }); fet.always(function() { - currently_fetching = false; $("#fetching-indicator").fadeOut(800); }); } @@ -870,8 +847,6 @@ var usr = change_unauthenticated_username($(this).val()); usr.done(function(data) { Materialize.toast('Username changed successfully!', 10000); - priority_query_guild = true; - clearTimeout(fetchtimeout); run_fetch_routine(); }); usr.fail(function(data) { @@ -900,8 +875,6 @@ var funct = post(selected_channel, $(this).val()); funct.done(function(data) { $("#messagebox").val(""); - clearTimeout(fetchtimeout); - run_fetch_routine(); }); funct.fail(function(data) { Materialize.toast('Failed to send message.', 10000); @@ -945,13 +918,40 @@ }); function initiate_websockets() { - var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + "/gateway", {path: '/gateway', transports: ['websocket']}); - socket.on('connect', function() { + if (socket) { + return; + } + + socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + "/gateway", {path: '/gateway', transports: ['websocket']}); + socket.on('connect', function () { socket.emit('identify', {"guild_id": guild_id}); }); - socket.on('MESSAGE_CREATE', function(msg) { - console.log(msg); + socket.on("disconnect", function () { + socket = null; + }); + + socket.on("revoke", function () { + $('#loginmodal').modal('open'); + setVisitorMode(true); + primeEmbed(); + Materialize.toast('Authentication error! You have been disconnected by the server.', 10000); + }); + + socket.on("MESSAGE_CREATE", function (msg) { + var thismsgchan = msg.channel_id; + if (selected_channel != thismsgchan) { + return; + } + var jumpscroll = element_in_view($('#discordmessage_'+last_message_id), true); + last_message_id = fill_discord_messages([msg], jumpscroll); }); } + + function send_socket_heartbeat() { + if (!socket) { + return; + } + socket.emit("heartbeat", {"guild_id": guild_id, "visitor_mode": visitor_mode}); + } })(); diff --git a/webapp/titanembeds/userbookkeeping.py b/webapp/titanembeds/userbookkeeping.py new file mode 100644 index 0000000..576a161 --- /dev/null +++ b/webapp/titanembeds/userbookkeeping.py @@ -0,0 +1,179 @@ +from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, GuildMembers, get_guild_member +from titanembeds.utils import guild_accepts_visitors, guild_query_unauth_users_bool, get_client_ipaddr +from titanembeds.oauth import check_user_can_administrate_guild, user_has_permission +from flask import session +from sqlalchemy import and_ +import json + +def user_unauthenticated(): + if 'unauthenticated' in session: + return session['unauthenticated'] + return True + +def checkUserRevoke(guild_id, user_key=None): + revoked = True #guilty until proven not revoked + if user_unauthenticated(): + dbUser = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key).first() + revoked = dbUser.isRevoked() + else: + banned = checkUserBanned(guild_id) + if banned: + return revoked + dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first() + revoked = not dbUser or not dbUser.active + return revoked + +def checkUserBanned(guild_id, ip_address=None): + banned = True + if user_unauthenticated(): + dbUser = UnauthenticatedBans.query.filter(and_(UnauthenticatedBans.guild_id == guild_id, UnauthenticatedBans.ip_address == ip_address)).all() + if not dbUser: + banned = False + else: + for usr in dbUser: + if usr.lifter_id is not None: + banned = False + else: + banned = False + dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first() + if not dbUser: + banned = False + else: + banned = dbUser.banned + return banned + +def update_user_status(guild_id, username, user_key=None): + if user_unauthenticated(): + ip_address = get_client_ipaddr() + status = { + 'authenticated': False, + 'avatar': 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'], + 'banned': checkUserBanned(guild_id, ip_address), + 'revoked': checkUserRevoke(guild_id, user_key), + } + if status['banned'] or status['revoked']: + session['user_keys'].pop(guild_id, None) + return status + dbUser = UnauthenticatedUsers.query.filter(and_(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key)).first() + dbUser.bumpTimestamp() + if dbUser.username != username or dbUser.ip_address != ip_address: + dbUser.username = username + dbUser.ip_address = ip_address + db.session.commit() + else: + status = { + 'authenticated': True, + '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'], + 'banned': checkUserBanned(guild_id), + 'revoked': checkUserRevoke(guild_id) + } + 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 + +def check_user_in_guild(guild_id): + if user_unauthenticated(): + return guild_id in session.get("user_keys", {}) + else: + dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == session['user_id'])).first() + return dbUser is not None and not checkUserRevoke(guild_id) + +def get_member_roles(guild_id, user_id): + q = db.session.query(GuildMembers).filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == user_id).first() + return json.loads(q.roles) + +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']) + if guild_id not in member_roles: + member_roles.append(guild_id) + dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first() + guild_channels = json.loads(dbguild.channels) + guild_roles = json.loads(dbguild.roles) + guild_owner = str(dbguild.owner_id) + result_channels = [] + for channel in guild_channels: + if channel['type'] == "text": + result = {"channel": channel, "read": False, "write": False, "mention_everyone": False} + if guild_owner == session.get("user_id"): + result["read"] = True + result["write"] = True + result["mention_everyone"] = True + result_channels.append(result) + continue + channel_perm = 0 + + # @everyone + for role in guild_roles: + if role["id"] == guild_id: + channel_perm |= role["permissions"] + continue + + # User Guild Roles + for m_role in member_roles: + for g_role in guild_roles: + if g_role["id"] == m_role: + channel_perm |= g_role["permissions"] + continue + + # If has server administrator permission + if user_has_permission(channel_perm, 3): + result["read"] = True + result["write"] = True + result["mention_everyone"] = True + result_channels.append(result) + continue + + denies = 0 + allows = 0 + + # channel specific + for overwrite in channel["permission_overwrites"]: + if overwrite["type"] == "role" and overwrite["id"] in member_roles: + denies |= overwrite["deny"] + allows |= overwrite["allow"] + + channel_perm = (channel_perm & ~denies) | allows + + # member specific + for overwrite in channel["permission_overwrites"]: + if overwrite["type"] == "member" and overwrite["id"] == session.get("user_id"): + channel_perm = (channel_perm & ~overwrite['deny']) | overwrite['allow'] + break + + result["read"] = user_has_permission(channel_perm, 10) + result["write"] = user_has_permission(channel_perm, 11) + result["mention_everyone"] = user_has_permission(channel_perm, 17) + + # If default channel, you can read + if channel["id"] == guild_id: + result["read"] = True + + # If you cant read channel, you cant write in it + if not user_has_permission(channel_perm, 10): + result["read"] = False + result["write"] = False + result["mention_everyone"] = False + + result_channels.append(result) + return sorted(result_channels, key=lambda k: k['channel']['position']) \ No newline at end of file