mirror of
https://github.com/TitanEmbeds/Titan.git
synced 2025-06-16 11:25:22 +02:00
Initial structure for discordbot addition
This commit is contained in:
0
webapp/titanembeds/__init__.py
Normal file
0
webapp/titanembeds/__init__.py
Normal file
36
webapp/titanembeds/app.py
Normal file
36
webapp/titanembeds/app.py
Normal file
@ -0,0 +1,36 @@
|
||||
from config import config
|
||||
from database import db
|
||||
from flask import Flask, render_template, request, session, url_for, redirect, jsonify
|
||||
from flask_sslify import SSLify
|
||||
from titanembeds.utils import rate_limiter, cache, discord_api
|
||||
import blueprints.api
|
||||
import blueprints.user
|
||||
import blueprints.embed
|
||||
import os
|
||||
|
||||
|
||||
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.config['SQLALCHEMY_POOL_RECYCLE'] = 250
|
||||
app.config['RATELIMIT_STORAGE_URL'] = 'keyvalprops://'
|
||||
app.secret_key = config['app-secret']
|
||||
|
||||
db.init_app(app)
|
||||
rate_limiter.init_app(app)
|
||||
sslify = SSLify(app, permanent=True)
|
||||
|
||||
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.embed.embed, url_prefix="/embed", template_folder="/templates")
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html.j2")
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
db.create_all()
|
||||
discord_api.init_discordrest()
|
0
webapp/titanembeds/blueprints/__init__.py
Normal file
0
webapp/titanembeds/blueprints/__init__.py
Normal file
1
webapp/titanembeds/blueprints/api/__init__.py
Normal file
1
webapp/titanembeds/blueprints/api/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from api import api
|
385
webapp/titanembeds/blueprints/api/api.py
Normal file
385
webapp/titanembeds/blueprints/api/api.py
Normal file
@ -0,0 +1,385 @@
|
||||
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties
|
||||
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.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_
|
||||
import random
|
||||
import requests
|
||||
import json
|
||||
import datetime
|
||||
import re
|
||||
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
|
||||
member = discord_api.get_guild_member_nocache(guild_id, session['user_id'])
|
||||
if member['code'] == 200:
|
||||
revoked = False
|
||||
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
|
||||
bans = discord_api.get_guild_bans(guild_id)['content']
|
||||
for user in bans:
|
||||
if session['user_id'] == user['user']['id']:
|
||||
return True
|
||||
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,
|
||||
'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,
|
||||
'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
|
||||
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 200 == discord_api.get_guild_member_nocache(guild_id, session['user_id'])['code'] and dbUser is not None
|
||||
|
||||
def format_post_content(message):
|
||||
message = message.replace("<", "\<")
|
||||
message = message.replace(">", "\>")
|
||||
|
||||
pattern = re.compile(r'\[@[0-9]+\]')
|
||||
for match in re.findall(pattern, message):
|
||||
mention = "<@" + match[2: len(match) - 1] + ">"
|
||||
message = message.replace(match, mention, 1)
|
||||
|
||||
if (session['unauthenticated']):
|
||||
message = "**[{}#{}]** {}".format(session['username'], session['user_id'], message)
|
||||
else:
|
||||
message = "**<{}#{}>** {}".format(session['username'], session['discriminator'], message) # I would like to do a @ mention, but i am worried about notif spam
|
||||
return message
|
||||
|
||||
def get_guild_channels(guild_id):
|
||||
if user_unauthenticated():
|
||||
member_roles = [guild_id] #equivilant to @everyone role
|
||||
else:
|
||||
member = discord_api.get_guild_member(guild_id, session['user_id'])['content']
|
||||
member_roles = member['roles']
|
||||
if guild_id not in member_roles:
|
||||
member_roles.append(guild_id)
|
||||
guild_channels = discord_api.get_guild_channels(guild_id)['content']
|
||||
guild_roles = discord_api.get_guild_roles(guild_id)["content"]
|
||||
guild_owner = discord_api.get_guild(guild_id)['content']['owner_id']
|
||||
result_channels = []
|
||||
for channel in guild_channels:
|
||||
if channel['type'] == 0:
|
||||
result = {"channel": channel, "read": False, "write": False}
|
||||
if guild_owner == session['user_id']:
|
||||
result["read"] = True
|
||||
result["write"] = 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_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["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)
|
||||
|
||||
# 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
|
||||
|
||||
#if result["read"]:
|
||||
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)
|
||||
for chan in channels:
|
||||
if chan["channel"]["id"] == guild_id:
|
||||
return chan
|
||||
return None
|
||||
|
||||
def get_online_discord_users(guild_id):
|
||||
embed = discord_api.get_widget(guild_id)
|
||||
apimembers = discord_api.list_all_guild_members(guild_id)
|
||||
apimembers_filtered = {}
|
||||
for member in apimembers:
|
||||
apimembers_filtered[member["user"]["id"]] = member
|
||||
guild_roles = discord_api.get_guild_roles(guild_id)["content"]
|
||||
guildroles_filtered = {}
|
||||
for role in guild_roles:
|
||||
guildroles_filtered[role["id"]] = role
|
||||
for member in embed['members']:
|
||||
apimem = apimembers_filtered.get(member["id"])
|
||||
member["hoist-role"] = None
|
||||
member["color"] = None
|
||||
if apimem:
|
||||
for roleid in reversed(apimem["roles"]):
|
||||
role = guildroles_filtered[roleid]
|
||||
if role["color"] != 0:
|
||||
member["color"] = '{0:02x}'.format(role["color"]) #int to hex
|
||||
if role["hoist"]:
|
||||
member["hoist-role"] = {}
|
||||
member["hoist-role"]["name"] = role["name"]
|
||||
member["hoist-role"]["id"] = role["id"]
|
||||
member["hoist-role"]["position"] = role["position"]
|
||||
return embed['members']
|
||||
|
||||
def get_online_embed_users(guild_id):
|
||||
time_past = (datetime.datetime.now() - datetime.timedelta(seconds = 60)).strftime('%Y-%m-%d %H:%M:%S')
|
||||
unauths = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.last_timestamp > time_past, UnauthenticatedUsers.revoked == False, UnauthenticatedUsers.guild_id == guild_id).all()
|
||||
auths = db.session.query(AuthenticatedUsers).filter(AuthenticatedUsers.last_timestamp > time_past, AuthenticatedUsers.guild_id == guild_id).all()
|
||||
users = {'unauthenticated':[], 'authenticated':[]}
|
||||
for user in unauths:
|
||||
meta = {
|
||||
'username': user.username,
|
||||
'discriminator': user.discriminator,
|
||||
}
|
||||
users['unauthenticated'].append(meta)
|
||||
for user in auths:
|
||||
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_url': generate_avatar_url(u['id'], u['avatar']),
|
||||
}
|
||||
users['authenticated'].append(meta)
|
||||
return users
|
||||
|
||||
@api.route("/fetch", methods=["GET"])
|
||||
@valid_session_required(api=True)
|
||||
@rate_limiter.limit("2 per 2 second", key_func = channel_ratelimit_key)
|
||||
def fetch():
|
||||
guild_id = request.args.get("guild_id")
|
||||
channel_id = request.args.get('channel_id')
|
||||
after_snowflake = request.args.get('after', None, type=int)
|
||||
if user_unauthenticated():
|
||||
key = session['user_keys'][guild_id]
|
||||
else:
|
||||
key = None
|
||||
status = update_user_status(guild_id, session['username'], key)
|
||||
messages = {}
|
||||
if status['banned'] or status['revoked']:
|
||||
status_code = 403
|
||||
else:
|
||||
chan = filter_guild_channel(guild_id, channel_id)
|
||||
if not chan.get("read"):
|
||||
status_code = 401
|
||||
else:
|
||||
messages = discord_api.get_channel_messages(channel_id, after_snowflake)
|
||||
status_code = messages['code']
|
||||
response = jsonify(messages=messages.get('content', messages), status=status)
|
||||
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)
|
||||
def post():
|
||||
guild_id = request.form.get("guild_id")
|
||||
channel_id = request.form.get('channel_id')
|
||||
content = request.form.get('content')
|
||||
content = format_post_content(content)
|
||||
if user_unauthenticated():
|
||||
key = session['user_keys'][guild_id]
|
||||
else:
|
||||
key = None
|
||||
status = update_user_status(guild_id, session['username'], key)
|
||||
message = {}
|
||||
if status['banned'] or status['revoked']:
|
||||
status_code = 401
|
||||
else:
|
||||
chan = filter_guild_channel(guild_id, channel_id)
|
||||
if not chan.get("write"):
|
||||
status_code = 401
|
||||
else:
|
||||
message = discord_api.create_message(channel_id, content)
|
||||
status_code = message['code']
|
||||
response = jsonify(message=message.get('content', message), status=status)
|
||||
response.status_code = status_code
|
||||
return response
|
||||
|
||||
@api.route("/create_unauthenticated_user", methods=["POST"])
|
||||
@rate_limiter.limit("1 per 15 minute", key_func=guild_ratelimit_key)
|
||||
def create_unauthenticated_user():
|
||||
session['unauthenticated'] = True
|
||||
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):
|
||||
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
|
||||
if 'user_keys' not in session:
|
||||
session['user_keys'] = {guild_id: key}
|
||||
else:
|
||||
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
|
||||
|
||||
@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):
|
||||
channels = get_guild_channels(guild_id)
|
||||
discordmembers = get_online_discord_users(guild_id)
|
||||
embedmembers = get_online_embed_users(guild_id)
|
||||
return jsonify(channels=channels, discordmembers=discordmembers, embedmembers=embedmembers)
|
||||
abort(403)
|
||||
abort(404)
|
||||
|
||||
@api.route("/create_authenticated_user", methods=["POST"])
|
||||
@discord_users_only(api=True)
|
||||
def create_authenticated_user():
|
||||
guild_id = request.form.get('guild_id')
|
||||
if session['unauthenticated']:
|
||||
response = jsonify(error=True)
|
||||
response.status_code = 401
|
||||
return response
|
||||
else:
|
||||
if not check_guild_existance(guild_id):
|
||||
abort(404)
|
||||
if not checkUserBanned(guild_id):
|
||||
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)
|
||||
db.session.commit()
|
||||
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(status=status)
|
||||
else:
|
||||
status = {'banned': True}
|
||||
response = jsonify(status=status)
|
||||
response.status_code = 403
|
||||
return response
|
||||
|
||||
@api.route("/cleanup-keyval-db", methods=["DELETE"])
|
||||
def cleanup_keyval_db():
|
||||
if request.form.get("secret", None) == config["app-secret"]:
|
||||
q = KeyValueProperties.query.filter(KeyValueProperties.expiration < datetime.datetime.now()).all()
|
||||
for m in q:
|
||||
db.session.delete(m)
|
||||
db.session.commit()
|
||||
return ('', 204)
|
||||
abort(401)
|
1
webapp/titanembeds/blueprints/embed/__init__.py
Normal file
1
webapp/titanembeds/blueprints/embed/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from embed import embed
|
41
webapp/titanembeds/blueprints/embed/embed.py
Normal file
41
webapp/titanembeds/blueprints/embed/embed.py
Normal file
@ -0,0 +1,41 @@
|
||||
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
|
||||
from config import config
|
||||
import random
|
||||
|
||||
embed = Blueprint("embed", __name__)
|
||||
|
||||
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("/<string:guild_id>")
|
||||
def guild_embed(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),
|
||||
client_id=config['client-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)))
|
1
webapp/titanembeds/blueprints/user/__init__.py
Normal file
1
webapp/titanembeds/blueprints/user/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from user import user
|
226
webapp/titanembeds/blueprints/user/user.py
Normal file
226
webapp/titanembeds/blueprints/user/user.py
Normal file
@ -0,0 +1,226 @@
|
||||
from flask import Blueprint, request, redirect, jsonify, abort, session, url_for, render_template
|
||||
from config import config
|
||||
from titanembeds.decorators import discord_users_only
|
||||
from titanembeds.utils import discord_api
|
||||
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans
|
||||
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
|
||||
|
||||
user = Blueprint("user", __name__)
|
||||
|
||||
@user.route("/login_authenticated", methods=["GET"])
|
||||
def login_authenticated():
|
||||
session["redirect"] = request.args.get("redirect")
|
||||
scope = ['identify', 'guilds', 'guilds.join']
|
||||
discord = make_authenticated_session(scope=scope)
|
||||
authorization_url, state = discord.authorization_url(
|
||||
authorize_url,
|
||||
access_type="offline"
|
||||
)
|
||||
session['oauth2_state'] = state
|
||||
return redirect(authorization_url)
|
||||
|
||||
@user.route('/callback', methods=["GET"])
|
||||
def callback():
|
||||
state = session.get('oauth2_state')
|
||||
if not state or request.values.get('error'):
|
||||
return redirect(url_for('user.logout'))
|
||||
discord = make_authenticated_session(state=state)
|
||||
discord_token = discord.fetch_token(
|
||||
token_url,
|
||||
client_secret=config['client-secret'],
|
||||
authorization_response=request.url)
|
||||
if not discord_token:
|
||||
return redirect(url_for('user.logout'))
|
||||
session['user_keys'] = discord_token
|
||||
session['unauthenticated'] = False
|
||||
user = get_current_authenticated_user()
|
||||
session['user_id'] = user['id']
|
||||
session['username'] = user['username']
|
||||
session['discriminator'] = user['discriminator']
|
||||
session['avatar'] = generate_avatar_url(user['id'], user['avatar'])
|
||||
if session["redirect"]:
|
||||
redir = session["redirect"]
|
||||
session['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():
|
||||
guilds = get_user_managed_servers()
|
||||
if not guilds:
|
||||
session["redirect"] = url_for("user.dashboard")
|
||||
return redirect(url_for("user.logout"))
|
||||
error = request.args.get("error")
|
||||
if session["redirect"] and not (error and error == "access_denied"):
|
||||
redir = session['redirect']
|
||||
session['redirect'] = None
|
||||
return redirect(redir)
|
||||
return render_template("dashboard.html.j2", servers=guilds, icon_generate=generate_guild_icon_url)
|
||||
|
||||
@user.route("/administrate_guild/<guild_id>", 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'] != 200:
|
||||
session["redirect"] = url_for("user.administrate_guild", guild_id=guild_id, _external=True)
|
||||
return redirect(generate_bot_invite_url(guild_id))
|
||||
session["redirect"] = None
|
||||
db_guild = db.session.query(Guilds).filter(Guilds.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).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 = {"unauth_users": db_guild.unauth_users}
|
||||
return render_template("administrate_guild.html.j2", guild=guild['content'], dbguild=dbguild_dict, members=users, permissions=permissions)
|
||||
|
||||
@user.route("/administrate_guild/<guild_id>", methods=["POST"])
|
||||
@discord_users_only()
|
||||
def update_administrate_guild(guild_id):
|
||||
if not check_user_can_administrate_guild(guild_id):
|
||||
abort(403)
|
||||
guild = discord_api.get_guild(guild_id)
|
||||
if guild['code'] != 200:
|
||||
abort(guild['code'])
|
||||
db_guild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
|
||||
if db_guild is None:
|
||||
abort(400)
|
||||
db_guild.unauth_users = request.form.get("unauth_users", db_guild.unauth_users) in ["true", True]
|
||||
db.session.commit()
|
||||
return jsonify(
|
||||
id=db_guild.id,
|
||||
guild_id=db_guild.guild_id,
|
||||
unauth_users=db_guild.unauth_users,
|
||||
)
|
||||
|
||||
@user.route('/me')
|
||||
@discord_users_only()
|
||||
def me():
|
||||
return jsonify(user=get_current_authenticated_user())
|
||||
|
||||
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
|
||||
|
||||
@user.route("/ban", methods=["POST"])
|
||||
@discord_users_only(api=True)
|
||||
def ban_unauthenticated_user():
|
||||
guild_id = request.form.get("guild_id", None)
|
||||
user_id = request.form.get("user_id", None)
|
||||
reason = request.form.get("reason", None)
|
||||
if reason is not None:
|
||||
reason = reason.strip()
|
||||
if reason == "":
|
||||
reason = None
|
||||
if not guild_id or not user_id:
|
||||
abort(400)
|
||||
if not check_user_permission(guild_id, 2):
|
||||
abort(401)
|
||||
db_user = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.id == user_id).order_by(UnauthenticatedUsers.id.desc()).first()
|
||||
if db_user is None:
|
||||
abort(404)
|
||||
db_ban = db.session.query(UnauthenticatedBans).filter(UnauthenticatedBans.guild_id == guild_id, UnauthenticatedBans.ip_address == db_user.ip_address).first()
|
||||
if db_ban is not None:
|
||||
if db_ban.lifter_id is None:
|
||||
abort(409)
|
||||
db.session.delete(db_ban)
|
||||
db_ban = UnauthenticatedBans(guild_id, db_user.ip_address, db_user.username, db_user.discriminator, reason, session["user_id"])
|
||||
db.session.add(db_ban)
|
||||
db.session.commit()
|
||||
return ('', 204)
|
||||
|
||||
@user.route("/ban", methods=["DELETE"])
|
||||
@discord_users_only(api=True)
|
||||
def unban_unauthenticated_user():
|
||||
guild_id = request.args.get("guild_id", None)
|
||||
user_id = request.args.get("user_id", None)
|
||||
if not guild_id or not user_id:
|
||||
abort(400)
|
||||
if not check_user_permission(guild_id, 2):
|
||||
abort(401)
|
||||
db_user = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.id == user_id).order_by(UnauthenticatedUsers.id.desc()).first()
|
||||
if db_user is None:
|
||||
abort(404)
|
||||
db_ban = db.session.query(UnauthenticatedBans).filter(UnauthenticatedBans.guild_id == guild_id, UnauthenticatedBans.ip_address == db_user.ip_address).first()
|
||||
if db_ban is None:
|
||||
abort(404)
|
||||
if db_ban.lifter_id is not None:
|
||||
abort(409)
|
||||
db_ban.liftBan(session["user_id"])
|
||||
return ('', 204)
|
||||
|
||||
@user.route("/revoke", methods=["POST"])
|
||||
@discord_users_only(api=True)
|
||||
def revoke_unauthenticated_user():
|
||||
guild_id = request.form.get("guild_id", None)
|
||||
user_id = request.form.get("user_id", None)
|
||||
if not guild_id or not user_id:
|
||||
abort(400)
|
||||
if not check_user_permission(guild_id, 1):
|
||||
abort(401)
|
||||
db_user = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.id == user_id).order_by(UnauthenticatedUsers.id.desc()).first()
|
||||
if db_user is None:
|
||||
abort(404)
|
||||
if db_user.isRevoked():
|
||||
abort(409)
|
||||
db_user.revokeUser()
|
||||
return ('', 204)
|
9
webapp/titanembeds/database/__init__.py
Normal file
9
webapp/titanembeds/database/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
from guilds import Guilds
|
||||
from unauthenticated_users import UnauthenticatedUsers
|
||||
from unauthenticated_bans import UnauthenticatedBans
|
||||
from authenticated_users import AuthenticatedUsers
|
||||
from keyvalue_properties import KeyValueProperties, set_keyvalproperty, get_keyvalproperty, getexpir_keyvalproperty, setexpir_keyvalproperty, ifexists_keyvalproperty, delete_keyvalproperty
|
20
webapp/titanembeds/database/authenticated_users.py
Normal file
20
webapp/titanembeds/database/authenticated_users.py
Normal file
@ -0,0 +1,20 @@
|
||||
from titanembeds.database import db
|
||||
import datetime
|
||||
import time
|
||||
|
||||
class AuthenticatedUsers(db.Model):
|
||||
__tablename__ = "authenticated_users"
|
||||
id = db.Column(db.Integer, primary_key=True) # Auto increment id
|
||||
guild_id = db.Column(db.String(255)) # Guild pretaining to the authenticated user
|
||||
client_id = db.Column(db.String(255)) # Client ID of the authenticated user
|
||||
last_timestamp = db.Column(db.TIMESTAMP) # The timestamp of when the user has last sent the heartbeat
|
||||
|
||||
def __init__(self, guild_id, client_id):
|
||||
self.guild_id = guild_id
|
||||
self.client_id = client_id
|
||||
self.last_timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
def bumpTimestamp(self):
|
||||
self.last_timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
|
||||
db.session.commit()
|
||||
return self.last_timestamp
|
35
webapp/titanembeds/database/custom_redislite.py
Normal file
35
webapp/titanembeds/database/custom_redislite.py
Normal file
@ -0,0 +1,35 @@
|
||||
import urlparse
|
||||
from limits.storage import Storage
|
||||
from redislite import Redis
|
||||
import time
|
||||
|
||||
class LimitsRedisLite(Storage): # For Python Limits
|
||||
STORAGE_SCHEME = "redislite"
|
||||
def __init__(self, uri, **options):
|
||||
self.redis_instance = Redis(urlparse.urlparse(uri).netloc)
|
||||
|
||||
def check(self):
|
||||
return True
|
||||
|
||||
def get_expiry(self, key):
|
||||
return (self.redis_instance.ttl(key) or 0) + time.time()
|
||||
|
||||
def incr(self, key, expiry, elastic_expiry=False):
|
||||
if not self.redis_instance.exists(key):
|
||||
self.redis_instance.set(key, 1, ex=expiry)
|
||||
else:
|
||||
oldexp = oldexp = self.get_expiry(key) - time.time()
|
||||
if oldexp <= 0:
|
||||
self.redis_instance.delete(key)
|
||||
return self.incr(key, expiry, elastic_expiry)
|
||||
self.redis_instance.set(key, int(self.redis_instance.get(key))+1, ex=int(round(oldexp)))
|
||||
return int(self.get(key))
|
||||
|
||||
def get(self, key):
|
||||
value = self.redis_instance.get(key)
|
||||
if value:
|
||||
return int(value)
|
||||
return 0
|
||||
|
||||
def reset(self):
|
||||
return self.redis_instance.flushdb()
|
19
webapp/titanembeds/database/guilds.py
Normal file
19
webapp/titanembeds/database/guilds.py
Normal file
@ -0,0 +1,19 @@
|
||||
from titanembeds.database import db
|
||||
|
||||
class Guilds(db.Model):
|
||||
__tablename__ = "guilds"
|
||||
id = db.Column(db.Integer, primary_key=True) # Auto incremented id
|
||||
guild_id = db.Column(db.String(255)) # Discord guild id
|
||||
unauth_users = db.Column(db.Boolean()) # If allowed unauth users
|
||||
|
||||
def __init__(self, guild_id):
|
||||
self.guild_id = guild_id
|
||||
self.unauth_users = True # defaults to true
|
||||
|
||||
def __repr__(self):
|
||||
return '<Guilds {0} {1}>'.format(self.id, self.guild_id)
|
||||
|
||||
def set_unauthUsersBool(self, value):
|
||||
self.unauth_users = value
|
||||
db.session.commit()
|
||||
return self.unauth_users
|
98
webapp/titanembeds/database/keyvalue_properties.py
Normal file
98
webapp/titanembeds/database/keyvalue_properties.py
Normal file
@ -0,0 +1,98 @@
|
||||
from titanembeds.database import db
|
||||
from datetime import datetime, timedelta
|
||||
from limits.storage import Storage
|
||||
import time
|
||||
|
||||
def set_keyvalproperty(key, value, expiration=None):
|
||||
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
|
||||
if q.count() == 0:
|
||||
db.session.add(KeyValueProperties(key=key, value=value, expiration=expiration))
|
||||
else:
|
||||
if expiration is not None:
|
||||
converted_expr = datetime.fromtimestamp(time.time() + expiration)
|
||||
else:
|
||||
converted_expr = None
|
||||
firstobj = q.first()
|
||||
firstobj.value = value
|
||||
firstobj.expiration = converted_expr
|
||||
db.session.commit()
|
||||
|
||||
def get_keyvalproperty(key):
|
||||
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
|
||||
now = datetime.now()
|
||||
if q.count() > 0 and (q.first().expiration is None or q.first().expiration > now):
|
||||
return q.first().value
|
||||
return None
|
||||
|
||||
def getexpir_keyvalproperty(key):
|
||||
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
|
||||
now = datetime.now()
|
||||
if q.count() > 0 and (q.first().expiration is not None and q.first().expiration > now):
|
||||
return int(q.first().expiration.strftime('%s'))
|
||||
return 0
|
||||
|
||||
def setexpir_keyvalproperty(key, expiration=None):
|
||||
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
|
||||
if q.count() > 0:
|
||||
if expiration:
|
||||
q.first().expiration = datetime.now()
|
||||
else:
|
||||
q.first().expiration = None
|
||||
db.session.commit()
|
||||
|
||||
def ifexists_keyvalproperty(key):
|
||||
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
|
||||
return q.count() > 0
|
||||
|
||||
def delete_keyvalproperty(key):
|
||||
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key).first()
|
||||
if q:
|
||||
db.session.delete(q)
|
||||
db.session.commit()
|
||||
|
||||
class KeyValueProperties(db.Model):
|
||||
__tablename__ = "keyvalue_properties"
|
||||
id = db.Column(db.Integer, primary_key=True) # Auto incremented id
|
||||
key = db.Column(db.String(255)) # Property Key
|
||||
value = db.Column(db.Text()) # Property value
|
||||
expiration = db.Column(db.TIMESTAMP) # Suggested Expiration for value (None = no expire) in secs
|
||||
|
||||
def __init__(self, key, value, expiration=None):
|
||||
self.key = key
|
||||
self.value = value
|
||||
if expiration:
|
||||
self.expiration = datetime.now() + timedelta(seconds = expiration)
|
||||
else:
|
||||
self.expiration = None
|
||||
|
||||
|
||||
class LimitsKeyValueProperties(Storage): # For Python Limits
|
||||
STORAGE_SCHEME = "keyvalprops"
|
||||
def __init__(self, uri, **options):
|
||||
pass
|
||||
|
||||
def check(self):
|
||||
return True
|
||||
|
||||
def get_expiry(self, key):
|
||||
return getexpir_keyvalproperty(key) + time.time()
|
||||
|
||||
def incr(self, key, expiry, elastic_expiry=False):
|
||||
if not ifexists_keyvalproperty(key):
|
||||
set_keyvalproperty(key, 1, expiration=expiry)
|
||||
else:
|
||||
oldexp = getexpir_keyvalproperty(key) - time.time()
|
||||
if oldexp <= 0:
|
||||
delete_keyvalproperty(key)
|
||||
return self.incr(key, expiry, elastic_expiry)
|
||||
set_keyvalproperty(key, int(get_keyvalproperty(key))+1, expiration=int(round(oldexp)))
|
||||
return int(self.get(key))
|
||||
|
||||
def get(self, key):
|
||||
value = get_keyvalproperty(key)
|
||||
if value:
|
||||
return int(value)
|
||||
return 0
|
||||
|
||||
def reset(self):
|
||||
return False
|
33
webapp/titanembeds/database/unauthenticated_bans.py
Normal file
33
webapp/titanembeds/database/unauthenticated_bans.py
Normal file
@ -0,0 +1,33 @@
|
||||
from titanembeds.database import db
|
||||
import datetime
|
||||
import time
|
||||
|
||||
class UnauthenticatedBans(db.Model):
|
||||
__tablename__ = "unauthenticated_bans"
|
||||
id = db.Column(db.Integer, primary_key=True) # Auto increment id
|
||||
guild_id = db.Column(db.String(255)) # Guild pretaining to the unauthenticated user
|
||||
ip_address = db.Column(db.String(255)) # The IP Address of the user
|
||||
last_username = db.Column(db.String(255)) # The username when they got banned
|
||||
last_discriminator = db.Column(db.Integer) # The discrim when they got banned
|
||||
timestamp = db.Column(db.TIMESTAMP) # The timestamp of when the user got banned
|
||||
reason = db.Column(db.Text()) # The reason of the ban set by the guild moderators
|
||||
lifter_id = db.Column(db.String(255)) # Discord Client ID of the user who lifted the ban
|
||||
placer_id = db.Column(db.String(255)) # The id of who placed the ban
|
||||
|
||||
def __init__(self, guild_id, ip_address, last_username, last_discriminator, reason, placer_id):
|
||||
self.guild_id = guild_id
|
||||
self.ip_address = ip_address
|
||||
self.last_username = last_username
|
||||
self.last_discriminator = last_discriminator
|
||||
self.timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.reason = reason
|
||||
self.lifter_id = None
|
||||
self.placer_id = placer_id
|
||||
|
||||
def liftBan(self, lifter_id):
|
||||
self.lifter_id = lifter_id
|
||||
db.session.commit()
|
||||
return self.lifter_id
|
||||
|
||||
def __repr__(self):
|
||||
return '<UnauthenticatedBans {0} {1} {2} {3} {4} {5}'.format(self.id, self.guild_id, self.ip_address, self.last_username, self.last_discriminator, self.timestamp)
|
46
webapp/titanembeds/database/unauthenticated_users.py
Normal file
46
webapp/titanembeds/database/unauthenticated_users.py
Normal file
@ -0,0 +1,46 @@
|
||||
from titanembeds.database import db
|
||||
import datetime
|
||||
import time
|
||||
import random
|
||||
import string
|
||||
|
||||
class UnauthenticatedUsers(db.Model):
|
||||
__tablename__ = "unauthenticated_users"
|
||||
id = db.Column(db.Integer, primary_key=True) # Auto increment id
|
||||
guild_id = db.Column(db.String(255)) # Guild pretaining to the unauthenticated user
|
||||
username = db.Column(db.String(255)) # The username of the user
|
||||
discriminator = db.Column(db.Integer) # The discriminator to distinguish unauth users with each other
|
||||
user_key = db.Column(db.Text()) # The secret key used to identify the user holder
|
||||
ip_address = db.Column(db.String(255)) # The IP Address of the user
|
||||
last_timestamp = db.Column(db.TIMESTAMP) # The timestamp of when the user has last sent the heartbeat
|
||||
revoked = db.Column(db.Boolean()) # If the user's key has been revoked and a new one is required to be generated
|
||||
|
||||
def __init__(self, guild_id, username, discriminator, ip_address):
|
||||
self.guild_id = guild_id
|
||||
self.username = username
|
||||
self.discriminator = discriminator
|
||||
self.user_key = "".join(random.choice(string.ascii_letters) for _ in range(0, 32))
|
||||
self.ip_address = ip_address
|
||||
self.last_timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
|
||||
self.revoked = False
|
||||
|
||||
def __repr__(self):
|
||||
return '<UnauthenticatedUsers {0} {1} {2} {3} {4} {5} {6} {7}>'.format(self.id, self.guild_id, self.username, self.discriminator, self.user_key, self.ip_address, self.last_timestamp, self.revoked)
|
||||
|
||||
def isRevoked(self):
|
||||
return self.revoked
|
||||
|
||||
def changeUsername(self, username):
|
||||
self.username = username
|
||||
db.session.commit()
|
||||
return self.username
|
||||
|
||||
def revokeUser(self):
|
||||
self.revoked = True
|
||||
db.session.commit()
|
||||
return self.revoked
|
||||
|
||||
def bumpTimestamp(self):
|
||||
self.last_timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
|
||||
db.session.commit()
|
||||
return self.last_timestamp
|
28
webapp/titanembeds/decorators.py
Normal file
28
webapp/titanembeds/decorators.py
Normal file
@ -0,0 +1,28 @@
|
||||
from functools import wraps
|
||||
from flask import url_for, redirect, session, jsonify, abort
|
||||
|
||||
def valid_session_required(api=False):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
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"), 401
|
||||
redirect(url_for('user.logout'))
|
||||
if session['unauthenticated'] and 'user_keys' not in session:
|
||||
session['user_keys'] = {}
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
def discord_users_only(api=False):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
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"), 401
|
||||
return redirect(url_for("user.login_authenticated"))
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
230
webapp/titanembeds/discordrest.py
Normal file
230
webapp/titanembeds/discordrest.py
Normal file
@ -0,0 +1,230 @@
|
||||
import requests
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
from titanembeds.utils import cache
|
||||
from titanembeds.database import db, KeyValueProperties, get_keyvalproperty, set_keyvalproperty, ifexists_keyvalproperty
|
||||
from flask import request
|
||||
|
||||
_DISCORD_API_BASE = "https://discordapp.com/api/v6"
|
||||
|
||||
def json_or_text(response):
|
||||
text = response.text
|
||||
if response.headers['content-type'] == 'application/json':
|
||||
return response.json()
|
||||
return text
|
||||
|
||||
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__)
|
||||
|
||||
def init_discordrest(self):
|
||||
if not self._bucket_contains("global_limited"):
|
||||
self._set_bucket("global_limited", False)
|
||||
self._set_bucket("global_limit_expire", 0)
|
||||
|
||||
def _get_bucket(self, key):
|
||||
value = get_keyvalproperty(self.global_redis_prefix + key)
|
||||
return value
|
||||
|
||||
def _set_bucket(self, key, value):
|
||||
return set_keyvalproperty(self.global_redis_prefix + key, value)
|
||||
|
||||
def _bucket_contains(self, key):
|
||||
return ifexists_keyvalproperty(self.global_redis_prefix + key)
|
||||
|
||||
def request(self, verb, url, **kwargs):
|
||||
headers = {
|
||||
'User-Agent': self.user_agent,
|
||||
'Authorization': 'Bot {}'.format(self.bot_token),
|
||||
}
|
||||
params = None
|
||||
if 'params' in kwargs:
|
||||
params = kwargs['params']
|
||||
data = None
|
||||
if 'data' in kwargs:
|
||||
data = kwargs['data']
|
||||
if 'json' in kwargs:
|
||||
headers['Content-Type'] = 'application/json'
|
||||
data = json.dumps(data)
|
||||
|
||||
for tries in range(5):
|
||||
curepoch = time.time()
|
||||
if self._get_bucket("global_limited") == "True":
|
||||
time.sleep(int(self._get_bucket("global_limit_expire")) - curepoch)
|
||||
curepoch = time.time()
|
||||
|
||||
if self._bucket_contains(url) and int(self._get_bucket(url)) > curepoch:
|
||||
time.sleep(int(self._get_bucket(url)) - curepoch)
|
||||
|
||||
url_formatted = _DISCORD_API_BASE + url
|
||||
req = requests.request(verb, url_formatted, params=params, data=data, headers=headers)
|
||||
|
||||
remaining = None
|
||||
if 'X-RateLimit-Remaining' in req.headers:
|
||||
remaining = req.headers['X-RateLimit-Remaining']
|
||||
if remaining == '0' and req.status_code != 429:
|
||||
self._set_bucket(url, int(req.headers['X-RateLimit-Reset']))
|
||||
|
||||
if 300 > req.status_code >= 200:
|
||||
self._set_bucket("global_limited", False)
|
||||
return {
|
||||
'success': True,
|
||||
'content': json_or_text(req),
|
||||
'code': req.status_code,
|
||||
}
|
||||
|
||||
if req.status_code == 429:
|
||||
if 'X-RateLimit-Global' not in req.headers:
|
||||
self._set_bucket(url, int(req.headers['X-RateLimit-Reset']))
|
||||
else:
|
||||
self._set_bucket("global_limited", True)
|
||||
self._set_bucket("global_limit_expire", time.time() + int(req.headers['Retry-After']))
|
||||
|
||||
if req.status_code == 502 and tries <= 5:
|
||||
time.sleep(1 + tries * 2)
|
||||
continue
|
||||
|
||||
if req.status_code == 403 or req.status_code == 404:
|
||||
return {
|
||||
'success': False,
|
||||
'code': req.status_code,
|
||||
}
|
||||
return {
|
||||
'success': False,
|
||||
'code': req.status_code,
|
||||
'content': json_or_text(req),
|
||||
}
|
||||
|
||||
#####################
|
||||
# Channel
|
||||
#####################
|
||||
|
||||
def get_channel_messages(self, channel_id, after_snowflake=None):
|
||||
_endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
|
||||
params = {}
|
||||
if after_snowflake is not None:
|
||||
params = {'after': after_snowflake}
|
||||
r = self.request("GET", _endpoint, params=params)
|
||||
return r
|
||||
|
||||
def create_message(self, channel_id, content):
|
||||
_endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
|
||||
payload = {'content': content}
|
||||
r = self.request("POST", _endpoint, data=payload)
|
||||
return r
|
||||
|
||||
#####################
|
||||
# Guild
|
||||
#####################
|
||||
|
||||
def get_guild(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}".format(guild_id=guild_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
@cache.cache('get_guild_channels', expire=200)
|
||||
def get_guild_channels(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}/channels".format(guild_id=guild_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
def get_guild_roles(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}/roles".format(guild_id=guild_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
@cache.cache('get_guild_member', expire=200)
|
||||
def get_guild_member(self, guild_id, user_id):
|
||||
_endpoint = "/guilds/{guild_id}/members/{user_id}".format(guild_id=guild_id, user_id=user_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
def get_guild_member_nocache(self, guild_id, user_id):
|
||||
_endpoint = "/guilds/{guild_id}/members/{user_id}".format(guild_id=guild_id, user_id=user_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
def modify_guild_member(self, guild_id, user_id, **kwargs):
|
||||
_endpoint = "/guilds/{guild_id}/members/{user_id}".format(guild_id=guild_id, user_id=user_id)
|
||||
r = self.request("PATCH", _endpoint, data=kwargs, json=True)
|
||||
return r
|
||||
|
||||
def add_guild_member(self, guild_id, user_id, access_token, **kwargs):
|
||||
_endpoint = "/guilds/{guild_id}/members/{user_id}".format(user_id=user_id, guild_id=guild_id)
|
||||
payload = {'access_token': access_token}
|
||||
payload.update(kwargs)
|
||||
r = self.request("PUT", _endpoint, data=payload, json=True)
|
||||
return r
|
||||
|
||||
def get_guild_embed(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}/embed".format(guild_id=guild_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
def modify_guild_embed(self, guild_id, **kwargs):
|
||||
_endpoint = "/guilds/{guild_id}/embed".format(guild_id=guild_id)
|
||||
r = self.request("PATCH", _endpoint, data=kwargs, json=True)
|
||||
return r
|
||||
|
||||
def get_guild_bans(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}/bans".format(guild_id=guild_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
@cache.cache('list_all_guild_members', expire=200)
|
||||
def list_all_guild_members(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}/members".format(guild_id=guild_id)
|
||||
count = 1
|
||||
last_usrid = ""
|
||||
users = []
|
||||
params = {"limit": 1000}
|
||||
while count > 0:
|
||||
r = self.request("GET", _endpoint, params=params)
|
||||
if r["success"] == True:
|
||||
content = r["content"]
|
||||
count = len(content)
|
||||
users.extend(content)
|
||||
if count > 0:
|
||||
params["after"] = content[-1]["user"]["id"]
|
||||
else:
|
||||
count = 0
|
||||
return users
|
||||
|
||||
#####################
|
||||
# User
|
||||
#####################
|
||||
|
||||
@cache.cache('get_all_guilds', expire=100)
|
||||
def get_all_guilds(self):
|
||||
_endpoint = "/users/@me/guilds"
|
||||
params = {}
|
||||
guilds = []
|
||||
count = 1 #priming the loop
|
||||
last_guild = ""
|
||||
while count > 0:
|
||||
r = self.request("GET", _endpoint, params=params)
|
||||
if r['success'] == True:
|
||||
content = r['content']
|
||||
count = len(content)
|
||||
guilds.extend(content)
|
||||
if count > 0:
|
||||
params['after'] = content[-1]['id']
|
||||
else:
|
||||
count = 0
|
||||
return guilds
|
||||
|
||||
#####################
|
||||
# Widget Handler
|
||||
#####################
|
||||
|
||||
@cache.cache('get_widget', expire=200)
|
||||
def get_widget(self, guild_id):
|
||||
_endpoint = _DISCORD_API_BASE + "/servers/{guild_id}/widget.json".format(guild_id=guild_id)
|
||||
embed = self.get_guild_embed(guild_id)
|
||||
if not embed['content']['enabled']:
|
||||
self.modify_guild_embed(guild_id, enabled=True, channel_id=guild_id)
|
||||
widget = requests.get(_endpoint).json()
|
||||
return widget
|
95
webapp/titanembeds/oauth.py
Normal file
95
webapp/titanembeds/oauth.py
Normal file
@ -0,0 +1,95 @@
|
||||
from config import config
|
||||
from requests_oauthlib import OAuth2Session
|
||||
from titanembeds.utils import cache, make_guilds_cache_key
|
||||
from flask import session, abort, url_for
|
||||
|
||||
authorize_url = "https://discordapp.com/api/oauth2/authorize"
|
||||
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'],
|
||||
token=token,
|
||||
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):
|
||||
token = session['user_keys']
|
||||
discord = make_authenticated_session(token=token)
|
||||
req = discord.get("https://discordapp.com/api/v6{}".format(endpoint))
|
||||
return req
|
||||
|
||||
def get_current_authenticated_user():
|
||||
req = discordrest_from_user("/users/@me")
|
||||
if req.status_code != 200:
|
||||
abort(req.status_code)
|
||||
user = req.json()
|
||||
return user
|
||||
|
||||
def user_has_permission(permission, index):
|
||||
return bool((int(permission) >> index) & 1)
|
||||
|
||||
@cache.cache(make_guilds_cache_key, expire=120)
|
||||
def get_user_guilds():
|
||||
req = discordrest_from_user("/users/@me/guilds")
|
||||
return req
|
||||
|
||||
def get_user_managed_servers():
|
||||
guilds = get_user_guilds()
|
||||
if guilds.status_code != 200:
|
||||
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) or guild['owner']
|
||||
return False
|
||||
|
||||
def generate_avatar_url(id, av):
|
||||
return avatar_base_url + str(id) + '/' + str(av) + '.jpg'
|
||||
|
||||
def generate_guild_icon_url(id, hash):
|
||||
return guild_icon_url + str(id) + "/" + str(hash) + ".jpg"
|
||||
|
||||
def generate_bot_invite_url(guild_id):
|
||||
url = "https://discordapp.com/oauth2/authorize?&client_id={}&scope=bot&permissions={}&guild_id={}&response_type=code&redirect_uri={}".format(config['client-id'], '536083583', guild_id, url_for("user.dashboard", _external=True))
|
||||
return url
|
77
webapp/titanembeds/static/css/administrate_guild.css
Normal file
77
webapp/titanembeds/static/css/administrate_guild.css
Normal file
@ -0,0 +1,77 @@
|
||||
/* Responsive table CSS, directly from materializecss - slightly modified */
|
||||
/* Used to accomplish having permanent horizontal table */
|
||||
|
||||
table.responsive-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
display: block;
|
||||
position: relative;
|
||||
table-layout: fixed;
|
||||
/* sort out borders */
|
||||
}
|
||||
table.responsive-table td:empty:before {
|
||||
content: '\00a0';
|
||||
}
|
||||
table.responsive-table th,
|
||||
table.responsive-table td {
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.responsive-table th {
|
||||
text-align: left;
|
||||
}
|
||||
table.responsive-table thead {
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
table.responsive-table thead tr {
|
||||
display: block;
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
table.responsive-table thead tr th::before {
|
||||
content: "\00a0";
|
||||
}
|
||||
table.responsive-table tbody {
|
||||
display: block;
|
||||
width: auto;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.responsive-table tbody tr {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.responsive-table th {
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
table.responsive-table td {
|
||||
display: block;
|
||||
min-height: 1.25em;
|
||||
text-align: left;
|
||||
padding: 13px 5px;
|
||||
}
|
||||
table.responsive-table tr {
|
||||
padding: 0 10px;
|
||||
}
|
||||
table.responsive-table thead {
|
||||
border: 0;
|
||||
border-right: 1px solid #d0d0d0;
|
||||
}
|
||||
table.responsive-table.bordered th {
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
table.responsive-table.bordered td {
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
table.responsive-table.bordered tr {
|
||||
border: 0;
|
||||
}
|
||||
table.responsive-table.bordered tbody tr {
|
||||
border-right: 1px solid #d0d0d0;
|
||||
}
|
187
webapp/titanembeds/static/css/embedstyle.css
Normal file
187
webapp/titanembeds/static/css/embedstyle.css
Normal file
@ -0,0 +1,187 @@
|
||||
html {
|
||||
background-color: #455a64;
|
||||
color: white;
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: calc(100vh - 80px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.role-title {
|
||||
margin-bottom: -15px !important;
|
||||
font-variant: normal !important;
|
||||
font-size: 80% !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
/* 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 {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.chatusername {
|
||||
font-weight: bold;
|
||||
color: #eceff1;
|
||||
}
|
||||
|
||||
.chattimestamp {
|
||||
font-size: 10px;
|
||||
color: #90a4ae;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.footercontainer {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#messageboxouter {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.betatag {
|
||||
font-variant: small-caps;
|
||||
font-size: 15px;
|
||||
color: #eceff1;
|
||||
}
|
||||
|
||||
#channeltopic {
|
||||
width: 80%;
|
||||
margin-left: 30px;
|
||||
margin-right: auto;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #82b1ff;
|
||||
}
|
||||
|
||||
#fetching-indicator {
|
||||
position: absolute;
|
||||
right: 1vw;
|
||||
bottom: 13px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
60
webapp/titanembeds/static/css/style.css
Normal file
60
webapp/titanembeds/static/css/style.css
Normal file
@ -0,0 +1,60 @@
|
||||
html {
|
||||
background-color: #7986cb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: #3f51b5;
|
||||
background: linear-gradient(rgba(63, 81, 181, 1), rgba(255,0,0,0));
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
nav .brand-logo {
|
||||
left: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #303f9f;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #3f51b5;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
background-color: #536dfe;
|
||||
}
|
||||
|
||||
.avatar_menu {
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.center_content {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.betatag {
|
||||
font-variant: small-caps;
|
||||
font-size: 25px;
|
||||
border-radius: 50px;
|
||||
border: 2px solid #37474f;
|
||||
color: #eceff1;
|
||||
}
|
77
webapp/titanembeds/static/js/administrate_guild.js
Normal file
77
webapp/titanembeds/static/js/administrate_guild.js
Normal file
@ -0,0 +1,77 @@
|
||||
$('#unauth_users').change(function() {
|
||||
var pathname = window.location.pathname;
|
||||
var checked = $(this).is(':checked')
|
||||
var payload = {"unauth_users": checked}
|
||||
$.post(pathname, payload, function(data) {
|
||||
Materialize.toast('Updated guest users setting!', 2000)
|
||||
});
|
||||
});
|
||||
|
||||
function initiate_ban(guild_id, user_id) {
|
||||
var reason = prompt("Please enter your reason for ban");
|
||||
var payload = {
|
||||
"reason": reason,
|
||||
"guild_id": guild_id,
|
||||
"user_id": user_id,
|
||||
}
|
||||
var pathname = document.location.origin + "/user/ban"
|
||||
|
||||
if (reason != null) {
|
||||
$.post(pathname, payload)
|
||||
.done(function(){
|
||||
location.reload();
|
||||
})
|
||||
.fail(function(xhr, status, error) {
|
||||
if (error == "CONFLICT") {
|
||||
Materialize.toast('User is already banned!', 2000)
|
||||
} else {
|
||||
Materialize.toast('An error has occured!', 2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function remove_ban(guild_id, user_id) {
|
||||
var payload = {
|
||||
"guild_id": guild_id,
|
||||
"user_id": user_id,
|
||||
}
|
||||
var pathname = document.location.origin + "/user/ban"
|
||||
|
||||
$.ajax({
|
||||
url: pathname + '?' + $.param(payload),
|
||||
type: 'DELETE',
|
||||
success: function() {
|
||||
location.reload();
|
||||
},
|
||||
error: function(jqxhr, status, error) {
|
||||
if (error == "CONFLICT") {
|
||||
Materialize.toast('User is already pardoned!', 2000)
|
||||
} else {
|
||||
Materialize.toast('An error has occured!', 2000)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function revoke_user(guild_id, user_id) {
|
||||
var payload = {
|
||||
"guild_id": guild_id,
|
||||
"user_id": user_id,
|
||||
}
|
||||
var confirmation = confirm("Are you sure that you want to kick user?")
|
||||
var pathname = document.location.origin + "/user/revoke"
|
||||
if (confirmation) {
|
||||
$.post(pathname, payload)
|
||||
.done(function(){
|
||||
location.reload();
|
||||
})
|
||||
.fail(function(xhr, status, error) {
|
||||
if (error == "CONFLICT") {
|
||||
Materialize.toast('User is already revoked!', 2000)
|
||||
} else {
|
||||
Materialize.toast('An error has occured!', 2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
550
webapp/titanembeds/static/js/embed.js
Normal file
550
webapp/titanembeds/static/js/embed.js
Normal file
@ -0,0 +1,550 @@
|
||||
/* global $ */
|
||||
/* global Materialize */
|
||||
/* global Mustache */
|
||||
/* global guild_id */
|
||||
/* global bot_client_id */
|
||||
/* global moment */
|
||||
|
||||
(function () {
|
||||
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 = guild_id; // user selected channel, defaults to #general channel
|
||||
var guild_channels = {}; // all server channels used to highlight channels in messages
|
||||
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
|
||||
|
||||
function element_in_view(element, fullyInView) {
|
||||
var pageTop = $(window).scrollTop();
|
||||
var pageBottom = pageTop + $(window).height();
|
||||
var elementTop = $(element).offset().top;
|
||||
var elementBottom = elementTop + $(element).height();
|
||||
|
||||
if (fullyInView === true) {
|
||||
return ((pageTop < elementTop) && (pageBottom > elementBottom));
|
||||
} else {
|
||||
return ((elementTop <= pageBottom) && (elementBottom >= pageTop));
|
||||
}
|
||||
}
|
||||
|
||||
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: {"guild_id": guild_id,"channel_id": channel_id, "after": after}
|
||||
});
|
||||
return funct.promise();
|
||||
}
|
||||
|
||||
function post(channel_id, content) {
|
||||
var funct = $.ajax({
|
||||
method: "POST",
|
||||
dataType: "json",
|
||||
url: "/api/post",
|
||||
data: {"guild_id": guild_id, "channel_id": channel_id, "content": content}
|
||||
});
|
||||
return funct.promise();
|
||||
}
|
||||
|
||||
$(function(){
|
||||
$("#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
|
||||
}
|
||||
);
|
||||
|
||||
$('#loginmodal').modal('open');
|
||||
lock_login_fields();
|
||||
|
||||
var guild = query_guild();
|
||||
guild.fail(function() {
|
||||
unlock_login_fields();
|
||||
});
|
||||
|
||||
guild.done(function(data) {
|
||||
initialize_embed(data);
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
if (guildobj === undefined) {
|
||||
var guild = query_guild();
|
||||
guild.done(function(data) {
|
||||
prepare_guild(data);
|
||||
$('#loginmodal').modal('close');
|
||||
unlock_login_fields();
|
||||
});
|
||||
} else {
|
||||
prepare_guild(guildobj);
|
||||
$('#loginmodal').modal('close');
|
||||
unlock_login_fields();
|
||||
}
|
||||
}
|
||||
|
||||
function prepare_guild(guildobj) {
|
||||
fill_channels(guildobj.channels);
|
||||
fill_discord_members(guildobj.discordmembers);
|
||||
fill_authenticated_users(guildobj.embedmembers.authenticated);
|
||||
fill_unauthenticated_users(guildobj.embedmembers.unauthenticated);
|
||||
run_fetch_routine();
|
||||
}
|
||||
|
||||
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];
|
||||
guild_channels[chan.channel.id] = chan;
|
||||
if (chan.read) {
|
||||
var rendered = Mustache.render(template, {"channelid": chan.channel.id, "channelname": chan.channel.name});
|
||||
$("#channels-list").append(rendered);
|
||||
$("#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
$("#channel-"+selected_channel).parent().addClass("active");
|
||||
}
|
||||
|
||||
function mention_member(member_id) {
|
||||
if (!$('#messagebox').prop('disabled')) {
|
||||
$('#messagebox').val( $('#messagebox').val() + "[@" + member_id + "] " );
|
||||
$('.button-collapse').sideNav('hide');
|
||||
$("#messagebox").focus();
|
||||
}
|
||||
}
|
||||
|
||||
function fill_discord_members(discordmembers) {
|
||||
var template = $('#mustache_authedusers').html();
|
||||
Mustache.parse(template);
|
||||
$("#discord-members").empty();
|
||||
var guild_members = {};
|
||||
for (var i = 0; i < discordmembers.length; i++) {
|
||||
var member = discordmembers[i];
|
||||
if (member["hoist-role"]) {
|
||||
if (!(member["hoist-role"]["id"] in guild_members)) {
|
||||
guild_members[member["hoist-role"]["id"]] = {};
|
||||
guild_members[member["hoist-role"]["id"]]["name"] = member["hoist-role"]["name"];
|
||||
guild_members[member["hoist-role"]["id"]]["members"] = [];
|
||||
guild_members[member["hoist-role"]["id"]]["position"] = member["hoist-role"]["position"]
|
||||
}
|
||||
guild_members[member["hoist-role"]["id"]]["members"].push(member);
|
||||
} else {
|
||||
if (!("0" in guild_members)) {
|
||||
guild_members["0"] = {};
|
||||
guild_members["0"]["name"] = null;
|
||||
guild_members["0"]["members"] = [];
|
||||
guild_members["0"]["position"] = 0;
|
||||
}
|
||||
guild_members["0"]["members"].push(member);
|
||||
}
|
||||
}
|
||||
var guild_members_arr = [];
|
||||
for (key in guild_members) {
|
||||
guild_members_arr.push(guild_members[key]);
|
||||
}
|
||||
guild_members_arr.sort(function(a, b) {
|
||||
return parseInt(b.position) - parseInt(a.position);
|
||||
});
|
||||
var template_role = $('#mustache_memberrole').html();
|
||||
Mustache.parse(template_role);
|
||||
var template_user = $('#mustache_authedusers').html();
|
||||
Mustache.parse(template_user);
|
||||
$("#discord-members").empty();
|
||||
var discordmembercnt = 0;
|
||||
for (var i = 0; i < guild_members_arr.length; i++) {
|
||||
var roleobj = guild_members_arr[i];
|
||||
if (!roleobj["name"]) {
|
||||
roleobj["name"] = "Uncategorized";
|
||||
}
|
||||
var rendered_role = Mustache.render(template_role, {"name": roleobj["name"] + " - " + roleobj["members"].length});
|
||||
discordmembercnt += roleobj["members"].length;
|
||||
$("#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});
|
||||
$("#discord-members").append(rendered_user);
|
||||
$( "#discorduser-" + member.id.toString() + "d").click({"member_id": member.id.toString()}, function(event) {
|
||||
mention_member(event.data.member_id);
|
||||
});
|
||||
if (member.color) {
|
||||
$( "#discorduser-" + member.id.toString() + "d").css("color", "#" + member.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
$("#discord-members-count").html(discordmembercnt);
|
||||
}
|
||||
|
||||
function fill_authenticated_users(users) {
|
||||
var template = $('#mustache_authedusers').html();
|
||||
Mustache.parse(template);
|
||||
$("#embed-discord-members").empty();
|
||||
$("#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});
|
||||
$("#embed-discord-members").append(rendered);
|
||||
$( "#discorduser-" + member.id.toString() + "a").click({"member_id": member.id.toString()}, function(event) {
|
||||
mention_member(event.data.member_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function fill_unauthenticated_users(users) {
|
||||
var template = $('#mustache_unauthedusers').html();
|
||||
Mustache.parse(template);
|
||||
$("#embed-unauth-users").empty();
|
||||
$("#guest-members-count").html(users.length);
|
||||
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);
|
||||
}
|
||||
|
||||
function select_channel(channel_id) {
|
||||
if (selected_channel != channel_id) {
|
||||
selected_channel = channel_id;
|
||||
last_message_id = null;
|
||||
$("#channels-list > li.active").removeClass("active");
|
||||
$("#channel-"+selected_channel).parent().addClass("active");
|
||||
clearTimeout(fetchtimeout);
|
||||
run_fetch_routine();
|
||||
}
|
||||
}
|
||||
|
||||
function replace_message_mentions(message) {
|
||||
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);
|
||||
message.content = message.content.replace("<@&" + guild_id + ">", "@everyone");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function getPosition(string, subString, index) {
|
||||
return string.split(subString, index).join(subString).length;
|
||||
}
|
||||
|
||||
function format_bot_message(message) {
|
||||
if (message.author.id == bot_client_id && (message.content.includes("**") && ( (message.content.includes("<")&&message.content.includes(">")) || (message.content.includes("[") && message.content.includes("]")) ))) {
|
||||
var usernamefield = message.content.substring(getPosition(message.content, "**", 1)+3, getPosition(message.content, "**", 2)-1);
|
||||
message.content = message.content.substring(usernamefield.length+7);
|
||||
message.author.username = usernamefield.split("#")[0];
|
||||
message.author.discriminator = usernamefield.split("#")[1];
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function parse_message_time(message) {
|
||||
var mome = moment(message.timestamp);
|
||||
message.formatted_timestamp = mome.toDate().toString();
|
||||
message.formatted_time = mome.format("h:mm A");
|
||||
return message;
|
||||
}
|
||||
|
||||
function parse_message_attachments(message) {
|
||||
for (var i = 0; i < message.attachments.length; i++) {
|
||||
var attach = "";
|
||||
if (message.content.length != 0) {
|
||||
attach = " ";
|
||||
}
|
||||
attach += message.attachments[i].url;
|
||||
message.content += attach;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function handle_last_message_mention() {
|
||||
var lastmsg = $("#chatcontent p:last-child");
|
||||
var content = lastmsg.text().toLowerCase();
|
||||
var username_discrim = $("#currentusername").text().toLowerCase();
|
||||
if (content.includes("@everyone") || content.includes("@" + username_discrim)) {
|
||||
lastmsg.css( "color", "#ff5252" );
|
||||
lastmsg.css( "font-weight", "bold" );
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(unsafe) { /* http://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript */
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function nl2br (str, is_xhtml) { /* http://stackoverflow.com/questions/2919337/jquery-convert-line-breaks-to-br-nl2br-equivalent/ */
|
||||
var breakTag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';
|
||||
return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
|
||||
}
|
||||
|
||||
function parse_channels_in_message(message) {
|
||||
var channelids = Object.keys(guild_channels);
|
||||
for (var i = 0; i < channelids.length; i++) {
|
||||
var pattern = "<#" + channelids[i] + ">";
|
||||
message.content = message.content.replace(new RegExp(pattern, "g"), "#" + guild_channels[channelids[i]].channel.name);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function fill_discord_messages(messages, jumpscroll) {
|
||||
if (messages.length == 0) {
|
||||
return last_message_id;
|
||||
}
|
||||
var last = 0;
|
||||
var template = $('#mustache_usermessage').html();
|
||||
Mustache.parse(template);
|
||||
for (var i = messages.length-1; i >= 0; i--) {
|
||||
var message = messages[i];
|
||||
message = replace_message_mentions(message);
|
||||
message = format_bot_message(message);
|
||||
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))});
|
||||
$("#chatcontent").append(rendered);
|
||||
last = message.id;
|
||||
handle_last_message_mention();
|
||||
}
|
||||
$("html, body").animate({ scrollTop: $(document).height() }, "slow");
|
||||
$('#chatcontent').linkify({
|
||||
target: "_blank"
|
||||
});
|
||||
return last;
|
||||
}
|
||||
|
||||
function run_fetch_routine() {
|
||||
if (currently_fetching) {
|
||||
return;
|
||||
}
|
||||
currently_fetching = true;
|
||||
times_fetched += 1;
|
||||
var channel_id = selected_channel;
|
||||
var fet;
|
||||
var jumpscroll;
|
||||
$("#fetching-indicator").fadeIn(800);
|
||||
if (last_message_id == null) {
|
||||
$("#chatcontent").empty();
|
||||
fet = fetch(channel_id);
|
||||
jumpscroll = true;
|
||||
} else {
|
||||
fet = fetch(channel_id, last_message_id);
|
||||
jumpscroll = element_in_view($('#discordmessage_'+last_message_id), true);
|
||||
}
|
||||
fet.done(function(data) {
|
||||
var status = data.status;
|
||||
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) {
|
||||
$("#administrate_link").show();
|
||||
} else {
|
||||
$("#administrate_link").hide();
|
||||
}
|
||||
if (times_fetched % 10 == 0) {
|
||||
var guild = query_guild();
|
||||
guild.done(function(guildobj) {
|
||||
fill_channels(guildobj.channels);
|
||||
fill_discord_members(guildobj.discordmembers);
|
||||
fill_authenticated_users(guildobj.embedmembers.authenticated);
|
||||
fill_unauthenticated_users(guildobj.embedmembers.unauthenticated);
|
||||
fetchtimeout = setTimeout(run_fetch_routine, 5000);
|
||||
});
|
||||
} else {
|
||||
fetchtimeout = setTimeout(run_fetch_routine, 5000);
|
||||
}
|
||||
});
|
||||
fet.fail(function(data) {
|
||||
if (data.status == 403) {
|
||||
$('#loginmodal').modal('open');
|
||||
Materialize.toast('Authentication error! You have been disconnected by the server.', 10000);
|
||||
} else if (data.status == 401) {
|
||||
$('#loginmodal').modal('open');
|
||||
Materialize.toast('Session expired! You have been logged out.', 10000);
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
});
|
||||
fet.always(function() {
|
||||
currently_fetching = false;
|
||||
$("#fetching-indicator").fadeOut(800);
|
||||
});
|
||||
}
|
||||
|
||||
function update_embed_userchip(authenticated, avatar, username, userid, discrim=null) {
|
||||
if (authenticated) {
|
||||
$("#currentuserimage").show();
|
||||
$("#currentuserimage").attr("src", avatar);
|
||||
$("#currentusername").text(username + "#" + discrim);
|
||||
} else {
|
||||
$("#currentuserimage").hide();
|
||||
$("#currentusername").text(username + "#" + userid);
|
||||
}
|
||||
}
|
||||
|
||||
$("#discordlogin_btn").click(function() {
|
||||
lock_login_fields();
|
||||
wait_for_discord_login();
|
||||
});
|
||||
|
||||
$("#custom_username_field").keyup(function(event){
|
||||
if (event.keyCode == 13) {
|
||||
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) {
|
||||
lock_login_fields();
|
||||
var usr = create_unauthenticated_user($(this).val());
|
||||
usr.done(function(data) {
|
||||
initialize_embed();
|
||||
});
|
||||
usr.fail(function(data) {
|
||||
if (data.status == 429) {
|
||||
Materialize.toast('Sorry! You are allowed to log in as a guest 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);
|
||||
}
|
||||
unlock_login_fields();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("#messagebox").keyup(function(event){
|
||||
if ($(this).val().length == 1) {
|
||||
$(this).val($.trim($(this).val()));
|
||||
}
|
||||
if(event.keyCode == 13 && $(this).val().length >= 1 && $(this).val().length <= 350) {
|
||||
$(this).val($.trim($(this).val()));
|
||||
$(this).blur();
|
||||
$("#messagebox").attr('readonly', true);
|
||||
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);
|
||||
});
|
||||
funct.catch(function(data) {
|
||||
if (data.status == 429) {
|
||||
Materialize.toast('You are sending messages too fast! 1 message per 10 seconds', 10000);
|
||||
}
|
||||
});
|
||||
funct.always(function() {
|
||||
$("#messagebox").attr('readonly', false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#guild-btn').sideNav({
|
||||
menuWidth: 300, // Default is 300
|
||||
edge: 'left', // Choose the horizontal origin
|
||||
closeOnClick: true, // Closes side-nav on <a> 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
|
||||
}
|
||||
);
|
||||
})();
|
BIN
webapp/titanembeds/static/titanembeds.mp4
Normal file
BIN
webapp/titanembeds/static/titanembeds.mp4
Normal file
Binary file not shown.
BIN
webapp/titanembeds/static/titanembeds.ogg
Normal file
BIN
webapp/titanembeds/static/titanembeds.ogg
Normal file
Binary file not shown.
BIN
webapp/titanembeds/static/titanembeds.webm
Normal file
BIN
webapp/titanembeds/static/titanembeds.webm
Normal file
Binary file not shown.
127
webapp/titanembeds/templates/administrate_guild.html.j2
Normal file
127
webapp/titanembeds/templates/administrate_guild.html.j2
Normal file
@ -0,0 +1,127 @@
|
||||
{% extends 'site_layout.html.j2' %}
|
||||
{% block title %}Administrate Guild: {{ guild['name'] }}{% endblock %}
|
||||
|
||||
{% block additional_head_elements %}
|
||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/administrate_guild.css') }}">
|
||||
{% 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 readonly value="{{ url_for("embed.guild_embed", guild_id=guild['id'], _external=True) }}" id="disabled" type="text" onClick="this.setSelectionRange(0, this.value.length)">
|
||||
<p class="flow-text">iFrame Embed</p>
|
||||
<input readonly value="<iframe src="{{ url_for("embed.guild_embed", guild_id=guild['id'], _external=True) }}" height="600" width="800" />" id="disabled" type="text" onClick="this.setSelectionRange(0, this.value.length)">
|
||||
</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>
|
||||
<div class="switch">
|
||||
<label>
|
||||
Disable
|
||||
<input type="checkbox" id="unauth_users" name="unauth_users" {% if dbguild['unauth_users'] %}checked{% endif %} >
|
||||
<span class="lever"></span>
|
||||
Enable
|
||||
</label>
|
||||
</div>
|
||||
</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" style="overflow-x: hidden;">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
|
||||
<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 Hash</th>
|
||||
<th>Banned Timestamp</th>
|
||||
<th>Banned by</th>
|
||||
<th>Banned Reason</th>
|
||||
<th>Ban Lifted by</th>
|
||||
<th>Recent Aliases</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td><a class="waves-effect waves-light btn orange" {% if "Kick Members" not in permissions or member["kicked"] %}disabled{% endif %} onclick='revoke_user( "{{ guild['id'] }}" , {{ member['id'] }} )' >Kick</a></td>
|
||||
{% if not member["banned"] %}
|
||||
<td><a class="waves-effect waves-light btn red" {% if "Ban Members" not in permissions %}disabled{% endif %} {% if "Ban Members" in permissions %} onclick='initiate_ban( "{{ guild['id'] }}" , {{ member['id'] }} )' {% endif %} >Ban</a></td>
|
||||
{% else %}
|
||||
<td><a class="waves-effect waves-light btn red lighten-2" {% if "Ban Members" not in permissions %}disabled{% endif %} {% if "Ban Members" in permissions %} onclick='remove_ban( "{{ guild['id'] }}" , {{ member['id'] }} )' {% endif %} >Lift</a></td>
|
||||
{% endif %}
|
||||
<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>
|
||||
<td>
|
||||
<ul>
|
||||
{% if member['aliases']|length > 0 %}
|
||||
{% for alias in member['aliases'] %}
|
||||
<li>{{ alias }}</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<li>None</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Note that all bans are by IP. Seeing duplicates? It is because users are generated a unique session on each browser load. (Though we try to remove/concat any duplicates IP hashes)</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/administrate_guild.js') }}"></script>
|
||||
{% endblock %}
|
32
webapp/titanembeds/templates/dashboard.html.j2
Normal file
32
webapp/titanembeds/templates/dashboard.html.j2
Normal file
@ -0,0 +1,32 @@
|
||||
{% extends 'site_layout.html.j2' %}
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>User Dashboard</h1>
|
||||
<p class="flow-text">Select a server to configure Titan Embeds.</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">
|
||||
{% for server in servers %}
|
||||
<div class="col l4 m6 s12">
|
||||
<div class="card-panel indigo lighten-5 z-depth-3 hoverable">
|
||||
<div class="row valign-wrapper">
|
||||
<div class="col s3">
|
||||
{% if server.icon %}
|
||||
<img src="{{ icon_generate(server.id, server.icon) }}" alt="" class="circle responsive-img">
|
||||
{% else %}
|
||||
<span class="black-text">No icon :(</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col s7">
|
||||
<span class="black-text">
|
||||
<p class="flow-text truncate">{{ server.name }}</p>
|
||||
<br>
|
||||
<a class="waves-effect waves-light btn" href="{{url_for('user.administrate_guild', guild_id=server['id'])}}">Modify</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
151
webapp/titanembeds/templates/embed.html.j2
Normal file
151
webapp/titanembeds/templates/embed.html.j2
Normal file
@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--Import Google Icon Font-->
|
||||
<link href="//fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<!--Import materialize.css-->
|
||||
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/css/materialize.min.css" integrity="sha256-6DQKO56c9MZL0LAc7QNtxqJyqSa3rS9Gq5FVcIhtA+w=" crossorigin="anonymous" media="screen,projection"/>
|
||||
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/embedstyle.css') }}">
|
||||
|
||||
<!--Let browser know website is optimized for mobile-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<title>{{ guild['name'] }} - Embed - Titan Embeds for Discord</title>
|
||||
{% include 'google_analytics.html.j2' %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar-fixed">
|
||||
<nav>
|
||||
<div class="nav-wrapper">
|
||||
<a href="#" data-activates="guild-nav" class="button-collapse" id="guild-btn"><i class="material-icons">menu</i></a>
|
||||
<div class="container">
|
||||
<a href="{{ url_for("index") }}" target="_blank" class="brand-logo"><b>Titan</b>Embeds <span class="betatag">BETA</span></a>
|
||||
</div>
|
||||
<a href="#" data-activates="members-nav" class="button-collapse right" id="members-btn"><i class="material-icons">person</i></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<main>
|
||||
<div id="chatcontent" class="chatcontent"></div>
|
||||
</main>
|
||||
|
||||
<ul id="guild-nav" class="side-nav">
|
||||
<li>
|
||||
<div class="userView">
|
||||
{% if guild['icon'] %}
|
||||
<img class="circle" src="{{ generate_guild_icon( guild['id'], guild['icon'] ) }}">
|
||||
{% endif %}
|
||||
<span class="name">{{ guild['name'] }}</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><a class="subheader">Actions</a></li>
|
||||
<li><a href="{{ url_for("user.administrate_guild", guild_id=guild['id']) }}" class="waves-effect" target="_blank" id="administrate_link" style="display: none;">Manage Guild Embed</a></li>
|
||||
<li><a href="https://discordapp.com/channels/{{ guild['id'] }}/" class="waves-effect" target="_blank">Open Server on Discordapp</a></li>
|
||||
|
||||
<li><div class="divider"></div></li>
|
||||
|
||||
<li><a class="subheader">Channel Topic</a></li>
|
||||
<div id="channeltopic"></div>
|
||||
|
||||
<li><div class="divider"></div></li>
|
||||
|
||||
<li><a class="subheader">Channels</a></li>
|
||||
<span id="channels-list"></span>
|
||||
</ul>
|
||||
|
||||
<ul id="members-nav" class="side-nav">
|
||||
<li><a class="subheader">Online Server Members - <span id="discord-members-count"></span></a></li>
|
||||
<span id="discord-members"></span>
|
||||
|
||||
<li><div class="divider"></div></li>
|
||||
|
||||
<li><a class="subheader">Authenticated Embed Users - <span id="embed-discord-members-count"></span></a></li>
|
||||
<span id="embed-discord-members"></span>
|
||||
|
||||
<li><a class="subheader">Guest Embed Users - <span id="guest-members-count"></span></a></li>
|
||||
<span id="embed-unauth-users"></span>
|
||||
</ul>
|
||||
|
||||
<div id="loginmodal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>{{ login_greeting }}</h4>
|
||||
<p class="flow-text">Please choose one of the following methods to authenticate!</p>
|
||||
<div class="progress" id="loginProgress" style="display: none;">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12 m4">
|
||||
<a id="discordlogin_btn" href="{{ url_for("embed.login_discord", _external=True) }}" class="waves-effect waves-light btn-large" target="_blank">Discord Login</a>
|
||||
<p>*You will be invited into this server.</p>
|
||||
</div>
|
||||
{% if unauth_enabled %}
|
||||
<div class="col s12 m8">
|
||||
<p>Of course, you also have the option to login by picking a temporary username for your current browsing session.</p>
|
||||
<input id="custom_username_field" type="text" {% if session.unauthenticated and session.username %}value="{{ session['username'] }}"{% endif %}>
|
||||
<label class="active" for="custom_username_field">Username (Hit ENTER/RETURN key to confirm)</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer id="footer" class="footer">
|
||||
<div id="fetching-indicator" class="preloader-wrapper small active" style="display: none;">
|
||||
<div class="spinner-layer spinner-blue-only">
|
||||
<div class="circle-clipper left">
|
||||
<div class="circle"></div>
|
||||
</div><div class="gap-patch">
|
||||
<div class="circle"></div>
|
||||
</div><div class="circle-clipper right">
|
||||
<div class="circle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footercontainer" class="footercontainer">
|
||||
<div class="currentuserchip left" id="nameplate">
|
||||
<div class="left"><img id="currentuserimage" src="" class="circle left currentuserimage" style="display: none;"></div>
|
||||
<div id="currentusername" class="currentusername left">Titan#0001</div>
|
||||
</div>
|
||||
<div id="messageboxouter" class="input-field inline"><textarea placeholder="Enter message" id="messagebox" type="text" class="materialize-textarea" rows="1"></textarea></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!--Import jQuery before materialize.js-->
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/js/materialize.min.js" integrity="sha256-ToPQhpo/E89yaCd7+V8LUCjobNRkjilRXfho6x3twLU=" crossorigin="anonymous"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js" integrity="sha256-iaqfO5ue0VbSGcEiQn+OeXxnxAMK2+QgHXIDA5bWtGI=" crossorigin="anonymous"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js" integrity="sha256-1hjUhpc44NwiNg8OwMu2QzJXhD8kcj+sJA3aCQZoUjg=" crossorigin="anonymous"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jQuery-linkify/2.1.4/linkify.min.js" integrity="sha256-/qh8j6L0/OTx+7iY8BAeLirxCDBsu3P15Ci5bo7BJaU=" crossorigin="anonymous"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jQuery-linkify/2.1.4/linkify-jquery.min.js" integrity="sha256-BlSfVPlZijMLojgte2AtSget879chk1+8Z8bEH/L4Cs=" crossorigin="anonymous"></script>
|
||||
|
||||
{% raw %}
|
||||
<script id="mustache_channellistings" type="text/template">
|
||||
<li><a class="waves-effect truncate" id="channel-{{channelid}}"><span class="channel-hash">#</span> {{channelname}}</a></li>
|
||||
</script>
|
||||
|
||||
<script id="mustache_authedusers" type="text/template">
|
||||
<li><a class="waves-effect truncate" id="discorduser-{{id}}"><img class="circle membercircle" src="{{avatar}}"> <span class="membername">{{username}}</span></a></li>
|
||||
</script>
|
||||
|
||||
<script id="mustache_unauthedusers" type="text/template">
|
||||
<li><a class="waves-effect truncate"><span class="membername">{{username}}#{{discriminator}}</span></a></li>
|
||||
</script>
|
||||
|
||||
<script id="mustache_usermessage" type="text/template">
|
||||
<p><span id="discordmessage_{{id}}" title="{{full_timestamp}}" class="chattimestamp">{{time}}</span> <span class="chatusername">{{username}}#{{discriminator}}</span> {{{content}}}</p>
|
||||
</script>
|
||||
|
||||
<script id="mustache_memberrole" type="text/template">
|
||||
<li><a class="subheader role-title">{{name}}</a></li>
|
||||
</script>
|
||||
{% endraw %}
|
||||
|
||||
<script>
|
||||
const guild_id = "{{ guild_id }}";
|
||||
const bot_client_id = "{{ client_id }}";
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/embed.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
9
webapp/titanembeds/templates/google_analytics.html.j2
Normal file
9
webapp/titanembeds/templates/google_analytics.html.j2
Normal file
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
!function(T,i,t,a,n){T.GoogleAnalyticsObject=t;T[t]||(T[t]=function(){
|
||||
(T[t].q=T[t].q||[]).push(arguments)});T[t].l=+new Date;a=i.createElement('script');
|
||||
n=i.scripts[0];a.src='//www.google-analytics.com/analytics.js';
|
||||
n.parentNode.insertBefore(a,n)}(window,document,'ga');
|
||||
|
||||
ga('create', 'UA-97073231-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
17
webapp/titanembeds/templates/index.html.j2
Normal file
17
webapp/titanembeds/templates/index.html.j2
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends 'site_layout.html.j2' %}
|
||||
{% block title %}Index{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="center-align">Embed Discord like a<br><strong>true Titan</strong></h1>
|
||||
<p class="flow-text center-align">Add <strong>Titan</strong> to your discord server to create your own personalized chat embed!</p>
|
||||
<a class="waves-effect waves-light btn btn-large center_content" href="{{ url_for('user.dashboard') }}">Start here!</a>
|
||||
<br /><br />
|
||||
<div style="display: flex;align-items: center;">
|
||||
<video preload="true" autoplay loop style="width:100%; border-radius: 10px;">
|
||||
<source src="{{url_for('static', filename='titanembeds.mp4')}}" type="video/mp4">
|
||||
<source src="{{url_for('static', filename='titanembeds.webm')}}" type="video/webm; codecs=vp8, vorbis">
|
||||
<source type="video/ogg; codecs=theora, vorbis" src="{{url_for('static', filename='titanembeds.ogg')}}">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
{% endblock %}
|
12
webapp/titanembeds/templates/signin_complete.html.j2
Normal file
12
webapp/titanembeds/templates/signin_complete.html.j2
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Sign in completed - Titan Embeds</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Sign in complete! You may now close the window.</p>
|
||||
<script>
|
||||
window.close()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
60
webapp/titanembeds/templates/site_layout.html.j2
Normal file
60
webapp/titanembeds/templates/site_layout.html.j2
Normal file
@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--Import Google Icon Font-->
|
||||
<link href="//fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<!--Import materialize.css-->
|
||||
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/css/materialize.min.css" integrity="sha256-6DQKO56c9MZL0LAc7QNtxqJyqSa3rS9Gq5FVcIhtA+w=" crossorigin="anonymous" media="screen,projection"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
|
||||
<!--Let browser know website is optimized for mobile-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<title>{% block title %}{% endblock %} - Titan Embeds for Discord</title>
|
||||
|
||||
{% block additional_head_elements %}{% endblock %}
|
||||
|
||||
{% include 'google_analytics.html.j2' %}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{% if session['unauthenticated'] is defined and not session['unauthenticated'] %}
|
||||
<ul id="menu_dropdown" class="dropdown-content">
|
||||
<li><a href="{{ url_for('user.dashboard') }}">Dashboard</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="{{ url_for('user.logout') }}">Logout</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<nav>
|
||||
<div class="nav-wrapper container">
|
||||
<a href="/" class="brand-logo"><b>Titan</b>Embeds <span class="betatag">BETA</span></a>
|
||||
<ul id="nav-mobile" class="right">
|
||||
<li><a href="{{url_for("embed.guild_embed", guild_id="295085744249110529")}}" class="waves-effect btn z-depth-3">Visit Us!</a></li>
|
||||
{% if session['unauthenticated'] is defined and not session['unauthenticated'] %}
|
||||
<li><a id="menu_drop" data-activates="menu_dropdown" class="waves-effect btn z-depth-3 btn-floating dropdown-button avatar_menu" style='background-image: url(" {{ session['avatar'] }} ")'></a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('user.login_authenticated') }}" class="waves-effect btn z-depth-3">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
<footer class="page-footer">
|
||||
<div class="footer-copyright">
|
||||
<div class="container">
|
||||
A project by EndenDragon
|
||||
<a class="grey-text text-lighten-4 right" href="https://github.com/EndenDragon/Titan">GitHub Repo</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!--Import jQuery before materialize.js-->
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/js/materialize.min.js" integrity="sha256-ToPQhpo/E89yaCd7+V8LUCjobNRkjilRXfho6x3twLU=" crossorigin="anonymous"></script>
|
||||
{% block script %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
77
webapp/titanembeds/utils.py
Normal file
77
webapp/titanembeds/utils.py
Normal file
@ -0,0 +1,77 @@
|
||||
from beaker.cache import CacheManager
|
||||
from beaker.util import parse_cache_config_options
|
||||
from titanembeds.database import db, Guilds, KeyValueProperties
|
||||
from flask import request, session
|
||||
from flask_limiter import Limiter
|
||||
from config import config
|
||||
import random
|
||||
import string
|
||||
import hashlib
|
||||
|
||||
cache_opts = {
|
||||
'cache.type': 'ext:database',
|
||||
'cache.lock_dir': 'tmp/cachelock',
|
||||
'cache.url': config["database-uri"],
|
||||
'cache.sa.pool_recycle': 250,
|
||||
}
|
||||
cache = CacheManager(**parse_cache_config_options(cache_opts))
|
||||
|
||||
from titanembeds.discordrest import DiscordREST
|
||||
|
||||
discord_api = DiscordREST(config['bot-token'])
|
||||
|
||||
def get_client_ipaddr():
|
||||
if "X-Real-IP" in request.headers: # pythonanywhere specific
|
||||
ip = request.headers['X-Real-IP']
|
||||
else: # general
|
||||
ip = request.remote_addr
|
||||
return hashlib.sha512(config['app-secret'] + ip).hexdigest()[:15]
|
||||
|
||||
def generate_session_key():
|
||||
sess = session.get("sessionunique", None)
|
||||
if not sess:
|
||||
rand_str = lambda n: ''.join([random.choice(string.lowercase) for i in xrange(n)])
|
||||
session['sessionunique'] = rand_str(25)
|
||||
sess = session['sessionunique']
|
||||
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')
|
||||
|
||||
def make_guildchannels_cache_key():
|
||||
guild_id = request.values.get('guild_id', "0")
|
||||
sess = generate_session_key()
|
||||
ip = get_client_ipaddr()
|
||||
return (sess + ip + guild_id + "user_guild_channels").encode('utf-8')
|
||||
|
||||
def channel_ratelimit_key(): # Generate a bucket with given channel & unique session key
|
||||
sess = generate_session_key()
|
||||
channel_id = request.values.get('channel_id', "0")
|
||||
return (sess + channel_id).encode('utf-8')
|
||||
|
||||
def guild_ratelimit_key():
|
||||
ip = get_client_ipaddr()
|
||||
guild_id = request.values.get('guild_id', "0")
|
||||
return (ip + 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
|
||||
guild = discord_api.get_guild(guild_id)
|
||||
return guild['code'] == 200
|
||||
|
||||
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
|
Reference in New Issue
Block a user