mirror of
				https://github.com/TitanEmbeds/Titan.git
				synced 2025-11-03 23:37:09 +01:00 
			
		
		
		
	Initial structure for discordbot addition
This commit is contained in:
		
							
								
								
									
										8
									
								
								webapp/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								webapp/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
# Titan - WebApp Portion
 | 
			
		||||
The webapp portion handles the frontend (it's what the users see). The webapp highly depends on the discordbot to push websockets data to the database.
 | 
			
		||||
 | 
			
		||||
# Installation
 | 
			
		||||
1. Clone the repo (make sure you have **Python 2.7** installed on your system. This webapp portion depends on that specific python version)
 | 
			
		||||
2. Install the pip requirements `pip install -r requirements.txt`
 | 
			
		||||
3. Clone `config.example.py` and rename it to `config.py`. Edit the file to your standards
 | 
			
		||||
4. Run the development web via `python run.py` -- Though we suggest to use a better server software (look into gunicorn, nginx, uwsgi, etc)
 | 
			
		||||
							
								
								
									
										12
									
								
								webapp/config.example.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								webapp/config.example.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
config = {
 | 
			
		||||
    # Create an app over here https://discordapp.com/developers/applications/me
 | 
			
		||||
    # and fill these fields out
 | 
			
		||||
    'client-id': "Your app client id",
 | 
			
		||||
    'client-secret': "Your discord client secret",
 | 
			
		||||
    'bot-token': "Discord bot token",
 | 
			
		||||
 | 
			
		||||
    'app-location': "/var/www/Titan/webapp/",
 | 
			
		||||
    'app-secret': "Type something random here, go wild.",
 | 
			
		||||
 | 
			
		||||
    'database-uri': "driver://username:password@host:port/database",
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								webapp/requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								webapp/requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
flask
 | 
			
		||||
flask-sqlalchemy
 | 
			
		||||
flask_limiter
 | 
			
		||||
requests_oauthlib
 | 
			
		||||
mysql-python
 | 
			
		||||
Flask-SSLify
 | 
			
		||||
beaker
 | 
			
		||||
							
								
								
									
										35
									
								
								webapp/run.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								webapp/run.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
#!/usr/bin/env python2
 | 
			
		||||
from titanembeds.app import app
 | 
			
		||||
 | 
			
		||||
def init_debug():
 | 
			
		||||
    import os
 | 
			
		||||
    from flask import jsonify, request
 | 
			
		||||
 | 
			
		||||
    os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Testing oauthlib
 | 
			
		||||
 | 
			
		||||
    # Session viewer https://gist.github.com/babldev/502364a3f7c9bafaa6db
 | 
			
		||||
    def decode_flask_cookie(secret_key, cookie_str):
 | 
			
		||||
        import hashlib
 | 
			
		||||
        from itsdangerous import URLSafeTimedSerializer
 | 
			
		||||
        from flask.sessions import TaggedJSONSerializer
 | 
			
		||||
        salt = 'cookie-session'
 | 
			
		||||
        serializer = TaggedJSONSerializer()
 | 
			
		||||
        signer_kwargs = {
 | 
			
		||||
            'key_derivation': 'hmac',
 | 
			
		||||
            'digest_method': hashlib.sha1
 | 
			
		||||
        }
 | 
			
		||||
        s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)
 | 
			
		||||
        return s.loads(cookie_str)
 | 
			
		||||
 | 
			
		||||
    @app.route("/session")
 | 
			
		||||
    def session():
 | 
			
		||||
        cookie = request.cookies.get('session')
 | 
			
		||||
        if cookie:
 | 
			
		||||
            decoded = decode_flask_cookie(app.secret_key, request.cookies.get('session'))
 | 
			
		||||
        else:
 | 
			
		||||
            decoded = None
 | 
			
		||||
        return jsonify(session_cookie=decoded)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    init_debug()
 | 
			
		||||
    app.run(host="0.0.0.0",port=3000,debug=True,processes=3)
 | 
			
		||||
							
								
								
									
										6
									
								
								webapp/run_c9.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								webapp/run_c9.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
from run import app, init_debug
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    init_debug()
 | 
			
		||||
    app.run(host=os.getenv('IP', '0.0.0.0'), port=int(os.getenv('PORT', 8080)), debug=True)
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										0
									
								
								webapp/tmp/.gitinclude
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								webapp/tmp/.gitinclude
									
									
									
									
									
										Normal file
									
								
							
		Reference in New Issue
	
	Block a user