Implement file uploading from the embeds

This commit is contained in:
Jeremy Zhang 2018-08-17 03:12:21 +00:00
parent 487d4c3d76
commit a742f08f6d
13 changed files with 306 additions and 26 deletions

View File

@ -0,0 +1,28 @@
"""Add file upload column to guilds table
Revision ID: ce2b9c930a7a
Revises: 12267ce662e9
Create Date: 2018-08-16 21:20:09.103071
"""
# revision identifiers, used by Alembic.
revision = 'ce2b9c930a7a'
down_revision = '12267ce662e9'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('guilds', sa.Column('file_upload', sa.Boolean(), server_default='0', nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('guilds', 'file_upload')
# ### end Alembic commands ###

View File

@ -32,6 +32,7 @@ app.config['SQLALCHEMY_POOL_SIZE'] = 15
app.config['RATELIMIT_STORAGE_URL'] = config["redis-uri"]
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=3)
app.config['REDIS_URL'] = config["redis-uri"]
app.config['MAX_CONTENT_LENGTH'] = 4 * 1024 * 1024 # Limit upload size to 4mb
app.secret_key = config['app-secret']
sentry.init_app(app)

View File

@ -193,7 +193,8 @@ def administrate_guild(guild_id):
"banned_words_global_included": db_guild.banned_words_global_included,
"banned_words": json.loads(db_guild.banned_words),
"autorole_unauth": db_guild.autorole_unauth,
"autorole_discord": db_guild.autorole_discord
"autorole_discord": db_guild.autorole_discord,
"file_upload": db_guild.file_upload,
}
return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics)
@ -214,6 +215,7 @@ def update_administrate_guild(guild_id):
db_guild.banned_words_global_included = request.form.get("banned_words_global_included", db_guild.banned_words_global_included) in ["true", True]
db_guild.autorole_unauth = request.form.get("autorole_unauth", db_guild.autorole_unauth, type=int)
db_guild.autorole_discord = request.form.get("autorole_discord", db_guild.autorole_discord, type=int)
db_guild.file_upload = request.form.get("file_upload", db_guild.file_upload) in ["true", True]
invite_link = request.form.get("invite_link", db_guild.invite_link)
if invite_link != None and invite_link.strip() == "":
invite_link = None
@ -250,7 +252,8 @@ def update_administrate_guild(guild_id):
banned_words_global_included=db_guild.banned_words_global_included,
banned_words=json.loads(db_guild.banned_words),
autorole_unauth=db_guild.autorole_unauth,
autorole_discord=db_guild.autorole_discord
autorole_discord=db_guild.autorole_discord,
file_upload=db_guild.file_upload,
)
@admin.route("/guilds")

View File

@ -273,7 +273,12 @@ def get_post_content_max_len(guild_id):
def post():
guild_id = request.form.get("guild_id")
channel_id = request.form.get('channel_id')
content = request.form.get('content')
content = request.form.get('content', "")
file = None
if "file" in request.files:
file = request.files["file"]
if file and file.filename == "":
file = None
if "user_id" in session:
dbUser = redisqueue.get_guild_member(guild_id, session["user_id"])
else:
@ -293,6 +298,8 @@ def post():
chan = filter_guild_channel(guild_id, channel_id)
if not chan.get("write") or chan["channel"]["type"] != "text":
status_code = 401
elif file and not chan.get("attach_files"):
status_code = 406
elif not illegal_post:
userid = session["user_id"]
content = format_everyone_mention(chan, content)
@ -318,9 +325,9 @@ def post():
# username = "(Titan Dev) " + username
username = username + "#" + str(session['discriminator'])
avatar = session['avatar']
message = discord_api.execute_webhook(webhook.get("id"), webhook.get("token"), username, avatar, content)
message = discord_api.execute_webhook(webhook.get("id"), webhook.get("token"), username, avatar, content, file)
else:
message = discord_api.create_message(channel_id, content)
message = discord_api.create_message(channel_id, content, file)
status_code = message['code']
db.session.commit()
response = jsonify(message=message.get('content', message), status=status, illegal_reasons=illegal_reasons)

View File

@ -230,7 +230,8 @@ def administrate_guild(guild_id):
"banned_words_global_included": db_guild.banned_words_global_included,
"banned_words": json.loads(db_guild.banned_words),
"autorole_unauth": db_guild.autorole_unauth,
"autorole_discord": db_guild.autorole_discord
"autorole_discord": db_guild.autorole_discord,
"file_upload": db_guild.file_upload,
}
return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics, disabled=(guild_id in list_disabled_guilds()))
@ -259,6 +260,7 @@ def update_administrate_guild(guild_id):
db_guild.banned_words_global_included = request.form.get("banned_words_global_included", db_guild.banned_words_global_included) in ["true", True]
db_guild.autorole_unauth = request.form.get("autorole_unauth", db_guild.autorole_unauth, type=int)
db_guild.autorole_discord = request.form.get("autorole_discord", db_guild.autorole_discord, type=int)
db_guild.file_upload = request.form.get("file_upload", db_guild.file_upload) in ["true", True]
invite_link = request.form.get("invite_link", db_guild.invite_link)
if invite_link != None and invite_link.strip() == "":
@ -299,7 +301,8 @@ def update_administrate_guild(guild_id):
banned_words_global_included=db_guild.banned_words_global_included,
banned_words=json.loads(db_guild.banned_words),
autorole_unauth=db_guild.autorole_unauth,
autorole_discord=db_guild.autorole_discord
autorole_discord=db_guild.autorole_discord,
file_upload=db_guild.file_upload,
)
@user.route("/add-bot/<guild_id>")

View File

@ -19,6 +19,7 @@ class Guilds(db.Model):
banned_words = db.Column(db.Text(), nullable=False, server_default="[]") # JSON list of strings to block from sending
autorole_unauth = db.Column(db.BigInteger, nullable=True, server_default=None) # Automatic Role inherit for unauthenticated users
autorole_discord = db.Column(db.BigInteger, nullable=True, server_default=None) # Automatic Role inherit for discord users
file_upload = db.Column(db.Boolean(), nullable=False, server_default="0") # Allow file uploading for server
def __init__(self, guild_id):
self.guild_id = guild_id

View File

@ -2,6 +2,7 @@ import requests
import sys
import time
import json
import urllib
from titanembeds.utils import redis_store
from flask import request
@ -61,7 +62,12 @@ class DiscordREST:
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)
if data and "payload_json" in data:
if "Content-Type" in headers:
del headers["Content-Type"]
req = requests.request(verb, url_formatted, params=params, files=data, headers=headers)
else:
req = requests.request(verb, url_formatted, params=params, data=data, headers=headers)
remaining = None
if 'X-RateLimit-Remaining' in req.headers:
@ -103,9 +109,11 @@ class DiscordREST:
# Channel
#####################
def create_message(self, channel_id, content):
def create_message(self, channel_id, content, file=None):
_endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
payload = {'content': content}
if file:
payload = {"payload_json": (None, json.dumps(payload)), "file": (file.filename, file.read(), 'application/octet-stream')}
r = self.request("POST", _endpoint, data=payload)
return r
@ -164,7 +172,7 @@ class DiscordREST:
r = self.request("POST", _endpoint, data=payload, json=True)
return r
def execute_webhook(self, webhook_id, webhook_token, username, avatar, content, wait=True):
def execute_webhook(self, webhook_id, webhook_token, username, avatar, content, file=None, wait=True):
_endpoint = "/webhooks/{id}/{token}".format(id=webhook_id, token=webhook_token)
if wait:
_endpoint += "?wait=true"
@ -173,6 +181,8 @@ class DiscordREST:
'avatar_url': avatar,
'username': username
}
if file:
payload = {"payload_json": (None, json.dumps(payload)), "file": (file.filename, file.read(), 'application/octet-stream')}
r = self.request("POST", _endpoint, data=payload)
return r

View File

@ -873,16 +873,65 @@ p.mentioned span.chatmessage {
padding: 2px;
}
@media only screen and (max-device-width: 320px) {
@media only screen and (max-width: 320px) {
.wdt-emoji-picker {
display: none;
}
#upload-file-btn {
display: none;
}
}
@media only screen and (min-device-width: 321px) {
@media only screen and (min-width: 321px) {
.wdt-emoji-picker {
display: block;
}
#upload-file-btn {
display: block;
}
}
#upload-file-btn {
position: absolute;
bottom: 5px;
right: 38px;
color: gray;
padding: 1px;
transition: .3s ease-out;
}
#upload-file-btn:hover {
color: white;
}
#fileinput {
display: none;
}
@media only screen and (min-width: 500px) {
#filemodal-body {
display: flex;
justify-content: space-evenly;
}
#filemodal-right {
width: 75%;
}
}
#filepreview {
max-width: 100px;
max-height: 100px;
}
#messagebox-filemodal {
background-color: rgba(0, 0, 0, 0.07);
}
#messagebox-filemodal::placeholder {
color: #90a4ae;
}
#mention-picker {
@ -934,7 +983,7 @@ p.mentioned span.chatmessage {
margin-left: auto;
}
@media only screen and (max-device-width: 320px) {
@media only screen and (max-width: 320px) {
#mention-picker .realname {
display: none;
}

View File

@ -173,6 +173,15 @@ $("#autorole_discord").change(function () {
});
});
$('#file_upload').change(function() {
var pathname = window.location.pathname;
var checked = $(this).is(':checked')
var payload = {"file_upload": checked}
$.post(pathname, payload, function(data) {
Materialize.toast('Updated file uploads setting!', 2000)
});
});
function initiate_ban(guild_id, user_id) {
var reason = prompt("Please enter your reason for ban");
var payload = {

View File

@ -137,13 +137,44 @@
return funct.promise();
}
function post(channel_id, content) {
var funct = $.ajax({
function post(channel_id, content, file) {
if (content == "") {
content = null;
}
var data = null;
var ajaxobj = {
method: "POST",
dataType: "json",
url: "/api/post",
data: {"guild_id": guild_id, "channel_id": channel_id, "content": content}
});
url: "/api/post"
}
if (file) {
data = new FormData();
data.append("guild_id", guild_id);
data.append("channel_id", channel_id);
if (content) {
data.append("content", content);
}
data.append("file", file);
ajaxobj.cache = false;
ajaxobj.contentType = false;
ajaxobj.processData = false;
ajaxobj.xhr = function() {
var myXhr = $.ajaxSettings.xhr();
if (myXhr.upload) {
// For handling the progress of the upload
myXhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
$("#filemodalprogress-inner").css("width", (e.loaded/e.total) + "%")
}
} , false);
}
return myXhr;
}
} else {
data = {"guild_id": guild_id, "channel_id": channel_id, "content": content};
}
ajaxobj.data = data;
var funct = $.ajax(ajaxobj);
return funct.promise();
}
@ -214,6 +245,13 @@
inDuration: 400,
outDuration: 400,
});
$("#filemodal").modal({
dismissible: true,
opacity: .3,
inDuration: 400,
outDuration: 400,
complete: function () { $("#fileinput").val(""); }
});
$("#usercard").modal({
opacity: .5,
});
@ -237,6 +275,44 @@
$("#nsfwmodal").modal("close");
});
$("#upload-file-btn").click(function () {
$("#fileinput").trigger('click');
});
$("#proceed_fileupload_btn").click(function () {
$("#messagebox-filemodal").trigger(jQuery.Event("keydown", { keyCode: 13 } ));
});
$("#fileinput").change(function (e){
var files = e.target.files;
if (files && files.length > 0) {
$("#messagebox-filemodal").val($("#messagebox").val());
$("#filename").text($("#fileinput")[0].files[0].name);
$("#filemodal").modal("open");
$("#messagebox-filemodal").focus();
var file = files[0];
var file_size = file.size;
var file_max_size = 4 * 1024 * 1024;
if (file_size > file_max_size) {
$("#filemodal").modal("close");
Materialize.toast('Your file is too powerful! The maximum file size is 4 megabytes.', 5000);
return;
}
var name = file.name;
var extension = name.substr(-4).toLowerCase();
var image_extensions = [".png", ".jpg", ".jpeg", ".gif"];
$("#filepreview").hide();
if (FileReader && image_extensions.indexOf(extension) > -1) {
var reader = new FileReader();
reader.onload = function() {
$("#filepreview").show();
$("#filepreview")[0].src = reader.result;
};
reader.readAsDataURL(file);
}
}
});
$( "#theme-selector" ).change(function () {
var theme = $("#theme-selector option:selected").val();
var keep_custom_css = $("#overwrite_theme_custom_css_checkbox").is(':checked');
@ -444,11 +520,13 @@
$("#messagebox").hide();
$("#emoji-tray-toggle").hide();
$(".wdt-emoji-picker").hide();
$("#upload-file-btn").hide();
} else {
$("#visitor_mode_message").hide();
$("#messagebox").show();
$("#emoji-tray-toggle").show();
$(".wdt-emoji-picker").show();
$("#upload-file-btn").show();
}
}
@ -658,6 +736,7 @@
if (curr_default_channel == null) {
$("#messagebox").prop('disabled', true);
$("#messagebox").prop('placeholder', "NO TEXT CHANNELS");
$("#upload-file-btn").hide();
Materialize.toast("You find yourself in a strange place. You don't have access to any text channels, or there are none in this server.", 20000);
return;
}
@ -667,9 +746,18 @@
if (this_channel.write) {
$("#messagebox").prop('disabled', false);
$("#messagebox").prop('placeholder', "Enter message");
$("#upload-file-btn").show();
$(".wdt-emoji-picker").show();
} else {
$("#messagebox").prop('disabled', true);
$("#messagebox").prop('placeholder', "Messages is disabled in this channel.");
$("#upload-file-btn").hide();
$(".wdt-emoji-picker").hide();
}
if (this_channel.attach_files) {
$("#upload-file-btn").show();
} else {
$("#upload-file-btn").hide();
}
$("#channeltopic").text(this_channel.channel.topic);
$("#channel-"+selected_channel).parent().addClass("active");
@ -1744,9 +1832,11 @@
if (event.keyCode == 16) {
shift_pressed = true;
}
if(event.keyCode == 13 && !shift_pressed && $(this).val().length >= 1) {
if(event.keyCode == 13 && !shift_pressed && ($(this).val().length >= 1 || $("#fileinput").val().length >= 1)) {
$(this).val($.trim($(this).val()));
$(this).blur();
$("#messagebox-filemodal").attr('readonly', true);
$("#proceed_fileupload_btn").attr("disabled", true);
$("#messagebox").attr('readonly', true);
var emojiConvertor = new EmojiConvertor();
emojiConvertor.init_env();
@ -1754,9 +1844,18 @@
emojiConvertor.allow_native = true;
var messageInput = emojiConvertor.replace_colons($(this).val());
messageInput = stringToDefaultEmote(messageInput);
var funct = post(selected_channel, messageInput);
var file = null;
if ($("#fileinput")[0].files.length > 0) {
file = $("#fileinput")[0].files[0];
}
$("#filemodalprogress").show();
var funct = post(selected_channel, messageInput, file);
funct.done(function(data) {
$("#messagebox").val("");
$("#messagebox-filemodal").val("");
$("#fileinput").val("");
$("#filemodal").modal("close");
$("#filemodalprogress").hide();
});
funct.fail(function(data) {
Materialize.toast('Failed to send message.', 10000);
@ -1773,11 +1872,39 @@
});
funct.always(function() {
$("#messagebox").attr('readonly', false);
$("#messagebox").focus();
$("#messagebox-filemodal").attr('readonly', false);
$("#proceed_fileupload_btn").attr("disabled", false);
if ($("#filemodal").is(":visible")) {
$("#messagebox-filemodal").focus();
} else {
$("#messagebox").focus();
}
});
}
});
$("#messagebox-filemodal").keyup(function (event) {
if (event.keyCode == 16) {
shift_pressed = false;
}
});
$("#messagebox-filemodal").keydown(function (event) {
if ($(this).val().length == 1) {
$(this).val($.trim($(this).val()));
}
if (event.keyCode == 16) {
shift_pressed = true;
}
if(event.keyCode == 13 && !shift_pressed) {
$(this).val($.trim($(this).val()));
$(this).blur();
$("#messagebox").val($(this).val());
$("#messagebox").trigger(jQuery.Event("keydown", { keyCode: 13 } ));
}
});
$('#guild-btn').sideNav({
menuWidth: 300, // Default is 300
edge: 'left', // Choose the horizontal origin

View File

@ -214,6 +214,19 @@
</div>
</div>
<br>
<p class="flow-text">Toggle File Attachments</p>
<p>Allow embed users to attach files to your Discord server in messages</p>
<div class="switch">
<label>
Disable
<input type="checkbox" id="file_upload" name="file_upload" {% if guild['file_upload'] %}checked{% endif %} >
<span class="lever"></span>
Enable
</label>
</div>
</div>
</div>
</div>

View File

@ -79,6 +79,7 @@
<div id="messageboxouter" class="input-field inline">
<textarea placeholder="Enter message" id="messagebox" type="text" class="materialize-textarea wdt-emoji-open-on-colon" rows="1"></textarea>
<span id="visitor_mode_message" style="display:none;"><span id="visitor_mode_message_note">{{ _("Please login to post a message.") }}</span> <a id="visitor_login_btn" class="waves-effect waves-light btn">{{ _("Login") }}</a></span>
<a id="upload-file-btn" class="btn-flat"><i class="material-icons">file_upload</i></a>
</div>
</div>
</footer>
@ -243,6 +244,31 @@
</div>
</div>
<div id="filemodal" class="modal">
<div class="modal-content">
<h4 class="center-align">{{ _("Attach File") }}</h4>
<h5 id="filename" class="center-align"></h5>
<div id="filemodalprogress" class="progress" style="display: none;">
<div id="filemodalprogress-inner" class="determinate"></div>
</div>
<div id="filemodal-body">
<div id="filemodal-left" class="valign-wrapper">
<img id="filepreview" class="responsive-img" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png">
</div>
<div id="filemodal-right">
<p>{{ _("Add a comment (optional)") }}</p>
<p class="center-align">
<textarea placeholder="Enter message" id="messagebox-filemodal" type="text" class="materialize-textarea" rows="1"></textarea>
</p>
</div>
</div>
<p class="right-align">
<a id="proceed_fileupload_btn" class="waves-effect waves-light btn">{{ _("Upload") }}</a>
</p>
<input type="file" id="fileinput">
</div>
</div>
<div id="usercard" class="modal bottom-sheet">
<div class="modal-content">
<div class="row">

View File

@ -214,6 +214,7 @@ def get_guild_channels(guild_id, force_everyone=False, forced_role=0):
if guild_id not in bot_member_roles:
bot_member_roles.append(guild_id)
guild = redisqueue.get_guild(guild_id)
db_guild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
guild_channels = guild["channels"]
guild_roles = guild["roles"]
guild_owner = guild["owner_id"]
@ -228,17 +229,20 @@ def get_guild_channels(guild_id, force_everyone=False, forced_role=0):
result["write"] = False
if not bot_result["mention_everyone"]:
result["mention_everyone"] = False
if not bot_result["attach_files"] or not db_guild.file_upload:
result["attach_files"] = False
result_channels.append(result)
return sorted(result_channels, key=lambda k: k['channel']['position'])
def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_roles, user_id=None, force_everyone=False):
result = {"channel": channel, "read": False, "write": False, "mention_everyone": False}
result = {"channel": channel, "read": False, "write": False, "mention_everyone": False, "attach_files": False}
if not user_id:
user_id = str(session.get("user_id"))
if guild_owner == user_id:
result["read"] = True
result["write"] = True
result["mention_everyone"] = True
result["attach_files"] = True
return result
channel_perm = 0
@ -265,6 +269,7 @@ def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_r
result["read"] = True
result["write"] = True
result["mention_everyone"] = True
result["attach_files"] = True
return result
denies = 0
@ -287,16 +292,14 @@ def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_r
result["read"] = user_has_permission(channel_perm, 10)
result["write"] = user_has_permission(channel_perm, 11)
result["mention_everyone"] = user_has_permission(channel_perm, 17)
# If default channel, you can read
if channel["id"] == guild_id:
result["read"] = True
result["attach_files"] = user_has_permission(channel_perm, 15)
# If you cant read channel, you cant write in it
if not user_has_permission(channel_perm, 10):
result["read"] = False
result["write"] = False
result["mention_everyone"] = False
result["attach_files"] = False
return result
def get_forced_role(guild_id):