Enhanced dashboard, bot adding handeling, list members, embed settings, etc

This commit is contained in:
Jeremy Zhang 2017-03-25 20:31:47 -07:00
parent a6bc5f753e
commit 53c06ed050
9 changed files with 246 additions and 29 deletions

View File

@ -1,6 +1,7 @@
from config import config from config import config
from database import db from database import db
from flask import Flask, render_template, request, session, url_for, redirect, jsonify from flask import Flask, render_template, request, session, url_for, redirect, jsonify
from titanembeds.utils import cache
import blueprints.api import blueprints.api
import blueprints.user import blueprints.user
import os import os
@ -13,6 +14,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no
app.secret_key = config['app-secret'] app.secret_key = config['app-secret']
db.init_app(app) 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.api.api, url_prefix="/api", template_folder="/templates")
app.register_blueprint(blueprints.user.user, url_prefix="/user", 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("/") @app.route("/")
def index(): def index():
return render_template("index.html.jinja2") return render_template("index.html.j2")
@app.route("/embed/<guildid>/<channelid>") @app.route("/embed/<guildid>/<channelid>")
def embed_get(guildid, channelid): def embed_get(guildid, channelid):

View File

@ -1,6 +1,6 @@
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers
from titanembeds.decorators import valid_session_required, discord_users_only 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 flask import Blueprint, abort, jsonify, session, request
from sqlalchemy import and_ from sqlalchemy import and_
import random import random
@ -39,20 +39,11 @@ def checkUserBanned(guild_id, ip_address=None):
pass #todo: handle authenticated user banned status pass #todo: handle authenticated user banned status
return banned 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): def check_guild_existance(guild_id):
dbGuild = Guilds.query.filter_by(guild_id=guild_id).first() dbGuild = Guilds.query.filter_by(guild_id=guild_id).first()
if not dbGuild: if not dbGuild:
return False return False
guilds = cache.get('bot_guilds')
if guilds is None:
guilds = discord_api.get_all_guilds() guilds = discord_api.get_all_guilds()
cache.set('bot_guilds', guilds)
for guild in guilds: for guild in guilds:
if guild_id == guild['id']: if guild_id == guild['id']:
return True return True

View File

@ -2,7 +2,8 @@ from flask import Blueprint, request, redirect, jsonify, abort, session, url_for
from requests_oauthlib import OAuth2Session from requests_oauthlib import OAuth2Session
from config import config from config import config
from titanembeds.decorators import discord_users_only 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__) user = Blueprint("user", __name__)
redirect_url = config['app-base-url'] + "/user/callback" 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/" avatar_base_url = "https://cdn.discordapp.com/avatars/"
guild_icon_url = "https://cdn.discordapp.com/icons/" 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): def make_authenticated_session(token=None, state=None, scope=None):
return OAuth2Session( return OAuth2Session(
client_id=config['client-id'], client_id=config['client-id'],
@ -18,6 +22,12 @@ def make_authenticated_session(token=None, state=None, scope=None):
state=state, state=state,
scope=scope, scope=scope,
redirect_uri=url_for("user.callback", _external=True), 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): def discordrest_from_user(endpoint):
@ -36,19 +46,50 @@ def get_current_authenticated_user():
def user_has_permission(permission, index): def user_has_permission(permission, index):
return bool((int(permission) >> index) & 1) return bool((int(permission) >> index) & 1)
@cache.cached(timeout=120, key_prefix=make_guilds_cache_key)
def get_user_guilds(): def get_user_guilds():
req = discordrest_from_user("/users/@me/guilds") req = discordrest_from_user("/users/@me/guilds")
return req return req
def get_user_managed_servers(): 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 = [] filtered = []
for guild in guilds: for guild in guilds:
permission = guild['permissions'] # Manage Server, Ban Members, Kick Members 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): 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.append(guild)
filtered = sorted(filtered, key=lambda guild: guild['name'])
return filtered 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): def generate_avatar_url(id, av):
return avatar_base_url + str(id) + '/' + str(av) + '.jpg' return avatar_base_url + str(id) + '/' + str(av) + '.jpg'
@ -90,28 +131,83 @@ def callback():
session['username'] = user['username'] session['username'] = user['username']
session['avatar'] = generate_avatar_url(user['id'], user['avatar']) session['avatar'] = generate_avatar_url(user['id'], user['avatar'])
if session["redirect"]: if session["redirect"]:
return redirect(session["redirect"]) redir = session["redirect"]
session.pop('redirect', None)
return redirect(redir)
return redirect(url_for("user.dashboard")) return redirect(url_for("user.dashboard"))
@user.route('/logout', methods=["GET"]) @user.route('/logout', methods=["GET"])
def logout(): def logout():
redir = session.get("redirect", None)
session.clear() session.clear()
if redir:
session['redirect'] = redir
return redirect(session['redirect'])
return redirect(url_for("index")) return redirect(url_for("index"))
@user.route("/dashboard") @user.route("/dashboard")
@discord_users_only() @discord_users_only()
def dashboard(): 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/<guild_id>") @user.route("/administrate_guild/<guild_id>", methods=["GET"])
@discord_users_only() @discord_users_only()
def administrate_guild(guild_id): 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) guild = discord_api.get_guild(guild_id)
if guild['code'] == 403: if guild['code'] != 200:
return redirect(generate_bot_invite_url(guild_id)) 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') @user.route('/me')
@discord_users_only() @discord_users_only()
def me(): def me():
return jsonify(user=get_current_authenticated_user()) 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

View File

@ -8,7 +8,7 @@ class Guilds(db.Model):
def __init__(self, guild_id): def __init__(self, guild_id):
self.guild_id = guild_id self.guild_id = guild_id
self.unauth_users = true # defaults to true self.unauth_users = True # defaults to true
def __repr__(self): def __repr__(self):
return '<Guilds {0} {1}>'.format(self.id, self.guild_id) return '<Guilds {0} {1}>'.format(self.id, self.guild_id)

View File

@ -0,0 +1,99 @@
{% extends 'site_layout.html.j2' %}
{% block title %}Administrate Guild: {{ guild['name'] }}{% endblock %}
{% block content %}
<h1>Administrating: {{ guild['name'] }}</h1>
<p class="flow-text">For this server, you are allowed the following actions:
{% for permission in permissions %}
{{ permission }}
{% if not loop.last %}
,
{% endif %}
{% endfor %}
.</p>
<div class="row">
<div class="col s12">
<h2 class="header">Embed URLs</h2>
<div class="card horizontal black-text indigo lighten-5 z-depth-3 hoverable">
<div class="card-stacked">
<div class="card-content">
<p class="flow-text">Direct Link</p>
<input disabled value="http://localhost:3000/embed/{{ guild['id'] }}" id="disabled" type="text"> <!-- Switch to url_for later -->
<p class="flow-text">iFrame Embed</p>
</div>
</div>
</div>
</div>
{% if "Manage Embed Settings" in permissions %}
<div class="col s12">
<h2 class="header">Embed Settings</h2>
<div class="card horizontal black-text indigo lighten-5 z-depth-3 hoverable">
<div class="card-stacked">
<div class="card-content">
<p class="flow-text">Unauthenticated (Guest) Users</p>
<form action="#">
<div class="switch">
<label>
Disable
<input type="checkbox" id="unauth_users" name="unauth_users">
<span class="lever"></span>
Enable
</label>
</div>
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% if "Ban Members" in permissions or "Kick Members" in permissions %}
<div class="col s12">
<h2 class="header">Moderate Unauthenticated Members</h2>
<div class="card horizontal black-text indigo lighten-5 z-depth-3 hoverable">
<div class="card-stacked">
<div class="card-content">
<p class="flow-text">Select Action</p>
<table class="striped responsive-table">
<thead>
<tr>
<th>Kick User</th>
<th>Ban User</th>
<th>Username</th>
<th>Discrim</th>
<th>Last Visit</th>
<th>IP Address</th>
<th>Banned Timestamp</th>
<th>Banned by</th>
<th>Banned Reason</th>
<th>Ban Lifted by</th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td><a class="waves-effect waves-light btn orange" {% if "Kick Members" not in permissions %}disabled{% endif %} >Kick</a></td>
<td><a class="waves-effect waves-light btn red" {% if "Ban Members" not in permissions %}disabled{% endif %} >Ban</a></td>
<td>{{ member['username'] }}</td>
<td>{{ member['discrim'] }}</td>
<td>{{ member['last_visit'] }}</td>
<td>{{ member['ip'] }}</td>
<td>{{ member['banned_timestamp'] }}</td>
<td>{{ member['banned_by'] }}</td>
<td>{{ member['banned_reason'] }}</td>
<td>{{ member['ban_lifted_by'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'site_layout.html.jinja2' %} {% extends 'site_layout.html.j2' %}
{% block title %}Dashboard{% endblock %} {% block title %}Dashboard{% endblock %}
{% block content %} {% block content %}
@ -7,19 +7,19 @@
<p>*List missing some servers? It's because you must have either <strong>Manage Server</strong>, <strong>Kick Members</strong>, or <strong>Ban Members</strong> permissions to modify embed settings.</p> <p>*List missing some servers? It's because you must have either <strong>Manage Server</strong>, <strong>Kick Members</strong>, or <strong>Ban Members</strong> permissions to modify embed settings.</p>
<div class="row"> <div class="row">
{% for server in servers %} {% for server in servers %}
<div class="col s6"> <div class="col s4">
<div class="card-panel grey lighten-5 z-depth-1"> <div class="card-panel indigo lighten-5 z-depth-3 hoverable">
<div class="row valign-wrapper"> <div class="row valign-wrapper">
<div class="col s2"> <div class="col s5">
{% if server.icon %} {% if server.icon %}
<img src="{{ icon_generate(server.id, server.icon) }}" alt="" class="circle responsive-img"> <img src="{{ icon_generate(server.id, server.icon) }}" alt="" class="circle responsive-img">
{% else %} {% else %}
<span class="black-text">No icon :(</span> <span class="black-text">No icon :(</span>
{% endif %} {% endif %}
</div> </div>
<div class="col s10"> <div class="col s7">
<span class="black-text"> <span class="black-text">
<p class="flow-text">{{ server.name }}</p> <p class="flow-text truncate">{{ server.name }}</p>
<br> <br>
<a class="waves-effect waves-light btn" href="{{url_for('user.administrate_guild', guild_id=server['id'])}}">Modify</a> <a class="waves-effect waves-light btn" href="{{url_for('user.administrate_guild', guild_id=server['id'])}}">Modify</a>
</span> </span>

View File

@ -1,4 +1,4 @@
{% extends 'site_layout.html.jinja2' %} {% extends 'site_layout.html.j2' %}
{% block title %}Index{% endblock %} {% block title %}Index{% endblock %}
{% block content %} {% block content %}

View File

@ -1,6 +1,35 @@
from werkzeug.contrib.cache import SimpleCache
from titanembeds.discordrest import DiscordREST from titanembeds.discordrest import DiscordREST
from flask import request, session
from flask.ext.cache import Cache
from config import config from config import config
import random
import string
discord_api = DiscordREST(config['bot-token']) 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')