From 030d492af1c9f0e56143fa021a04f02b6cdada21 Mon Sep 17 00:00:00 2001 From: Jeremy Zhang Date: Thu, 9 Feb 2017 21:10:44 -0800 Subject: [PATCH] Organization and better creating and posting message support for unauth users --- app.py | 54 ------- run.py | 5 + storage.txt | 37 +++++ titanembeds/__init__.py | 0 titanembeds/app.py | 38 +++++ titanembeds/blueprints/__init__.py | 0 titanembeds/blueprints/api/__init__.py | 1 + titanembeds/blueprints/api/api.py | 140 ++++++++++++++++++ titanembeds/blueprints/embed/embed.py | 0 titanembeds/database/__init__.py | 6 + titanembeds/database/authenticated_users.py | 19 +++ titanembeds/database/guilds.py | 19 +++ titanembeds/database/unauthenticated_bans.py | 32 ++++ titanembeds/database/unauthenticated_users.py | 45 ++++++ titanembeds/decorators.py | 17 +++ .../templates}/embed.html | 0 .../templates}/set_username.html | 0 17 files changed, 359 insertions(+), 54 deletions(-) delete mode 100644 app.py create mode 100644 run.py create mode 100644 storage.txt create mode 100644 titanembeds/__init__.py create mode 100644 titanembeds/app.py create mode 100644 titanembeds/blueprints/__init__.py create mode 100644 titanembeds/blueprints/api/__init__.py create mode 100644 titanembeds/blueprints/api/api.py create mode 100644 titanembeds/blueprints/embed/embed.py create mode 100644 titanembeds/database/__init__.py create mode 100644 titanembeds/database/authenticated_users.py create mode 100644 titanembeds/database/guilds.py create mode 100644 titanembeds/database/unauthenticated_bans.py create mode 100644 titanembeds/database/unauthenticated_users.py create mode 100644 titanembeds/decorators.py rename {templates => titanembeds/templates}/embed.html (100%) rename {templates => titanembeds/templates}/set_username.html (100%) diff --git a/app.py b/app.py deleted file mode 100644 index a484ea8..0000000 --- a/app.py +++ /dev/null @@ -1,54 +0,0 @@ -from config import config -from flask import Flask, render_template, request, jsonify, session, url_for, redirect -import requests -import json - -app = Flask(__name__) -app.secret_key = config['client-secret'] - -_DISCORD_API_BASE = "https://discordapp.com/api/v6" - -@app.route("/api/Get_Channel_Messages") -def get_channel_messages(): - channel_id = request.args.get('channel_id') - after_snowflake = request.args.get('after', None, type=int) - _endpoint = _DISCORD_API_BASE + "/channels/{channel_id}/messages".format(channel_id=channel_id) - payload = {} - if after_snowflake is not None: - payload = {'after': after_snowflake} - headers = {'Authorization': 'Bot ' + config['bot-token']} - r = requests.get(_endpoint, params=payload, headers=headers) - return jsonify(j=json.loads(r.content)) - -@app.route("/api/Create_Message", methods=['POST']) -def post_create_message(): - channel_id = request.form.get('channel_id') - content = request.form.get('content') - username = session['username'] - _endpoint = _DISCORD_API_BASE + "/channels/{channel_id}/messages".format(channel_id=channel_id) - payload = {'content': username + ": " + content} - headers = {'Authorization': 'Bot ' + config['bot-token'], 'Content-Type': 'application/json'} - r = requests.post(_endpoint, headers=headers, data=json.dumps(payload)) - return jsonify(j=json.loads(r.content)) - -@app.route("/set_username", methods=["GET"]) -def get_set_username(): - return render_template("set_username.html") - -@app.route("/set_username", methods=["POST"]) -def post_set_username(): - session['username'] = request.form.get('username') - return redirect(url_for("embed_get", channelid=1)) - -@app.route("/") -def hello(): - return "This page is not blank" - -@app.route("/embed/") -def embed_get(channelid): - if 'username' not in session: - return redirect(url_for("get_set_username")) - return render_template("embed.html") - -if __name__ == "__main__": - app.run(host="0.0.0.0",port=3000,debug=True) diff --git a/run.py b/run.py new file mode 100644 index 0000000..48d7ffa --- /dev/null +++ b/run.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python2 +from titanembeds.app import app + +if __name__ == "__main__": + app.run(host="0.0.0.0",port=3000,debug=True) diff --git a/storage.txt b/storage.txt new file mode 100644 index 0000000..5b82431 --- /dev/null +++ b/storage.txt @@ -0,0 +1,37 @@ +session: +- unauthenticated T/F +- user_id - random generated 4 digit discrim upon nick creation, otherwise the discord client id if authed +- username +- user_keys {guildid: key, ...} - unused if authed + +database: + +unauthenticated Users: +- id +- guildid +- username +- discrim +- user key +- ip address +- last used timestamp +- revoked - cannot be used again if true + +unauth bans: +- id +- guildid +- ip address +- last known username +- last known discrim +- timestamp +- reason + +auth users: +- id +- guildid +- clientid +- timestamp + +guilds: +- id +- guildid +- enable unauthenticated users diff --git a/titanembeds/__init__.py b/titanembeds/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/titanembeds/app.py b/titanembeds/app.py new file mode 100644 index 0000000..ee30acc --- /dev/null +++ b/titanembeds/app.py @@ -0,0 +1,38 @@ +from config import config +from database import db +from flask import Flask, render_template, request, session, url_for, redirect +import blueprints.api +import os + + +os.chdir(config['app-location']) +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = config['database-uri'] +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no need this on for now. +app.secret_key = config['app-secret'] + +db.init_app(app) + +app.register_blueprint(blueprints.api.api, url_prefix="/api", template_folder="/templates") + +@app.route("/set_username//", methods=["GET"]) +def get_set_username(guildid, channelid): + return render_template("set_username.html") + +@app.route("/set_username//", methods=["POST"]) +def post_set_username(guildid, channelid): + session['username'] = request.form.get('username') + return redirect(url_for("embed_get", guildid=guildid, channelid=channelid)) + +@app.route("/") +def hello(): + return "This page is not blank" + +@app.route("/embed//") +def embed_get(guildid, channelid): + if 'username' not in session: + return redirect(url_for("get_set_username", guildid=guildid, channelid=channelid)) + return render_template("embed.html") + +if __name__ == "__main__": + app.run(host="0.0.0.0",port=3000,debug=True) diff --git a/titanembeds/blueprints/__init__.py b/titanembeds/blueprints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/titanembeds/blueprints/api/__init__.py b/titanembeds/blueprints/api/__init__.py new file mode 100644 index 0000000..2b132fc --- /dev/null +++ b/titanembeds/blueprints/api/__init__.py @@ -0,0 +1 @@ +from api import api diff --git a/titanembeds/blueprints/api/api.py b/titanembeds/blueprints/api/api.py new file mode 100644 index 0000000..3ab4784 --- /dev/null +++ b/titanembeds/blueprints/api/api.py @@ -0,0 +1,140 @@ +from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans +from titanembeds.decorators import valid_session_required +from flask import Blueprint, jsonify, session, request +from sqlalchemy import and_ +import random +import requests +import json +from config import config + +api = Blueprint("api", __name__) + +_DISCORD_API_BASE = "https://discordapp.com/api/v6" + +def user_unauthenticated(): + return session['unauthenticated'] + +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: + pass # Todo: handle authenticated user revocation status + 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() + print dbUser + if not dbUser: + banned = False + else: + for usr in dbUser: + if usr.lifter_id is not None: + banned = False + else: + pass #todo: handle authenticated user banned status + return banned + +def get_client_ipaddr(): + if hasattr(request.headers, "X-Real-IP"): # pythonanywhere specific + return request.headers['X-Real-IP'] + else: # general + return request.remote_addr + +def update_user_status(guild_id, username, user_key=None): + if user_unauthenticated(): + ip_address = get_client_ipaddr() + status = { + 'ip_address': ip_address, + 'username': username, + 'user_key': user_key, + 'guild_id': guild_id, + 'banned': checkUserBanned(guild_id, ip_address), + 'revoked': checkUserRevoke(guild_id, user_key), + } + if status['banned'] or status['revoked']: + 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: + pass #authenticated user todo + return status + +def get_channel_messages(channel_id, after_snowflake=None): + _endpoint = _DISCORD_API_BASE + "/channels/{channel_id}/messages".format(channel_id=channel_id) + payload = {} + if after_snowflake is not None: + payload = {'after': after_snowflake} + headers = {'Authorization': 'Bot ' + config['bot-token']} + r = requests.get(_endpoint, params=payload, headers=headers) + return json.loads(r.content) + +def post_create_message(channel_id, content): + _endpoint = _DISCORD_API_BASE + "/channels/{channel_id}/messages".format(channel_id=channel_id) + payload = {'content': session['username'] + ": " + content} + headers = {'Authorization': 'Bot ' + config['bot-token'], 'Content-Type': 'application/json'} + r = requests.post(_endpoint, headers=headers, data=json.dumps(payload)) + return json.loads(r.content) + +@valid_session_required(api=True) +@api.route("/fetch", methods=["GET"]) +def fetch(): + channel_id = request.args.get('channel_id') + after_snowflake = request.args.get('after', None, type=int) + if user_unauthenticated(): + key = session['user_keys'][channel_id] + else: + key = None + status = update_user_status(channel_id, session['username'], key) + print status['revoked'] + if status['banned'] or status['revoked']: + messages = {} + else: + messages = get_channel_messages(channel_id, after_snowflake) + return jsonify(messages=messages, status=status) + +@valid_session_required(api=True) +@api.route("/post", methods=["POST"]) +def post(): + channel_id = request.form.get('channel_id') + content = request.form.get('content') + if user_unauthenticated(): + key = session['user_keys'][channel_id] + else: + key = None + status = update_user_status(channel_id, session['username'], key) + if status['banned'] or status['revoked']: + return jsonify(status=status) + message = post_create_message(channel_id, content) + return jsonify(message=message, status=status) + +@api.route("/create_unauthenticated_user", methods=["POST"]) +def create_unauthenticated_user(): + session['unauthenticated'] = True + username = request.form['username'] + guild_id = request.form['guild_id'] + ip_address = get_client_ipaddr() + if not checkUserBanned(guild_id, ip_address): + session['username'] = username + if 'user_id' not in session: + 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} + return jsonify(status=status) diff --git a/titanembeds/blueprints/embed/embed.py b/titanembeds/blueprints/embed/embed.py new file mode 100644 index 0000000..e69de29 diff --git a/titanembeds/database/__init__.py b/titanembeds/database/__init__.py new file mode 100644 index 0000000..c3cdf87 --- /dev/null +++ b/titanembeds/database/__init__.py @@ -0,0 +1,6 @@ +from flask_sqlalchemy import SQLAlchemy +db = SQLAlchemy() + +from guilds import Guilds +from unauthenticated_users import UnauthenticatedUsers +from unauthenticated_bans import UnauthenticatedBans diff --git a/titanembeds/database/authenticated_users.py b/titanembeds/database/authenticated_users.py new file mode 100644 index 0000000..ad51898 --- /dev/null +++ b/titanembeds/database/authenticated_users.py @@ -0,0 +1,19 @@ +from titanembeds.database import db +import datetime + +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.now + + def bumpTimestamp(self): + self.last_timestamp = datetime.datetime.now + db.session.commit() + return self.last_timestamp diff --git a/titanembeds/database/guilds.py b/titanembeds/database/guilds.py new file mode 100644 index 0000000..6efbb37 --- /dev/null +++ b/titanembeds/database/guilds.py @@ -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 ''.format(self.id, self.guild_id) + + def set_unauthUsersBool(self, value): + self.unauth_users = value + db.session.commit() + return self.unauth_users diff --git a/titanembeds/database/unauthenticated_bans.py b/titanembeds/database/unauthenticated_bans.py new file mode 100644 index 0000000..4634706 --- /dev/null +++ b/titanembeds/database/unauthenticated_bans.py @@ -0,0 +1,32 @@ +from titanembeds.database import db +import datetime + +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.now + self.reason = reason + self.lifter_id = null + 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 ''.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.now + db.session.commit() + return self.last_timestamp diff --git a/titanembeds/decorators.py b/titanembeds/decorators.py new file mode 100644 index 0000000..b93282c --- /dev/null +++ b/titanembeds/decorators.py @@ -0,0 +1,17 @@ +from functools import wraps +from flask import url_for, redirect, session, jsonify + +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: + session.clear() + if api: + return jsonify(error=True, message="Unauthenticated session"), 403 + redirect(url_for('index')) + if session['unauthenticated'] and 'user_keys' not in session: + session['user_keys'] = {} + return f(*args, **kwargs) + return decorated_function + return decorator diff --git a/templates/embed.html b/titanembeds/templates/embed.html similarity index 100% rename from templates/embed.html rename to titanembeds/templates/embed.html diff --git a/templates/set_username.html b/titanembeds/templates/set_username.html similarity index 100% rename from templates/set_username.html rename to titanembeds/templates/set_username.html