Implement sending rich embeds

This commit is contained in:
Jeremy Zhang 2019-11-06 13:45:48 -08:00
parent 8dec93d087
commit aa49b2d473
15 changed files with 529 additions and 53 deletions

View File

@ -0,0 +1,30 @@
"""Add send_rich_embed fields to guilds and cosmetics
Revision ID: 08f6b59be038
Revises: ecf3e6bf950e
Create Date: 2019-11-06 11:54:01.324095
"""
# revision identifiers, used by Alembic.
revision = '08f6b59be038'
down_revision = 'ecf3e6bf950e'
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('cosmetics', sa.Column('send_rich_embed', sa.Boolean(), server_default=sa.text('false'), nullable=False))
op.add_column('guilds', sa.Column('send_rich_embed', sa.Boolean(), server_default='0', nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('guilds', 'send_rich_embed')
op.drop_column('cosmetics', 'send_rich_embed')
# ### end Alembic commands ###

View File

@ -49,6 +49,7 @@ def cosmetics_post():
css = request.form.get("css", None) css = request.form.get("css", None)
css_limit = int(request.form.get("css_limit", 0)) css_limit = int(request.form.get("css_limit", 0))
guest_icon = request.form.get("guest_icon", None) guest_icon = request.form.get("guest_icon", None)
send_rich_embed = request.form.get("send_rich_embed", None)
badges = request.form.get("badges", None) badges = request.form.get("badges", None)
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first() entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first()
if entry: if entry:
@ -62,6 +63,9 @@ def cosmetics_post():
if guest_icon is not None: if guest_icon is not None:
guest_icon = guest_icon.lower() == "true" guest_icon = guest_icon.lower() == "true"
user.guest_icon = guest_icon user.guest_icon = guest_icon
if send_rich_embed:
send_rich_embed = send_rich_embed.lower() == "true"
user.send_rich_embed = send_rich_embed
if badges is not None: if badges is not None:
badges = badges.split(",") badges = badges.split(",")
if badges == [""]: if badges == [""]:
@ -93,6 +97,7 @@ def cosmetics_patch():
css = request.form.get("css", None) css = request.form.get("css", None)
css_limit = request.form.get("css_limit", None) css_limit = request.form.get("css_limit", None)
guest_icon = request.form.get("guest_icon", None) guest_icon = request.form.get("guest_icon", None)
send_rich_embed = request.form.get("send_rich_embed", None)
badges = request.form.get("badges", None) badges = request.form.get("badges", None)
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first() entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first()
if not entry: if not entry:
@ -105,6 +110,9 @@ def cosmetics_patch():
if guest_icon: if guest_icon:
guest_icon = guest_icon.lower() == "true" guest_icon = guest_icon.lower() == "true"
entry.guest_icon = guest_icon entry.guest_icon = guest_icon
if send_rich_embed:
send_rich_embed = send_rich_embed.lower() == "true"
entry.send_rich_embed = send_rich_embed
if badges is not None: if badges is not None:
badges = badges.split(",") badges = badges.split(",")
if badges == [""]: if badges == [""]:
@ -195,6 +203,7 @@ def administrate_guild(guild_id):
"autorole_unauth": db_guild.autorole_unauth, "autorole_unauth": db_guild.autorole_unauth,
"autorole_discord": db_guild.autorole_discord, "autorole_discord": db_guild.autorole_discord,
"file_upload": db_guild.file_upload, "file_upload": db_guild.file_upload,
"send_rich_embed": db_guild.send_rich_embed,
} }
return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics) return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics)
@ -216,6 +225,7 @@ def update_administrate_guild(guild_id):
db_guild.autorole_unauth = request.form.get("autorole_unauth", db_guild.autorole_unauth, type=int) 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.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] db_guild.file_upload = request.form.get("file_upload", db_guild.file_upload) in ["true", True]
db_guild.send_rich_embed = request.form.get("send_rich_embed", db_guild.send_rich_embed) in ["true", True]
invite_link = request.form.get("invite_link", db_guild.invite_link) invite_link = request.form.get("invite_link", db_guild.invite_link)
if invite_link != None and invite_link.strip() == "": if invite_link != None and invite_link.strip() == "":
invite_link = None invite_link = None
@ -254,6 +264,7 @@ def update_administrate_guild(guild_id):
autorole_unauth=db_guild.autorole_unauth, autorole_unauth=db_guild.autorole_unauth,
autorole_discord=db_guild.autorole_discord, autorole_discord=db_guild.autorole_discord,
file_upload=db_guild.file_upload, file_upload=db_guild.file_upload,
send_rich_embed=db_guild.send_rich_embed,
) )
@admin.route("/guilds") @admin.route("/guilds")

View File

@ -303,6 +303,9 @@ def post():
file = request.files["file"] file = request.files["file"]
if file and file.filename == "": if file and file.filename == "":
file = None file = None
rich_embed = request.form.get("richembed", None)
if rich_embed:
rich_embed = json.loads(rich_embed)
if "user_id" in session: if "user_id" in session:
dbUser = redisqueue.get_guild_member(guild_id, session["user_id"]) dbUser = redisqueue.get_guild_member(guild_id, session["user_id"])
else: else:
@ -322,7 +325,7 @@ def post():
chan = filter_guild_channel(guild_id, channel_id) chan = filter_guild_channel(guild_id, channel_id)
if not chan.get("write") or chan["channel"]["type"] != "text": if not chan.get("write") or chan["channel"]["type"] != "text":
status_code = 401 status_code = 401
elif file and not chan.get("attach_files"): elif (file and not chan.get("attach_files")) or (rich_embed and not chan.get("embed_links")):
status_code = 406 status_code = 406
elif not illegal_post: elif not illegal_post:
userid = session["user_id"] userid = session["user_id"]
@ -354,10 +357,10 @@ def post():
username = username[:25] username = username[:25]
username = username + "#" + str(session['discriminator']) username = username + "#" + str(session['discriminator'])
avatar = session['avatar'] avatar = session['avatar']
message = discord_api.execute_webhook(webhook.get("id"), webhook.get("token"), username, avatar, content, file) message = discord_api.execute_webhook(webhook.get("id"), webhook.get("token"), username, avatar, content, file, rich_embed)
delete_webhook_if_too_much(webhook) delete_webhook_if_too_much(webhook)
else: else:
message = discord_api.create_message(channel_id, content, file) message = discord_api.create_message(channel_id, content, file, rich_embed)
status_code = message['code'] status_code = message['code']
db.session.commit() db.session.commit()
response = jsonify(message=message.get('content', message), status=status, illegal_reasons=illegal_reasons) response = jsonify(message=message.get('content', message), status=status, illegal_reasons=illegal_reasons)

View File

@ -233,6 +233,7 @@ def administrate_guild(guild_id):
"autorole_unauth": db_guild.autorole_unauth, "autorole_unauth": db_guild.autorole_unauth,
"autorole_discord": db_guild.autorole_discord, "autorole_discord": db_guild.autorole_discord,
"file_upload": db_guild.file_upload, "file_upload": db_guild.file_upload,
"send_rich_embed": db_guild.send_rich_embed,
} }
return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics, disabled=(guild_id in list_disabled_guilds())) return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions, cosmetics=cosmetics, disabled=(guild_id in list_disabled_guilds()))
@ -262,6 +263,7 @@ def update_administrate_guild(guild_id):
db_guild.autorole_unauth = request.form.get("autorole_unauth", db_guild.autorole_unauth, type=int) 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.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] db_guild.file_upload = request.form.get("file_upload", db_guild.file_upload) in ["true", True]
db_guild.send_rich_embed = request.form.get("send_rich_embed", db_guild.send_rich_embed) in ["true", True]
invite_link = request.form.get("invite_link", db_guild.invite_link) invite_link = request.form.get("invite_link", db_guild.invite_link)
if invite_link != None and invite_link.strip() == "": if invite_link != None and invite_link.strip() == "":
@ -304,6 +306,7 @@ def update_administrate_guild(guild_id):
autorole_unauth=db_guild.autorole_unauth, autorole_unauth=db_guild.autorole_unauth,
autorole_discord=db_guild.autorole_discord, autorole_discord=db_guild.autorole_discord,
file_upload=db_guild.file_upload, file_upload=db_guild.file_upload,
send_rich_embed=db_guild.send_rich_embed,
) )
@user.route("/add-bot/<guild_id>") @user.route("/add-bot/<guild_id>")

View File

@ -7,6 +7,7 @@ class Cosmetics(db.Model):
css = db.Column(db.Boolean(), nullable=False) # If they can create/edit custom CSS css = db.Column(db.Boolean(), nullable=False) # If they can create/edit custom CSS
css_limit = db.Column(db.Integer, nullable=False, server_default="0") # Custom CSS Limit css_limit = db.Column(db.Integer, nullable=False, server_default="0") # Custom CSS Limit
guest_icon = db.Column(db.Boolean(), nullable=False, server_default=db.false()) # If they can set the guest icon for all guilds guest_icon = db.Column(db.Boolean(), nullable=False, server_default=db.false()) # If they can set the guest icon for all guilds
send_rich_embed = db.Column(db.Boolean(), nullable=False, server_default=db.false()) # If they can set the send rich embed for all guilds
badges = db.Column(db.String(255), nullable=False, server_default="[]") # JSON list of all the badges the user has badges = db.Column(db.String(255), nullable=False, server_default="[]") # JSON list of all the badges the user has
def __init__(self, user_id, **kwargs): def __init__(self, user_id, **kwargs):
@ -27,6 +28,11 @@ class Cosmetics(db.Model):
else: else:
self.guest_icon = False self.guest_icon = False
if "send_rich_embed" in kwargs:
self.send_rich_embed = kwargs["send_rich_embed"]
else:
self.send_rich_embed = False
if "badges" in kwargs: if "badges" in kwargs:
self.badges = json.dumps(kwargs["badges"]) self.badges = json.dumps(kwargs["badges"])
else: else:

View File

@ -20,6 +20,7 @@ class Guilds(db.Model):
autorole_unauth = db.Column(db.BigInteger, nullable=True, server_default=None) # Automatic Role inherit for unauthenticated users 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 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 file_upload = db.Column(db.Boolean(), nullable=False, server_default="0") # Allow file uploading for server
send_rich_embed = db.Column(db.Boolean(), nullable=False, server_default="0") # Allow sending rich embed messages
def __init__(self, guild_id): def __init__(self, guild_id):
self.guild_id = guild_id self.guild_id = guild_id

View File

@ -109,12 +109,20 @@ class DiscordREST:
# Channel # Channel
##################### #####################
def create_message(self, channel_id, content, file=None): def create_message(self, channel_id, content, file=None, richembed=None):
_endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id) _endpoint = "/channels/{channel_id}/messages".format(channel_id=channel_id)
payload = {'content': content} payload = {'content': content}
is_json = False
if file: if file:
payload = {"payload_json": (None, json.dumps(payload)), "file": (file.filename, file.read(), 'application/octet-stream')} payload = {"payload_json": (None, json.dumps(payload)), "file": (file.filename, file.read(), 'application/octet-stream')}
r = self.request("POST", _endpoint, data=payload) if richembed:
if richembed.get("type"):
del richembed["type"]
payload["embed"] = richembed
if not content:
del payload["content"]
is_json = True
r = self.request("POST", _endpoint, data=payload, json=is_json)
return r return r
##################### #####################
@ -172,10 +180,11 @@ class DiscordREST:
r = self.request("POST", _endpoint, data=payload, json=True) r = self.request("POST", _endpoint, data=payload, json=True)
return r return r
def execute_webhook(self, webhook_id, webhook_token, username, avatar, content, file=None, wait=True): def execute_webhook(self, webhook_id, webhook_token, username, avatar, content, file=None, richembed=None, wait=True):
_endpoint = "/webhooks/{id}/{token}".format(id=webhook_id, token=webhook_token) _endpoint = "/webhooks/{id}/{token}".format(id=webhook_id, token=webhook_token)
if wait: if wait:
_endpoint += "?wait=true" _endpoint += "?wait=true"
is_json = False
payload = { payload = {
'content': content, 'content': content,
'avatar_url': avatar, 'avatar_url': avatar,
@ -183,7 +192,14 @@ class DiscordREST:
} }
if file: if file:
payload = {"payload_json": (None, json.dumps(payload)), "file": (file.filename, file.read(), 'application/octet-stream')} payload = {"payload_json": (None, json.dumps(payload)), "file": (file.filename, file.read(), 'application/octet-stream')}
r = self.request("POST", _endpoint, data=payload) if richembed:
if richembed.get("type"):
del richembed["type"]
payload["embeds"] = [richembed]
if not content:
del payload["content"]
is_json = True
r = self.request("POST", _endpoint, data=payload, json=is_json)
return r return r
def delete_webhook(self, webhook_id, webhook_token): def delete_webhook(self, webhook_id, webhook_token):

View File

@ -927,6 +927,10 @@ p.mentioned span.chatmessage {
display: none; display: none;
} }
#send-rich-embed-btn {
display: none;
}
#upload-file-btn { #upload-file-btn {
display: none; display: none;
} }
@ -941,6 +945,10 @@ p.mentioned span.chatmessage {
display: block; display: block;
} }
#send-rich-embed-btn {
display: block;
}
#upload-file-btn { #upload-file-btn {
display: block; display: block;
} }
@ -963,6 +971,19 @@ p.mentioned span.chatmessage {
color: white; color: white;
} }
#send-rich-embed-btn {
position: absolute;
bottom: 5px;
right: 91px;
color: gray;
padding: 1px;
transition: .3s ease-out;
}
#send-rich-embed-btn:hover {
color: white;
}
#send-msg-btn { #send-msg-btn {
position: absolute; position: absolute;
bottom: 7px; bottom: 7px;
@ -980,6 +1001,11 @@ p.mentioned span.chatmessage {
display: none; display: none;
} }
#richembedmodal {
width: 80%;
max-height: 89vh;
}
@media only screen and (min-width: 500px) { @media only screen and (min-width: 500px) {
#filemodal-body { #filemodal-body {
display: flex; display: flex;
@ -996,14 +1022,37 @@ p.mentioned span.chatmessage {
max-height: 100px; max-height: 100px;
} }
#messagebox-filemodal { #messagebox-filemodal, #messagebox-richembedmodal, #richembedmodal-left input {
background-color: rgba(0, 0, 0, 0.07); background-color: rgba(0, 0, 0, 0.07);
} }
#messagebox-filemodal::placeholder { #messagebox-filemodal::placeholder, #messagebox-richembedmodal::placeholder, #richembedmodal-left input::placeholder {
color: #90a4ae; color: #90a4ae;
} }
#richembedmodal-left .input-field label {
color: white;
}
#richembedmodal-left input[type=text] {
height: 25px;
margin-bottom: 5px;
}
#richembedmodal-fields .input-field {
margin-top: 25px;
}
#richembedmodal-fields .delete-field {
padding-left: 0;
padding-right: 0;
color: #F5B7D0;
}
#richembedmodal-fields > div {
margin-bottom: 0;
}
#mention-picker { #mention-picker {
color: black; color: black;
position: fixed; position: fixed;

View File

@ -1,13 +1,13 @@
/* global $, Materialize, location */ /* global $, Materialize, location */
function postForm(user_id, css, css_limit, guest_icon, badges) { function postForm(user_id, css, css_limit, guest_icon, send_rich_embed, badges) {
if (css_limit == "") { if (css_limit == "") {
css_limit = 0; css_limit = 0;
} }
var funct = $.ajax({ var funct = $.ajax({
dataType: "json", dataType: "json",
method: "POST", method: "POST",
data: {"user_id": user_id, "css": css, "css_limit": css_limit, "guest_icon": guest_icon, "badges": badges} data: {"user_id": user_id, "css": css, "css_limit": css_limit, "guest_icon": guest_icon, "send_rich_embed": send_rich_embed, "badges": badges}
}); });
return funct.promise(); return funct.promise();
} }
@ -42,8 +42,9 @@ $(function() {
var css_checked = $("#new_css_switch").is(':checked'); var css_checked = $("#new_css_switch").is(':checked');
var css_limit = $("#new_css_limit").val(); var css_limit = $("#new_css_limit").val();
var guest_icon_checked = $("#new_guest_icon_switch").is(':checked'); var guest_icon_checked = $("#new_guest_icon_switch").is(':checked');
var send_rich_embed_checked = $("#new_send_rich_embed_switch").is(":checked");
var badges = $("#new_badges > option:selected").map(function(){ return this.value }).get().join(); var badges = $("#new_badges > option:selected").map(function(){ return this.value }).get().join();
var formPost = postForm(user_id, css_checked, css_limit, guest_icon_checked, badges); var formPost = postForm(user_id, css_checked, css_limit, guest_icon_checked, send_rich_embed_checked, badges);
formPost.done(function (data) { formPost.done(function (data) {
location.reload(); location.reload();
}); });
@ -118,6 +119,21 @@ function update_guest_icon_switch(user_id, element) {
}); });
} }
function update_send_rich_embed_switch(user_id, element) {
var send_rich_checked = $(element).is(':checked');
var formPatch = patchForm(user_id, {"send_rich_embed": send_rich_checked});
formPatch.done(function (data) {
Materialize.toast('Send Rich Embed updated!', 10000);
});
formPatch.fail(function (data) {
if (data.status == 409) {
Materialize.toast('This user id does not exists!', 10000);
} else {
Materialize.toast('Oh no! Something has failed changing the send rich embed toggle!', 10000);
}
});
}
function update_badges(user_id, element) { function update_badges(user_id, element) {
var badges = $(element).val().join(); var badges = $(element).val().join();
var formPatch = patchForm(user_id, {"badges": badges}); var formPatch = patchForm(user_id, {"badges": badges});

View File

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

View File

@ -155,7 +155,7 @@
return funct.promise(); return funct.promise();
} }
function post(channel_id, content, file) { function post(channel_id, content, file, richembed) {
if (content == "") { if (content == "") {
content = null; content = null;
} }
@ -188,6 +188,11 @@
} }
return myXhr; return myXhr;
} }
} else if (richembed) {
data = {"guild_id": guild_id, "channel_id": channel_id, "richembed": richembed};
if (content) {
data["content"] = content;
}
} else { } else {
data = {"guild_id": guild_id, "channel_id": channel_id, "content": content}; data = {"guild_id": guild_id, "channel_id": channel_id, "content": content};
} }
@ -290,6 +295,7 @@
} }
} }
$("#send-rich-embed-btn").hide();
$("#upload-file-btn").hide(); $("#upload-file-btn").hide();
$("#send-msg-btn").hide(); $("#send-msg-btn").hide();
@ -332,6 +338,14 @@
outDuration: 400, outDuration: 400,
complete: function () { $("#fileinput").val(""); } complete: function () { $("#fileinput").val(""); }
}); });
$("#richembedmodal").modal({
dismissible: true,
opacity: .3,
inDuration: 400,
outDuration: 400,
endingTop: '4%',
complete: function () { }
});
$("#usercard").modal({ $("#usercard").modal({
opacity: .5, opacity: .5,
}); });
@ -374,6 +388,10 @@
$("#messagebox-filemodal").trigger(jQuery.Event("keydown", { keyCode: 13 } )); $("#messagebox-filemodal").trigger(jQuery.Event("keydown", { keyCode: 13 } ));
}); });
$("#proceed_richembedmodal_btn").click(function () {
$("#messagebox-richembedmodal").trigger(jQuery.Event("keydown", { keyCode: 13 } ));
});
$("#fileinput").change(function (e){ $("#fileinput").change(function (e){
var files = e.target.files; var files = e.target.files;
if (files && files.length > 0) { if (files && files.length > 0) {
@ -404,6 +422,52 @@
} }
}); });
$("#send-rich-embed-btn").click(function () {
$("#richembedmodal").modal("open");
$("#messagebox-richembedmodal").val($("#messagebox").val());
$("#richembedform-color").val("#6BABDA");
$("#richembedform-title").val("");
$("#richembedform-description").val("");
$("#richembedform-url").val("");
$("#richembedform-thumbnailurl").val("");
$("#richembedform-authorname").val("");
$("#richembedform-authorurl").val("");
$("#richembedform-authoricon").val("");
$("#richembedform-footertext").val("");
$("#richembedmodal-fields").empty();
$("#richembedmodal-preview").empty();
});
$("#richembedmodal-left input").keyup(function () {
genPreviewPopulateRichEmbed();
});
$("#richembedmodal_addfield_btn").click(function () {
var count = $("#richembedmodal-fields > .row").length;
if (count >= 10) {
$("#richembedmodal_addfield_btn").addClass("disabled");
return;
}
var template = $($("#mustache_richembedfieldinput").html());
$("#richembedmodal-fields").append(template);
template.find("input.name").keyup(function () {
genPreviewPopulateRichEmbed();
});
template.find("input.value").keyup(function () {
genPreviewPopulateRichEmbed();
});
template.find(".delete-field").click(function () {
$(this).closest(".row").remove();
$("#richembedmodal_addfield_btn").removeClass("disabled");
});
count = $("#richembedmodal-fields > .row").length;
if (count >= 10) {
$("#richembedmodal_addfield_btn").addClass("disabled");
return;
}
});
$( "#theme-selector" ).change(function () { $( "#theme-selector" ).change(function () {
var theme = $("#theme-selector option:selected").val(); var theme = $("#theme-selector option:selected").val();
var keep_custom_css = $("#overwrite_theme_custom_css_checkbox").is(':checked'); var keep_custom_css = $("#overwrite_theme_custom_css_checkbox").is(':checked');
@ -621,6 +685,7 @@
$("#messagebox").hide(); $("#messagebox").hide();
$("#emoji-tray-toggle").hide(); $("#emoji-tray-toggle").hide();
$(".wdt-emoji-picker").hide(); $(".wdt-emoji-picker").hide();
$("#send-rich-embed-btn").hide();
$("#upload-file-btn").hide(); $("#upload-file-btn").hide();
$("#send-msg-btn").hide(); $("#send-msg-btn").hide();
} else { } else {
@ -628,6 +693,7 @@
$("#messagebox").show(); $("#messagebox").show();
$("#emoji-tray-toggle").show(); $("#emoji-tray-toggle").show();
$(".wdt-emoji-picker").show(); $(".wdt-emoji-picker").show();
$("#send-rich-embed-btn").show();
$("#upload-file-btn").show(); $("#upload-file-btn").show();
$("#send-msg-btn").show(); $("#send-msg-btn").show();
} }
@ -834,6 +900,7 @@
if (curr_default_channel == null) { if (curr_default_channel == null) {
$("#messagebox").prop('disabled', true); $("#messagebox").prop('disabled', true);
$("#messagebox").prop('placeholder', "NO TEXT CHANNELS"); $("#messagebox").prop('placeholder', "NO TEXT CHANNELS");
$("#send-rich-embed-btn").hide();
$("#upload-file-btn").hide(); $("#upload-file-btn").hide();
$("#send-msg-btn").hide(); $("#send-msg-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); 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);
@ -845,12 +912,14 @@
if (this_channel.write) { if (this_channel.write) {
$("#messagebox").prop('disabled', false); $("#messagebox").prop('disabled', false);
$("#messagebox").prop('placeholder', "Enter message"); $("#messagebox").prop('placeholder', "Enter message");
$("#send-rich-embed-btn").show();
$("#upload-file-btn").show(); $("#upload-file-btn").show();
$("#send-msg-btn").show(); $("#send-msg-btn").show();
$(".wdt-emoji-picker").show(); $(".wdt-emoji-picker").show();
} else { } else {
$("#messagebox").prop('disabled', true); $("#messagebox").prop('disabled', true);
$("#messagebox").prop('placeholder', "Messaging is disabled in this channel."); $("#messagebox").prop('placeholder', "Messaging is disabled in this channel.");
$("#send-rich-embed-btn").hide();
$("#upload-file-btn").hide(); $("#upload-file-btn").hide();
$("#send-msg-btn").hide(); $("#send-msg-btn").hide();
$(".wdt-emoji-picker").hide(); $(".wdt-emoji-picker").hide();
@ -860,6 +929,11 @@
} else { } else {
$("#upload-file-btn").hide(); $("#upload-file-btn").hide();
} }
if (this_channel.embed_links) {
$("#send-rich-embed-btn").show();
} else {
$("#send-rich-embed-btn").hide();
}
$("#channeltopic").text(this_channel.channel.topic); $("#channeltopic").text(this_channel.channel.topic);
$("#channel-"+selected_channel).parent().addClass("active"); $("#channel-"+selected_channel).parent().addClass("active");
} }
@ -1110,6 +1184,107 @@
$("#usercard").modal('open'); $("#usercard").modal('open');
} }
function generatePreviewRichEmbed() {
var richembed = generateRichEmbedObjFromModalForm();
var rendered = render_embed(richembed);
$("#richembedmodal-preview").html(rendered);
}
function genPreviewPopulateRichEmbed() {
generatePreviewRichEmbed();
var richembed = generateRichEmbedObjFromModalForm();
$("#richembedmodal-object").val("");
if (Object.keys(richembed).length > 2) {
if (richembed.fields && richembed.fields.length == 0) {
return;
}
$("#richembedmodal-object").val(JSON.stringify(richembed));
}
}
function generateRichEmbedObjFromModalForm() {
var color = $("#richembedform-color").val();
var title = $("#richembedform-title").val();
var description = $("#richembedform-description").val();
var url = $("#richembedform-url").val();
var thumbnailurl = $("#richembedform-thumbnailurl").val();
var authorname = $("#richembedform-authorname").val();
var authorurl = $("#richembedform-authorurl").val();
var authoricon = $("#richembedform-authoricon").val();
var footertext = $("#richembedform-footertext").val();
var output = {
"type": "rich"
};
if (color.startsWith("#")) {
color = color.substr(1);
}
color = "0x" + color;
color = parseInt(color, 16);
output["color"] = color;
if (title) {
output["title"] = title;
}
if (description) {
output["description"] = description;
}
if (url) {
output["url"] = url;
}
if (thumbnailurl) {
output["thumbnail"] = {
"url": thumbnailurl,
"proxy_url": thumbnailurl,
};
}
if (authorname || authorurl || authoricon) {
output["author"] = {};
}
if (authorname) {
output["author"]["name"] = authorname;
}
if (authorurl) {
output["author"]["url"] = authorurl;
}
if (authoricon) {
output["author"]["icon_url"] = authoricon;
output["author"]["proxy_icon_url"] = authoricon;
}
if (footertext) {
output["footer"] = {
"text": footertext
}
}
var fieldRows = $("#richembedmodal-fields > .row");
if (fieldRows.length) {
output["fields"] = [];
}
for (var i = 0; i < Math.min(fieldRows.length, 10); i++) {
var fieldRow = $(fieldRows[i]);
var fieldName = fieldRow.find("input.name").val().trim();
var fieldValue = fieldRow.find("input.value").val().trim();
if (fieldName && fieldValue) {
output["fields"].push({
"name": fieldName,
"value": fieldValue,
"inline": false,
});
}
}
return output;
}
function flashElement(element) { function flashElement(element) {
var opacity = element.css("opacity"); var opacity = element.css("opacity");
for (var i = 0; i < 3; i++) { for (var i = 0; i < 3; i++) {
@ -1378,48 +1553,51 @@
// if ($.inArray(disembed.type, ["rich", "link", "video"]) == -1) { // if ($.inArray(disembed.type, ["rich", "link", "video"]) == -1) {
// continue; // continue;
// } // }
var rendered = render_embed(disembed);
if (disembed.type == "image") {
var img = "<img class=\"image attachment materialboxed\" src=\"" + disembed.thumbnail.proxy_url + "\">";
emb.push(img);
continue;
}
disembed.isVideo = false;
if (disembed.type == "video") {
disembed.isVideo = true;
if (disembed.video) {
var url = new URL(disembed.video.url);
if (url.hostname.endsWith("twitch.tv")) {
if (url.searchParams.has("autoplay")) {
url.searchParams.set("autoplay", "false");
disembed.video.url = url.toString();
}
}
}
}
disembed.toRenderFooter = false;
if (disembed.footer) {
disembed.toRenderFooter = true;
} else if (disembed.timestamp) {
disembed.toRenderFooter = true;
}
disembed.footerVerticalBar = disembed.footer && disembed.timestamp;
if (disembed.timestamp) {
disembed.formatted_timestamp = moment(disembed.timestamp).format('ddd MMM Do, YYYY [at] h:mm A');
}
if (disembed.color) {
disembed.hexColor = "#" + disembed.color.toString(16);
}
var template = $('#mustache_richembed').html();
Mustache.parse(template);
var rendered = Mustache.render(template, disembed);
emb.push(rendered); emb.push(rendered);
} }
} }
return emb; return emb;
} }
function render_embed(disembed) {
if (disembed.type == "image") {
var img = "<img class=\"image attachment materialboxed\" src=\"" + disembed.thumbnail.proxy_url + "\">";
return img;
}
disembed.isVideo = false;
if (disembed.type == "video") {
disembed.isVideo = true;
if (disembed.video) {
var url = new URL(disembed.video.url);
if (url.hostname.endsWith("twitch.tv")) {
if (url.searchParams.has("autoplay")) {
url.searchParams.set("autoplay", "false");
disembed.video.url = url.toString();
}
}
}
}
disembed.toRenderFooter = false;
if (disembed.footer) {
disembed.toRenderFooter = true;
} else if (disembed.timestamp) {
disembed.toRenderFooter = true;
}
disembed.footerVerticalBar = disembed.footer && disembed.timestamp;
if (disembed.timestamp) {
disembed.formatted_timestamp = moment(disembed.timestamp).format('ddd MMM Do, YYYY [at] h:mm A');
}
if (disembed.color) {
disembed.hexColor = "#" + disembed.color.toString(16);
}
var template = $('#mustache_richembed').html();
Mustache.parse(template);
var rendered = Mustache.render(template, disembed);
return rendered;
}
function parse_message_reactions(reactions) { function parse_message_reactions(reactions) {
var reacts = [] var reacts = []
var template = $("#mustache_reactionchip").html(); var template = $("#mustache_reactionchip").html();
@ -2010,12 +2188,15 @@
if (event.keyCode == 16) { if (event.keyCode == 16) {
shift_pressed = true; shift_pressed = true;
} }
if(event.keyCode == 13 && !shift_pressed && ($(this).val().length >= 1 || $("#fileinput").val().length >= 1)) { if(event.keyCode == 13 && !shift_pressed && ($(this).val().length >= 1 || $("#fileinput").val().length >= 1 || $("#richembedmodal-object").val().length >= 1)) {
$(this).val($.trim($(this).val())); $(this).val($.trim($(this).val()));
$(this).blur(); $(this).blur();
$("#messagebox-richembedmodal").attr('readonly', true);
$("#messagebox-filemodal").attr('readonly', true); $("#messagebox-filemodal").attr('readonly', true);
$("#proceed_fileupload_btn").attr("disabled", true); $("#proceed_fileupload_btn").attr("disabled", true);
$("#proceed_richembedmodal_btn").attr("disabled", true);
$("#messagebox").attr('readonly', true); $("#messagebox").attr('readonly', true);
$("#richembedmodal-left input").attr('readonly', true);
$("#send-msg-btn").attr("disabled", true); $("#send-msg-btn").attr("disabled", true);
var emojiConvertor = new EmojiConvertor(); var emojiConvertor = new EmojiConvertor();
emojiConvertor.init_env(); emojiConvertor.init_env();
@ -2028,12 +2209,18 @@
file = $("#fileinput")[0].files[0]; file = $("#fileinput")[0].files[0];
} }
$("#filemodalprogress").show(); $("#filemodalprogress").show();
var funct = post(selected_channel, messageInput, file); var richembed = $("#richembedmodal-object").val();
if (!richembed) {
richembed = null;
}
var funct = post(selected_channel, messageInput, file, richembed);
funct.done(function(data) { funct.done(function(data) {
$("#messagebox").val(""); $("#messagebox").val("");
$("#messagebox-filemodal").val(""); $("#messagebox-filemodal").val("");
$("#fileinput").val(""); $("#fileinput").val("");
$("#richembedmodal-object").val("");
$("#filemodal").modal("close"); $("#filemodal").modal("close");
$("#richembedmodal").modal("close");
}); });
funct.fail(function(data) { funct.fail(function(data) {
Materialize.toast('Failed to send message.', 10000); Materialize.toast('Failed to send message.', 10000);
@ -2055,6 +2242,9 @@
$("#messagebox-filemodal").attr('readonly', false); $("#messagebox-filemodal").attr('readonly', false);
$("#proceed_fileupload_btn").attr("disabled", false); $("#proceed_fileupload_btn").attr("disabled", false);
$("#filemodalprogress").hide(); $("#filemodalprogress").hide();
$("#messagebox-richembedmodal").attr('readonly', false);
$("#proceed_richembedmodal_btn").attr("disabled", false);
$("#richembedmodal-left input").attr('readonly', false);
$("#send-msg-btn").attr("disabled", false); $("#send-msg-btn").attr("disabled", false);
if ($("#filemodal").is(":visible")) { if ($("#filemodal").is(":visible")) {
$("#messagebox-filemodal").focus(); $("#messagebox-filemodal").focus();
@ -2087,6 +2277,28 @@
} }
}); });
$("#messagebox-richembedmodal").keyup(function (event) {
if (event.keyCode == 16) {
shift_pressed = false;
}
});
$("#messagebox-richembedmodal").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 } ));
}
});
$("#send-msg-btn").click(function () { $("#send-msg-btn").click(function () {
$("#messagebox").focus(); $("#messagebox").focus();
$("#messagebox").trigger(jQuery.Event("keydown", { keyCode: 13 } )); $("#messagebox").trigger(jQuery.Event("keydown", { keyCode: 13 } ));

View File

@ -15,6 +15,7 @@
<th>CSS</th> <th>CSS</th>
<th>CSS Limit</th> <th>CSS Limit</th>
<th>Guest Icon</th> <th>Guest Icon</th>
<th>Send Rich Embed</th>
<th>Badges</th> <th>Badges</th>
<th>Submit</th> <th>Submit</th>
</tr> </tr>
@ -51,6 +52,16 @@
</label> </label>
</div> </div>
</td> </td>
<td>
<div class="switch">
<label>
Off
<input type="checkbox" id="new_send_rich_embed_switch">
<span class="lever"></span>
On
</label>
</div>
</td>
<td> <td>
<div class="input-field col s12"> <div class="input-field col s12">
<select multiple id="new_badges"> <select multiple id="new_badges">
@ -80,6 +91,7 @@
<th>CSS</th> <th>CSS</th>
<th>CSS Limit</th> <th>CSS Limit</th>
<th>Guest Icon</th> <th>Guest Icon</th>
<th>Send Rich Embed</th>
<th>Badges</th> <th>Badges</th>
</tr> </tr>
</thead> </thead>
@ -113,6 +125,16 @@
</label> </label>
</div> </div>
</td> </td>
<td>
<div class="switch">
<label>
Off
<input type="checkbox" {% if cosmetic.send_rich_embed %}checked{% endif %} onchange="update_send_rich_embed_switch('{{ cosmetic.user_id }}', this)">
<span class="lever"></span>
On
</label>
</div>
</td>
<td> <td>
<div class="input-field col s12"> <div class="input-field col s12">
<select multiple id="new_badges" onchange="update_badges('{{ cosmetic.user_id }}', this)"> <select multiple id="new_badges" onchange="update_badges('{{ cosmetic.user_id }}', this)">

View File

@ -227,6 +227,22 @@
</label> </label>
</div> </div>
<br>
<p class="flow-text">Send Rich Embed</p>
<p>Allow users to send rich embedded messages (both the bot & user need "embed links" permission)</p>
{% if not cosmetics.send_rich_embed %}
<p class="red lighten-4"><strong>Your user account does not have access to change send rich embed. Please visit the Titan Tokens shop to activate this cosmetic item.</strong></p>
{% endif %}
<div class="switch">
<label>
Disable
<input type="checkbox" id="send_rich_embed" name="send_rich_embed" {% if guild['send_rich_embed'] %}checked{% endif %} {% if not cosmetics.send_rich_embed %}disabled{% endif %} >
<span class="lever"></span>
Enable
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -83,6 +83,7 @@
<div id="messageboxouter" class="input-field inline"> <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> <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> <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="send-rich-embed-btn" class="btn-flat"><i class="material-icons">queue</i></a>
<a id="upload-file-btn" class="btn-flat"><i class="material-icons">file_upload</i></a> <a id="upload-file-btn" class="btn-flat"><i class="material-icons">file_upload</i></a>
<a id="send-msg-btn" class="btn-floating btn-large waves-effect waves-light"><i class="material-icons">send</i></a> <a id="send-msg-btn" class="btn-floating btn-large waves-effect waves-light"><i class="material-icons">send</i></a>
</div> </div>
@ -298,6 +299,67 @@
</div> </div>
</div> </div>
<div id="richembedmodal" class="modal">
<div class="modal-content">
<h4 class="center-align">Send a Rich Embed Message</h4>
<p>Add a comment (optional)</p>
<p class="center-align">
<textarea placeholder="Enter message" id="messagebox-richembedmodal" type="text" class="materialize-textarea" rows="1"></textarea>
</p>
<div id="richembedmodal-body" class="row">
<div id="richembedmodal-left" class="col s12 m5">
<div class="input-field">
<span>Color</span>
<input id="richembedform-color" type="color" value="#6BABDA">
</div>
<div class="input-field">
<input placeholder="title" id="richembedform-title" type="text" data-length="100" maxlength="100">
<label for="richembedform-title">Title</label>
</div>
<div class="input-field">
<input placeholder="description" id="richembedform-description" type="text" data-length="100" maxlength="100">
<label for="richembedform-description">Description</label>
</div>
<div class="input-field">
<input placeholder="link url" id="richembedform-url" type="text" data-length="100" maxlength="100">
<label for="richembedform-url">Link URL</label>
</div>
<div class="input-field">
<input placeholder="thumbnail url" id="richembedform-thumbnailurl" type="text" data-length="100" maxlength="100">
<label for="richembedform-thumbnailurl">Thumbnail URL (must begin with http(s))</label>
</div>
<div class="input-field">
<input placeholder="author name" id="richembedform-authorname" type="text" data-length="100" maxlength="100">
<label for="richembedform-authorname">Author Name</label>
</div>
<div class="input-field">
<input placeholder="author link url" id="richembedform-authorurl" type="text" data-length="100" maxlength="100">
<label for="richembedform-authorurl">Author Link URL (must begin with http(s))</label>
</div>
<div class="input-field">
<input placeholder="author icon url" id="richembedform-authoricon" type="text" data-length="100" maxlength="100">
<label for="richembedform-authoricon">Author Icon URL (must begin with http(s))</label>
</div>
<div class="input-field">
<input placeholder="footer text" id="richembedform-footertext" type="text" data-length="100" maxlength="100">
<label for="richembedform-footertext">Footer Text</label>
</div>
<span>Fields:</span>
<div id="richembedmodal-fields"></div>
<a id="richembedmodal_addfield_btn" class="waves-effect waves-light btn">+ Field</a>
</div>
<div id="richembedmodal-right" class="col s12 m7">
<div id="richembedmodal-preview"></div>
<span>Preview</span>
</div>
</div>
<input type="hidden" value="" id="richembedmodal-object">
<p class="right-align">
<a id="proceed_richembedmodal_btn" class="waves-effect waves-light btn">Send</a>
</p>
</div>
</div>
<div id="usercard" class="modal bottom-sheet"> <div id="usercard" class="modal bottom-sheet">
<div class="modal-content"> <div class="modal-content">
<div class="row"> <div class="row">
@ -545,6 +607,20 @@
<script id="mustache_reactionchip" type="text/template"> <script id="mustache_reactionchip" type="text/template">
<span class="reaction tooltipped" data-position="top" data-delay="200" data-tooltip="{{#emoji.id}}:{{/emoji.id}}{{emoji.name}}{{#emoji.id}}:{{/emoji.id}}"><img src="{{img_url}}"> <span class="count">{{count}}</span></span> <span class="reaction tooltipped" data-position="top" data-delay="200" data-tooltip="{{#emoji.id}}:{{/emoji.id}}{{emoji.name}}{{#emoji.id}}:{{/emoji.id}}"><img src="{{img_url}}"> <span class="count">{{count}}</span></span>
</script> </script>
<script id="mustache_richembedfieldinput" type="text/template">
<div class="row">
<div class="input-field col s5">
<input placeholder="Name" type="text" data-length="100" class="name" maxlength="100">
</div>
<div class="input-field col s5">
<input placeholder="Value" type="text" data-length="100" class="value" maxlength="100">
</div>
<div class="col s2">
<a class="btn-flat delete-field"><i class="material-icons">delete_forever</i></a>
</div>
</div>
</script>
{% endraw %} {% endraw %}
<script> <script>

View File

@ -233,11 +233,13 @@ def get_guild_channels(guild_id, force_everyone=False, forced_role=0):
result["mention_everyone"] = False result["mention_everyone"] = False
if not bot_result["attach_files"] or not db_guild.file_upload or not result["write"]: if not bot_result["attach_files"] or not db_guild.file_upload or not result["write"]:
result["attach_files"] = False result["attach_files"] = False
if not bot_result["embed_links"] or not db_guild.send_rich_embed or not result["write"]:
result["embed_links"] = False
result_channels.append(result) result_channels.append(result)
return sorted(result_channels, key=lambda k: k['channel']['position']) 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): 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, "attach_files": False} result = {"channel": channel, "read": False, "write": False, "mention_everyone": False, "attach_files": False, "embed_links": False}
if not user_id: if not user_id:
user_id = str(session.get("user_id")) user_id = str(session.get("user_id"))
if guild_owner == user_id: if guild_owner == user_id:
@ -245,6 +247,7 @@ def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_r
result["write"] = True result["write"] = True
result["mention_everyone"] = True result["mention_everyone"] = True
result["attach_files"] = True result["attach_files"] = True
result["embed_links"] = True
return result return result
channel_perm = 0 channel_perm = 0
@ -272,6 +275,7 @@ def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_r
result["write"] = True result["write"] = True
result["mention_everyone"] = True result["mention_everyone"] = True
result["attach_files"] = True result["attach_files"] = True
result["embed_links"] = True
return result return result
denies = 0 denies = 0
@ -295,6 +299,7 @@ def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_r
result["write"] = user_has_permission(channel_perm, 11) result["write"] = user_has_permission(channel_perm, 11)
result["mention_everyone"] = user_has_permission(channel_perm, 17) result["mention_everyone"] = user_has_permission(channel_perm, 17)
result["attach_files"] = user_has_permission(channel_perm, 15) result["attach_files"] = user_has_permission(channel_perm, 15)
result["embed_links"] = user_has_permission(channel_perm, 14)
# If you cant read channel, you cant write in it # If you cant read channel, you cant write in it
if not user_has_permission(channel_perm, 10): if not user_has_permission(channel_perm, 10):
@ -302,6 +307,7 @@ def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_r
result["write"] = False result["write"] = False
result["mention_everyone"] = False result["mention_everyone"] = False
result["attach_files"] = False result["attach_files"] = False
result["embed_links"] = False
return result return result
def get_forced_role(guild_id): def get_forced_role(guild_id):