Automatic webhook creation and deletion, Resolves #42

This commit is contained in:
Jeremy Zhang 2017-08-27 21:41:36 +00:00
parent 8038a1b4fc
commit 654dd3faf3
11 changed files with 225 additions and 8 deletions

View File

@ -7,6 +7,7 @@ class Guilds(Base):
name = db.Column(db.String(255)) # Name
unauth_users = db.Column(db.Boolean()) # If allowed unauth users
visitor_view = db.Column(db.Boolean()) # If users are automatically "signed in" and can view chat
webhook_messages = db.Column(db.Boolean()) # Use webhooks to send messages instead of the bot
chat_links = db.Column(db.Boolean()) # If users can post links
bracket_links = db.Column(db.Boolean()) # If appending brackets to links to prevent embed
mentions_limit = db.Column(db.Integer) # If there is a limit on the number of mentions in a msg
@ -23,6 +24,7 @@ class Guilds(Base):
self.name = name
self.unauth_users = True # defaults to true
self.visitor_view = False
self.webhook_messages = False
self.chat_links = True
self.bracket_links = True
self.mentions_limit = -1 # -1 = unlimited mentions

View File

@ -0,0 +1,144 @@
"""Added webhook messages boolean column to guilds
Revision ID: dadcb876cdd9
Revises: 2a2f32ac91d6
Create Date: 2017-08-27 20:01:30.874376
"""
# revision identifiers, used by Alembic.
revision = 'dadcb876cdd9'
down_revision = '2a2f32ac91d6'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('cosmetics', 'css',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False)
op.alter_column('guild_members', 'active',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.alter_column('guild_members', 'banned',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text("'0'"))
op.add_column('guilds', sa.Column('webhook_messages', sa.Boolean(), nullable=False))
op.alter_column('guilds', 'bracket_links',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.alter_column('guilds', 'channels',
existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
type_=sa.Text(length=4294967295),
existing_nullable=False)
op.alter_column('guilds', 'chat_links',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.alter_column('guilds', 'emojis',
existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
type_=sa.Text(length=4294967295),
existing_nullable=False)
op.alter_column('guilds', 'roles',
existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
type_=sa.Text(length=4294967295),
existing_nullable=False)
op.alter_column('guilds', 'unauth_users',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.alter_column('guilds', 'visitor_view',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False)
op.alter_column('guilds', 'webhooks',
existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
type_=sa.Text(length=4294967295),
existing_nullable=False)
op.alter_column('unauthenticated_users', 'revoked',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text("'0'"))
op.alter_column('user_css', 'css',
existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
type_=sa.Text(length=4294967295),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('user_css', 'css',
existing_type=sa.Text(length=4294967295),
type_=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
existing_nullable=True)
op.alter_column('unauthenticated_users', 'revoked',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text("'0'"))
op.alter_column('guilds', 'webhooks',
existing_type=sa.Text(length=4294967295),
type_=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column('guilds', 'visitor_view',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False)
op.alter_column('guilds', 'unauth_users',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.alter_column('guilds', 'roles',
existing_type=sa.Text(length=4294967295),
type_=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column('guilds', 'emojis',
existing_type=sa.Text(length=4294967295),
type_=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column('guilds', 'chat_links',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.alter_column('guilds', 'channels',
existing_type=sa.Text(length=4294967295),
type_=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column('guilds', 'bracket_links',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.drop_column('guilds', 'webhook_messages')
op.alter_column('guild_members', 'banned',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text("'0'"))
op.alter_column('guild_members', 'active',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text("'1'"))
op.alter_column('cosmetics', 'css',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False)
# ### end Alembic commands ###

View File

@ -135,6 +135,7 @@ def administrate_guild(guild_id):
"name": db_guild.name,
"unauth_users": db_guild.unauth_users,
"visitor_view": db_guild.visitor_view,
"webhook_messages": db_guild.webhook_messages,
"chat_links": db_guild.chat_links,
"bracket_links": db_guild.bracket_links,
"mentions_limit": db_guild.mentions_limit,
@ -149,6 +150,7 @@ def update_administrate_guild(guild_id):
db_guild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
db_guild.unauth_users = request.form.get("unauth_users", db_guild.unauth_users) in ["true", True]
db_guild.visitor_view = request.form.get("visitor_view", db_guild.visitor_view) in ["true", True]
db_guild.webhook_messages = request.form.get("webhook_messages", db_guild.webhook_messages) in ["true", True]
db_guild.chat_links = request.form.get("chat_links", db_guild.chat_links) in ["true", True]
db_guild.bracket_links = request.form.get("bracket_links", db_guild.bracket_links) in ["true", True]
db_guild.mentions_limit = request.form.get("mentions_limit", db_guild.mentions_limit)
@ -161,6 +163,8 @@ def update_administrate_guild(guild_id):
id=db_guild.id,
guild_id=db_guild.guild_id,
unauth_users=db_guild.unauth_users,
visitor_view=db_guild.visitor_view,
webhook_messages=db_guild.webhook_messages,
chat_links=db_guild.chat_links,
bracket_links=db_guild.bracket_links,
mentions_limit=db_guild.mentions_limit,

View File

@ -2,7 +2,7 @@ from titanembeds.database import db, Guilds, UnauthenticatedUsers, Unauthenticat
from titanembeds.decorators import valid_session_required, discord_users_only
from titanembeds.utils import check_guild_existance, guild_accepts_visitors, 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 titanembeds.userbookkeeping import user_unauthenticated, checkUserRevoke, checkUserBanned, update_user_status, check_user_in_guild, get_guild_channels
from titanembeds.userbookkeeping import user_unauthenticated, checkUserRevoke, checkUserBanned, update_user_status, check_user_in_guild, get_guild_channels, guild_webhooks_enabled
from flask import Blueprint, abort, jsonify, session, request, url_for
from flask_socketio import emit
from sqlalchemy import and_
@ -50,7 +50,7 @@ def format_post_content(guild_id, channel_id, message, dbUser):
mention = "<@" + match[2: len(match) - 1] + ">"
message = message.replace(match, mention, 1)
if not get_channel_webhook_url(guild_id, channel_id):
if not guild_webhooks_enabled(guild_id):
if (session['unauthenticated']):
message = u"**[{}#{}]** {}".format(session['username'], session['user_id'], message)
else:
@ -137,15 +137,23 @@ def get_guild_emojis(guild_id):
# Returns webhook url if exists and can post w/webhooks, otherwise None
def get_channel_webhook_url(guild_id, channel_id):
if not guild_webhooks_enabled(guild_id):
return None
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
guild_webhooks = json.loads(dbguild.webhooks)
name = "[Titan] "
if user_unauthenticated():
name = name + session["username"] + "#" + str(session["user_id"])
else:
name = name + session["username"] + "#" + str(session["discriminator"])
for webhook in guild_webhooks:
if channel_id == webhook["channel_id"] and (webhook["name"].lower().startswith("titan") or webhook["name"].lower().startswith("[titan]")):
if channel_id == webhook["channel_id"] and webhook["name"] == name:
return {
"id": webhook["id"],
"token": webhook["token"]
}
return None
webhook = discord_api.create_webhook(channel_id, name)
return webhook["content"]
@api.route("/fetch", methods=["GET"])
@valid_session_required(api=True)

View File

@ -1,10 +1,11 @@
from titanembeds.utils import socketio, guild_accepts_visitors, get_client_ipaddr
from titanembeds.userbookkeeping import check_user_in_guild, get_guild_channels, update_user_status
from titanembeds.database import db, GuildMembers, get_guild_member
from titanembeds.utils import socketio, guild_accepts_visitors, get_client_ipaddr, discord_api
from titanembeds.userbookkeeping import check_user_in_guild, get_guild_channels, update_user_status, guild_webhooks_enabled
from titanembeds.database import db, GuildMembers, get_guild_member, Guilds
from flask_socketio import Namespace, emit, disconnect, join_room, leave_room
import functools
from flask import request, session
import time
import json
class Gateway(Namespace):
def on_connect(self):
@ -48,6 +49,17 @@ class Gateway(Namespace):
else:
msg = {"unauthenticated": False, "id": session["user_id"]}
emit("embed_user_disconnect", msg, room="GUILD_"+guild_id)
if guild_webhooks_enabled(guild_id): # Delete webhooks
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
guild_webhooks = json.loads(dbguild.webhooks)
name = "[Titan] "
if session["unauthenticated"]:
name = name + session["username"] + "#" + str(session["user_id"])
else:
name = name + session["username"] + "#" + str(session["discriminator"])
for webhook in guild_webhooks:
if webhook["name"] == name:
discord_api.delete_webhook(webhook["id"], webhook["token"])
def on_heartbeat(self, data):
guild_id = data["guild_id"]

View File

@ -181,6 +181,7 @@ def administrate_guild(guild_id):
"name": db_guild.name,
"unauth_users": db_guild.unauth_users,
"visitor_view": db_guild.visitor_view,
"webhook_messages": db_guild.webhook_messages,
"chat_links": db_guild.chat_links,
"bracket_links": db_guild.bracket_links,
"mentions_limit": db_guild.mentions_limit,
@ -199,6 +200,7 @@ def update_administrate_guild(guild_id):
abort(400)
db_guild.unauth_users = request.form.get("unauth_users", db_guild.unauth_users) in ["true", True]
db_guild.visitor_view = request.form.get("visitor_view", db_guild.visitor_view) in ["true", True]
db_guild.webhook_messages = request.form.get("webhook_messages", db_guild.webhook_messages) in ["true", True]
db_guild.chat_links = request.form.get("chat_links", db_guild.chat_links) in ["true", True]
db_guild.bracket_links = request.form.get("bracket_links", db_guild.bracket_links) in ["true", True]
db_guild.mentions_limit = request.form.get("mentions_limit", db_guild.mentions_limit)
@ -212,6 +214,8 @@ def update_administrate_guild(guild_id):
id=db_guild.id,
guild_id=db_guild.guild_id,
unauth_users=db_guild.unauth_users,
visitor_view=db_guild.visitor_view,
webhook_messages=db_guild.webhook_messages,
chat_links=db_guild.chat_links,
bracket_links=db_guild.bracket_links,
mentions_limit=db_guild.mentions_limit,

View File

@ -7,6 +7,7 @@ class Guilds(db.Model):
name = db.Column(db.String(255), nullable=False) # Name
unauth_users = db.Column(db.Boolean(), nullable=False, default=1) # If allowed unauth users
visitor_view = db.Column(db.Boolean(), nullable=False, default=0) # If users are automatically "signed in" and can view chat
webhook_messages = db.Column(db.Boolean(), nullable=False, default=0) # Use webhooks to send messages instead of the bot
chat_links = db.Column(db.Boolean(), nullable=False, default=1) # If users can post links
bracket_links = db.Column(db.Boolean(), nullable=False, default=1) # If appending brackets to links to prevent embed
mentions_limit = db.Column(db.Integer, nullable=False, default=11) # If there is a limit on the number of mentions in a msg
@ -23,6 +24,7 @@ class Guilds(db.Model):
self.name = name
self.unauth_users = True # defaults to true
self.visitor_view = False
self.webhook_messages = False
self.chat_links = True
self.bracket_links = True
self.mentions_limit = -1 # -1 = unlimited mentions

View File

@ -145,6 +145,16 @@ class DiscordREST:
# Webhook
#####################
def create_webhook(self, channel_id, name, avatar=None):
_endpoint = "/channels/{channel_id}/webhooks".format(channel_id=channel_id)
payload = {
"name": name,
}
if avatar:
payload["avatar"] = avatar
r = self.request("POST", _endpoint, data=payload, json=True)
return r
def execute_webhook(self, webhook_id, webhook_token, username, avatar, content, wait=True):
_endpoint = "/webhooks/{id}/{token}".format(id=webhook_id, token=webhook_token)
if wait:
@ -155,4 +165,9 @@ class DiscordREST:
'username': username
}
r = self.request("POST", _endpoint, data=payload)
return r
def delete_webhook(self, webhook_id, webhook_token):
_endpoint = "/webhooks/{id}/{token}".format(id=webhook_id, token=webhook_token)
r = self.request("DELETE", _endpoint)
return r

View File

@ -16,6 +16,15 @@ $('#visitor_view').change(function() {
});
});
$('#webhook_messages').change(function() {
var pathname = window.location.pathname;
var checked = $(this).is(':checked')
var payload = {"webhook_messages": checked}
$.post(pathname, payload, function(data) {
Materialize.toast('Updated webhook messages setting!', 2000)
});
});
$('#chat_links').change(function() {
var pathname = window.location.pathname;
var checked = $(this).is(':checked')

View File

@ -62,6 +62,19 @@
</label>
</div>
<br>
<p class="flow-text">Toggle Webhooks Messages</p>
<p>Instead of sending user messages directly from the Titan bot, webhook messages allows Titan to take advantage of the built-in webhooks to create messages that looks more real. Reading messages in Discord can be <a href="{{ url_for('static', filename='img/webhook_comparison.png') }}" target="_blank" title="A quick comparison between having webhook messages enabled vs disabled for a text channel">20% more cooler</a>!</p>
<div class="switch">
<label>
Disable
<input type="checkbox" id="webhook_messages" name="webhook_messages" {% if guild['webhook_messages'] %}checked{% endif %} >
<span class="lever"></span>
Enable
</label>
</div>
<br>
<p class="flow-text">Chat Links</p>

View File

@ -176,4 +176,8 @@ def get_guild_channels(guild_id, force_everyone=False):
result["mention_everyone"] = False
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 guild_webhooks_enabled(guild_id):
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
return dbguild.webhook_messages