Merge pull request #45 from TitanEmbeds/websockets

Websockets
This commit is contained in:
Jeremy "EndenDragon" Zhang 2017-09-02 12:25:40 -07:00 committed by GitHub
commit 52a24d7618
45 changed files with 1202 additions and 497 deletions

View File

@ -1,5 +1,5 @@
# Titan
**Visit our website! [https://titanembeds.tk/](https://titanembeds.tk/) And get started *right away*!**
**Visit our website! [https://titanembeds.com/](https://titanembeds.com/) And get started *right away*!**
There was a time when Discord doesn't support embedding the chat on a webpage. But with Titan, you can! It is as simple as 1, 2, 3!
1. Invite the bot to your server (You must have "Manage Server" permissions)
@ -14,11 +14,11 @@ There was a time when Discord doesn't support embedding the chat on a webpage. B
# Installation
Would you like to run your own copy of Titan Embeds? There are two parts that integrate nicely together. The webapp (website) handles the frontend and communication with the database to retrieve server messages, etc. The discordbot (bot) handles the communcation
between Discord's websockets and pushing out the data to the database for the webapp. Check out the respective folder for their installation (pay attention to the python versions!) instructions.
between Discord's websockets and pushing out the data to the database for the webapp. Check out the respective folder for their installation instructions.
# Database installation
To set up the database for it to work with the webapp and the discordbot, one must use **alembic** to *migrate* their databases to the current database state. To do so, please follow these instructions.
1. Install alembic with **Python 2.7's pip** `pip install alembic`
1. Install alembic with **Python 3.5's pip** `pip install alembic`
2. Change your directory to the webapp where the alembic files are located `cd webapp`
3. Clone `alembic.example.ini` into your own `alembic.ini` file to find and edit the following line `sqlalchemy.url` to equal your database uri. [See here](http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls) if you need help understanding how database uri works in SQLalchemy.
4. In your terminal, run `alembic upgrade head` to upgrade your database tables to the current version on git. As long as there are only *INFO* messages and no errors, you should be fine.

View File

@ -9,17 +9,13 @@ cp ~/workspace/webapp/config.example.py ~/workspace/webapp/config.py
cp ~/workspace/discordbot/config.example.py ~/workspace/discordbot/config.py
cp ~/workspace/webapp/alembic.example.ini ~/workspace/webapp/alembic.ini
echo "[C9Setup] Installing discordbot dependencies"
cd ~/workspace/discordbot/
echo "[C9Setup] Installing Titan dependencies"
cd ~/workspace/
sudo python3.5 -m pip install -r requirements.txt
sudo python3.5 -m pip install pymysql
echo "[C9Setup] Installing webapp dependencies"
cd ~/workspace/webapp
sudo pip install -r requirements.txt
sudo pip install alembic pymysql
sudo python3.5 -m pip install alembic pymysql eventlet uwsgi
echo "[C9Setup] Auto populating alembic.ini database url and titan database table"
cd ~/workspace/webapp
#sqlalchemy.url = mysql+pymysql://root@localhost/titan
sed -i '32s/.*/sqlalchemy.url = mysql+pymysql:\/\/root@localhost\/titan/' ~/workspace/webapp/alembic.ini
alembic upgrade head
@ -34,6 +30,7 @@ sed -i "11s/.*/\'database-uri\': \"mysql+pymysql:\/\/root@localhost\/titan\",/"
sed -i "8s/.*/\'app-location\': \"\/home\/ubuntu\/workspace\/webapp\/\",/" ~/workspace/webapp/config.py
echo "[C9Setup] Making sure everything can be ran"
cd ~/workspace/
sudo chmod -R 777 *
echo "------------------------------"

View File

@ -3,5 +3,7 @@ config = {
'database-uri': "driver://username:password@host:port/database",
'redis-uri': "redis://",
'errorreporting-channelid': "",
}

View File

@ -1,3 +0,0 @@
https://github.com/TitanEmbeds/discord.py/archive/async.zip#egg=discord.py[voice]
sqlalchemy
asyncio_extras

View File

@ -1,6 +1,7 @@
from config import config
from titanembeds.database import DatabaseInterface
from titanembeds.commands import Commands
from titanembeds.socketio import SocketIOInterface
import discord
import aiohttp
import asyncio
@ -17,6 +18,7 @@ class Titan(discord.Client):
self.http.user_agent += ' TitanEmbeds-Bot'
self.database = DatabaseInterface(self)
self.command = Commands(self, self.database)
self.socketio = SocketIOInterface(self, config["redis-uri"])
self.database_connected = False
self.loop.create_task(self.send_webserver_heartbeat())
@ -78,7 +80,7 @@ class Titan(discord.Client):
print('------')
await self.change_presence(
game=discord.Game(name="Embed your Discord server! Visit https://TitanEmbeds.tk/"), status=discord.Status.online
game=discord.Game(name="Embed your Discord server! Visit https://TitanEmbeds.com/"), status=discord.Status.online
)
try:
@ -123,6 +125,7 @@ class Titan(discord.Client):
return
await self.wait_until_dbonline()
await self.database.push_message(message)
await self.socketio.on_message(message)
msg_arr = message.content.split() # split the message
if len(message.content.split()) > 1 and message.server: #making sure there is actually stuff in the message and have arguments and check if it is sent in server (not PM)
@ -136,10 +139,12 @@ class Titan(discord.Client):
async def on_message_edit(self, message_before, message_after):
await self.wait_until_dbonline()
await self.database.update_message(message_after)
await self.socketio.on_message_update(message_after)
async def on_message_delete(self, message):
await self.wait_until_dbonline()
await self.database.delete_message(message)
await self.socketio.on_message_delete(message)
async def on_server_join(self, guild):
await self.wait_until_dbonline()
@ -166,46 +171,56 @@ class Titan(discord.Client):
async def on_server_update(self, guildbefore, guildafter):
await self.wait_until_dbonline()
await self.database.update_guild(guildafter)
await self.socketio.on_guild_update(guildafter)
async def on_server_role_create(self, role):
await self.wait_until_dbonline()
if role.name == self.user.name and role.managed:
await asyncio.sleep(2)
await self.database.update_guild(role.server)
await self.socketio.on_guild_role_create(role)
async def on_server_role_delete(self, role):
await self.wait_until_dbonline()
if role.server.me not in role.server.members:
return
await self.database.update_guild(role.server)
await self.socketio.on_guild_role_delete(role)
async def on_server_role_update(self, rolebefore, roleafter):
await self.wait_until_dbonline()
await self.database.update_guild(roleafter.server)
await self.socketio.on_guild_role_update(role)
async def on_channel_delete(self, channel):
await self.wait_until_dbonline()
await self.database.update_guild(channel.server)
await self.socketio.on_channel_delete(channel)
async def on_channel_create(self, channel):
await self.wait_until_dbonline()
await self.database.update_guild(channel.server)
await self.socketio.on_channel_create(channel)
async def on_channel_update(self, channelbefore, channelafter):
await self.wait_until_dbonline()
await self.database.update_guild(channelafter.server)
await self.socketio.on_channel_update(channelafter)
async def on_member_join(self, member):
await self.wait_until_dbonline()
await self.database.update_guild_member(member, active=True, banned=False)
await self.socketio.on_guild_member_add(member)
async def on_member_remove(self, member):
await self.wait_until_dbonline()
await self.database.update_guild_member(member, active=False, banned=False)
await self.socketio.on_guild_member_remove(member)
async def on_member_update(self, memberbefore, memberafter):
await self.wait_until_dbonline()
await self.database.update_guild_member(memberafter)
await self.socketio.on_guild_member_update(memberafter)
async def on_member_ban(self, member):
await self.wait_until_dbonline()
@ -221,8 +236,10 @@ class Titan(discord.Client):
await self.wait_until_dbonline()
if len(after) == 0:
await self.database.update_guild(before[0].server)
await self.socketio.on_guild_emojis_update(before)
else:
await self.database.update_guild(after[0].server)
await self.socketio.on_guild_emojis_update(after)
async def on_webhooks_update(self, server):
await self.wait_until_dbonline()

View File

@ -18,6 +18,8 @@ from titanembeds.database.unauthenticated_users import UnauthenticatedUsers
from titanembeds.database.unauthenticated_bans import UnauthenticatedBans
from titanembeds.database.keyvalue_properties import KeyValueProperties
from titanembeds.utils import get_message_author, get_message_mentions, get_webhooks_list, get_emojis_list, get_roles_list, get_channels_list, list_role_ids
class DatabaseInterface(object):
# Courtesy of https://github.com/SunDwarf/Jokusoramame
def __init__(self, bot):
@ -58,38 +60,15 @@ class DatabaseInterface(object):
message.channel.id,
message.id,
message.content,
json.dumps(self.get_message_author(message)),
json.dumps(get_message_author(message)),
str(message.timestamp),
edit_ts,
json.dumps(self.get_message_mentions(message.mentions)),
json.dumps(get_message_mentions(message.mentions)),
json.dumps(message.attachments)
)
session.add(msg)
session.commit()
def get_message_author(self, message):
author = message.author
obj = {
"username": author.name,
"discriminator": author.discriminator,
"bot": author.bot,
"id": author.id,
"avatar": author.avatar
}
return obj
def get_message_mentions(self, mentions):
ments = []
for author in mentions:
ments.append({
"username": author.name,
"discriminator": author.discriminator,
"bot": author.bot,
"id": author.id,
"avatar": author.avatar
})
return ments
async def update_message(self, message):
if message.server:
async with threadpool():
@ -101,9 +80,9 @@ class DatabaseInterface(object):
if msg:
msg.content = message.content
msg.edited_timestamp = message.edited_timestamp
msg.mentions = json.dumps(self.get_message_mentions(message.mentions))
msg.mentions = json.dumps(get_message_mentions(message.mentions))
msg.attachments = json.dumps(message.attachments)
msg.author = json.dumps(self.get_message_author(message))
msg.author = json.dumps(get_message_author(message))
session.commit()
async def delete_message(self, message):
@ -127,91 +106,23 @@ class DatabaseInterface(object):
gui = Guilds(
guild.id,
guild.name,
json.dumps(self.get_roles_list(guild.roles)),
json.dumps(self.get_channels_list(guild.channels)),
json.dumps(self.get_webhooks_list(server_webhooks)),
json.dumps(self.get_emojis_list(guild.emojis)),
json.dumps(get_roles_list(guild.roles)),
json.dumps(get_channels_list(guild.channels)),
json.dumps(get_webhooks_list(server_webhooks)),
json.dumps(get_emojis_list(guild.emojis)),
guild.owner_id,
guild.icon
)
session.add(gui)
else:
gui.name = guild.name
gui.roles = json.dumps(self.get_roles_list(guild.roles))
gui.channels = json.dumps(self.get_channels_list(guild.channels))
gui.webhooks = json.dumps(self.get_webhooks_list(server_webhooks))
gui.emojis = json.dumps(self.get_emojis_list(guild.emojis))
gui.roles = json.dumps(get_roles_list(guild.roles))
gui.channels = json.dumps(get_channels_list(guild.channels))
gui.webhooks = json.dumps(get_webhooks_list(server_webhooks))
gui.emojis = json.dumps(get_emojis_list(guild.emojis))
gui.owner_id = guild.owner_id
gui.icon = guild.icon
session.commit()
def get_webhooks_list(self, guild_webhooks):
webhooks = []
for webhook in guild_webhooks:
webhooks.append({
"id": webhook.id,
"guild_id": webhook.server.id,
"channel_id": webhook.channel.id,
"name": webhook.name,
"token": webhook.token,
})
return webhooks
def get_emojis_list(self, guildemojis):
emojis = []
for emote in guildemojis:
emojis.append({
"id": emote.id,
"name": emote.name,
"require_colons": emote.require_colons,
"managed": emote.managed,
"roles": self.list_role_ids(emote.roles),
"url": emote.url
})
return emojis
def get_roles_list(self, guildroles):
roles = []
for role in guildroles:
roles.append({
"id": role.id,
"name": role.name,
"color": role.color.value,
"hoist": role.hoist,
"position": role.position,
"permissions": role.permissions.value
})
return roles
def get_channels_list(self, guildchannels):
channels = []
for channel in guildchannels:
if str(channel.type) == "text":
overwrites = []
for target, overwrite in channel.overwrites:
if isinstance(target, discord.Role):
type = "role"
else:
type = "member"
allow, deny = overwrite.pair()
allow = allow.value
deny = deny.value
overwrites.append({
"id": target.id,
"type": type,
"allow": allow,
"deny": deny,
})
channels.append({
"id": channel.id,
"name": channel.name,
"topic": channel.topic,
"position": channel.position,
"type": str(channel.type),
"permission_overwrites": overwrites
})
return channels
async def remove_unused_guilds(self, guilds):
async with threadpool():
@ -256,7 +167,7 @@ class DatabaseInterface(object):
member.avatar,
active,
banned,
json.dumps(self.list_role_ids(member.roles))
json.dumps(list_role_ids(member.roles))
)
session.add(dbmember)
else:
@ -266,7 +177,7 @@ class DatabaseInterface(object):
dbmember.discriminator = member.discriminator
dbmember.nickname = member.nick
dbmember.avatar = member.avatar
dbmember.roles = json.dumps(self.list_role_ids(member.roles))
dbmember.roles = json.dumps(list_role_ids(member.roles))
session.commit()
async def unban_server_user(self, user, server):
@ -294,12 +205,6 @@ class DatabaseInterface(object):
if changed:
session.commit()
def list_role_ids(self, usr_roles):
ids = []
for role in usr_roles:
ids.append(role.id)
return ids
async def flag_unactive_bans(self, guild_id, guildbans):
async with threadpool():
with self.get_session() as session:

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 @@
from .socketiointerface import SocketIOInterface

View File

@ -0,0 +1,174 @@
import socketio
from titanembeds.utils import get_message_author, get_message_mentions, get_roles_list
import time
from email import utils as emailutils
class SocketIOInterface:
def __init__(self, bot, redis_uri):
self.io = socketio.AsyncRedisManager(redis_uri, write_only=True, channel='flask-socketio')
self.bot = bot
def format_datetime(self, datetimeobj):
return emailutils.formatdate(time.mktime(datetimeobj.timetuple())) # https://stackoverflow.com/questions/3453177/convert-python-datetime-to-rfc-2822
def get_formatted_message(self, message):
edit_ts = message.edited_timestamp
if not edit_ts:
edit_ts = None
else:
edit_ts = self.format_datetime(edit_ts)
msg = {
"id": message.id,
"channel_id": message.channel.id,
"content": message.content,
"author": get_message_author(message),
"timestamp": self.format_datetime(message.timestamp),
"edited_timestamp": edit_ts,
"mentions": get_message_mentions(message.mentions),
"attachments": message.attachments,
}
nickname = None
if hasattr(message.author, 'nick') and message.author.nick:
nickname = message.author.nick
msg["author"]["nickname"] = nickname
for mention in msg["mentions"]:
mention["nickname"] = None
member = message.server.get_member(mention["id"])
if member:
mention["nickname"] = member.nick
return msg
async def on_message(self, message):
if message.server:
msg = self.get_formatted_message(message)
await self.io.emit('MESSAGE_CREATE', data=msg, room=str("CHANNEL_"+message.channel.id), namespace='/gateway')
async def on_message_delete(self, message):
if message.server:
msg = self.get_formatted_message(message)
await self.io.emit('MESSAGE_DELETE', data=msg, room=str("CHANNEL_"+message.channel.id), namespace='/gateway')
async def on_message_update(self, message):
if message.server:
msg = self.get_formatted_message(message)
await self.io.emit('MESSAGE_UPDATE', data=msg, room=str("CHANNEL_"+message.channel.id), namespace='/gateway')
def get_formatted_user(self, user):
userobj = {
"avatar": user.avatar,
"avatar_url": user.avatar_url,
"color": str(user.color)[1:],
"discriminator": user.discriminator,
"game": None,
"hoist-role": None,
"id": user.id,
"status": str(user.status),
"username": user.name,
"nick": None,
}
if userobj["color"] == "000000":
userobj["color"] = None
# if userobj["avatar_url"][len(userobj["avatar_url"])-15:] != ".jpg":
# userobj["avatar_url"] = userobj["avatar_url"][:len(userobj["avatar_url"])-14] + ".jpg"
if user.nick:
userobj["nick"] = user.nick
if user.game:
userobj["game"] = {
"name": user.game.name
}
roles = sorted(user.roles, key=lambda k: k.position, reverse=True)
for role in roles:
if role.hoist:
userobj["hoist-role"] = {
"id": role.id,
"name": role.name,
"position": role.position,
}
break
return userobj
async def on_guild_member_add(self, member):
user = self.get_formatted_user(member)
await self.io.emit('GUILD_MEMBER_ADD', data=user, room=str("GUILD_"+member.server.id), namespace='/gateway')
async def on_guild_member_remove(self, member):
user = self.get_formatted_user(member)
await self.io.emit('GUILD_MEMBER_REMOVE', data=user, room=str("GUILD_"+member.server.id), namespace='/gateway')
async def on_guild_member_update(self, member):
user = self.get_formatted_user(member)
await self.io.emit('GUILD_MEMBER_UPDATE', data=user, room=str("GUILD_"+member.server.id), namespace='/gateway')
def get_formatted_emojis(self, emojis):
emotes = []
for emo in emojis:
emotes.append({
"id": emo.id,
"managed": emo.managed,
"name": emo.name,
"require_colons": emo.require_colons,
"roles": get_roles_list(emo.roles),
"url": emo.url,
})
return emotes
async def on_guild_emojis_update(self, emojis):
emotes = self.get_formatted_emojis(emojis)
await self.io.emit('GUILD_EMOJIS_UPDATE', data=emotes, room=str("GUILD_"+emojis[0].server.id), namespace='/gateway')
def get_formatted_guild(self, guild):
guil = {
"id": guild.id,
"name": guild.name,
"icon": guild.icon,
"icon_url": guild.icon_url,
}
return guil
async def on_guild_update(self, guild):
guildobj = self.get_formatted_guild(guild)
await self.io.emit('GUILD_UPDATE', data=guildobj, room=str("GUILD_"+guild.id), namespace='/gateway')
def get_formatted_channel(self, channel):
chan = {
"id": channel.id,
"guild_id": channel.server.id,
}
return chan
async def on_channel_delete(self, channel):
if str(channel.type) != "text":
return
chan = self.get_formatted_channel(channel)
await self.io.emit('CHANNEL_DELETE', data=chan, room=str("GUILD_"+channel.server.id), namespace='/gateway')
async def on_channel_create(self, channel):
if str(channel.type) != "text":
return
chan = self.get_formatted_channel(channel)
await self.io.emit('CHANNEL_CREATE', data=chan, room=str("GUILD_"+channel.server.id), namespace='/gateway')
async def on_channel_update(self, channel):
if str(channel.type) != "text":
return
chan = self.get_formatted_channel(channel)
await self.io.emit('CHANNEL_UPDATE', data=chan, room=str("GUILD_"+channel.server.id), namespace='/gateway')
def get_formatted_role(self, role):
rol = {
"id": role.id,
"guild_id": role.server.id,
}
return rol
async def on_guild_role_create(self, role):
rol = self.get_formatted_role(role)
await self.io.emit('GUILD_ROLE_CREATE', data=rol, room=str("GUILD_"+role.server.id), namespace='/gateway')
async def on_guild_role_update(self, role):
rol = self.get_formatted_role(role)
await self.io.emit('GUILD_ROLE_UPDATE', data=rol, room=str("GUILD_"+role.server.id), namespace='/gateway')
async def on_guild_role_delete(self, role):
rol = self.get_formatted_role(role)
await self.io.emit('GUILD_ROLE_DELETE', data=rol, room=str("GUILD_"+role.server.id), namespace='/gateway')

View File

@ -0,0 +1,98 @@
import discord
def get_message_author(message):
author = message.author
obj = {
"username": author.name,
"discriminator": author.discriminator,
"bot": author.bot,
"id": author.id,
"avatar": author.avatar
}
return obj
def get_message_mentions(mentions):
ments = []
for author in mentions:
ments.append({
"username": author.name,
"discriminator": author.discriminator,
"bot": author.bot,
"id": author.id,
"avatar": author.avatar
})
return ments
def get_webhooks_list(guild_webhooks):
webhooks = []
for webhook in guild_webhooks:
webhooks.append({
"id": webhook.id,
"guild_id": webhook.server.id,
"channel_id": webhook.channel.id,
"name": webhook.name,
"token": webhook.token,
})
return webhooks
def get_emojis_list(guildemojis):
emojis = []
for emote in guildemojis:
emojis.append({
"id": emote.id,
"name": emote.name,
"require_colons": emote.require_colons,
"managed": emote.managed,
"roles": list_role_ids(emote.roles),
"url": emote.url
})
return emojis
def get_roles_list(guildroles):
roles = []
for role in guildroles:
roles.append({
"id": role.id,
"name": role.name,
"color": role.color.value,
"hoist": role.hoist,
"position": role.position,
"permissions": role.permissions.value
})
return roles
def get_channels_list(guildchannels):
channels = []
for channel in guildchannels:
if str(channel.type) == "text":
overwrites = []
for target, overwrite in channel.overwrites:
if isinstance(target, discord.Role):
type = "role"
else:
type = "member"
allow, deny = overwrite.pair()
allow = allow.value
deny = deny.value
overwrites.append({
"id": target.id,
"type": type,
"allow": allow,
"deny": deny,
})
channels.append({
"id": channel.id,
"name": channel.name,
"topic": channel.topic,
"position": channel.position,
"type": str(channel.type),
"permission_overwrites": overwrites
})
return channels
def list_role_ids(usr_roles):
ids = []
for role in usr_roles:
ids.append(role.id)
return ids

12
requirements.txt Normal file
View File

@ -0,0 +1,12 @@
flask
flask-sqlalchemy
flask_limiter
requests_oauthlib
Flask-SSLify
flask_socketio
paypalrestsdk
https://github.com/TitanEmbeds/discord.py/archive/async.zip#egg=discord.py[voice]
asyncio_extras
kombu
redis
aioredis

View File

@ -2,7 +2,7 @@
The webapp portion handles the frontend (it's what the users see). The webapp highly depends on the discordbot to push websockets data to the database.
# Installation
1. Clone the repo (make sure you have **Python 2.7** installed on your system. This webapp portion depends on that specific python version)
1. Clone the repo (make sure you have **Python 3.5** installed on your system. This webapp portion depends on that specific python version)
2. Install the pip requirements `pip install -r requirements.txt`
3. Clone `config.example.py` and rename it to `config.py`. Edit the file to your standards
4. Run the development web via `python run.py` -- Though we suggest to use a better server software (look into gunicorn, nginx, uwsgi, etc)

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

@ -13,4 +13,6 @@ config = {
'app-secret': "Type something random here, go wild.",
'database-uri': "driver://username:password@host:port/database",
'redis-uri': "redis://",
'websockets-mode': "LITTERALLY None or eventlet or gevent",
}

View File

@ -1,6 +0,0 @@
flask
flask-sqlalchemy
flask_limiter
requests_oauthlib
Flask-SSLify
paypalrestsdk

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python2
from titanembeds.app import app
from titanembeds.app import app, socketio
import subprocess
def init_debug():
@ -41,4 +41,4 @@ def init_debug():
return "OK"
if __name__ == "__main__":
init_debug()
app.run(host="0.0.0.0",port=3000,debug=True,processes=3)
socketio.run(app, host="0.0.0.0",port=3000,debug=True)

View File

@ -1,6 +1,6 @@
from run import app, init_debug
from run import app, socketio, init_debug
import os
if __name__ == "__main__":
init_debug()
app.run(host=os.getenv('IP', '0.0.0.0'), port=int(os.getenv('PORT', 8080)), debug=True, processes=3)
socketio.run(app, host=os.getenv('IP', '0.0.0.0'), port=int(os.getenv('PORT', 8080)), debug=True)

View File

@ -1,15 +1,23 @@
from config import config
from database import db
from .database import db
from flask import Flask, render_template, request, session, url_for, redirect, jsonify
from flask_sslify import SSLify
from titanembeds.utils import rate_limiter, discord_api, bot_alive
import blueprints.api
import blueprints.user
import blueprints.admin
import blueprints.embed
from titanembeds.utils import rate_limiter, discord_api, bot_alive, socketio
from .blueprints import api, user, admin, embed, gateway
import os
from titanembeds.database import get_administrators_list
try:
import uwsgi
from gevent import monkey
monkey.patch_all()
except:
if config.get("websockets-mode", None) == "eventlet":
import eventlet
eventlet.monkey_patch()
elif config.get("websockets-mode", None) == "gevent":
from gevent import monkey
monkey.patch_all()
os.chdir(config['app-location'])
app = Flask(__name__, static_folder="static")
@ -23,11 +31,13 @@ app.secret_key = config['app-secret']
db.init_app(app)
rate_limiter.init_app(app)
sslify = SSLify(app, permanent=True)
socketio.init_app(app, message_queue=config["redis-uri"], path='gateway', async_mode=config.get("websockets-mode", None))
app.register_blueprint(blueprints.api.api, url_prefix="/api", template_folder="/templates")
app.register_blueprint(blueprints.admin.admin, url_prefix="/admin", template_folder="/templates")
app.register_blueprint(blueprints.user.user, url_prefix="/user", template_folder="/templates")
app.register_blueprint(blueprints.embed.embed, url_prefix="/embed", template_folder="/templates")
app.register_blueprint(api.api, url_prefix="/api", template_folder="/templates")
app.register_blueprint(admin.admin, url_prefix="/admin", template_folder="/templates")
app.register_blueprint(user.user, url_prefix="/user", template_folder="/templates")
app.register_blueprint(embed.embed, url_prefix="/embed", template_folder="/templates")
socketio.on_namespace(gateway.Gateway('/gateway'))
@app.route("/")
def index():

View File

@ -1 +1 @@
from admin import admin
from .admin import admin

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

@ -1 +1 @@
from api import api
from .api import api

View File

@ -2,11 +2,11 @@ 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.database import get_administrators_list
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_
import random
import requests
import json
import datetime
import re
@ -14,97 +14,6 @@ from config import config
api = Blueprint("api", __name__)
def user_unauthenticated():
if 'unauthenticated' in session:
return session['unauthenticated']
return True
def checkUserRevoke(guild_id, user_key=None):
revoked = True #guilty until proven not revoked
if user_unauthenticated():
dbUser = UnauthenticatedUsers.query.filter(and_(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key)).first()
revoked = dbUser.isRevoked()
else:
banned = checkUserBanned(guild_id)
if banned:
return revoked
dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first()
revoked = not dbUser or not dbUser.active
return revoked
def checkUserBanned(guild_id, ip_address=None):
banned = True
if user_unauthenticated():
dbUser = UnauthenticatedBans.query.filter(and_(UnauthenticatedBans.guild_id == guild_id, UnauthenticatedBans.ip_address == ip_address)).all()
if not dbUser:
banned = False
else:
for usr in dbUser:
if usr.lifter_id is not None:
banned = False
else:
banned = False
dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first()
if not dbUser:
banned = False
else:
banned = dbUser.banned
return banned
def update_user_status(guild_id, username, user_key=None):
if user_unauthenticated():
ip_address = get_client_ipaddr()
status = {
'authenticated': False,
'avatar': None,
'manage_embed': False,
'ip_address': ip_address,
'username': username,
'nickname': None,
'user_key': user_key,
'guild_id': guild_id,
'user_id': session['user_id'],
'banned': checkUserBanned(guild_id, ip_address),
'revoked': checkUserRevoke(guild_id, user_key),
}
if status['banned'] or status['revoked']:
session['user_keys'].pop(guild_id, None)
return status
dbUser = UnauthenticatedUsers.query.filter(and_(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key)).first()
dbUser.bumpTimestamp()
if dbUser.username != username or dbUser.ip_address != ip_address:
dbUser.username = username
dbUser.ip_address = ip_address
db.session.commit()
else:
status = {
'authenticated': True,
'avatar': session["avatar"],
'manage_embed': check_user_can_administrate_guild(guild_id),
'username': username,
'nickname': None,
'discriminator': session['discriminator'],
'guild_id': guild_id,
'user_id': session['user_id'],
'banned': checkUserBanned(guild_id),
'revoked': checkUserRevoke(guild_id)
}
if status['banned'] or status['revoked']:
return status
dbMember = get_guild_member(guild_id, status["user_id"])
if dbMember:
status["nickname"] = dbMember.nickname
dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == status['user_id'])).first()
dbUser.bumpTimestamp()
return status
def check_user_in_guild(guild_id):
if user_unauthenticated():
return guild_id in session['user_keys']
else:
dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == session['user_id'])).first()
return dbUser is not None and not checkUserRevoke(guild_id)
def parse_emoji(textToParse, guild_id):
guild_emojis = get_guild_emojis(guild_id)
for gemoji in guild_emojis:
@ -141,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:
@ -160,92 +69,6 @@ def format_everyone_mention(channel, content):
content = content.replace("@here", u"@\u200Bhere")
return content
def get_member_roles(guild_id, user_id):
q = db.session.query(GuildMembers).filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == user_id).first()
return json.loads(q.roles)
def get_dbguild_channels(guild_id):
q = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
return json.loads(q.channels)
def get_guild_channels(guild_id, force_everyone=False):
if user_unauthenticated() or force_everyone:
member_roles = [guild_id] #equivilant to @everyone role
else:
member_roles = get_member_roles(guild_id, session['user_id'])
if guild_id not in member_roles:
member_roles.append(guild_id)
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
guild_channels = json.loads(dbguild.channels)
guild_roles = json.loads(dbguild.roles)
guild_owner = str(dbguild.owner_id)
result_channels = []
for channel in guild_channels:
if channel['type'] == "text":
result = {"channel": channel, "read": False, "write": False, "mention_everyone": False}
if guild_owner == session.get("user_id"):
result["read"] = True
result["write"] = True
result["mention_everyone"] = True
result_channels.append(result)
continue
channel_perm = 0
# @everyone
for role in guild_roles:
if role["id"] == guild_id:
channel_perm |= role["permissions"]
continue
# User Guild Roles
for m_role in member_roles:
for g_role in guild_roles:
if g_role["id"] == m_role:
channel_perm |= g_role["permissions"]
continue
# If has server administrator permission
if user_has_permission(channel_perm, 3):
result["read"] = True
result["write"] = True
result["mention_everyone"] = True
result_channels.append(result)
continue
denies = 0
allows = 0
# channel specific
for overwrite in channel["permission_overwrites"]:
if overwrite["type"] == "role" and overwrite["id"] in member_roles:
denies |= overwrite["deny"]
allows |= overwrite["allow"]
channel_perm = (channel_perm & ~denies) | allows
# member specific
for overwrite in channel["permission_overwrites"]:
if overwrite["type"] == "member" and overwrite["id"] == session.get("user_id"):
channel_perm = (channel_perm & ~overwrite['deny']) | overwrite['allow']
break
result["read"] = user_has_permission(channel_perm, 10)
result["write"] = user_has_permission(channel_perm, 11)
result["mention_everyone"] = user_has_permission(channel_perm, 17)
# If default channel, you can read
if channel["id"] == guild_id:
result["read"] = True
# If you cant read channel, you cant write in it
if not user_has_permission(channel_perm, 10):
result["read"] = False
result["write"] = False
result["mention_everyone"] = False
result_channels.append(result)
return sorted(result_channels, key=lambda k: k['channel']['position'])
def filter_guild_channel(guild_id, channel_id, force_everyone=False):
channels = get_guild_channels(guild_id, force_everyone)
for chan in channels:
@ -287,7 +110,7 @@ def get_online_discord_users(guild_id, embed):
return embed['members']
def get_online_embed_users(guild_id):
time_past = (datetime.datetime.now() - datetime.timedelta(seconds = 60)).strftime('%Y-%m-%d %H:%M:%S')
time_past = (datetime.datetime.now() - datetime.timedelta(seconds = 15)).strftime('%Y-%m-%d %H:%M:%S')
unauths = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.last_timestamp > time_past, UnauthenticatedUsers.revoked == False, UnauthenticatedUsers.guild_id == guild_id).all()
auths = db.session.query(AuthenticatedUsers).filter(AuthenticatedUsers.last_timestamp > time_past, AuthenticatedUsers.guild_id == guild_id).all()
users = {'unauthenticated':[], 'authenticated':[]}
@ -316,15 +139,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)
@ -343,6 +174,7 @@ def fetch():
status_code = 403
if user_unauthenticated():
session['user_keys'].pop(guild_id, None)
session.modified = True
else:
chan = filter_guild_channel(guild_id, channel_id)
if not chan:
@ -379,7 +211,7 @@ def fetch_visitor():
@api.route("/post", methods=["POST"])
@valid_session_required(api=True)
@rate_limiter.limit("1 per 10 second", key_func = channel_ratelimit_key)
@rate_limiter.limit("1 per 5 second", key_func = channel_ratelimit_key)
def post():
guild_id = request.form.get("guild_id")
channel_id = request.form.get('channel_id')
@ -487,6 +319,7 @@ def change_unauthenticated_username():
if not checkUserBanned(guild_id, ip_address):
if 'user_keys' not in session or guild_id not in session['user_keys'] or not session['unauthenticated']:
abort(424)
emitmsg = {"unauthenticated": True, "username": session["username"], "discriminator": session["user_id"]}
session['username'] = username
if 'user_id' not in session or len(str(session["user_id"])) > 4:
session['user_id'] = random.randint(0,9999)
@ -496,6 +329,7 @@ def change_unauthenticated_username():
key = user.user_key
session['user_keys'][guild_id] = key
status = update_user_status(guild_id, username, key)
emit("embed_user_disconnect", emitmsg, room="GUILD_"+guild_id, namespace="/gateway")
return jsonify(status=status)
else:
status = {'banned': True}

View File

@ -1 +1 @@
from embed import embed
from .embed import embed

View File

@ -0,0 +1 @@
from .gateway import Gateway

View File

@ -0,0 +1,106 @@
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):
emit('hello')
def on_identify(self, data):
guild_id = data["guild_id"]
if not guild_accepts_visitors(guild_id) and not check_user_in_guild(guild_id):
disconnect()
return
session["socket_guild_id"] = guild_id
channels = []
if guild_accepts_visitors(guild_id) and not check_user_in_guild(guild_id):
channels = get_guild_channels(guild_id, force_everyone=True)
else:
channels = get_guild_channels(guild_id)
join_room("GUILD_"+guild_id)
for chan in channels:
if chan["read"]:
join_room("CHANNEL_"+chan["channel"]["id"])
if session.get("unauthenticated", True) and guild_id in session.get("user_keys", {}):
join_room("IP_"+get_client_ipaddr())
elif not session.get("unauthenticated", True):
join_room("USER_"+session["user_id"])
visitor_mode = data["visitor_mode"]
if not visitor_mode:
if session["unauthenticated"]:
emit("embed_user_connect", {"unauthenticated": True, "username": session["username"], "discriminator": session["user_id"]}, room="GUILD_"+guild_id)
else:
nickname = db.session.query(GuildMembers).filter(GuildMembers.guild_id == guild_id, GuildMembers.user_id == session["user_id"]).first().nickname
emit("embed_user_connect", {"unauthenticated": False, "id": session["user_id"], "nickname": nickname, "username": session["username"],"discriminator": session["discriminator"], "avatar_url": session["avatar"]}, room="GUILD_"+guild_id)
emit("identified")
def on_disconnect(self):
if "user_keys" not in session:
return
guild_id = session["socket_guild_id"]
msg = {}
if session["unauthenticated"]:
msg = {"unauthenticated": True, "username": session["username"], "discriminator": session["user_id"]}
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"]
visitor_mode = data["visitor_mode"]
if not visitor_mode:
key = None
if session["unauthenticated"]:
key = session["user_keys"][guild_id]
status = update_user_status(guild_id, session["username"], key)
if status["revoked"] or status["banned"]:
emit("revoke")
time.sleep(1000)
disconnect()
else:
if not guild_accepts_visitors(guild_id):
disconnect()
def on_channel_list(self, data):
guild_id = data["guild_id"]
visitor_mode = data["visitor_mode"]
channels = None
if visitor_mode or session.get("unauthenticated", True):
channels = get_guild_channels(guild_id, True)
else:
channels = get_guild_channels(guild_id)
for chan in channels:
if chan["read"]:
join_room("CHANNEL_"+chan["channel"]["id"])
else:
leave_room("CHANNEL_"+chan["channel"]["id"])
emit("channel_list", channels)
def on_current_user_info(self, data):
guild_id = data["guild_id"]
if "user_keys" in session and not session["unauthenticated"]:
dbMember = get_guild_member(guild_id, session["user_id"])
usr = {
'avatar': session["avatar"],
'username': dbMember.username,
'nickname': dbMember.nickname,
'discriminator': dbMember.discriminator,
'user_id': session['user_id'],
}
emit("current_user_info", usr)

View File

@ -1 +1 @@
from user import user
from .user import user

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

@ -2,18 +2,18 @@ from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
from guilds import Guilds
from unauthenticated_users import UnauthenticatedUsers
from unauthenticated_bans import UnauthenticatedBans
from authenticated_users import AuthenticatedUsers
from guild_members import GuildMembers, list_all_guild_members, get_guild_member
from keyvalue_properties import KeyValueProperties, set_keyvalproperty, get_keyvalproperty, getexpir_keyvalproperty, setexpir_keyvalproperty, ifexists_keyvalproperty, delete_keyvalproperty
from messages import Messages, get_channel_messages
from cosmetics import Cosmetics
from user_css import UserCSS
from administrators import Administrators, get_administrators_list
from titan_tokens import TitanTokens, get_titan_token
from token_transactions import TokenTransactions
from .guilds import Guilds
from .unauthenticated_users import UnauthenticatedUsers
from .unauthenticated_bans import UnauthenticatedBans
from .authenticated_users import AuthenticatedUsers
from .guild_members import GuildMembers, list_all_guild_members, get_guild_member
from .keyvalue_properties import KeyValueProperties, set_keyvalproperty, get_keyvalproperty, getexpir_keyvalproperty, setexpir_keyvalproperty, ifexists_keyvalproperty, delete_keyvalproperty
from .messages import Messages, get_channel_messages
from .cosmetics import Cosmetics
from .user_css import UserCSS
from .administrators import Administrators, get_administrators_list
from .titan_tokens import TitanTokens, get_titan_token
from .token_transactions import TokenTransactions
def set_titan_token(user_id, amt_change, action):
token_count = get_titan_token(user_id)

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

@ -45,14 +45,14 @@ def user_has_permission(permission, index):
return bool((int(permission) >> index) & 1)
def get_user_guilds():
cache = get_keyvalproperty("OAUTH/USERGUILDS/"+make_user_cache_key())
cache = get_keyvalproperty("OAUTH/USERGUILDS/"+str(make_user_cache_key()))
if cache:
return cache
req = discordrest_from_user("/users/@me/guilds")
if req.status_code != 200:
abort(req.status_code)
req = json.dumps(req.json())
set_keyvalproperty("OAUTH/USERGUILDS/"+make_user_cache_key(), req, 250)
set_keyvalproperty("OAUTH/USERGUILDS/"+str(make_user_cache_key()), req, 250)
return req
def get_user_managed_servers():

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

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

@ -7,25 +7,27 @@
/* global localStorage */
/* global visitors_enabled */
/* global cheet */
/* global location */
/* global io */
(function () {
const theme_options = ["DiscordDark", "BetterTitan"]; // All the avaliable theming names
var user_def_css; // Saves the user defined css
var has_already_been_focused = false; // keep track of if the embed has initially been focused.
var has_already_been_initially_resized = false; // keep track if the embed initially been resized
var logintimer; // timer to keep track of user inactivity after hitting login
var fetchtimeout; // fetch routine timer
var currently_fetching; // fetch lock- if true, do not fetch
var last_message_id; // last message tracked
var selected_channel = null; // user selected channel
var guild_channels = {}; // all server channels used to highlight channels in messages
var emoji_store = {}; // all server emojis
var times_fetched = 0; // kept track of how many times that it has fetched
var fetch_error_count = 0; // Number of errors fetch has encountered
var priority_query_guild = false; // So you have selected a channel? Let's populate it.
var emoji_store = []; // all server emojis
var current_username_discrim; // Current username/discrim pair, eg EndenDraogn#4151
var current_user_discord_id; // Current user discord snowflake id, eg mine is 140252024666062848
var visitor_mode = false; // Keep track of if using the visitor mode or authenticate mode
var socket = null; // Socket.io object
var authenticated_users_list = []; // List of all authenticated users
var unauthenticated_users_list = []; // List of all guest users
var discord_users_list = []; // List of all discord users that are probably online
var guild_channels_list = []; // guild channels, but as a list of them
var shift_pressed = false; // Track down if shift pressed on messagebox
function element_in_view(element, fullyInView) {
@ -137,16 +139,6 @@
}
);
$('#loginmodal').modal('open');
$("#focusmodal").modal({
dismissible: true,
opacity: .5,
inDuration: 400,
outDuration: 400,
startingTop: "4%",
endingTop: "10%",
});
$("#focusmodal").modal("open");
$("#userembedmodal").modal({
dismissible: true,
opacity: .5,
@ -235,23 +227,11 @@
}
});
primeEmbed();
setInterval(send_socket_heartbeat, 5000);
if (getParameterByName("username")) {
$("#custom_username_field").val(getParameterByName("username"));
}
if (getParameterByName("forcefocus") == "1") {
if (document.hasFocus()) {
primeEmbed();
}
$(window).focus(function() {
if (!has_already_been_focused) {
primeEmbed();
}
});
} else {
primeEmbed();
}
});
function changeTheme(theme=null, keep_custom_css=true) {
@ -300,9 +280,6 @@
}
function primeEmbed() {
$("#focusmodal").modal("close");
has_already_been_focused = true;
lock_login_fields();
var guild = query_guild();
@ -342,6 +319,10 @@
}
function initialize_embed(guildobj) {
if (socket) {
socket.disconnect();
socket = null;
}
if (guildobj === undefined) {
var guild = query_guild();
guild.done(function(data) {
@ -382,9 +363,11 @@
fill_unauthenticated_users(guildobj.embedmembers.unauthenticated);
$("#instant-inv").attr("href", guildobj.instant_invite);
run_fetch_routine();
initiate_websockets();
}
function fill_channels(channels) {
guild_channels_list = channels;
var template = $('#mustache_channellistings').html();
Mustache.parse(template);
$("#channels-list").empty();
@ -438,6 +421,7 @@
}
function fill_discord_members(discordmembers) {
discord_users_list = discordmembers;
var template = $('#mustache_authedusers').html();
Mustache.parse(template);
$("#discord-members").empty();
@ -483,6 +467,21 @@
var rendered_role = Mustache.render(template_role, {"name": roleobj["name"] + " - " + roleobj["members"].length});
discordmembercnt += roleobj["members"].length;
$("#discord-members").append(rendered_role);
roleobj.members.sort(function(a, b){
var name_a = a.username;
var name_b = b.username;
if (a.nick) {
name_a = a.nick;
}
if (b.nick) {
name_b = b.nick;
}
name_a = name_a.toUpperCase();
name_b = name_b.toUpperCase();
if(name_a < name_b) return -1;
if(name_a > name_b) return 1;
return 0;
});
for (var j = 0; j < roleobj.members.length; j++) {
var member = roleobj.members[j];
var member_name = member.nick;
@ -519,6 +518,7 @@
mention_member(event.data.member_id);
});
}
authenticated_users_list = users;
}
function fill_unauthenticated_users(users) {
@ -531,6 +531,7 @@
var rendered = Mustache.render(template, {"username": member.username, "discriminator": member.discriminator});
$("#embed-unauth-users").append(rendered);
}
unauthenticated_users_list = users;
}
function wait_for_discord_login() {
@ -562,8 +563,6 @@
last_message_id = null;
$("#channels-list > li.active").removeClass("active");
$("#channel-"+selected_channel).parent().addClass("active");
priority_query_guild = true;
clearTimeout(fetchtimeout);
run_fetch_routine();
}
}
@ -685,7 +684,7 @@
return text;
}
function fill_discord_messages(messages, jumpscroll) {
function fill_discord_messages(messages, jumpscroll, replace=null) {
if (messages.length == 0) {
return last_message_id;
}
@ -707,12 +706,19 @@
username = message.author.nickname;
}
var rendered = Mustache.render(template, {"id": message.id, "full_timestamp": message.formatted_timestamp, "time": message.formatted_time, "username": username, "discriminator": message.author.discriminator, "content": nl2br(message.content)});
$("#chatcontent").append(rendered);
if (replace == null) {
$("#chatcontent").append(rendered);
handle_last_message_mention();
$("#chatcontent p:last-child").find(".blockcode").find("br").remove(); // Remove excessive breaks in codeblocks
} else {
replace.html($(rendered).html());
replace.find(".blockcode").find("br").remove();
}
last = message.id;
handle_last_message_mention();
$("#chatcontent p:last-child").find(".blockcode").find("br").remove(); // Remove excessive breaks in codeblocks
}
$("html, body").animate({ scrollTop: $(document).height() }, "slow");
if (replace == null) {
$("html, body").animate({ scrollTop: $(document).height() }, "slow");
}
$('#chatcontent').linkify({
target: "_blank"
});
@ -721,11 +727,6 @@
}
function run_fetch_routine() {
if (currently_fetching) {
return;
}
currently_fetching = true;
times_fetched += 1;
var channel_id = selected_channel;
var fet;
var jumpscroll;
@ -746,6 +747,7 @@
} else {
update_embed_userchip(status.authenticated, status.avatar, status.username, status.nickname, status.user_id, status.discriminator);
update_change_username_modal(status.authenticated, status.username);
current_user_discord_id = status.user_id;
}
last_message_id = fill_discord_messages(data.messages, jumpscroll);
if (!visitor_mode && status.manage_embed) {
@ -753,20 +755,14 @@
} else {
$("#administrate_link").hide();
}
if (times_fetched % 10 == 0 || priority_query_guild) {
var guild = query_guild();
guild.done(function(guildobj) {
priority_query_guild = false;
fill_channels(guildobj.channels);
fill_discord_members(guildobj.discordmembers);
fill_authenticated_users(guildobj.embedmembers.authenticated);
fill_unauthenticated_users(guildobj.embedmembers.unauthenticated);
$("#instant-inv").attr("href", guildobj.instant_invite);
fetchtimeout = setTimeout(run_fetch_routine, 5000);
});
} else {
fetchtimeout = setTimeout(run_fetch_routine, 5000);
}
var guild = query_guild();
guild.done(function(guildobj) {
fill_channels(guildobj.channels);
fill_discord_members(guildobj.discordmembers);
fill_authenticated_users(guildobj.embedmembers.authenticated);
fill_unauthenticated_users(guildobj.embedmembers.unauthenticated);
$("#instant-inv").attr("href", guildobj.instant_invite);
});
});
fet.fail(function(data) {
if (data.status == 403) {
@ -776,25 +772,9 @@
$('#loginmodal').modal('open');
Materialize.toast('Session expired! You have been logged out.', 10000);
}
if (data.status != 429) {
setVisitorMode(true);
if (visitor_mode) {
fetchtimeout = setTimeout(run_fetch_routine, 5000);
}
}
});
fet.catch(function(data) {
if (500 <= data.status && data.status < 600) {
if (fetch_error_count % 5 == 0) {
Materialize.toast('Fetching messages error! EndenDragon probably broke something. Sorry =(', 10000);
}
fetch_error_count += 1;
fetchtimeout = setTimeout(run_fetch_routine, 10000);
}
setVisitorMode(true);
});
fet.always(function() {
currently_fetching = false;
$("#fetching-indicator").fadeOut(800);
});
}
@ -877,9 +857,12 @@
var usr = change_unauthenticated_username($(this).val());
usr.done(function(data) {
Materialize.toast('Username changed successfully!', 10000);
priority_query_guild = true;
clearTimeout(fetchtimeout);
run_fetch_routine();
if (socket) {
run_fetch_routine();
socket.disconnect();
socket = null;
}
initiate_websockets();
});
usr.fail(function(data) {
if (data.status == 429) {
@ -916,8 +899,6 @@
var funct = post(selected_channel, $(this).val());
funct.done(function(data) {
$("#messagebox").val("");
clearTimeout(fetchtimeout);
run_fetch_routine();
});
funct.fail(function(data) {
Materialize.toast('Failed to send message.', 10000);
@ -929,7 +910,7 @@
});
funct.catch(function(data) {
if (data.status == 429) {
Materialize.toast('You are sending messages too fast! 1 message per 10 seconds', 10000);
Materialize.toast('You are sending messages too fast! 1 message per 5 seconds', 10000);
}
});
funct.always(function() {
@ -959,4 +940,202 @@
// basically copied and pasted of browser ponies bookmarklet
(function (srcs,cfg) { var cbcount = 1; var callback = function () { -- cbcount; if (cbcount === 0) { BrowserPonies.setBaseUrl(cfg.baseurl); if (!BrowserPoniesBaseConfig.loaded) { BrowserPonies.loadConfig(BrowserPoniesBaseConfig); BrowserPoniesBaseConfig.loaded = true; } BrowserPonies.loadConfig(cfg); if (!BrowserPonies.running()) BrowserPonies.start(); } }; if (typeof(BrowserPoniesConfig) === "undefined") { window.BrowserPoniesConfig = {}; } if (typeof(BrowserPoniesBaseConfig) === "undefined") { ++ cbcount; BrowserPoniesConfig.onbasecfg = callback; } if (typeof(BrowserPonies) === "undefined") { ++ cbcount; BrowserPoniesConfig.oninit = callback; } var node = (document.body || document.documentElement || document.getElementsByTagName('head')[0]); for (var id in srcs) { if (document.getElementById(id)) continue; if (node) { var s = document.createElement('script'); s.type = 'text/javascript'; s.id = id; s.src = srcs[id]; node.appendChild(s); } else { document.write('\u003cscript type="text/javscript" src="'+ srcs[id]+'" id="'+id+'"\u003e\u003c/script\u003e'); } } callback();})({"browser-ponies-script":"https://panzi.github.io/Browser-Ponies/browserponies.js","browser-ponies-config":"https://panzi.github.io/Browser-Ponies/basecfg.js"},{"baseurl":"https://panzi.github.io/Browser-Ponies/","fadeDuration":500,"volume":1,"fps":25,"speed":3,"audioEnabled":false,"showFps":false,"showLoadProgress":true,"speakProbability":0.1,"spawn":{"applejack":1,"fluttershy":1,"pinkie pie":1,"rainbow dash":1,"rarity":1,"twilight sparkle":1}});
});
function initiate_websockets() {
if (socket) {
return;
}
socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + "/gateway", {path: '/gateway', transports: ['websocket']});
socket.on('connect', function () {
socket.emit('identify', {"guild_id": guild_id, "visitor_mode": visitor_mode});
});
socket.on("disconnect", function () {
});
socket.on("revoke", function () {
socket.disconnect();
socket = null;
$('#loginmodal').modal('open');
primeEmbed();
Materialize.toast('Authentication error! You have been disconnected by the server.', 10000);
});
socket.on("embed_user_connect", function (msg) {
if (msg.unauthenticated) {
for (var i = 0; i < unauthenticated_users_list.length; i++) {
var item = unauthenticated_users_list[i];
if (item.username == msg.username && item.discriminator == msg.discriminator) {
return;
}
}
unauthenticated_users_list.push(msg);
fill_unauthenticated_users(unauthenticated_users_list);
} else {
for (var i = 0; i < authenticated_users_list.length; i++) {
var item = authenticated_users_list[i];
if (item.id == msg.id) {
return;
}
}
authenticated_users_list.push(msg);
fill_authenticated_users(authenticated_users_list);
}
});
socket.on("embed_user_disconnect", function (msg) {
if (msg.unauthenticated) {
for (var i = 0; i < unauthenticated_users_list.length; i++) {
var item = unauthenticated_users_list[i];
if (item.username == msg.username && item.discriminator == msg.discriminator) {
unauthenticated_users_list.splice(i, 1);
fill_unauthenticated_users(unauthenticated_users_list);
return;
}
}
} else {
for (var i = 0; i < authenticated_users_list.length; i++) {
var item = authenticated_users_list[i];
if (item.id == msg.id) {
authenticated_users_list.splice(i, 1);
fill_authenticated_users(authenticated_users_list);
return;
}
}
}
});
socket.on("MESSAGE_CREATE", function (msg) {
var thismsgchan = msg.channel_id;
if (selected_channel != thismsgchan) {
return;
}
var jumpscroll = element_in_view($('#discordmessage_'+last_message_id), true);
last_message_id = fill_discord_messages([msg], jumpscroll);
});
socket.on("MESSAGE_DELETE", function (msg) {
var msgchan = msg.channel_id;
if (selected_channel != msgchan) {
return;
}
$("#discordmessage_"+msg.id).parent().remove();
last_message_id = $("#chatcontent").find("[id^=discordmessage_]").last().attr('id').substring(15);
});
socket.on("MESSAGE_UPDATE", function (msg) {
var msgelem = $("#discordmessage_"+msg.id);
if (msgelem.length == 0) {
return;
}
var msgelem_parent = msgelem.parent();
fill_discord_messages([msg], false, msgelem_parent);
});
socket.on("GUILD_MEMBER_ADD", function (usr) {
if (usr.status != "offline") {
discord_users_list.push(usr);
fill_discord_members(discord_users_list);
}
});
socket.on("GUILD_MEMBER_UPDATE", function (usr) {
if (usr.id == current_user_discord_id) {
update_socket_channels();
socket.emit("current_user_info", {"guild_id": guild_id});
}
for (var i = 0; i < discord_users_list.length; i++) {
if (usr.id == discord_users_list[i].id) {
discord_users_list.splice(i, 1);
if (usr.status != "offline") {
discord_users_list.push(usr);
}
fill_discord_members(discord_users_list);
return;
}
}
discord_users_list.push(usr);
fill_discord_members(discord_users_list);
});
socket.on("GUILD_MEMBER_REMOVE", function (usr) {
for (var i = 0; i < discord_users_list.length; i++) {
if (usr.id == discord_users_list[i].id) {
discord_users_list.splice(i, 1);
fill_discord_members(discord_users_list);
return;
}
}
});
socket.on("GUILD_EMOJIS_UPDATE", function (emo) {
emoji_store = emo;
});
socket.on("GUILD_UPDATE", function (guil) {
$("#guild_name").text(guil.name);
if (guil.icon) {
$("#guild_icon").attr("src", guil.icon_url);
$("#guild_icon").show();
} else {
$("#guild_icon").hide();
}
});
socket.on("CHANNEL_DELETE", function (chan) {
for (var i = 0; i < guild_channels_list.length; i++) {
var thatchannel = guild_channels_list[i];
if (thatchannel.channel.id == chan.id) {
guild_channels_list.splice(i, 1);
fill_channels(guild_channels_list);
return;
}
}
});
socket.on("CHANNEL_UPDATE", function (chan) {
update_socket_channels();
});
socket.on("CHANNEL_CREATE", function (chan) {
update_socket_channels();
});
socket.on("GUILD_ROLE_UPDATE", function (chan) {
update_socket_channels();
});
socket.on("GUILD_ROLE_DELETE", function (chan) {
update_socket_channels();
});
socket.on("channel_list", function (chans) {
fill_channels(chans);
for (var i = 0; i < chans.length; i++) {
var thischan = chans[i];
if (thischan.channel.id == selected_channel) {
$("#channeltopic").text(thischan.channel.topic);
}
}
});
socket.on("current_user_info", function (usr) {
update_embed_userchip(true, usr.avatar, usr.username, usr.nickname, usr.userid, usr.discriminator);
});
}
function update_socket_channels() {
if (!socket) {
return;
}
socket.emit("channel_list", {"guild_id": guild_id, "visitor_mode": visitor_mode});
}
function send_socket_heartbeat() {
if (socket) {
socket.emit("heartbeat", {"guild_id": guild_id, "visitor_mode": visitor_mode});
}
}
})();

View File

@ -44,11 +44,9 @@ etc.</p>
{% include 'card_queryparams.html.j2' %}
{% include 'card_webhookmsgs.html.j2' %}
<h3>Chat with us!</h3>
<div class="video-container">
<iframe src="https://titanembeds.tk/embed/295085744249110529" frameborder="0"></iframe>
<iframe src="https://titanembeds.com/embed/295085744249110529" frameborder="0"></iframe>
</div>
<h3>Cool People</h3>
@ -88,12 +86,57 @@ etc.</p>
<div class="card-panel indigo lighten-5 z-depth-1 no-height-padding">
<div class="row valign-wrapper">
<div class="col s2">
<img src="{{ url_for('static', filename='img/people/riva.png') }}" alt="Riva" class="circle responsive-img">
<img src="{{ url_for('static', filename='img/people/appledash.png') }}" alt="AppleDash" class="circle responsive-img">
</div>
<div class="col s10">
<h5 class="black-text card-title">AppleDash</h5>
<p class="black-text flow-text">Server Hosting</p>
<p class="black-text">From the shadows of Poniverse, AppleDash swiftly jumps in and offered his server to make websockets possible!</p>
</div>
</div>
</div>
</div>
<div class="col s12 m6">
<div class="card-panel indigo lighten-5 z-depth-1 no-height-padding">
<div class="row valign-wrapper">
<div class="col s2">
<img src="/static/img/people/riva.png" alt="Riva" class="circle responsive-img">
</div>
<div class="col s10">
<h5 class="black-text card-title">Riva</h5>
<p class="black-text flow-text">Bot Hosting</p>
<p class="black-text">This guy hosts our lovely bot, without him Titan would probably not exist.</p>
<p class="black-text flow-text">Previous Bot Hosting</p>
<p class="black-text">This guy hosts our lovely bot, without him Titan would probably not been that great (ahem, indefinitely offline bot).</p>
</div>
</div>
</div>
</div>
<div class="col s12 m6">
<div class="card-panel indigo lighten-5 z-depth-1 no-height-padding">
<div class="row valign-wrapper">
<div class="col s2">
<img src="/static/img/people/dotjs.jpg" alt="dotJS" class="circle responsive-img">
</div>
<div class="col s10">
<h5 class="black-text card-title">dotJS</h5>
<p class="black-text flow-text">CSS Architect</p>
<p class="black-text">Although been well known for his name of JavaScript, he helped us design the embeds with his CSS skillz.</p>
</div>
</div>
</div>
</div>
<div class="col s12 m6">
<div class="card-panel indigo lighten-5 z-depth-1 no-height-padding">
<div class="row valign-wrapper">
<div class="col s2">
<img src="{{ url_for('static', filename='img/people/semic.png') }}" alt="Semic" class="circle responsive-img">
</div>
<div class="col s10">
<h5 class="black-text card-title">Semic</h5>
<p class="black-text flow-text">Logo Designer</p>
<p class="black-text">From our friends over at ProCord, Semic made most of our awesome and heroic Titan logo.</p>
</div>
</div>
</div>

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>
@ -178,8 +191,6 @@
{% include 'card_queryparams.html.j2' %}
{% include 'card_webhookmsgs.html.j2' %}
{% endblock %}
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='js/administrate_guild.js') }}"></script>

View File

@ -5,7 +5,7 @@
<div class="row">
<div class="col s12 black-text">
<p class="flow-text">Use query parameters to customize your individual embeds out of this world!</p>
<p>Query parameters are in the format of key-value pairs. They are appended after your embed url such that it would look like so: <br><em>https://titanembeds.tk/embed/1234567890<strong>?css=1&theme=DiscordDark&forcefocus=1</strong></em></p>
<p>Query parameters are in the format of key-value pairs. They are appended after your embed url such that it would look like so: <br><em>https://titanembeds.com/embed/1234567890<strong>?css=1&theme=DiscordDark&theme=DiscordDark</strong></em></p>
<p>Below is the reference of all the avaliable query parameters that may be used.</p>
<ul class="collection">
<li class="collection-item">
@ -18,18 +18,6 @@
Instead of having the top channel as the first channel your users see, you may change it. Enable Discord's Developer mode in the Appearances tab of the User Settings and copy the channel ID. <br>
<em>Eg: defaultchannel=1234567890</em>
</li>
<li class="collection-item">
<strong>forcefocus=&lt;integer&gt;</strong> <br>
Embeds are activated once the frame is loaded. However, if you have more than one embeds on the page, the rate limits might bite your user's toes. Having this setting set means that the embed will not be loaded until the user mouseovers the frame. <br>
<hr>
<strong>Avaliable Options:</strong>
<ul class="browser-default">
<li><strong>0</strong> = disabled (default)</li>
<li><strong>1</strong> = enabled</li>
</ul>
<hr>
<em>Eg: forcefocus=1</em>
</li>
<li class="collection-item">
<strong>theme=&lt;string&gt;</strong> <br>
Want your embed to use one of our premade themes? Look no further! <br>

View File

@ -1,23 +0,0 @@
<h3><strong>NEW!</strong> Webhook Messages</h3>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-1">
<div class="row">
<div class="col s12 black-text">
<p class="flow-text">Reading messages in Discord can be 20% more cooler!</p>
<p>A quick comparison between having webhook messages <em>enabled vs disabled</em> for a text channel.</p>
<ul class="collection">
<li class="collection-item">
<img src="{{ url_for('static', filename='img/webhook_comparison.png') }}" alt="Old vs new comparison of having webhook messages enabled/disabled for the channel">
</li>
</ul>
<hr>
<p>If you want your messages to look more real, just add a webhook to the channel <strong>starting</strong> with a name of <code>Titan</code> or <code>[Titan]</code> (case insensitive).</p>
<img src="{{ url_for('static', filename='img/webhook_inuse.png') }}" alt="Webhook configuration" class="responsive-img">
<p><em>(Webhook Messages enabled for the #general channel)</em></p>
<p>The new message format should automatically take effect for the channel the webhook is set to send from.</p>
</div>
</div>
</div>
</div>
</div>

View File

@ -43,10 +43,8 @@
<ul id="guild-nav" class="side-nav">
<li>
<div class="userView">
{% if guild['icon'] %}
<img class="circle" src="{{ generate_guild_icon( guild['id'], guild['icon'] ) }}">
{% endif %}
<span class="name">{{ guild['name']|e }}</span>
<img id="guild_icon" class="circle" src="{{ generate_guild_icon( guild['id'], guild['icon'] ) }}" {% if not guild['icon'] %}style="display: none;"{% endif %}>
<span id="guild_name" class="name">{{ guild['name']|e }}</span>
</div>
</li>
@ -117,13 +115,6 @@
</div>
</div>
<div id="focusmodal" class="modal">
<div class="modal-content">
<h4>This embed is currently unfocused.</h4>
<p class="flow-text">Please click this part of the page to initialize the embed.</p>
</div>
</div>
<div id="userembedmodal" class="modal">
<div class="modal-content">
{% if unauth_enabled %}
@ -197,6 +188,7 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/jQuery-linkify/2.1.4/linkify.min.js" integrity="sha256-/qh8j6L0/OTx+7iY8BAeLirxCDBsu3P15Ci5bo7BJaU=" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jQuery-linkify/2.1.4/linkify-jquery.min.js" integrity="sha256-BlSfVPlZijMLojgte2AtSget879chk1+8Z8bEH/L4Cs=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cheet.js/0.3.3/cheet.min.js" integrity="sha256-FxQrnIC3BX45JRzOyFUlKiM6dY3A/ZakV6w4WpYyfyA=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js" integrity="sha256-sdmLD9jD1PIzq3KOQPNSGZYxjv76rds79Mnyk5JNp1M=" crossorigin="anonymous"></script>
{% raw %}
<script id="mustache_channellistings" type="text/template">

View File

@ -4,6 +4,6 @@
n=i.scripts[0];a.src='//www.google-analytics.com/analytics.js';
n.parentNode.insertBefore(a,n)}(window,document,'ga');
ga('create', 'UA-97073231-1', 'auto');
ga('create', 'UA-105774754-1', 'auto');
ga('send', 'pageview');
</script>

View File

@ -18,7 +18,7 @@ will have CSS cosmetic privilages removed, if caught. Please don't, we check the
<p><strong>This user defined CSS has a unique ID of <em style="font-size: 130%;">{{ css.id }}</em>.</strong></p>
<p>To use this CSS in the embed, you must apped a <code>?css={{ css.id }}</code> to the embed URL.</p>
<p>Something like this will work:</p>
<input readonly value="https://titanembeds.tk/embed/1234567890987654321?css={{ css.id }}" id="disabled" type="text" onClick="this.setSelectionRange(48, this.value.length)">
<input readonly value="https://titanembeds.com/embed/1234567890987654321?css={{ css.id }}" id="disabled" type="text" onClick="this.setSelectionRange(48, this.value.length)">
</div>
</div>
</div>

View File

@ -0,0 +1,183 @@
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, GuildMembers, get_guild_member
from titanembeds.utils import guild_accepts_visitors, guild_query_unauth_users_bool, get_client_ipaddr
from titanembeds.oauth import check_user_can_administrate_guild, user_has_permission
from flask import session
from sqlalchemy import and_
import json
def user_unauthenticated():
if 'unauthenticated' in session:
return session['unauthenticated']
return True
def checkUserRevoke(guild_id, user_key=None):
revoked = True #guilty until proven not revoked
if user_unauthenticated():
dbUser = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key).first()
revoked = dbUser.isRevoked()
else:
banned = checkUserBanned(guild_id)
if banned:
return revoked
dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first()
revoked = not dbUser or not dbUser.active
return revoked
def checkUserBanned(guild_id, ip_address=None):
banned = True
if user_unauthenticated():
dbUser = UnauthenticatedBans.query.filter(and_(UnauthenticatedBans.guild_id == guild_id, UnauthenticatedBans.ip_address == ip_address)).all()
if not dbUser:
banned = False
else:
for usr in dbUser:
if usr.lifter_id is not None:
banned = False
else:
banned = False
dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session["user_id"]).first()
if not dbUser:
banned = False
else:
banned = dbUser.banned
return banned
def update_user_status(guild_id, username, user_key=None):
if user_unauthenticated():
ip_address = get_client_ipaddr()
status = {
'authenticated': False,
'avatar': None,
'manage_embed': False,
'ip_address': ip_address,
'username': username,
'nickname': None,
'user_key': user_key,
'guild_id': guild_id,
'user_id': session['user_id'],
'banned': checkUserBanned(guild_id, ip_address),
'revoked': checkUserRevoke(guild_id, user_key),
}
if status['banned'] or status['revoked']:
session['user_keys'].pop(guild_id, None)
return status
dbUser = UnauthenticatedUsers.query.filter(and_(UnauthenticatedUsers.guild_id == guild_id, UnauthenticatedUsers.user_key == user_key)).first()
dbUser.bumpTimestamp()
if dbUser.username != username or dbUser.ip_address != ip_address:
dbUser.username = username
dbUser.ip_address = ip_address
db.session.commit()
else:
status = {
'authenticated': True,
'avatar': session["avatar"],
'manage_embed': check_user_can_administrate_guild(guild_id),
'username': username,
'nickname': None,
'discriminator': session['discriminator'],
'guild_id': guild_id,
'user_id': session['user_id'],
'banned': checkUserBanned(guild_id),
'revoked': checkUserRevoke(guild_id)
}
if status['banned'] or status['revoked']:
return status
dbMember = get_guild_member(guild_id, status["user_id"])
if dbMember:
status["nickname"] = dbMember.nickname
dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == status['user_id'])).first()
dbUser.bumpTimestamp()
return status
def check_user_in_guild(guild_id):
if user_unauthenticated():
return guild_id in session.get("user_keys", {})
else:
dbUser = db.session.query(AuthenticatedUsers).filter(and_(AuthenticatedUsers.guild_id == guild_id, AuthenticatedUsers.client_id == session['user_id'])).first()
return dbUser is not None and not checkUserRevoke(guild_id)
def get_member_roles(guild_id, user_id):
q = db.session.query(GuildMembers).filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == user_id).first()
return json.loads(q.roles)
def get_guild_channels(guild_id, force_everyone=False):
if user_unauthenticated() or force_everyone:
member_roles = [guild_id] #equivilant to @everyone role
else:
member_roles = get_member_roles(guild_id, session['user_id'])
if guild_id not in member_roles:
member_roles.append(guild_id)
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
guild_channels = json.loads(dbguild.channels)
guild_roles = json.loads(dbguild.roles)
guild_owner = str(dbguild.owner_id)
result_channels = []
for channel in guild_channels:
if channel['type'] == "text":
result = {"channel": channel, "read": False, "write": False, "mention_everyone": False}
if guild_owner == session.get("user_id"):
result["read"] = True
result["write"] = True
result["mention_everyone"] = True
result_channels.append(result)
continue
channel_perm = 0
# @everyone
for role in guild_roles:
if role["id"] == guild_id:
channel_perm |= role["permissions"]
continue
# User Guild Roles
for m_role in member_roles:
for g_role in guild_roles:
if g_role["id"] == m_role:
channel_perm |= g_role["permissions"]
continue
# If has server administrator permission
if user_has_permission(channel_perm, 3):
result["read"] = True
result["write"] = True
result["mention_everyone"] = True
result_channels.append(result)
continue
denies = 0
allows = 0
# channel specific
for overwrite in channel["permission_overwrites"]:
if overwrite["type"] == "role" and overwrite["id"] in member_roles:
denies |= overwrite["deny"]
allows |= overwrite["allow"]
channel_perm = (channel_perm & ~denies) | allows
# member specific
for overwrite in channel["permission_overwrites"]:
if overwrite["type"] == "member" and overwrite["id"] == session.get("user_id"):
channel_perm = (channel_perm & ~overwrite['deny']) | overwrite['allow']
break
result["read"] = user_has_permission(channel_perm, 10)
result["write"] = user_has_permission(channel_perm, 11)
result["mention_everyone"] = user_has_permission(channel_perm, 17)
# If default channel, you can read
if channel["id"] == guild_id:
result["read"] = True
# If you cant read channel, you cant write in it
if not user_has_permission(channel_perm, 10):
result["read"] = False
result["write"] = False
result["mention_everyone"] = False
result_channels.append(result)
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

View File

@ -1,6 +1,7 @@
from titanembeds.database import db, Guilds, KeyValueProperties, get_keyvalproperty
from flask import request, session
from flask_limiter import Limiter
from flask_socketio import SocketIO
from config import config
import random
import string
@ -16,12 +17,12 @@ def get_client_ipaddr():
ip = request.headers['X-Real-IP']
else: # general
ip = request.remote_addr
return hashlib.sha512(config['app-secret'] + ip).hexdigest()[:15]
return hashlib.sha512((config['app-secret'] + ip).encode('utf-8')).hexdigest()[:15]
def generate_session_key():
sess = session.get("sessionunique", None)
if not sess:
rand_str = lambda n: ''.join([random.choice(string.lowercase) for i in xrange(n)])
rand_str = lambda n: ''.join([random.choice(string.ascii_lowercase) for i in range(n)])
session['sessionunique'] = rand_str(25)
sess = session['sessionunique']
return sess #Totally unique
@ -31,33 +32,33 @@ def make_cache_key(*args, **kwargs):
args = str(hash(frozenset(request.args.items())))
ip = get_client_ipaddr()
sess = generate_session_key()
return (path + args + sess + ip).encode('utf-8')
return (path + args + sess + ip)
def make_user_cache_key(*args, **kwargs):
ip = get_client_ipaddr()
sess = generate_session_key()
return (sess + ip).encode('utf-8')
return (sess + ip)
def make_guilds_cache_key():
sess = generate_session_key()
ip = get_client_ipaddr()
return (sess + ip + "user_guilds").encode('utf-8')
return (sess + ip + "user_guilds")
def make_guildchannels_cache_key():
guild_id = request.values.get('guild_id', "0")
sess = generate_session_key()
ip = get_client_ipaddr()
return (sess + ip + guild_id + "user_guild_channels").encode('utf-8')
return (sess + ip + guild_id + "user_guild_channels")
def channel_ratelimit_key(): # Generate a bucket with given channel & unique session key
sess = generate_session_key()
channel_id = request.values.get('channel_id', "0")
return (sess + channel_id).encode('utf-8')
return (sess + channel_id)
def guild_ratelimit_key():
ip = get_client_ipaddr()
guild_id = request.values.get('guild_id', "0")
return (ip + guild_id).encode('utf-8')
return (ip + guild_id)
def check_guild_existance(guild_id):
dbGuild = Guilds.query.filter_by(guild_id=guild_id).first()
@ -89,3 +90,4 @@ def bot_alive():
return results
rate_limiter = Limiter(key_func=get_client_ipaddr) # Default limit by ip address
socketio = SocketIO()