From 53c06ed05011f5a92b2113cdbf4a68f7f3bb3588 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Sat, 25 Mar 2017 20:31:47 -0700 Subject: [PATCH] Enhanced dashboard, bot adding handeling, list members, embed settings, etc --- titanembeds/app.py | 4 +- titanembeds/blueprints/api/api.py | 13 +-- titanembeds/blueprints/user/user.py | 110 ++++++++++++++++-- titanembeds/database/guilds.py | 2 +- .../templates/administrate_guild.html.j2 | 99 ++++++++++++++++ ...ashboard.html.jinja2 => dashboard.html.j2} | 12 +- .../{index.html.jinja2 => index.html.j2} | 2 +- ...layout.html.jinja2 => site_layout.html.j2} | 0 titanembeds/utils.py | 33 +++++- 9 files changed, 246 insertions(+), 29 deletions(-) create mode 100644 titanembeds/templates/administrate_guild.html.j2 rename titanembeds/templates/{dashboard.html.jinja2 => dashboard.html.j2} (79%) rename titanembeds/templates/{index.html.jinja2 => index.html.j2} (95%) rename titanembeds/templates/{site_layout.html.jinja2 => site_layout.html.j2} (100%) diff --git a/titanembeds/app.py b/titanembeds/app.py index 14a75b9..86aff33 100644 --- a/titanembeds/app.py +++ b/titanembeds/app.py @@ -1,6 +1,7 @@ from config import config from database import db from flask import Flask, render_template, request, session, url_for, redirect, jsonify +from titanembeds.utils import cache import blueprints.api import blueprints.user import os @@ -13,6 +14,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no app.secret_key = config['app-secret'] db.init_app(app) +cache.init_app(app, config={'CACHE_TYPE': 'simple'}) app.register_blueprint(blueprints.api.api, url_prefix="/api", template_folder="/templates") app.register_blueprint(blueprints.user.user, url_prefix="/user", template_folder="/templates") @@ -28,7 +30,7 @@ def post_set_username(guildid, channelid): @app.route("/") def index(): - return render_template("index.html.jinja2") + return render_template("index.html.j2") @app.route("/embed//") def embed_get(guildid, channelid): diff --git a/titanembeds/blueprints/api/api.py b/titanembeds/blueprints/api/api.py index db8a138..8c87d74 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 discord_api, cache +from titanembeds.utils import get_client_ipaddr, discord_api from flask import Blueprint, abort, jsonify, session, request from sqlalchemy import and_ import random @@ -39,20 +39,11 @@ def checkUserBanned(guild_id, ip_address=None): pass #todo: handle authenticated user banned status return banned -def get_client_ipaddr(): - if hasattr(request.headers, "X-Real-IP"): # pythonanywhere specific - return request.headers['X-Real-IP'] - else: # general - return request.remote_addr - def check_guild_existance(guild_id): dbGuild = Guilds.query.filter_by(guild_id=guild_id).first() if not dbGuild: return False - guilds = cache.get('bot_guilds') - if guilds is None: - guilds = discord_api.get_all_guilds() - cache.set('bot_guilds', guilds) + guilds = discord_api.get_all_guilds() for guild in guilds: if guild_id == guild['id']: return True diff --git a/titanembeds/blueprints/user/user.py b/titanembeds/blueprints/user/user.py index a1210fe..1617965 100644 --- a/titanembeds/blueprints/user/user.py +++ b/titanembeds/blueprints/user/user.py @@ -2,7 +2,8 @@ from flask import Blueprint, request, redirect, jsonify, abort, session, url_for from requests_oauthlib import OAuth2Session from config import config from titanembeds.decorators import discord_users_only -from titanembeds.utils import discord_api +from titanembeds.utils import discord_api, cache, make_cache_key, make_guilds_cache_key +from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans user = Blueprint("user", __name__) redirect_url = config['app-base-url'] + "/user/callback" @@ -11,6 +12,9 @@ token_url = "https://discordapp.com/api/oauth2/token" avatar_base_url = "https://cdn.discordapp.com/avatars/" guild_icon_url = "https://cdn.discordapp.com/icons/" +def update_user_token(discord_token): + session['user_keys'] = discord_token + def make_authenticated_session(token=None, state=None, scope=None): return OAuth2Session( client_id=config['client-id'], @@ -18,6 +22,12 @@ def make_authenticated_session(token=None, state=None, scope=None): state=state, scope=scope, redirect_uri=url_for("user.callback", _external=True), + auto_refresh_kwargs={ + 'client_id': config['client-id'], + 'client_secret': config['client-secret'], + }, + auto_refresh_url=token_url, + token_updater=update_user_token, ) def discordrest_from_user(endpoint): @@ -36,19 +46,50 @@ def get_current_authenticated_user(): def user_has_permission(permission, index): return bool((int(permission) >> index) & 1) +@cache.cached(timeout=120, key_prefix=make_guilds_cache_key) def get_user_guilds(): req = discordrest_from_user("/users/@me/guilds") return req def get_user_managed_servers(): - guilds = get_user_guilds().json() + guilds = get_user_guilds() + if guilds.status_code != 200: + print(guilds.text) + print(guilds.headers) + abort(guilds.status_code) + guilds = guilds.json() filtered = [] for guild in guilds: permission = guild['permissions'] # Manage Server, Ban Members, Kick Members if guild['owner'] or user_has_permission(permission, 5) or user_has_permission(permission, 2) or user_has_permission(permission, 1): filtered.append(guild) + filtered = sorted(filtered, key=lambda guild: guild['name']) return filtered +def get_user_managed_servers_safe(): + guilds = get_user_managed_servers() + if guilds: + return guilds + return [] + +def get_user_managed_servers_id(): + guilds = get_user_managed_servers_safe() + ids=[] + for guild in guilds: + ids.append(guild['id']) + return ids + +def check_user_can_administrate_guild(guild_id): + guilds = get_user_managed_servers_id() + return guild_id in guilds + +def check_user_permission(guild_id, id): + guilds = get_user_managed_servers_safe() + for guild in guilds: + if guild['id'] == guild_id: + return user_has_permission(guild['permissions'], id) + return False + def generate_avatar_url(id, av): return avatar_base_url + str(id) + '/' + str(av) + '.jpg' @@ -90,28 +131,83 @@ def callback(): session['username'] = user['username'] session['avatar'] = generate_avatar_url(user['id'], user['avatar']) if session["redirect"]: - return redirect(session["redirect"]) + redir = session["redirect"] + session.pop('redirect', None) + return redirect(redir) return redirect(url_for("user.dashboard")) @user.route('/logout', methods=["GET"]) def logout(): + redir = session.get("redirect", None) session.clear() + if redir: + session['redirect'] = redir + return redirect(session['redirect']) return redirect(url_for("index")) @user.route("/dashboard") @discord_users_only() def dashboard(): - return render_template("dashboard.html.jinja2", servers=get_user_managed_servers(), icon_generate=generate_guild_icon_url) + guilds = get_user_managed_servers() + if not guilds: + session["redirect"] = url_for("user.dashboard") + return redirect(url_for("user.logout")) + return render_template("dashboard.html.j2", servers=guilds, icon_generate=generate_guild_icon_url) -@user.route("/administrate_guild/") +@user.route("/administrate_guild/", methods=["GET"]) @discord_users_only() def administrate_guild(guild_id): + if not check_user_can_administrate_guild(guild_id): + return redirect(url_for("user.dashboard")) guild = discord_api.get_guild(guild_id) - if guild['code'] == 403: + if guild['code'] != 200: return redirect(generate_bot_invite_url(guild_id)) - return str(guild) + db_guild = Guilds.query.filter_by(guild_id=guild_id).first() + if not db_guild: + db_guild = Guilds(guild_id) + db.session.add(db_guild) + db.session.commit() + permissions=[] + if check_user_permission(guild_id, 5): + permissions.append("Manage Embed Settings") + if check_user_permission(guild_id, 2): + permissions.append("Ban Members") + if check_user_permission(guild_id, 1): + permissions.append("Kick Members") + all_members = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id).all() + all_bans = db.session.query(UnauthenticatedBans).filter(UnauthenticatedBans.guild_id == guild_id).all() + users = prepare_guild_members_list(all_members, all_bans) + return render_template("administrate_guild.html.j2", guild=guild['content'], members=users, permissions=permissions) @user.route('/me') @discord_users_only() def me(): return jsonify(user=get_current_authenticated_user()) + +def prepare_guild_members_list(members, bans): + all_users = [] + 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, + } + 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 + all_users.append(user) + return all_users diff --git a/titanembeds/database/guilds.py b/titanembeds/database/guilds.py index 6efbb37..858be37 100644 --- a/titanembeds/database/guilds.py +++ b/titanembeds/database/guilds.py @@ -8,7 +8,7 @@ class Guilds(db.Model): def __init__(self, guild_id): self.guild_id = guild_id - self.unauth_users = true # defaults to true + self.unauth_users = True # defaults to true def __repr__(self): return ''.format(self.id, self.guild_id) diff --git a/titanembeds/templates/administrate_guild.html.j2 b/titanembeds/templates/administrate_guild.html.j2 new file mode 100644 index 0000000..be15ddc --- /dev/null +++ b/titanembeds/templates/administrate_guild.html.j2 @@ -0,0 +1,99 @@ +{% extends 'site_layout.html.j2' %} +{% block title %}Administrate Guild: {{ guild['name'] }}{% endblock %} + +{% block content %} +

Administrating: {{ guild['name'] }}

+

For this server, you are allowed the following actions: + {% for permission in permissions %} + {{ permission }} + {% if not loop.last %} + , + {% endif %} + {% endfor %} + .

+ +
+ +
+

Embed URLs

+
+
+
+

Direct Link

+ +

iFrame Embed

+
+
+
+
+ + {% if "Manage Embed Settings" in permissions %} +
+

Embed Settings

+
+
+
+

Unauthenticated (Guest) Users

+
+
+ +
+
+
+
+
+
+ {% endif %} + + {% if "Ban Members" in permissions or "Kick Members" in permissions %} +
+

Moderate Unauthenticated Members

+
+
+
+

Select Action

+ + + + + + + + + + + + + + + + + {% for member in members %} + + + + + + + + + + + + + {% endfor %} + +
Kick UserBan UserUsernameDiscrimLast VisitIP AddressBanned TimestampBanned byBanned ReasonBan Lifted by
KickBan{{ member['username'] }}{{ member['discrim'] }}{{ member['last_visit'] }}{{ member['ip'] }}{{ member['banned_timestamp'] }}{{ member['banned_by'] }}{{ member['banned_reason'] }}{{ member['ban_lifted_by'] }}
+
+
+
+
+ {% endif %} + +
+{% endblock %} diff --git a/titanembeds/templates/dashboard.html.jinja2 b/titanembeds/templates/dashboard.html.j2 similarity index 79% rename from titanembeds/templates/dashboard.html.jinja2 rename to titanembeds/templates/dashboard.html.j2 index 21d031c..ad10d1a 100644 --- a/titanembeds/templates/dashboard.html.jinja2 +++ b/titanembeds/templates/dashboard.html.j2 @@ -1,4 +1,4 @@ -{% extends 'site_layout.html.jinja2' %} +{% extends 'site_layout.html.j2' %} {% block title %}Dashboard{% endblock %} {% block content %} @@ -7,19 +7,19 @@

*List missing some servers? It's because you must have either Manage Server, Kick Members, or Ban Members permissions to modify embed settings.

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

{{ server.name }}

+

{{ server.name }}


Modify
diff --git a/titanembeds/templates/index.html.jinja2 b/titanembeds/templates/index.html.j2 similarity index 95% rename from titanembeds/templates/index.html.jinja2 rename to titanembeds/templates/index.html.j2 index 91b069d..d461277 100644 --- a/titanembeds/templates/index.html.jinja2 +++ b/titanembeds/templates/index.html.j2 @@ -1,4 +1,4 @@ -{% extends 'site_layout.html.jinja2' %} +{% extends 'site_layout.html.j2' %} {% block title %}Index{% endblock %} {% block content %} diff --git a/titanembeds/templates/site_layout.html.jinja2 b/titanembeds/templates/site_layout.html.j2 similarity index 100% rename from titanembeds/templates/site_layout.html.jinja2 rename to titanembeds/templates/site_layout.html.j2 diff --git a/titanembeds/utils.py b/titanembeds/utils.py index 8561517..d31e49d 100644 --- a/titanembeds/utils.py +++ b/titanembeds/utils.py @@ -1,6 +1,35 @@ -from werkzeug.contrib.cache import SimpleCache from titanembeds.discordrest import DiscordREST +from flask import request, session +from flask.ext.cache import Cache from config import config +import random +import string discord_api = DiscordREST(config['bot-token']) -cache = SimpleCache() +cache = Cache() + +def get_client_ipaddr(): + if hasattr(request.headers, "X-Real-IP"): # pythonanywhere specific + return request.headers['X-Real-IP'] + else: # general + return request.remote_addr + +def generate_session_key(): + sess = session.get("cachestring", None) + if not sess: + rand_str = lambda n: ''.join([random.choice(string.lowercase) for i in xrange(n)]) + session['cachestring'] = rand_str(25) + sess = session['cachestring'] + return sess #Totally unique + +def make_cache_key(*args, **kwargs): + path = request.path + args = str(hash(frozenset(request.args.items()))) + ip = get_client_ipaddr() + sess = generate_session_key() + return (path + args + sess + ip).encode('utf-8') + +def make_guilds_cache_key(): + sess = generate_session_key() + ip = get_client_ipaddr() + return (sess + ip + "user_guilds").encode('utf-8')