Merge pull request #48 from TitanEmbeds/master

Merge latest master to cssvars branch
This commit is contained in:
Jeremy "EndenDragon" Zhang 2017-09-07 14:42:10 -07:00 committed by GitHub
commit ccbbd59e9f
87 changed files with 4098 additions and 766 deletions

31
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,31 @@
# Contributing to Titan Embeds
There are many ways to contribute to Titan. There is no one right method to contribute. As long as your Pull Request is valid and is beneficial to the project, we'll take it. Whether you are a designated developer of this project, or just a seasonal hacker who want to fix a mistake that we probably made, you're welcomed to help as long as you abide by these guidelines. This document outlines of the practices and mistakes involving with contributing to the project.
## Development Environment
For those who would like to run the codebase yourself, you may follow the instructions to the webapp and discordbot to setup the components on your own server. You could however also develop the code on Cloud9. It is free* and very simple to get it off running under ten minutes or less. Either way, if you would like to contribute to the project, I strongly advise you to run Titan on a development server. Making changes on a dev server has many benefits. Especially if you are making changes other than wording issues, the GitHub editor is not the way to go. You may follow these steps to run Titan with Cloud9.
*If you happen to have a credit/debit card, you may skip steps 1-3 (With steps 4-5 links modified) to use the official Cloud9 site. They just use the card as a verification form and will refund your money immediately.*
**Credit to <https://forum.freecodecamp.com/t/can-i-create-a-cloud9-without-credit-card/25334/13> to make this possible**
1. Sign up for this class, its free, and you need it for credentials to cloud9 <https://www.edx.org/course/introduction-computer-science-harvardx-cs50x>
2. Once you signed up, go to your email and confirm their verification link
3. Visit their version of cloud9 (same thing as the offical, just more cats) <http://cs50.io/> This is where you can use c9
4. Add your SSH key from this link <https://cs50.io/account/ssh> to github <https://github.com/settings/keys>
5. At the top right corner, click on New Workspace (To create one for Titan) <http://i.imgur.com/em8N1TX.png>
6. Fill in the details, click on Python as the template environment
7. Set the `Clone from Git or Mercurial url` to `git@github.com:TitanEmbeds/Titan.git` This should pull titan to your workspace.
8. Right click `cloud9_install.sh` file at the left sidebar and click run. This will set everything up.
9. Afterwards, just edit the respective config.py files in the webapp/discordbot directories and you are ready to go!
10. Now you're ready to run Titan... webapp! To make the webapp to work, rightclick `run_c9.py` file and click run. Congratz! It will tell you the exact url where your stuff is running at.
11. For discord bot, you can change the directory to the discordbot `cd discordbot/` and run `python3.5 run.py` to start the bot from the bash console!
12. To make the login system work, go back to your discord bot applications page... for the redirect uris, add these: `http://xxx.cs50.io/user/callback` and `http://xxx.cs50.io/user/dashboard`. Replacing the `xxx` with your subdomain url in the webapp. That outta make the login work! (Take note that there is no http**s** in http).
Now that you set everything up, take a step back and learn some ubuntu/bash to get familiar with it. Some things like git commit/push/pull, etc might be helpful. Maybe you can get phpmyadmin and inspect the database yourself, in gui form <https://community.c9.io/t/setting-up-phpmyadmin/1723>.
## Pull Requesting
If you do not have write access to the codebase, please make a fork of the project. Edit your changes on your fork and push it out onto GitHub. Afterwards, you may submit a pull request for your changes to be merged to the master branch (production). If you do however have write access to the repository, please create a branch and propose pull requests for me to merge into the production.
I have recently decided to restrict pushing into the master branch so that all commits to the codebase is complete and meaningful. The production environment is not used for testing and every new errors in the error log makes me feel a little bit sadder. Using branches and pull requests also means that I may squash and edit the commit messages before pulling into the master so they may look more nicer.
To create a new branch, run this command `git checkout -b <new branch name>`. Then use `git checkout <branch name>` to switch between branches.
Make sure that you thoughly test your changes so that it works and doesn't introduce new bugs. *I won't merge any pull requests until your changes are complete.* I do not like to accept features that are "half done" as these may be left abandoned at any time and may look odd. Please keep in mind to create one branch/pull request for every new feature.
Although I try to be as lenient as possible, please follow the best coding and git practices. If you need help, please join the Discord server and talk to me - EndenDragon. I don't bite. I am more than welcomed to help you if you're stuck during the process of contribution. Sorry if the guidelines above are a bit scary, I just wanted to establish some common ground. Happy hacking, and thank you for making Titan better for everyone!

View File

@ -1,4 +1,6 @@
# Titan
**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)
2. Configure the embed to your liking (toggling guest users, etc)
@ -12,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

@ -1,25 +1,22 @@
#!/usr/bin/env bash
echo "[C9Setup] Installing mysql, and creating titan db table"
echo "[C9Setup] Installing mysql, redis, and creating titan db table"
cd ~/workspace/
mysql-ctl start
mysql -u root -e "CREATE DATABASE titan;"
sudo service redis-server start
echo "[C9Setup] Copying config.py for webapp/discordbot and alembic.ini"
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 dependancies for discordbot"
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 dependancies"
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
@ -33,7 +30,8 @@ sed -i "11s/.*/\'database-uri\': \"mysql+pymysql:\/\/root@localhost\/titan\",/"
#'app-location': "/home/ubuntu/workspace/webapp/",
sed -i "8s/.*/\'app-location\': \"\/home\/ubuntu\/workspace\/webapp\/\",/" ~/workspace/webapp/config.py
echo "[C9Setup] Making sure everything can be runned"
echo "[C9Setup] Making sure everything can be ran"
cd ~/workspace/
sudo chmod -R 777 *
echo "------------------------------"

View File

@ -0,0 +1,97 @@
from config import config
from titanembeds.database import DatabaseInterface, Guilds, Messages
from titanembeds.commands import Commands
import discord
import aiohttp
import asyncio
import sys
import logging
import json
from asyncio_extras import threadpool
logging.basicConfig(filename='titanbot.log',level=logging.INFO,format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.getLogger('TitanBot')
logging.getLogger('sqlalchemy')
###########################
# Cleanup DB Messages #
# #
# Cleans the database #
# messages store #
###########################
class Titan(discord.Client):
def __init__(self):
super().__init__()
self.aiosession = aiohttp.ClientSession(loop=self.loop)
self.http.user_agent += ' TitanEmbeds-Bot'
self.database = DatabaseInterface(self)
self.command = Commands(self, self.database)
def _cleanup(self):
try:
self.loop.run_until_complete(self.logout())
except: # Can be ignored
pass
pending = asyncio.Task.all_tasks()
gathered = asyncio.gather(*pending)
try:
gathered.cancel()
self.loop.run_until_complete(gathered)
gathered.exception()
except: # Can be ignored
pass
def run(self):
try:
self.loop.run_until_complete(self.start(config["bot-token"]))
except discord.errors.LoginFailure:
print("Invalid bot token in config!")
finally:
try:
self._cleanup()
except Exception as e:
print("Error in cleanup:", e)
self.loop.close()
async def on_ready(self):
print('Titan [DiscordBot] [UTILITY: Cleanup database messages]')
print('Logged in as the following user:')
print(self.user.name)
print(self.user.id)
print('------')
try:
await self.database.connect(config["database-uri"] + "?charset=utf8mb4")
except Exception:
self.logger.error("Unable to connect to specified database!")
traceback.print_exc()
await self.logout()
return
print("working on this...")
async with threadpool():
with self.database.get_session() as session:
guilds = session.query(Guilds).all()
for guild in guilds:
print("id-{} snowflake-{} name-{}".format(guild.id, guild.guild_id, guild.name))
try:
channelsjson = json.loads(guild.channels)
except:
continue
for channel in channelsjson:
chanid = channel["id"]
msgs = session.query(Messages).filter(Messages.channel_id == chanid).order_by(Messages.timestamp.desc()).offset(50).all()
for msg in msgs:
session.delete(msg)
session.commit()
print("done!")
await self.logout()
def main():
print("Starting...")
te = Titan()
te.run()
gc.collect()
if __name__ == '__main__':
main()

View File

@ -2,4 +2,10 @@ config = {
'bot-token': "Discord bot token",
'database-uri': "driver://username:password@host:port/database",
'redis-uri': "redis://",
'errorreporting-channelid': "",
'logging-location': "/home/titan/Titan/discordbot/titanbot.log",
}

View File

@ -1,3 +0,0 @@
discord.py
sqlalchemy
asyncio_extras

View File

@ -1,22 +1,29 @@
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
import sys
import logging
import json
logging.basicConfig(filename='titanbot.log',level=logging.INFO,format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
handler = logging.FileHandler(config.get("logging-location", "titanbot.log"))
logging.getLogger('TitanBot')
logging.getLogger('sqlalchemy')
class Titan(discord.Client):
def __init__(self):
super().__init__()
super().__init__(max_messages=20000)
self.aiosession = aiohttp.ClientSession(loop=self.loop)
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())
def _cleanup(self):
try:
@ -32,6 +39,29 @@ class Titan(discord.Client):
except: # Can be ignored
pass
async def wait_until_dbonline(self):
while not self.database_connected:
await asyncio.sleep(1) # Wait until db is connected
async def send_webserver_heartbeat(self):
await self.wait_until_ready()
await self.wait_until_dbonline()
last_db_conn_status = False
while not self.is_closed:
try:
await self.database.send_webserver_heartbeat()
self.database_connected = True
except:
self.database_connected = False
if last_db_conn_status != self.database_connected and config.get("errorreporting-channelid"):
error_channel = self.get_channel(config["errorreporting-channelid"])
if self.database_connected:
await self.send_message(error_channel, "Titan has obtained connection to the database!")
else:
await self.send_message(error_channel, "Titan has lost connection to the database! Don't panic!! We'll sort this out... hopefully soon.")
last_db_conn_status = self.database_connected
await asyncio.sleep(60)
def run(self):
try:
self.loop.run_until_complete(self.start(config["bot-token"]))
@ -52,11 +82,12 @@ 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:
await self.database.connect(config["database-uri"] + "?charset=utf8mb4")
await self.database.connect(config["database-uri"])
self.database_connected = True
except Exception:
self.logger.error("Unable to connect to specified database!")
traceback.print_exc()
@ -68,7 +99,10 @@ class Titan(discord.Client):
await self.database.update_guild(server)
if server.large:
await self.request_offline_members(server)
if server.me.server_permissions.ban_members:
server_bans = await self.get_bans(server)
else:
server_bans = []
for member in server.members:
banned = member.id in [u.id for u in server_bans]
await self.database.update_guild_member(
@ -83,7 +117,9 @@ class Titan(discord.Client):
print("Skipping indexing server due to no-init flag")
async def on_message(self, message):
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)
@ -95,75 +131,139 @@ class Titan(discord.Client):
await getattr(self.command, msg_cmd)(message) #actually run cmd, passing in msg obj
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 asyncio.sleep(1)
if not guild.me.server_permissions.administrator:
await asyncio.sleep(1)
await self.leave_server(guild)
return
await self.wait_until_dbonline()
await self.database.update_guild(guild)
for channel in guild.channels:
if not channel.permissions_for(channel.server.me).read_messages:
continue
async for message in self.logs_from(channel, limit=50, reverse=True):
await self.database.push_message(message)
for member in guild.members:
await self.database.update_guild_member(member, True, False)
if guild.me.server_permissions.ban_members:
banned = await self.get_bans(guild)
for ban in banned:
await self.database.update_guild_member(ban, False, True)
async def on_server_remove(self, guild):
await self.wait_until_dbonline()
await self.database.remove_guild(guild)
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(roleafter)
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()
if self.user.id == member.id:
return
await self.database.update_guild_member(member, active=False, banned=True)
async def on_member_unban(self, server, user):
await self.wait_until_dbonline()
await self.database.unban_server_user(user, server)
async def on_server_emojis_update(self, before, after):
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()
await self.database.update_guild(server)
async def on_socket_raw_receive(self, msg):
if type(msg) is not str:
return
msg = json.loads(msg)
if msg["op"] != 0:
return
action = msg["t"]
await asyncio.sleep(1)
if action == "MESSAGE_UPDATE":
if not self.in_messages_cache(msg["d"]["id"]):
channel = self.get_channel(msg["d"]["channel_id"])
message = await self.get_message(channel, msg["d"]["id"])
await self.on_message_edit(None, message)
if action == "MESSAGE_DELETE":
if not self.in_messages_cache(msg["d"]["id"]):
await self.process_raw_message_delete(msg["d"]["id"], msg["d"]["channel_id"])
if action == "MESSAGE_DELETE_BULK":
for msgid in msg["d"]["ids"]:
if not self.in_messages_cache(msgid):
await self.process_raw_message_delete(msgid, msg["d"]["channel_id"])
async def process_raw_message_delete(self, msg_id, channel_id):
channel = self.get_channel(channel_id)
msg = discord.Message(channel=channel, reactions=[], id=msg_id, type=0, timestamp="2017-01-15T02:59:58", content="What fun is there in making sense?") # Procreate a fake message object
await self.on_message_delete(msg)
def in_messages_cache(self, msg_id):
for msg in self.messages:
if msg.id == msg_id:
return True
return False

View File

@ -7,6 +7,7 @@ from sqlalchemy.ext.declarative import declarative_base
import json
import discord
import time
Base = declarative_base()
@ -15,6 +16,9 @@ from titanembeds.database.messages import Messages
from titanembeds.database.guild_members import GuildMembers
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
@ -56,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():
@ -98,10 +79,11 @@ class DatabaseInterface(object):
.filter(Messages.message_id == message.id).first()
if msg:
msg.content = message.content
msg.timestamp = message.timestamp
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):
@ -117,6 +99,10 @@ class DatabaseInterface(object):
session.commit()
async def update_guild(self, guild):
if guild.me.server_permissions.manage_webhooks:
server_webhooks = await self.bot.get_server_webhooks(guild)
else:
server_webhooks = []
async with threadpool():
with self.get_session() as session:
gui = session.query(Guilds).filter(Guilds.guild_id == guild.id).first()
@ -124,78 +110,24 @@ 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_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.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_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():
with self.get_session() as session:
@ -239,7 +171,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:
@ -247,9 +179,9 @@ class DatabaseInterface(object):
dbmember.active = active
dbmember.username = member.name
dbmember.discriminator = member.discriminator
dbmember.nick = member.nick
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):
@ -277,12 +209,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:
@ -363,3 +289,15 @@ class DatabaseInterface(object):
dbuser.revoked = True
session.commit()
return "Successfully kicked **{}#{}**!".format(dbuser.username, dbuser.discriminator)
async def send_webserver_heartbeat(self):
async with threadpool():
with self.get_session() as session:
key = "bot_heartbeat"
q = session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
if q.count() == 0:
session.add(KeyValueProperties(key=key, value=time.time()))
else:
firstobj = q.first()
firstobj.value = time.time()
session.commit()

View File

@ -6,25 +6,31 @@ class Guilds(Base):
guild_id = db.Column(db.String(255)) # Discord guild id
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
roles = db.Column(db.Text()) # Guild Roles
channels = db.Column(db.Text()) # Guild channels
emojis = db.Column(db.Text()) # Guild Emojis
roles = db.Column(db.Text().with_variant(db.Text(length=4294967295), 'mysql')) # Guild Roles
channels = db.Column(db.Text().with_variant(db.Text(length=4294967295), 'mysql'))# Guild channels
webhooks = db.Column(db.Text().with_variant(db.Text(length=4294967295), 'mysql'))# Guild webhooks
emojis = db.Column(db.Text().with_variant(db.Text(length=4294967295), 'mysql')) # Guild Emojis
owner_id = db.Column(db.String(255)) # Snowflake of the owner
icon = db.Column(db.String(255)) # The icon string, null if none
discordio = db.Column(db.String(255)) # Custom Discord.io Invite Link
def __init__(self, guild_id, name, roles, channels, emojis, owner_id, icon):
def __init__(self, guild_id, name, roles, channels, webhooks, emojis, owner_id, icon):
self.guild_id = guild_id
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
self.roles = roles
self.channels = channels
self.webhooks = webhooks
self.emojis = emojis
self.owner_id = owner_id
self.icon = icon

View File

@ -0,0 +1,17 @@
from titanembeds.database import db, Base
import datetime
class KeyValueProperties(Base):
__tablename__ = "keyvalue_properties"
id = db.Column(db.Integer, primary_key=True) # Auto incremented id
key = db.Column(db.String(255), nullable=False) # Property Key
value = db.Column(db.Text()) # Property value
expiration = db.Column(db.TIMESTAMP) # Suggested Expiration for value (None = no expire) in secs
def __init__(self, key, value, expiration=None):
self.key = key
self.value = value
if expiration:
self.expiration = datetime.now() + timedelta(seconds = expiration)
else:
self.expiration = None

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
git+https://github.com/TitanEmbeds/discord.py
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

@ -64,7 +64,8 @@ def run_migrations_online():
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata
target_metadata=target_metadata,
compare_type=True
)
with context.begin_transaction():

View File

@ -0,0 +1,160 @@
"""Added Titan Tokens
Revision ID: 2a2f32ac91d6
Revises: 6fe130518448
Create Date: 2017-08-13 22:44:15.996936
"""
# revision identifiers, used by Alembic.
revision = '2a2f32ac91d6'
down_revision = '6fe130518448'
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.create_table('titan_tokens',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.String(length=255), nullable=False),
sa.Column('tokens', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('token_transactions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.String(length=255), nullable=False),
sa.Column('timestamp', sa.TIMESTAMP(), nullable=False),
sa.Column('action', sa.String(length=255), nullable=False),
sa.Column('net_tokens', sa.Integer(), nullable=False),
sa.Column('start_tokens', sa.Integer(), nullable=False),
sa.Column('end_tokens', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.alter_column(u'cosmetics', 'css',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False)
op.alter_column(u'guild_members', 'active',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guild_members', 'banned',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'guilds', 'bracket_links',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'channels',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'guilds', 'chat_links',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'emojis',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'guilds', 'roles',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'guilds', 'unauth_users',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'visitor_view',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False)
op.alter_column(u'guilds', 'webhooks',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'unauthenticated_users', 'revoked',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'user_css', 'css',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(u'user_css', 'css',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=True)
op.alter_column(u'unauthenticated_users', 'revoked',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'guilds', 'webhooks',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'visitor_view',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False)
op.alter_column(u'guilds', 'unauth_users',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'roles',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'emojis',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'chat_links',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'channels',
existing_typesa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'bracket_links',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guild_members', 'banned',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'guild_members', 'active',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'cosmetics', 'css',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False)
op.drop_table('token_transactions')
op.drop_table('titan_tokens')
# ### end Alembic commands ###

View File

@ -0,0 +1,142 @@
"""Moved some text columns to longtext type
Revision ID: 40cbd3e0f22d
Revises: 9bf2adbc33e8
Create Date: 2017-07-10 00:01:53.940034
"""
# revision identifiers, used by Alembic.
revision = '40cbd3e0f22d'
down_revision = '9bf2adbc33e8'
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(u"'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(u"'0'"))
op.alter_column('guilds', 'bracket_links',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column('guilds', 'channels',
existing_type=mysql.MEDIUMTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
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(u"'1'"))
op.alter_column('guilds', 'emojis',
existing_type=mysql.TEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column('guilds', 'roles',
existing_type=mysql.MEDIUMTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
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(u"'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.TEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
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(u"'0'"))
op.alter_column('user_css', 'css',
existing_type=mysql.MEDIUMTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.MEDIUMTEXT(collation=u'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(u"'0'"))
op.alter_column('guilds', 'webhooks',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.TEXT(collation=u'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(u"'1'"))
op.alter_column('guilds', 'roles',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.MEDIUMTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column('guilds', 'emojis',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.TEXT(collation=u'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(u"'1'"))
op.alter_column('guilds', 'channels',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.MEDIUMTEXT(collation=u'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(u"'1'"))
op.alter_column('guild_members', 'banned',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'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(u"'1'"))
op.alter_column('cosmetics', 'css',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False)
# ### end Alembic commands ###

View File

@ -0,0 +1,148 @@
"""Create administrators table
Revision ID: 6fe130518448
Revises: 40cbd3e0f22d
Create Date: 2017-07-22 02:00:35.317471
"""
# revision identifiers, used by Alembic.
revision = '6fe130518448'
down_revision = '40cbd3e0f22d'
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.create_table('administrators',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.alter_column(u'cosmetics', 'css',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False)
op.alter_column(u'guild_members', 'active',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guild_members', 'banned',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'guilds', 'bracket_links',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'channels',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'guilds', 'chat_links',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'emojis',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'guilds', 'roles',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'guilds', 'unauth_users',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'visitor_view',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False)
op.alter_column(u'guilds', 'webhooks',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column(u'unauthenticated_users', 'revoked',
existing_type=mysql.TINYINT(display_width=1),
type_=sa.Boolean(),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'user_css', 'css',
existing_type=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(u'user_css', 'css',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=True)
op.alter_column(u'unauthenticated_users', 'revoked',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'guilds', 'webhooks',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'visitor_view',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False)
op.alter_column(u'guilds', 'unauth_users',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'roles',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'emojis',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'chat_links',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guilds', 'channels',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation=u'utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column(u'guilds', 'bracket_links',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'guild_members', 'banned',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'0'"))
op.alter_column(u'guild_members', 'active',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False,
existing_server_default=sa.text(u"'1'"))
op.alter_column(u'cosmetics', 'css',
existing_type=sa.Boolean(),
type_=mysql.TINYINT(display_width=1),
existing_nullable=False)
op.drop_table('administrators')
# ### end Alembic commands ###

View File

@ -0,0 +1,28 @@
"""Added webhooks column to guild table
Revision ID: 9bf2adbc33e8
Revises: b1124468bb2e
Create Date: 2017-06-30 07:24:10.700408
"""
# revision identifiers, used by Alembic.
revision = '9bf2adbc33e8'
down_revision = 'b1124468bb2e'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('guilds', sa.Column('webhooks', sa.Text(), nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('guilds', 'webhooks')
# ### end Alembic commands ###

View File

@ -0,0 +1,28 @@
"""Added visitor view column to guilds table
Revision ID: b1124468bb2e
Revises: 95ab6a63135d
Create Date: 2017-06-08 06:31:28.953304
"""
# revision identifiers, used by Alembic.
revision = 'b1124468bb2e'
down_revision = '95ab6a63135d'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('guilds', sa.Column('visitor_view', sa.Boolean(), nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('guilds', 'visitor_view')
# ### end Alembic commands ###

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().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
existing_nullable=False)
op.alter_column('guilds', 'roles',
existing_type=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
type_=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
type_=mysql.LONGTEXT(collation='utf8mb4_unicode_ci'),
existing_nullable=False)
op.alter_column('guilds', 'emojis',
existing_type=sa.Text().with_variant(sa.Text(length=4294967295), 'mysql'),
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().with_variant(sa.Text(length=4294967295), 'mysql'),
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

@ -5,8 +5,14 @@ config = {
'client-secret': "Your discord client secret",
'bot-token': "Discord bot token",
# Rest API in https://developer.paypal.com/developer/applications
'paypal-client-id': "Paypal client id",
'paypal-client-secret': "Paypal client secret",
'app-location': "/var/www/Titan/webapp/",
'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,5 +0,0 @@
flask
flask-sqlalchemy
flask_limiter
requests_oauthlib
Flask-SSLify

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python2
from titanembeds.app import app
from titanembeds.app import app, socketio
import subprocess
def init_debug():
import os
@ -30,6 +31,14 @@ def init_debug():
decoded = None
return jsonify(session_cookie=decoded)
@app.route("/github-update", methods=["POST"])
def github_update():
try:
subprocess.Popen("git pull", shell=True).wait()
except OSError:
return "ERROR"
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,13 +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
import blueprints.api
import blueprints.user
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")
@ -21,10 +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.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():
@ -36,5 +49,9 @@ def about():
@app.before_request
def before_request():
db.create_all()
discord_api.init_discordrest()
@app.context_processor
def context_processor():
bot_status = bot_alive()
return {"bot_status": bot_status, "devs": get_administrators_list()}

View File

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

View File

@ -0,0 +1,227 @@
from flask import Blueprint, url_for, redirect, session, render_template, abort, request, jsonify
from functools import wraps
from titanembeds.database import db, get_administrators_list, Cosmetics, Guilds, UnauthenticatedUsers, UnauthenticatedBans, TitanTokens, TokenTransactions, get_titan_token, set_titan_token
from titanembeds.oauth import generate_guild_icon_url
import datetime
admin = Blueprint("admin", __name__)
def is_admin(f):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
return redirect(url_for("index"))
if session['user_id'] not in get_administrators_list():
return redirect(url_for("index"))
return f(*args, **kwargs)
return decorated_function
return decorator(f)
@admin.route("/")
@is_admin
def index():
return render_template("admin_index.html.j2")
@admin.route("/cosmetics", methods=["GET"])
@is_admin
def cosmetics():
entries = db.session.query(Cosmetics).all()
return render_template("admin_cosmetics.html.j2", cosmetics=entries)
@admin.route("/cosmetics", methods=["POST"])
@is_admin
def cosmetics_post():
user_id = request.form.get("user_id", None)
if not user_id:
abort(400)
css = request.form.get("css", None)
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first()
if entry:
abort(409)
user = Cosmetics(user_id)
if css:
css = css.lower() == "true"
user.css = css
db.session.add(user)
db.session.commit()
return ('', 204)
@admin.route("/cosmetics", methods=["DELETE"])
@is_admin
def cosmetics_delete():
user_id = request.form.get("user_id", None)
if not user_id:
abort(400)
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first()
if not entry:
abort(409)
db.session.delete(entry)
db.session.commit()
return ('', 204)
@admin.route("/cosmetics", methods=["PATCH"])
@is_admin
def cosmetics_patch():
user_id = request.form.get("user_id", None)
if not user_id:
abort(400)
css = request.form.get("css", None)
entry = db.session.query(Cosmetics).filter(Cosmetics.user_id == user_id).first()
if not entry:
abort(409)
if css:
css = css.lower() == "true"
entry.css = css
db.session.commit()
return ('', 204)
def prepare_guild_members_list(members, bans):
all_users = []
ip_pool = []
members = sorted(members, key=lambda k: datetime.datetime.strptime(str(k.last_timestamp.replace(tzinfo=None, microsecond=0)), "%Y-%m-%d %H:%M:%S"), reverse=True)
for member in members:
user = {
"id": member.id,
"username": member.username,
"discrim": member.discriminator,
"ip": member.ip_address,
"last_visit": member.last_timestamp,
"kicked": member.revoked,
"banned": False,
"banned_timestamp": None,
"banned_by": None,
"banned_reason": None,
"ban_lifted_by": None,
"aliases": [],
}
for banned in bans:
if banned.ip_address == member.ip_address:
if banned.lifter_id is None:
user['banned'] = True
user["banned_timestamp"] = banned.timestamp
user['banned_by'] = banned.placer_id
user['banned_reason'] = banned.reason
user['ban_lifted_by'] = banned.lifter_id
continue
if user["ip"] not in ip_pool:
all_users.append(user)
ip_pool.append(user["ip"])
else:
for usr in all_users:
if user["ip"] == usr["ip"]:
alias = user["username"]+"#"+str(user["discrim"])
if len(usr["aliases"]) < 5 and alias not in usr["aliases"]:
usr["aliases"].append(alias)
continue
return all_users
@admin.route("/administrate_guild/<guild_id>", methods=["GET"])
@is_admin
def administrate_guild(guild_id):
db_guild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
if not db_guild:
abort(500)
return
session["redirect"] = None
permissions=[]
permissions.append("Manage Embed Settings")
permissions.append("Ban Members")
permissions.append("Kick Members")
all_members = db.session.query(UnauthenticatedUsers).filter(UnauthenticatedUsers.guild_id == guild_id).order_by(UnauthenticatedUsers.last_timestamp).all()
all_bans = db.session.query(UnauthenticatedBans).filter(UnauthenticatedBans.guild_id == guild_id).all()
users = prepare_guild_members_list(all_members, all_bans)
dbguild_dict = {
"id": db_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,
"icon": db_guild.icon,
"discordio": db_guild.discordio if db_guild.discordio != None else ""
}
return render_template("administrate_guild.html.j2", guild=dbguild_dict, members=users, permissions=permissions)
@admin.route("/administrate_guild/<guild_id>", methods=["POST"])
@is_admin
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)
discordio = request.form.get("discordio", db_guild.discordio)
if discordio and discordio.strip() == "":
discordio = None
db_guild.discordio = discordio
db.session.commit()
return jsonify(
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,
discordio=db_guild.discordio,
)
@admin.route("/guilds")
@is_admin
def guilds():
guilds = db.session.query(Guilds).all()
return render_template("admin_guilds.html.j2", servers=guilds, icon_generate=generate_guild_icon_url)
@admin.route("/tokens", methods=["GET"])
@is_admin
def manage_titan_tokens():
tokeners = db.session.query(TitanTokens).all()
donators = []
for usr in tokeners:
row = {
"user_id": usr.user_id,
"tokens": usr.tokens,
"transactions": []
}
transact = db.session.query(TokenTransactions).filter(TokenTransactions.user_id == usr.user_id).all()
for tr in transact:
row["transactions"].append({
"id": tr.id,
"user_id": tr.user_id,
"timestamp": tr.timestamp,
"action": tr.action,
"net_tokens": tr.net_tokens,
"start_tokens": tr.start_tokens,
"end_tokens": tr.end_tokens
})
donators.append(row)
return render_template("admin_token_transactions.html.j2", donators=donators)
@admin.route("/tokens", methods=["POST"])
@is_admin
def post_titan_tokens():
user_id = request.form.get("user_id", None)
amount = request.form.get("amount", None, type=int)
if not user_id or not amount:
abort(400)
if get_titan_token(user_id) != -1:
abort(409)
set_titan_token(user_id, amount, "NEW VIA ADMIN")
return ('', 204)
@admin.route("/tokens", methods=["PATCH"])
@is_admin
def patch_titan_tokens():
user_id = request.form.get("user_id", None)
amount = request.form.get("amount", None, type=int)
if not user_id or not amount:
abort(400)
if get_titan_token(user_id) == -1:
abort(409)
set_titan_token(user_id, amount, "MODIFY VIA ADMIN")
return ('', 204)

View File

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

View File

@ -1,11 +1,12 @@
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties, GuildMembers, Messages, get_channel_messages, list_all_guild_members
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers, KeyValueProperties, GuildMembers, Messages, get_channel_messages, list_all_guild_members, get_guild_member, get_administrators_list
from titanembeds.decorators import valid_session_required, discord_users_only
from titanembeds.utils import check_guild_existance, guild_query_unauth_users_bool, get_client_ipaddr, discord_api, rate_limiter, channel_ratelimit_key, guild_ratelimit_key
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 flask import Blueprint, abort, jsonify, session, request
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
@ -13,107 +14,16 @@ 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,
'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,
'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
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):
emojis = []
emojis = re.findall(":(.*?):", textToParse)
guild_emojis = get_guild_emojis(guild_id)
newText = textToParse
for gemoji in guild_emojis:
emoji_name = gemoji["name"]
emoji_id = gemoji["id"]
for usremote in emojis:
if usremote == emoji_name:
newText = newText.replace(":{}:".format(emoji_name), "<:{}:{}>".format(emoji_name, emoji_id))
return newText
textToParse = textToParse.replace(":{}:".format(emoji_name), "<:{}:{}>".format(emoji_name, emoji_id))
return textToParse
def format_post_content(guild_id, message):
def format_post_content(guild_id, channel_id, message, dbUser):
illegal_post = False
illegal_reasons = []
message = message.replace("<", "\<")
@ -140,10 +50,15 @@ def format_post_content(guild_id, message):
mention = "<@" + match[2: len(match) - 1] + ">"
message = message.replace(match, mention, 1)
if not guild_webhooks_enabled(guild_id):
if (session['unauthenticated']):
message = u"**[{}#{}]** {}".format(session['username'], session['user_id'], message)
else:
message = u"**<{}#{}>** {}".format(session['username'], session['discriminator'], message) # I would like to do a @ mention, but i am worried about notif spam
username = session['username']
if dbUser:
if dbUser.nickname:
username = dbUser.nickname
message = u"**<{}#{}>** {}".format(username, session['discriminator'], message) # I would like to do a @ mention, but i am worried about notify spam
return (message, illegal_post, illegal_reasons)
def format_everyone_mention(channel, content):
@ -154,94 +69,8 @@ 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):
if user_unauthenticated():
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['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["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):
channels = get_guild_channels(guild_id)
def filter_guild_channel(guild_id, channel_id, force_everyone=False):
channels = get_guild_channels(guild_id, force_everyone)
for chan in channels:
if chan["channel"]["id"] == channel_id:
return chan
@ -261,10 +90,18 @@ def get_online_discord_users(guild_id, embed):
member["hoist-role"] = None
member["color"] = None
if apimem:
for roleid in reversed(apimem["roles"]):
role = guildroles_filtered[roleid]
mem_roles = []
for roleid in apimem["roles"]:
role = guildroles_filtered.get(roleid)
if not role:
continue
mem_roles.append(role)
mem_roles = sorted(mem_roles, key=lambda k: k['position'])
for role in mem_roles:
if role["color"] != 0:
member["color"] = '{0:02x}'.format(role["color"]) #int to hex
while len(member["color"]) < 6:
member["color"] = "0" + member["color"]
if role["hoist"]:
member["hoist-role"] = {}
member["hoist-role"]["name"] = role["name"]
@ -273,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':[]}
@ -289,6 +126,7 @@ def get_online_embed_users(guild_id):
meta = {
'id': usrdb.user_id,
'username': usrdb.username,
'nickname': usrdb.nickname,
'discriminator': usrdb.discriminator,
'avatar_url': generate_avatar_url(usrdb.user_id, usrdb.avatar),
}
@ -299,6 +137,26 @@ def get_guild_emojis(guild_id):
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
return json.loads(dbguild.emojis)
# 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"] == name:
return {
"id": webhook["id"],
"token": webhook["token"]
}
webhook = discord_api.create_webhook(channel_id, name)
return webhook["content"]
@api.route("/fetch", methods=["GET"])
@valid_session_required(api=True)
@rate_limiter.limit("2 per 2 second", key_func = channel_ratelimit_key)
@ -316,29 +174,57 @@ 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:
abort(404)
if not chan.get("read"):
status_code = 401
else:
messages = get_channel_messages(channel_id, after_snowflake)
messages = get_channel_messages(guild_id, channel_id, after_snowflake)
status_code = 200
response = jsonify(messages=messages, status=status)
response.status_code = status_code
return response
@api.route("/fetch_visitor", methods=["GET"])
@rate_limiter.limit("2 per 2 second", key_func = channel_ratelimit_key)
def fetch_visitor():
guild_id = request.args.get("guild_id")
channel_id = request.args.get('channel_id')
after_snowflake = request.args.get('after', None, type=int)
if not guild_accepts_visitors(guild_id):
abort(403)
messages = {}
chan = filter_guild_channel(guild_id, channel_id, True)
if not chan:
abort(404)
if not chan.get("read"):
status_code = 401
else:
messages = get_channel_messages(guild_id, channel_id, after_snowflake)
status_code = 200
response = jsonify(messages=messages)
response.status_code = status_code
return response
@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')
content = request.form.get('content')
content, illegal_post, illegal_reasons = format_post_content(guild_id, content)
if "user_id" in session:
dbUser = GuildMembers.query.filter(GuildMembers.guild_id == guild_id).filter(GuildMembers.user_id == session['user_id']).first()
else:
dbUser = None
if user_unauthenticated():
key = session['user_keys'][guild_id]
else:
key = None
content, illegal_post, illegal_reasons = format_post_content(guild_id, channel_id, content, dbUser)
status = update_user_status(guild_id, session['username'], key)
message = {}
if illegal_post:
@ -350,7 +236,29 @@ def post():
if not chan.get("write"):
status_code = 401
elif not illegal_post:
userid = session["user_id"]
content = format_everyone_mention(chan, content)
webhook = get_channel_webhook_url(guild_id, channel_id)
if userid in get_administrators_list():
oldcontent = content
content = "(Titan Dev) " + oldcontent
if webhook:
if (session['unauthenticated']):
username = session["username"] + "#" + str(session["user_id"])
avatar = url_for('static', filename='img/titanembeds_round.png', _external=True)
else:
username = session["username"]
if dbUser:
if dbUser.nickname:
username = dbUser.nickname
if content.startswith("(Titan Dev) "):
content = content[12:]
username = "(Titan Dev) " + username
else:
username = username + "#" + str(session['discriminator'])
avatar = session['avatar']
message = discord_api.execute_webhook(webhook.get("id"), webhook.get("token"), username, avatar, content)
else:
message = discord_api.create_message(channel_id, content)
status_code = message['code']
response = jsonify(message=message.get('content', message), status=status, illegal_reasons=illegal_reasons)
@ -393,21 +301,75 @@ def create_unauthenticated_user():
response.status_code = 403
return response
@api.route("/change_unauthenticated_username", methods=["POST"])
@rate_limiter.limit("1 per 15 minute", key_func=guild_ratelimit_key)
def change_unauthenticated_username():
username = request.form['username']
guild_id = request.form['guild_id']
ip_address = get_client_ipaddr()
username = username.strip()
if len(username) < 2 or len(username) > 32:
abort(406)
if not all(x.isalnum() or x.isspace() or "-" == x or "_" == x for x in username):
abort(406)
if not check_guild_existance(guild_id):
abort(404)
if not guild_query_unauth_users_bool(guild_id):
abort(401)
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)
user = UnauthenticatedUsers(guild_id, username, session['user_id'], ip_address)
db.session.add(user)
db.session.commit()
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}
response = jsonify(status=status)
response.status_code = 403
return response
def process_query_guild(guild_id, visitor=False):
widget = discord_api.get_widget(guild_id)
channels = get_guild_channels(guild_id, visitor)
if widget.get("success", True):
discordmembers = get_online_discord_users(guild_id, widget)
else:
discordmembers = [{"id": 0, "color": "FFD6D6", "status": "dnd", "username": "Discord Server Widget is Currently Disabled"}]
embedmembers = get_online_embed_users(guild_id)
emojis = get_guild_emojis(guild_id)
if visitor:
for channel in channels:
channel["write"] = False
return jsonify(channels=channels, discordmembers=discordmembers, embedmembers=embedmembers, emojis=emojis, instant_invite=widget.get("instant_invite", None))
@api.route("/query_guild", methods=["GET"])
@valid_session_required(api=True)
def query_guild():
guild_id = request.args.get('guild_id')
if check_guild_existance(guild_id):
if check_user_in_guild(guild_id):
widget = discord_api.get_widget(guild_id)
channels = get_guild_channels(guild_id)
discordmembers = get_online_discord_users(guild_id, widget)
embedmembers = get_online_embed_users(guild_id)
emojis = get_guild_emojis(guild_id)
return jsonify(channels=channels, discordmembers=discordmembers, embedmembers=embedmembers, emojis=emojis, instant_invite=widget.get("instant_invite"))
return process_query_guild(guild_id)
abort(403)
abort(404)
@api.route("/query_guild_visitor", methods=["GET"])
def query_guild_visitor():
guild_id = request.args.get('guild_id')
if check_guild_existance(guild_id):
if not guild_accepts_visitors(guild_id):
abort(403)
return process_query_guild(guild_id, True)
abort(404)
@api.route("/create_authenticated_user", methods=["POST"])
@discord_users_only(api=True)
def create_authenticated_user():
@ -435,24 +397,32 @@ def create_authenticated_user():
response.status_code = 403
return response
def canCleanupDB():
canclean = False
if request.form.get("secret", None) == config['app-secret']:
canclean = True
if 'user_id' in session:
if session['user_id'] in get_administrators_list():
canclean = True
return canclean
@api.route("/cleanup-db", methods=["DELETE"])
def cleanup_keyval_db():
if request.form.get("secret", None) == config["app-secret"]:
q = KeyValueProperties.query.filter(KeyValueProperties.expiration < datetime.datetime.now()).all()
for m in q:
db.session.delete(m)
if canCleanupDB():
db.session.query(KeyValueProperties).filter(KeyValueProperties.expiration < datetime.datetime.now()).delete()
db.session.commit()
guilds = Guilds.query.all()
for guild in guilds:
try:
channelsjson = json.loads(guild.channels)
except:
continue
for channel in channelsjson:
chanid = channel["id"]
dbmsg = Messages.query.filter(Messages.channel_id == chanid).all()
for idx, val in enumerate(dbmsg):
if len(dbmsg) - idx > 50:
db.session.delete(val)
else:
continue
msgs = db.session.query(Messages).filter(Messages.channel_id == chanid).order_by(Messages.timestamp.desc()).offset(50).all()
for msg in msgs:
db.session.delete(msg)
db.session.commit()
return ('', 204)
abort(401)

View File

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

View File

@ -1,5 +1,5 @@
from flask import Blueprint, render_template, abort, redirect, url_for, session, request
from titanembeds.utils import check_guild_existance, guild_query_unauth_users_bool
from titanembeds.utils import check_guild_existance, guild_query_unauth_users_bool, guild_accepts_visitors
from titanembeds.oauth import generate_guild_icon_url, generate_avatar_url
from titanembeds.database import db, Guilds, UserCSS
from config import config
@ -66,6 +66,7 @@ def guild_embed(guild_id):
guild_id=guild_id, guild=guild_dict,
generate_guild_icon=generate_guild_icon_url,
unauth_enabled=guild_query_unauth_users_bool(guild_id),
visitors_enabled=guild_accepts_visitors(guild_id),
client_id=config['client-id'],
css=customcss,
cssvariables=parse_css_variable(customcss)

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

@ -1,10 +1,12 @@
from flask import Blueprint, request, redirect, jsonify, abort, session, url_for, render_template
from flask import current_app as app
from config import config
from titanembeds.decorators import discord_users_only
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, Cosmetics, UserCSS
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, Cosmetics, UserCSS, set_titan_token, get_titan_token
from titanembeds.oauth import authorize_url, token_url, make_authenticated_session, get_current_authenticated_user, get_user_managed_servers, check_user_can_administrate_guild, check_user_permission, generate_avatar_url, generate_guild_icon_url, generate_bot_invite_url
import time
import datetime
import paypalrestsdk
import json
user = Blueprint("user", __name__)
@ -40,6 +42,9 @@ def callback():
session['username'] = user['username']
session['discriminator'] = user['discriminator']
session['avatar'] = generate_avatar_url(user['id'], user['avatar'])
session["tokens"] = get_titan_token(session["user_id"])
if session["tokens"] == -1:
session["tokens"] = 0
if session["redirect"]:
redir = session["redirect"]
session['redirect'] = None
@ -59,9 +64,6 @@ def logout():
@discord_users_only()
def dashboard():
guilds = get_user_managed_servers()
if not guilds:
session["redirect"] = url_for("user.dashboard")
return redirect(url_for("user.logout"))
error = request.args.get("error")
if session["redirect"] and not (error and error == "access_denied"):
redir = session['redirect']
@ -179,6 +181,8 @@ def administrate_guild(guild_id):
"id": db_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,
@ -196,12 +200,14 @@ def update_administrate_guild(guild_id):
if not db_guild:
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)
discordio = request.form.get("discordio", db_guild.discordio)
if discordio.strip() == "":
if discordio and discordio.strip() == "":
discordio = None
db_guild.discordio = discordio
db.session.commit()
@ -209,6 +215,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,
@ -224,7 +232,7 @@ def add_bot(guild_id):
def prepare_guild_members_list(members, bans):
all_users = []
ip_pool = []
members = sorted(members, key=lambda k: datetime.datetime.strptime(str(k.last_timestamp), "%Y-%m-%d %H:%M:%S"), reverse=True)
members = sorted(members, key=lambda k: datetime.datetime.strptime(str(k.last_timestamp.replace(tzinfo=None, microsecond=0)), "%Y-%m-%d %H:%M:%S"), reverse=True)
for member in members:
user = {
"id": member.id,
@ -324,3 +332,70 @@ def revoke_unauthenticated_user():
abort(409)
db_user.revokeUser()
return ('', 204)
@user.route('/donate', methods=["GET"])
@discord_users_only()
def donate_get():
return render_template('donate.html.j2')
def get_paypal_api():
return paypalrestsdk.Api({
'mode': 'sandbox' if app.config["DEBUG"] else 'live',
'client_id': config["paypal-client-id"],
'client_secret': config["paypal-client-secret"]})
@user.route('/donate', methods=['POST'])
@discord_users_only()
def donate_post():
donation_amount = request.form.get('amount')
if not donation_amount:
abort(402)
donation_amount = "{0:.2f}".format(float(donation_amount))
payer = {"payment_method": "paypal"}
items = [{"name": "TitanEmbeds Donation",
"price": donation_amount,
"currency": "USD",
"quantity": "1"}]
amount = {"total": donation_amount,
"currency": "USD"}
description = "Donate and support TitanEmbeds development."
redirect_urls = {"return_url": url_for('user.donate_confirm', success="true", _external=True),
"cancel_url": url_for('index', _external=True)}
payment = paypalrestsdk.Payment({"intent": "sale",
"payer": payer,
"redirect_urls": redirect_urls,
"transactions": [{"item_list": {"items":
items},
"amount": amount,
"description":
description}]}, api=get_paypal_api())
if payment.create():
for link in payment.links:
if link['method'] == "REDIRECT":
return redirect(link["href"])
return redirect(url_for('index'))
@user.route("/donate/confirm")
@discord_users_only()
def donate_confirm():
if not request.args.get('success'):
return redirect(url_for('index'))
payment = paypalrestsdk.Payment.find(request.args.get('paymentId'), api=get_paypal_api())
if payment.execute({"payer_id": request.args.get('PayerID')}):
trans_id = str(payment.transactions[0]["related_resources"][0]["sale"]["id"])
amount = float(payment.transactions[0]["amount"]["total"])
tokens = int(amount * 100)
action = "PAYPAL {}".format(trans_id)
set_titan_token(session["user_id"], tokens, action)
session["tokens"] = get_titan_token(session["user_id"])
return redirect(url_for('user.donate_thanks', transaction=trans_id))
else:
return redirect(url_for('index'))
@user.route("/donate/thanks")
@discord_users_only()
def donate_thanks():
tokens = get_titan_token(session["user_id"])
transaction = request.args.get("transaction")
return render_template("donate_thanks.html.j2", tokens=tokens, transaction=transaction)

View File

@ -2,12 +2,34 @@ 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
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 .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)
if token_count >= 0:
token_usr = db.session.query(TitanTokens).filter(TitanTokens.user_id == user_id).first()
else:
token_count = 0
token_usr = TitanTokens(user_id, 0)
db.session.add(token_usr)
db.session.commit()
new_token_count = token_count + amt_change
if new_token_count < 0:
return False
transact = TokenTransactions(user_id, action, amt_change, token_count, new_token_count)
db.session.add(transact)
token_usr.tokens = new_token_count
db.session.add(token_usr)
db.session.commit()
return True

View File

@ -0,0 +1,13 @@
from titanembeds.database import db
class Administrators(db.Model):
__tablename__ = "administrators"
id = db.Column(db.Integer, primary_key=True) # Auto increment id
user_id = db.Column(db.String(255), nullable=False) # Discord user id of user of an administrator
def get_administrators_list():
q = db.session.query(Administrators).all()
their_ids = []
for admin in q:
their_ids.append(admin.user_id)
return their_ids

View File

@ -5,3 +5,11 @@ class Cosmetics(db.Model):
id = db.Column(db.Integer, primary_key=True) # Auto increment id
user_id = db.Column(db.String(255), nullable=False) # Discord user id of user of cosmetics
css = db.Column(db.Boolean(), nullable=False) # If they can create/edit custom CSS
def __init__(self, user_id, **kwargs):
self.user_id = user_id
if "css" in kwargs:
self.css = kwargs["css"]
else:
self.css = False

View File

@ -43,3 +43,6 @@ def list_all_guild_members(guild_id):
"nickname": member.nickname,
})
return memlist
def get_guild_member(guild_id, member_id):
return db.session.query(GuildMembers).filter(GuildMembers.guild_id == guild_id, GuildMembers.user_id == member_id).first()

View File

@ -6,25 +6,31 @@ class Guilds(db.Model):
guild_id = db.Column(db.String(255), nullable=False) # Discord guild id
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
roles = db.Column(db.Text(), nullable=False) # Guild Roles
channels = db.Column(db.Text(), nullable=False) # Guild channels
emojis = db.Column(db.Text(), nullable=False) # Guild Emojis
roles = db.Column(db.Text().with_variant(db.Text(4294967295), 'mysql'), nullable=False) # Guild Roles
channels = db.Column(db.Text().with_variant(db.Text(4294967295), 'mysql'), nullable=False) # Guild channels
webhooks = db.Column(db.Text().with_variant(db.Text(4294967295), 'mysql'), nullable=False) # Guild webhooks
emojis = db.Column(db.Text().with_variant(db.Text(4294967295), 'mysql'), nullable=False) # Guild Emojis
owner_id = db.Column(db.String(255), nullable=False) # Snowflake of the owner
icon = db.Column(db.String(255)) # The icon string, null if none
discordio = db.Column(db.String(255)) # Custom Discord.io Invite Link
def __init__(self, guild_id, name, roles, channels, emojis, owner_id, icon):
def __init__(self, guild_id, name, roles, channels, webhooks, emojis, owner_id, icon):
self.guild_id = guild_id
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
self.roles = roles
self.channels = channels
self.webhooks = webhooks
self.emojis = emojis
self.owner_id = owner_id
self.icon = icon

View File

@ -20,14 +20,14 @@ def set_keyvalproperty(key, value, expiration=None):
def get_keyvalproperty(key):
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
now = datetime.now()
if q.count() > 0 and (q.first().expiration is None or q.first().expiration > now):
if q.count() > 0 and (q.first().expiration is None or q.first().expiration.replace(tzinfo=None) > now.replace(tzinfo=None)):
return q.first().value
return None
def getexpir_keyvalproperty(key):
q = db.session.query(KeyValueProperties).filter(KeyValueProperties.key == key)
now = datetime.now()
if q.count() > 0 and (q.first().expiration is not None and q.first().expiration > now):
if q.count() > 0 and (q.first().expiration is not None and q.first().expiration.replace(tzinfo=None) > now.replace(tzinfo=None)):
return int(q.first().expiration.strftime('%s'))
return 0

View File

@ -1,4 +1,4 @@
from titanembeds.database import db
from titanembeds.database import db, get_guild_member
from sqlalchemy import cast
import json
@ -29,14 +29,18 @@ class Messages(db.Model):
def __repr__(self):
return '<Messages {0} {1} {2} {3} {4}>'.format(self.id, self.guild_id, self.guild_id, self.channel_id, self.message_id)
def get_channel_messages(channel_id, after_snowflake=None):
def get_channel_messages(guild_id, channel_id, after_snowflake=None):
if not after_snowflake:
q = db.session.query(Messages).filter(Messages.channel_id == channel_id).order_by(Messages.id.desc()).limit(50)
q = db.session.query(Messages).filter(Messages.channel_id == channel_id).order_by(Messages.timestamp.desc()).limit(50)
else:
q = db.session.query(Messages).filter(cast(Messages.channel_id, db.Integer) == int(channel_id)).filter(Messages.message_id > after_snowflake).order_by(Messages.id.desc()).limit(50)
q = db.session.query(Messages).filter(cast(Messages.channel_id, db.Integer) == int(channel_id)).filter(Messages.message_id > after_snowflake).order_by(Messages.timestamp.desc()).limit(50)
msgs = []
snowflakes = []
for x in q:
msgs.append({
if x.message_id in snowflakes:
continue
snowflakes.append(x.message_id)
message = {
"attachments": json.loads(x.attachments),
"timestamp": x.timestamp,
"id": x.message_id,
@ -45,5 +49,15 @@ def get_channel_messages(channel_id, after_snowflake=None):
"content": x.content,
"channel_id": x.channel_id,
"mentions": json.loads(x.mentions)
})
}
member = get_guild_member(guild_id, message["author"]["id"])
message["author"]["nickname"] = None
if member:
message["author"]["nickname"] = member.nickname
for mention in message["mentions"]:
author = get_guild_member(guild_id, mention["id"])
mention["nickname"] = None
if author:
mention["nickname"] = author.nickname
msgs.append(message)
return msgs

View File

@ -0,0 +1,18 @@
from titanembeds.database import db
class TitanTokens(db.Model):
__tablename__ = "titan_tokens"
id = db.Column(db.Integer, primary_key=True) # Auto increment id
user_id = db.Column(db.String(255), nullable=False) # Discord user id of user
tokens = db.Column(db.Integer, nullable=False, default=0) # Token amount
def __init__(self, user_id, tokens):
self.user_id = user_id
self.tokens = tokens
def get_titan_token(user_id):
q = db.session.query(TitanTokens).filter(TitanTokens.user_id == user_id).first()
if q:
return q.tokens
else:
return -1

View File

@ -0,0 +1,21 @@
from titanembeds.database import db
import datetime
import time
class TokenTransactions(db.Model):
__tablename__ = "token_transactions"
id = db.Column(db.Integer, primary_key=True) # Auto increment id
user_id = db.Column(db.String(255), nullable=False) # Discord user id of user
timestamp = db.Column(db.TIMESTAMP, nullable=False) # The timestamp of when the action took place
action = db.Column(db.String(255), nullable=False) # Very short description of the action
net_tokens = db.Column(db.Integer, nullable=False) # Net change of the token amount
start_tokens = db.Column(db.Integer, nullable=False) # Token amount before transaction
end_tokens = db.Column(db.Integer, nullable=False) # Tokens after transaction
def __init__(self, user_id, action, net_tokens, start_tokens, end_tokens):
self.user_id = user_id
self.timestamp = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')
self.action = action
self.net_tokens = net_tokens
self.start_tokens = start_tokens
self.end_tokens = end_tokens

View File

@ -6,7 +6,7 @@ class UserCSS(db.Model):
name = db.Column(db.String(255), nullable=False) # CSS Name
user_id = db.Column(db.String(255), nullable=False) # Discord client ID of the owner of the css (can edit)
css_variables = db.Column(db.Text()) # Customizeable CSS Variables
css = db.Column(db.Text()) # CSS contents
css = db.Column(db.Text().with_variant(db.Text(4294967295), 'mysql')) # CSS contents
def __init__(self, name, user_id, css_variables=None, css=None):
self.name = name

View File

@ -17,7 +17,7 @@ class DiscordREST:
def __init__(self, bot_token):
self.global_redis_prefix = "discordapiratelimit/"
self.bot_token = bot_token
self.user_agent = "TitanEmbeds (https://github.com/EndenDragon/Titan) Python/{} requests/{}".format(sys.version_info, requests.__version__)
self.user_agent = "TitanEmbeds (https://github.com/TitanEmbeds/Titan) Python/{} requests/{}".format(sys.version_info, requests.__version__)
def init_discordrest(self):
if not self._bucket_contains("global_limited"):
@ -135,7 +135,41 @@ class DiscordREST:
def get_widget(self, guild_id):
_endpoint = _DISCORD_API_BASE + "/servers/{guild_id}/widget.json".format(guild_id=guild_id)
embed = self.get_guild_embed(guild_id)
if not embed.get("success", True):
return {"success": False}
if not embed['content']['enabled']:
self.modify_guild_embed(guild_id, enabled=True, channel_id=guild_id)
widget = requests.get(_endpoint).json()
return widget
#####################
# 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:
_endpoint += "?wait=true"
payload = {
'content': content,
'avatar_url': avatar,
'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():
@ -96,5 +96,5 @@ def generate_guild_icon_url(id, hash):
return guild_icon_url + str(id) + "/" + str(hash) + ".jpg"
def generate_bot_invite_url(guild_id):
url = "https://discordapp.com/oauth2/authorize?&client_id={}&scope=bot&permissions={}&guild_id={}".format(config['client-id'], '536083583', guild_id)
url = "https://discordapp.com/oauth2/authorize?&client_id={}&scope=bot&permissions={}&guild_id={}".format(config['client-id'], '537349164', guild_id)
return url

View File

@ -1,54 +1,121 @@
@font-face {
font-family: Whitney;
font-style: light;
font-weight: 300;
src: url("/static/fonts/whitney_light.woff") format("woff");
}
@font-face {
font-family: Whitney;
font-style: normal;
font-weight: 500;
src: url("/static/fonts/whitney_normal.woff") format("woff");
}
@font-face {
font-family: Whitney;
font-style: medium;
font-weight: 600;
src: url("/static/fonts/whitney_medium.woff") format("woff");
}
@font-face {
font-family: Whitney;
font-style: bold;
font-weight: 700;
src: url("/static/fonts/whitney_bold.woff") format("woff");
}
html {
background-color: #455a64;
color: white;
background-color: #455a64;
color: white;
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
}
main {
min-height: calc(100vh - 80px);
overflow-x: hidden;
min-height: calc(100vh - 80px);
overflow-x: hidden;
}
footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background-color: #37474f;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background-color: #37474f;
}
nav {
background-color: #263238;
background: linear-gradient(rgba(38, 50, 56, 1), rgba(255,0,0,0));
box-shadow: none;
background-color: #263238;
background: linear-gradient(rgba(38, 50, 56, 1), rgba(255,0,0,0));
box-shadow: none;
}
nav .brand-logo {
font-size: 1.5rem;
font-size: 1.5rem;
}
@media only screen and (min-width: 601px) {
nav a.button-collapse {
display: block;
}
}
body > div.navbar-fixed > nav > div {
background: #263238 background: -webkit-linear-gradient(#263238, #37474f, #455a64);
/* For Safari 5.1 to 6.0 */
background: -o-linear-gradient(#263238, #37474f, #455a64);
/* For Opera 11.1 to 12.0 */
background: -moz-linear-gradient(#263238, #37474f, #455a64);
/* For Firefox 3.6 to 15 */
background: linear-gradient(#263238, #37474f, #455a64);
/* Standard syntax */
}
@media only screen and (min-width: 993px) {
.container {
.container {
width: 85%;
}
}
}
.side-nav {
color: white;
background-color: #607d8b;
color: white;
background-color: #607d8b;
max-width: 95%;
}
.side-nav .userView .name {
font-size: 20px;
font-size: 20px;
}
.side-nav li>a {
color: #eceff1;
color: #eceff1;
}
.side-nav .subheader {
color: #cfd8dc;
font-variant: small-caps;
color: #cfd8dc;
font-variant: small-caps;
}
.side-nav div.divider {
background-color: #90a4ae;
margin-left: 10px;
margin-right: 10px;
}
#members-btn > i {
visibility: hidden;
}
#members-btn {
visibility: visible;
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyNiI+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGQ9Ik0xIDFoMjR2MjRIMVYxeiIvPgogICAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE2Ljc3MjA3NTQgMTQuNjc5MTc3OGMtLjIyNDkwOTctLjIyNTY4ODYtLjQ3NDE1NzMtLjQzNTE2ODEtLjc0MjA3NTQtLjYyOTE3NzguMzUtLjAzLjY4LS4wNS45Ny0uMDUgMi4zMyAwIDcgMS4xNyA3IDMuNVYyMGgtNHYtMWMwLTEuODgxNjkxNC0xLjQwNzcwNTYtMy4zMjMwMTQzLTMuMjI3OTI0Ni00LjMyMDgyMjJ6bS0xLjQzODU3MzUtOC4xNzU4NTM0QzE1LjgwOTgwODIgNi4xODUyNTE3MyAxNi4zODI3ODQ1IDYgMTcgNmMxLjY2IDAgMi45OSAxLjM0IDIuOTkgM3MtMS4zMyAzLTIuOTkgM2MtLjYxNzE5MTQgMC0xLjE5MDE0NzEtLjE4NTIzNzMtMS42NjY0NDIzLS41MDMyODcyQzE1Ljc1NzQ4MzIgMTAuNzYyMTQwOTUgMTYgOS45MDk1NTYgMTYgOS4wMDAwNjY5M2MwLS45MDk1Mjg5Ni0uMjQyNTM4MS0xLjc2MjE0ODgtLjY2NjQ5ODEtMi40OTY3NDI1M3pNMTAgMTNjMi4yMSAwIDQtMS43OSA0LTRzLTEuNzktNC00LTQtNCAxLjc5LTQgNCAxLjc5IDQgNCA0em0wIDJjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6Ii8+CiAgPC9nPgo8L3N2Zz4=);
background-repeat: no-repeat;
margin-top: 18px
}
.role-title {
@ -57,71 +124,118 @@ font-variant: small-caps;
font-size: 80% !important;
}
.divider {
background-color: #90a4ae;
}
.channel-hash {
font-size: 95%;
color: #b0bec5;
font-size: 95%;
color: #b0bec5;
}
.membercircle {
margin-top: 5px;
height: 40px;
margin-top: 5px;
height: 40px;
}
.membername {
position: absolute;
padding-left: 10px;
position: absolute;
padding-left: 10px;
}
.chatcontent {
padding-left: 1%;
padding-top: 1%;
padding-bottom: 40px;
#chatcontent {
padding-left: 1%;
padding-top: 1%;
padding-bottom: 40px;
/* https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ */
/* These are technically the same, but use both */
overflow-wrap: break-word;
word-wrap: break-word;
/* https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ */
/* These are technically the same, but use both */
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
/* This is the dangerous one in WebKit, as it breaks things wherever */
word-break: break-all;
/* Instead use this non-standard one: */
word-break: break-word;
-ms-word-break: break-all;
/* This is the dangerous one in WebKit, as it breaks things wherever */
word-break: break-all;
/* Instead use this non-standard one: */
word-break: break-word;
/* Adds a hyphen where the word breaks, if supported (No Blink) */
-ms-hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
/* Adds a hyphen where the word breaks, if supported (No Blink) */
-ms-hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto;
}
@media only screen and (min-width: 601px) {
nav a.button-collapse {
#curuser_discrim,
#curuser_name {
display: block;
}
#chatcontent > p {
display: table;
width: 90%;
}
#chatcontent > p > span {
display: table-row
}
::-webkit-input-placeholder {
color: rgb(99, 99, 99);
}
:-moz-placeholder {
color: rgb(99, 99, 99);
}
::-moz-placeholder {
color: rgb(99, 99, 99);
}
:-ms-input-placeholder {
color: rgb(99, 99, 99);
}
::-ms-input-placeholder {
color: rgb(99, 99, 99);
}
#discord-members > li > a.subheader,
#members-nav > li:nth-child(1) > a,
#discord-members-count,
#embed-discord-members-count,
#members-nav > li:nth-child(4) > a,
#guest-members-count,
#members-nav > li:nth-child(6) > a {
text-transform: uppercase;
}
.circle:hover {
border-radius: 20px;
background: linear-gradient(to right, #f9f9f9 90%, #fff);
}
#channels-list > li:hover {
-webkit-filter: brightness(150%);
}
.chatusername {
font-weight: bold;
color: #eceff1;
font-weight: bold;
color: #eceff1;
}
.chattimestamp {
font-size: 10px;
color: #90a4ae;
margin-right: 3px;
font-size: 10px;
color: #90a4ae;
margin-right: 3px;
}
.footercontainer {
width: 100%;
position: relative;
margin: 10px;
white-space: nowrap;
overflow: hidden;
width: 100%;
position: relative;
margin: 10px;
white-space: nowrap;
overflow: hidden;
border-radius: 20px;
border: 1px solid rgb(99, 99, 99);
margin-left: -0px;
padding-left: -4px;
}
#messageboxouter {
@ -130,35 +244,53 @@ overflow: hidden;
}
.currentuserchip {
display: inline-block;
position: relative;
top: -6px;
padding: 6px;
padding-right: 9px;
background-color: #455a64;
display: inline-block;
position: relative;
top: -6px;
padding: 6px;
padding-right: 9px;
background-color: #455a64;
}
.currentuserimage {
width: 30px;
width: 30px;
}
.currentusername {
position: relative;
top: 7px;
left: 5px;
position: relative;
top: 7px;
left: 5px;
}
#curuser_discrim {
font-size: 50%;
}
#curuser_discrim,
#curuser_name {
margin-top: -2px;
}
#currentuserimage {
margin-top: 4px;
margin-right: 4px;
}
.input-field {
position: relative;
top: -19px;
position: relative;
top: -19px;
}
.left {
float: left;
float: left;
}
.modal {
background-color: #546e7a;
background-color: #546e7a;
}
.modal-overlay {
height: auto;
}
.betatag {
@ -174,6 +306,10 @@ background-color: #546e7a;
font-size: 85%;
}
.input-field label {
color: white;
}
a {
color: #82b1ff;
}
@ -232,105 +368,117 @@ a {
#nameplate {
cursor: pointer;
}
@font-face {
font-family: Whitney;
font-style: light;
font-weight: 300;
src: url("../fonts/whitney_light.woff") format("woff")
background: transparent;
margin-left: 10px;
}
@font-face {
font-family: Whitney;
font-style: normal;
font-weight: 500;
src: url("../fonts/whitney_normal.woff") format("woff")
#visitor_mode_message {
margin-right: auto;
margin-left: auto;
display: block;
width: 305px;
}
@font-face {
font-family: Whitney;
font-style: medium;
font-weight: 600;
src: url("../fonts/whitney_medium.woff") format("woff")
#visitor_mode_message_note {
display: none;
}
@font-face {
font-family: Whitney;
font-style: bold;
font-weight: 700;
src: url("../fonts/whitney_bold.woff") format("woff")
@media only screen and (min-width: 400px) {
#visitor_mode_message_note {
display: inline;
}
}
* {
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
#focusmodal {
background-color: rgba(84, 110, 122, 0.58);
text-shadow: 1px 1px 2px black, 0 0 1em #607d8b, 0 0 0.2em #b0bec5;
}
#footercontainer {
border-radius:20px;
border: 1px solid rgb(99,99,99);
margin-left:-0px!important;
padding-left:-4px!important;
.message_emoji {
height: 20px;
}
#nameplate {
background:transparent!important;
margin-left:10px
.message_emoji:hover {
cursor: pointer;
}
#chatcontent > p > span.chatusername,
#curuser_discrim,
#curuser_name {
display:block
#chatcontent .message_emoji:hover {
height: 30px;
}
#curuser_discrim {
font-size:50%;
.chatusername {
display: table-header-group;
}
#curuser_discrim,
#curuser_name {
margin-top:-2px
.chatmessage {
display: inline;
color: rgb(195, 196, 197);
}
#currentuserimage {
margin-top:4px;
margin-right:4px
p.mentioned {
font-weight: bold;
font-style: italic;
}
#chatcontent > p { display: table; }
#chatcontent > p > span { display: table-row }
#chatcontent > p > span.chatusername { display: table-header-group }
#chatcontent > p > span.chatmessage { display: table-footer-group;display:inline-block!important;color:rgb(195,196,197) }
::-webkit-input-placeholder { color:rgb(99,99,99) }
:-moz-placeholder { color:rgb(99,99,99) }
::-moz-placeholder { color:rgb(99,99,99) }
:-ms-input-placeholder { color:rgb(99,99,99) }
::-ms-input-placeholder { color:rgb(99,99,99) }
body > div.navbar-fixed > nav > div {
background:#263238
background: -webkit-linear-gradient(#263238, #37474f, #455a64); /* For Safari 5.1 to 6.0 */
background: -o-linear-gradient(#263238, #37474f, #455a64); /* For Opera 11.1 to 12.0 */
background: -moz-linear-gradient(#263238, #37474f, #455a64); /* For Firefox 3.6 to 15 */
background: linear-gradient(#263238, #37474f, #455a64); /* Standard syntax */
p.mentioned span.chatmessage {
color: #ff5252;
}
div.divider {
margin-left:10px;
margin-right:10px;
.chatmessage code {
background-color: gray;
color: lightgray;
border-radius: 5px;
padding: 2px;
}
#discord-members > li > a.subheader,
#members-nav > li:nth-child(1) > a,
#discord-members-count,
#embed-discord-members-count,
#members-nav > li:nth-child(4) > a,
#guest-members-count,
#members-nav > li:nth-child(6) > a {
text-transform: uppercase;
.chatmessage code.blockcode {
width: 100%;
display: inline-block;
white-space: pre-wrap;
line-height: 15px;
padding: 5px;
}
#members-btn > i { visibility:hidden }
#members-btn {
visibility:visible;
background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNiIgaGVpZ2h0PSIyNiI+CiAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgIDxwYXRoIGQ9Ik0xIDFoMjR2MjRIMVYxeiIvPgogICAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE2Ljc3MjA3NTQgMTQuNjc5MTc3OGMtLjIyNDkwOTctLjIyNTY4ODYtLjQ3NDE1NzMtLjQzNTE2ODEtLjc0MjA3NTQtLjYyOTE3NzguMzUtLjAzLjY4LS4wNS45Ny0uMDUgMi4zMyAwIDcgMS4xNyA3IDMuNVYyMGgtNHYtMWMwLTEuODgxNjkxNC0xLjQwNzcwNTYtMy4zMjMwMTQzLTMuMjI3OTI0Ni00LjMyMDgyMjJ6bS0xLjQzODU3MzUtOC4xNzU4NTM0QzE1LjgwOTgwODIgNi4xODUyNTE3MyAxNi4zODI3ODQ1IDYgMTcgNmMxLjY2IDAgMi45OSAxLjM0IDIuOTkgM3MtMS4zMyAzLTIuOTkgM2MtLjYxNzE5MTQgMC0xLjE5MDE0NzEtLjE4NTIzNzMtMS42NjY0NDIzLS41MDMyODcyQzE1Ljc1NzQ4MzIgMTAuNzYyMTQwOTUgMTYgOS45MDk1NTYgMTYgOS4wMDAwNjY5M2MwLS45MDk1Mjg5Ni0uMjQyNTM4MS0xLjc2MjE0ODgtLjY2NjQ5ODEtMi40OTY3NDI1M3pNMTAgMTNjMi4yMSAwIDQtMS43OSA0LTRzLTEuNzktNC00LTQtNCAxLjc5LTQgNCAxLjc5IDQgNCA0em0wIDJjLTIuNjcgMC04IDEuMzQtOCA0djJoMTZ2LTJjMC0yLjY2LTUuMzMtNC04LTR6Ii8+CiAgPC9nPgo8L3N2Zz4=);
background-repeat:no-repeat;
margin-top:18px
#emoji-picker {
color: black;
position: fixed;
bottom: 12%;
right: 1%;
z-index: 500;
width: 350px;
height: 110px;
max-width: 80%;
max-height: 80%;
background-color: #eceff1;
border-radius: 5px;
display: none;
}
.circle:hover {
border-radius:20px;
background: linear-gradient(to right, #f9f9f9 90%, #fff);
#emoji-picker-content {
overflow: auto;
height: 100%;
padding: 5px;
padding-top: 0;
}
#channels-list > li:hover {
-webkit-filter: brightness(150%);
#emoji-picker h6 {
font-weight: bold;
}
#loginmodal {
#emoji-tray-toggle {
position: absolute;
width: 10px;
height: 10px;
top: 14px;
right: 30px;
}
#emoji-tray-toggle > .btn-floating {
width: 30px;
height: 30px;
}
#emoji-tray-toggle > .btn-floating > i {
line-height: 0;
position: relative;
top: -5px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

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: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,82 @@
/* global $, Materialize, location */
function postForm(user_id, css) {
var funct = $.ajax({
dataType: "json",
method: "POST",
data: {"user_id": user_id, "css": css}
});
return funct.promise();
}
function deleteForm(user_id) {
var funct = $.ajax({
dataType: "json",
method: "DELETE",
data: {"user_id": user_id}
});
return funct.promise();
}
function patchForm(user_id, css) {
var funct = $.ajax({
dataType: "json",
method: "PATCH",
data: {"user_id": user_id, "css": css}
});
return funct.promise();
}
$(function() {
$("#new_submit").click(function () {
var user_id = $("#new_user_id").val();
if (user_id.length < 1) {
Materialize.toast("The user ID field can't be blank!", 2000);
return;
}
var css_checked = $("#new_css_switch").is(':checked');
var formPost = postForm(user_id, css_checked);
formPost.done(function (data) {
location.reload();
});
formPost.fail(function (data) {
if (data.status == 409) {
Materialize.toast('This user id already exists!', 10000);
} else {
Materialize.toast('Oh no! Something has failed submitting a new entry!', 10000);
}
});
});
});
function delete_user(user_id) {
var confirmation = confirm("Are you sure that you want to delete user?");
if (confirmation) {
var formDelete = deleteForm(user_id);
formDelete.done(function (data) {
location.reload();
});
formDelete.fail(function (data) {
if (data.status == 409) {
Materialize.toast('This user id does not exists!', 10000);
} else {
Materialize.toast('Oh no! Something has failed deleting this user entry!', 10000);
}
});
}
}
function update_css_switch(user_id, element) {
var css_checked = $(element).is(':checked');
var formPatch = patchForm(user_id, css_checked);
formPatch.done(function (data) {
Materialize.toast('CSS updated!', 10000);
});
formPatch.fail(function (data) {
if (data.status == 409) {
Materialize.toast('This user id does not exists!', 10000);
} else {
Materialize.toast('Oh no! Something has failed changing the css toggle!', 10000);
}
});
}

View File

@ -0,0 +1,30 @@
/* global $ */
/* global Materialize */
(function () {
function cleanup_database() {
var funct = $.ajax({
method: "DELETE",
url: "/api/cleanup-db",
});
return funct.promise();
}
$(function(){
$("#db_cleanup_btn").click(run_cleanup_db);
});
function run_cleanup_db() {
$("#db_cleanup_btn").attr("disabled",true);
Materialize.toast('Please wait for the cleanup database task to finish...', 10000);
var cleanupdb = cleanup_database();
cleanupdb.done(function () {
$("#db_cleanup_btn").attr("disabled",false);
Materialize.toast('Successfully cleaned up the database!', 10000);
});
cleanupdb.fail(function () {
$("#db_cleanup_btn").attr("disabled",false);
Materialize.toast('Database cleanup failiure.', 10000);
});
}
})();

View File

@ -0,0 +1,56 @@
/* global $, Materialize, location */
function postForm(user_id, amount) {
var funct = $.ajax({
dataType: "json",
method: "POST",
data: {"user_id": user_id, "amount": amount}
});
return funct.promise();
}
function patchForm(user_id, amount) {
var funct = $.ajax({
dataType: "json",
method: "PATCH",
data: {"user_id": user_id, "amount": amount}
});
return funct.promise();
}
$(function() {
$("#new_submit").click(function () {
var user_id = $("#new_user_id").val();
var user_token = $("#new_user_token").val();
if (user_id.length < 1 || user_token.length < 1) {
Materialize.toast("The user ID or balance field can't be blank!", 2000);
return;
}
var formPost = postForm(user_id, user_token);
formPost.done(function (data) {
location.reload();
});
formPost.fail(function (data) {
if (data.status == 409) {
Materialize.toast('This user id already exists!', 10000);
} else {
Materialize.toast('Oh no! Something has failed submitting a new entry!', 10000);
}
});
});
});
function submit_modify_user(user_id) {
var amount = $("#input_"+user_id).val();
var formPatch = patchForm(user_id, amount);
formPatch.done(function (data) {
location.reload();
});
formPatch.fail(function (data) {
if (data.status == 409) {
Materialize.toast('This user id does not exists!', 10000);
} else {
Materialize.toast('Oh no! Something has failed changing the css toggle!', 10000);
}
});
}

View File

@ -7,6 +7,24 @@ $('#unauth_users').change(function() {
});
});
$('#visitor_view').change(function() {
var pathname = window.location.pathname;
var checked = $(this).is(':checked')
var payload = {"visitor_view": checked}
$.post(pathname, payload, function(data) {
Materialize.toast('Updated visitor mode setting!', 2000)
});
});
$('#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

@ -0,0 +1,19 @@
/* global $ */
(function () {
$('#token-slider').on('input', function(){
var slider_value = $("#token-slider").val();
var multiplier = 100;
$("#money-display").text(slider_value);
$("#token-display").text(slider_value * multiplier);
});
$("#donate-btn").click(function () {
var slider_value = $("#token-slider").val();
var form = $('<form method="POST">' +
'<input type="hidden" name="amount" value="' + slider_value + '">' +
'</form>');
$(document.body).append(form);
form.submit();
});
})();

View File

@ -5,22 +5,31 @@
/* global bot_client_id */
/* global moment */
/* global localStorage */
/* global visitors_enabled */
/* global cheet */
/* global location */
/* global io */
/* global twemoji */
(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 = guild_id; // user selected channel, defaults to #general channel
var selected_channel = null; // user selected channel
var guild_channels = {}; // all server channels used to highlight channels in messages
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) {
var pageTop = $(window).scrollTop();
@ -35,10 +44,18 @@
}
}
String.prototype.replaceAll = function(target, replacement) {
return this.split(target).join(replacement);
};
function query_guild() {
var url = "/api/query_guild";
if (visitor_mode) {
url = url += "_visitor";
}
var funct = $.ajax({
dataType: "json",
url: "/api/query_guild",
url: url,
data: {"guild_id": guild_id}
});
return funct.promise();
@ -64,11 +81,25 @@
return funct.promise();
}
function change_unauthenticated_username(username) {
var funct = $.ajax({
method: "POST",
dataType: "json",
url: "/api/change_unauthenticated_username",
data: {"username": username, "guild_id": guild_id}
});
return funct.promise();
}
function fetch(channel_id, after=null) {
var url = "/api/fetch";
if (visitor_mode) {
url += "_visitor";
}
var funct = $.ajax({
method: "GET",
dataType: "json",
url: "/api/fetch",
url: url,
data: {"guild_id": guild_id,"channel_id": channel_id, "after": after}
});
return funct.promise();
@ -98,15 +129,17 @@
}
$('select').material_select();
$("#focusmodal").modal({
dismissible: true,
opacity: .5,
inDuration: 400,
outDuration: 400,
startingTop: "4%",
endingTop: "10%",
});
$("#focusmodal").modal("open");
$("#loginmodal").modal({
dismissible: visitors_enabled, // Modal can be dismissed by clicking outside of the modal
opacity: .5, // Opacity of modal background
inDuration: 300, // Transition in duration
outDuration: 200, // Transition out duration
startingTop: '4%', // Starting top style attribute
endingTop: '10%', // Ending top style attribute
}
);
$('#loginmodal').modal('open');
$("#userembedmodal").modal({
dismissible: true,
opacity: .5,
@ -118,14 +151,58 @@
$("#userembedmodal").modal("open");
});
$( "#theme-selector" ).change(function() {
$("#visitor_login_btn").click(function () {
$("#loginmodal").modal("open");
});
$("#emoji-tray-toggle").click(function () {
$("#emoji-picker").fadeToggle();
var offset = $("#emoji-tray-toggle").offset().top;
$("#emoji-picker").offset({"top": offset-120});
$("#emoji-picker-emojis").html("");
var template = $('#mustache_message_emoji').html();
Mustache.parse(template);
for (var i = 0; i < emoji_store.length; i++) {
var emoji = emoji_store[i];
var rendered = Mustache.render(template, {"id": emoji.id, "name": emoji.name}).trim();
var jqueryed = $(rendered);
jqueryed.click(function () {
var emote_name = $(this).attr("data-tooltip");
place_emoji(emote_name);
});
$("#emoji-picker-emojis").append(jqueryed);
}
$('.tooltipped').tooltip();
});
$("#chatcontent").click(function () {
var emojipck_display = $('#emoji-picker').css('display');
if (emojipck_display != "none") {
$("#emoji-picker").fadeToggle();
}
});
$("#messagebox").click(function () {
var emojipck_display = $('#emoji-picker').css('display');
if (emojipck_display != "none") {
$("#emoji-picker").fadeToggle();
}
});
$( "#theme-selector" ).change(function () {
var theme = $("#theme-selector option:selected").val();
changeTheme(theme);
var keep_custom_css = $("#overwrite_theme_custom_css_checkbox").is(':checked');
changeTheme(theme, keep_custom_css);
});
$("#overwrite_theme_custom_css_checkbox").change(function () {
var keep_custom_css = $("#overwrite_theme_custom_css_checkbox").is(':checked');
changeTheme(null, keep_custom_css);
});
var themeparam = getParameterByName('theme');
var localstore_theme = localStorage.getItem("theme");
if ((getParameterByName("css") == null) && ((themeparam && $.inArray(themeparam, theme_options) != -1) || (localstore_theme))) {
if ((themeparam && $.inArray(themeparam, theme_options) != -1) || (localstore_theme)) {
var theme;
if (themeparam) {
theme = themeparam;
@ -138,28 +215,43 @@
$('select').material_select();
}
if (document.hasFocus()) {
primeEmbed();
}
$(window).focus(function() {
if (!has_already_been_focused) {
primeEmbed();
}
});
var dembed = discord_embed();
dembed.done(function (data) {
$("#modal_invite_btn").attr("href", data.instant_invite);
});
function changeTheme(theme) {
$(window).resize(function(){
// For those who decides to hide the embed at first load (display: none), resulting in the messages being not scrolled down.
if (!has_already_been_initially_resized) {
has_already_been_initially_resized = true;
$("html, body").animate({ scrollTop: $(document).height() }, "fast");
}
});
primeEmbed();
setInterval(send_socket_heartbeat, 5000);
if (getParameterByName("username")) {
$("#custom_username_field").val(getParameterByName("username"));
}
});
function changeTheme(theme=null, keep_custom_css=true) {
if (theme == "") {
$("#css-theme").attr("href", "");
$("#user-defined-css").text(user_def_css);
localStorage.removeItem("theme");
} else if ($.inArray(theme, theme_options) != -1) {
} else if ($.inArray(theme, theme_options) != -1 || theme == null) {
if (!keep_custom_css) {
$("#user-defined-css").text("");
} else {
$("#user-defined-css").text(user_def_css);
}
if (theme) {
$("#css-theme").attr("href", "/static/themes/" + theme + "/css/style.css");
localStorage.setItem("theme", theme);
}
}
}
/* https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript */
function getParameterByName(name, url) {
@ -172,30 +264,38 @@
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function primeEmbed() {
$("#focusmodal").modal("close");
has_already_been_focused = true;
var dembed = discord_embed();
dembed.done(function (data) {
$("#modal_invite_btn").attr("href", data.instant_invite);
});
$("#loginmodal").modal({
dismissible: false, // Modal can be dismissed by clicking outside of the modal
opacity: .5, // Opacity of modal background
inDuration: 300, // Transition in duration
outDuration: 200, // Transition out duration
startingTop: '4%', // Starting top style attribute
endingTop: '10%', // Ending top style attribute
function setVisitorMode(enabled) {
if (!visitors_enabled) {
return;
}
);
$('#loginmodal').modal('open');
visitor_mode = enabled;
if (visitor_mode) {
$("#visitor_mode_message").show();
$("#messagebox").hide();
$("#emoji-tray-toggle").hide();
} else {
$("#visitor_mode_message").hide();
$("#messagebox").show();
$("#emoji-tray-toggle").show();
}
}
function primeEmbed() {
lock_login_fields();
var guild = query_guild();
guild.fail(function() {
unlock_login_fields();
if (visitors_enabled) {
setVisitorMode(true);
var guild2 = query_guild();
guild2.done(function(data) {
initialize_embed(data);
});
guild2.fail(function() {
setVisitorMode(false);
});
}
});
guild.done(function(data) {
@ -220,33 +320,59 @@
}
function initialize_embed(guildobj) {
if (socket) {
socket.disconnect();
socket = null;
}
if (guildobj === undefined) {
var guild = query_guild();
guild.done(function(data) {
switch_to_default_channel(data.channels);
prepare_guild(data);
$('#loginmodal').modal('close');
unlock_login_fields();
});
} else {
switch_to_default_channel(guildobj.channels);
prepare_guild(guildobj);
$('#loginmodal').modal('close');
unlock_login_fields();
}
}
function switch_to_default_channel(guildchannels) {
var defaultChannel = getParameterByName("defaultchannel");
if (!defaultChannel) {
return;
}
for (var i = 0; i < guildchannels.length; i++) {
if (guildchannels[i].channel.id == defaultChannel) {
if (!guildchannels[i].read) {
return;
}
selected_channel = defaultChannel;
return;
}
}
}
function prepare_guild(guildobj) {
emoji_store = guildobj.emojis;
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);
run_fetch_routine();
initiate_websockets();
}
function fill_channels(channels) {
guild_channels_list = channels;
var template = $('#mustache_channellistings').html();
Mustache.parse(template);
$("#channels-list").empty();
var curr_default_channel = selected_channel;
for (var i = 0; i < channels.length; i++) {
var chan = channels[i];
guild_channels[chan.channel.id] = chan;
@ -256,18 +382,23 @@
$("#channel-" + chan.channel.id.toString()).click({"channel_id": chan.channel.id.toString()}, function(event) {
select_channel(event.data.channel_id);
});
if (chan.channel.id == selected_channel) {
if (chan.write) {
if (!selected_channel && (!curr_default_channel || chan.channel.position < curr_default_channel.channel.position)) {
curr_default_channel = chan;
}
}
}
if (typeof curr_default_channel == "object") {
selected_channel = curr_default_channel.channel.id;
}
var this_channel = guild_channels[selected_channel];
if (this_channel.write) {
$("#messagebox").prop('disabled', false);
$("#messagebox").prop('placeholder', "Enter message");
} else {
$("#messagebox").prop('disabled', true);
$("#messagebox").prop('placeholder', "Messages is disabled in this channel.");
}
$("#channeltopic").text(chan.channel.topic);
}
}
}
$("#channeltopic").text(this_channel.channel.topic);
$("#channel-"+selected_channel).parent().addClass("active");
}
@ -279,7 +410,19 @@
}
}
function place_emoji(emoji_name) {
if (!$('#messagebox').prop('disabled')) {
$('#messagebox').val( $('#messagebox').val() + emoji_name + " " );
$("#messagebox").focus();
}
var emojipck_display = $('#emoji-picker').css('display');
if (emojipck_display != "none") {
$("#emoji-picker").fadeToggle();
}
}
function fill_discord_members(discordmembers) {
discord_users_list = discordmembers;
var template = $('#mustache_authedusers').html();
Mustache.parse(template);
$("#discord-members").empty();
@ -325,9 +468,28 @@
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 rendered_user = Mustache.render(template_user, {"id": member.id.toString() + "d", "username": member.username, "avatar": member.avatar_url});
var member_name = member.nick;
if (!member_name) {
member_name = member.username;
}
var rendered_user = Mustache.render(template_user, {"id": member.id.toString() + "d", "username": member_name, "avatar": member.avatar_url});
$("#discord-members").append(rendered_user);
$( "#discorduser-" + member.id.toString() + "d").click({"member_id": member.id.toString()}, function(event) {
mention_member(event.data.member_id);
@ -347,12 +509,17 @@
$("#embed-discord-members-count").html(users.length);
for (var i = 0; i < users.length; i++) {
var member = users[i];
var rendered = Mustache.render(template, {"id": member.id.toString() + "a", "username": member.username, "avatar": member.avatar_url});
var username = member.username;
if (member.nickname) {
username = member.nickname;
}
var rendered = Mustache.render(template, {"id": member.id.toString() + "a", "username": username, "avatar": member.avatar_url});
$("#embed-discord-members").append(rendered);
$( "#discorduser-" + member.id.toString() + "a").click({"member_id": member.id.toString()}, function(event) {
mention_member(event.data.member_id);
});
}
authenticated_users_list = users;
}
function fill_unauthenticated_users(users) {
@ -365,6 +532,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() {
@ -375,12 +543,14 @@
setTimeout(function() {
var usr = create_authenticated_user();
usr.done(function(data) {
setVisitorMode(false);
initialize_embed();
return;
});
usr.fail(function(data) {
if (data.status == 403) {
Materialize.toast('Authentication error! You have been banned.', 10000);
setVisitorMode(true);
} else if (index < 10) {
_wait_for_discord_login(index + 1);
}
@ -394,8 +564,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();
}
}
@ -404,8 +572,12 @@
var mentions = message.mentions;
for (var i = 0; i < mentions.length; i++) {
var mention = mentions[i];
message.content = message.content.replace(new RegExp("<@" + mention.id + ">", 'g'), "@" + mention.username + "#" + mention.discriminator);
message.content = message.content.replace(new RegExp("<@!" + mention.id + ">", 'g'), "@" + mention.username + "#" + mention.discriminator);
var username = mention.username;
if (mention.nickname) {
username = mention.nickname;
}
message.content = message.content.replace(new RegExp("<@" + mention.id + ">", 'g'), "@" + username + "#" + mention.discriminator);
message.content = message.content.replace(new RegExp("<@!" + mention.id + ">", 'g'), "@" + username + "#" + mention.discriminator);
message.content = message.content.replace("<@&" + guild_id + ">", "@everyone");
}
return message;
@ -418,9 +590,22 @@
function format_bot_message(message) {
if (message.author.id == bot_client_id && (message.content.includes("**") && ( (message.content.includes("<")&&message.content.includes(">")) || (message.content.includes("[") && message.content.includes("]")) ))) {
var usernamefield = message.content.substring(getPosition(message.content, "**", 1)+3, getPosition(message.content, "**", 2)-1);
message.content = message.content.substring(usernamefield.length+7);
if (message.content.startsWith("(Titan Dev) ")) {
message.content = message.content.substring(usernamefield.length + 18);
} else {
message.content = message.content.substring(usernamefield.length + 7);
}
message.author.username = usernamefield.split("#")[0];
message.author.discriminator = usernamefield.split("#")[1];
} else if (message.author.bot && message.author.discriminator == "0000" && message.author.username.substring(message.author.username.length-5, message.author.username.length-4) == "#") {
var namestr = message.author.username;
if (message.content.startsWith("(Titan Dev) ")) {
message.author.username = "(Titan Dev) " + namestr.substring(0,namestr.length-5);
message.content = message.content.substring(11);
} else {
message.author.username = namestr.substring(0,namestr.length-5);
}
message.author.discriminator = namestr.substring(namestr.length-4);
}
return message;
}
@ -449,9 +634,8 @@
var lastmsg = $("#chatcontent p:last-child");
var content = lastmsg.text().toLowerCase();
var username_discrim = current_username_discrim.toLowerCase();
if (content.includes("@everyone") || content.includes("@" + username_discrim)) {
lastmsg.css( "color", "#ff5252" );
lastmsg.css( "font-weight", "bold" );
if (content.includes("@everyone") || content.includes("@here") || content.includes("@" + username_discrim)) {
lastmsg.addClass( "mentioned" );
}
}
@ -478,7 +662,44 @@
return message;
}
function fill_discord_messages(messages, jumpscroll) {
function parse_emoji_in_message(message) {
var template = $('#mustache_message_emoji').html();
Mustache.parse(template);
for (var i = 0; i < emoji_store.length; i++) {
var emoji = emoji_store[i];
var emoji_format = "&lt;:" + emoji.name + ":" + emoji.id + "&gt;";
var rendered = Mustache.render(template, {"id": emoji.id, "name": emoji.name}).trim();
message.content = message.content.replaceAll(emoji_format, rendered);
}
var rendered = Mustache.render(template, {"id": "$2", "name": "$1"}).trim();
message.content = message.content.replace(/&lt;:(.*?):(.*?)&gt;/g, rendered);
message.content = twemoji.parse(message.content, {
className: "message_emoji",
callback: function(icon, options, variant) { // exclude special characters
switch (icon) {
case 'a9': // © copyright
case 'ae': // ® registered trademark
case '2122': // ™ trademark
return false;
}
return ''.concat(options.base, options.size, '/', icon, options.ext);
}
});
return message;
}
function parse_message_markdown(text) {
text = text.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>");
text = text.replace(/\*(.*?)\*/g, "<i>$1</i>");
text = text.replace(/__(.*?)__/g, "<u>$1</u>");
text = text.replace(/_(.*?)_/g, "<i>$1</i>");
text = text.replace(/~~(.*?)~~/g, "<del>$1</del>");
text = text.replace(/\`\`\`([^]+)\`\`\`/g, "<code class=\"blockcode\">$1</code>");
text = text.replace(/\`(.*?)\`/g, "<code>$1</code>");
return text;
}
function fill_discord_messages(messages, jumpscroll, replace=null) {
if (messages.length == 0) {
return last_message_id;
}
@ -492,24 +713,37 @@
message = parse_message_time(message);
message = parse_message_attachments(message);
message = parse_channels_in_message(message);
var rendered = Mustache.render(template, {"id": message.id, "full_timestamp": message.formatted_timestamp, "time": message.formatted_time, "username": message.author.username, "discriminator": message.author.discriminator, "content": nl2br(escapeHtml(message.content))});
$("#chatcontent").append(rendered);
last = message.id;
handle_last_message_mention();
message.content = message.content.replaceAll("\\<", "<");
message.content = message.content.replaceAll("\\>", ">");
message.content = escapeHtml(message.content);
message.content = parse_message_markdown(message.content);
message = parse_emoji_in_message(message);
var username = message.author.username;
if (message.author.nickname) {
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)});
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;
}
if (replace == null) {
$("html, body").animate({ scrollTop: $(document).height() }, "slow");
}
$('#chatcontent').linkify({
target: "_blank"
});
$('.tooltipped').tooltip();
return last;
}
function run_fetch_routine() {
if (currently_fetching) {
return;
}
currently_fetching = true;
times_fetched += 1;
var channel_id = selected_channel;
var fet;
var jumpscroll;
@ -524,27 +758,28 @@
}
fet.done(function(data) {
var status = data.status;
update_embed_userchip(status.authenticated, status.avatar, status.username, status.user_id, status.discriminator);
if (visitor_mode) {
update_embed_userchip(false, null, "Titan", null, "0001", null);
update_change_username_modal();
} 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 (status.manage_embed) {
if (!visitor_mode && status.manage_embed) {
$("#administrate_link").show();
} 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);
}
});
fet.fail(function(data) {
if (data.status == 403) {
@ -554,34 +789,44 @@
$('#loginmodal').modal('open');
Materialize.toast('Session expired! You have been logged out.', 10000);
}
});
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);
});
}
function update_embed_userchip(authenticated, avatar, username, userid, discrim=null) {
function update_embed_userchip(authenticated, avatar, username, nickname, userid, discrim=null) {
if (authenticated) {
$("#currentuserimage").show();
$("#currentuserimage").attr("src", avatar);
$("#curuser_name").text(username);
$("#curuser_discrim").text("#" + discrim);
current_username_discrim = username + "#" + discrim;
current_username_discrim = "#" + discrim;
} else {
$("#currentuserimage").hide();
$("#curuser_name").text(username);
$("#curuser_discrim").text("#" + userid);
current_username_discrim = username + "#" + userid;
current_username_discrim = "#" + userid;
}
if (nickname) {
$("#curuser_name").text(nickname);
current_username_discrim = nickname + current_username_discrim;
} else {
current_username_discrim = username + current_username_discrim;
}
}
function update_change_username_modal(authenticated=false, username=null) {
if (!$("#change_username_field") || $("#change_username_field").is(":focus")) {
return;
}
if (authenticated || visitor_mode) {
$("#change_username_field").attr("disabled", true);
$("#change_username_field").val("");
} else {
$("#change_username_field").attr("disabled", false);
$("#change_username_field").val(username);
}
}
@ -600,6 +845,7 @@
lock_login_fields();
var usr = create_unauthenticated_user($(this).val());
usr.done(function(data) {
setVisitorMode(false);
initialize_embed();
});
usr.fail(function(data) {
@ -611,24 +857,65 @@
Materialize.toast('Illegal username provided! Only alphanumeric, spaces, dashes, and underscores allowed in usernames.', 10000);
}
unlock_login_fields();
setVisitorMode(true);
});
}
}
});
$("#messagebox").keyup(function(event){
$("#change_username_field").keyup(function(event){
if (event.keyCode == 13) {
$(this).blur();
if (!(new RegExp(/^[a-z\d\-_\s]+$/i).test($(this).val()))) {
Materialize.toast('Illegal username provided! Only alphanumeric, spaces, dashes, and underscores allowed in usernames.', 10000);
return;
}
if(($(this).val().length >= 2 && $(this).val().length <= 32) && $("#curuser_name").text() != $(this).val()) {
var usr = change_unauthenticated_username($(this).val());
usr.done(function(data) {
Materialize.toast('Username changed successfully!', 10000);
if (socket) {
run_fetch_routine();
socket.disconnect();
socket = null;
}
initiate_websockets();
});
usr.fail(function(data) {
if (data.status == 429) {
Materialize.toast('Sorry! You are allowed to change your username once every 15 minutes.', 10000);
} else if (data.status == 403) {
Materialize.toast('Authentication error! You have been banned.', 10000);
} else if (data.status == 406) {
Materialize.toast('Illegal username provided! Only alphanumeric, spaces, dashes, and underscores allowed in usernames.', 10000);
} else {
Materialize.toast('Something unexpected happened! Error code of ' + data.status, 10000);
}
});
}
}
});
$("#messagebox").keyup(function (event) {
if (event.keyCode == 16) {
shift_pressed = false;
}
});
$("#messagebox").keydown(function(event){
if ($(this).val().length == 1) {
$(this).val($.trim($(this).val()));
}
if(event.keyCode == 13 && $(this).val().length >= 1 && $(this).val().length <= 350) {
if (event.keyCode == 16) {
shift_pressed = true;
}
if(event.keyCode == 13 && !shift_pressed && $(this).val().length >= 1 && $(this).val().length <= 350) {
$(this).val($.trim($(this).val()));
$(this).blur();
$("#messagebox").attr('readonly', true);
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);
@ -640,11 +927,12 @@
});
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() {
$("#messagebox").attr('readonly', false);
$("#messagebox").focus();
});
}
});
@ -663,4 +951,208 @@
draggable: true // Choose whether you can drag to open on touch screens
}
);
// enter konami code into the embed page for some ponies action!
cheet('↑ ↑ ↓ ↓ ← → ← → b a', function () {
// 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

@ -1,5 +1,5 @@
{% extends 'site_layout.html.j2' %}
{% block title %}About Titan{% endblock %}
{% set title="About Titan" %}
{% block content %}
<p class="flow-text center-align">In the beginning, there was <strong>silence</strong>.</p>
@ -40,34 +40,19 @@ used to replace Discord itself</strong>. (that's what the mobile apps are for!)
It is used in conjunction for a quick and dirty Discord embed for websites. Some uses include forum shoutboxes,
etc.</p>
<h3>Commands</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">All commands start by <strong>mentioning</strong> the bot user, <em>@Titan</em>.</p>
<h4>Guest User Moderation</h4>
<p>All guest users are denoted by <strong>square brackets</strong> around their username in the Discord channel, when sending messages.</p>
<ul class="collection">
<li class="collection-item"><strong>ban &lt;username-query&gt;[#&lt;discriminator&gt;]</strong> <br> Bans the user by the username. The username does not need to be the full string. The discriminator is optional. <br> <em>Eg: ban Titan#0001</em></li>
<li class="collection-item"><strong>kick &lt;username-query&gt;[#&lt;discriminator&gt;]</strong> <br> Kicks the user by the username. The username does not need to be the full string. The discriminator is optional. <br> <em>Eg: kick Titan#0001</em></li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% include 'card_commands.html.j2' %}
{% include 'card_queryparams.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>
<p class="flow-text">Keep in mind, this project is not complete without these awesome people!</p>
<div class="row">
<div class="col s12 m6">
<div class="row" id="cool-people">
<div class="col s12 m6 person">
<div class="card-panel indigo lighten-5 z-depth-1 no-height-padding">
<div class="row valign-wrapper">
<div class="col s2">
@ -82,7 +67,7 @@ etc.</p>
</div>
</div>
<div class="col s12 m6">
<div class="col s12 m6 person">
<div class="card-panel indigo lighten-5 z-depth-1 no-height-padding">
<div class="row valign-wrapper">
<div class="col s2">
@ -90,44 +75,74 @@ etc.</p>
</div>
<div class="col s10">
<h5 class="black-text card-title">iAmMaffie_</h5>
<p class="black-text flow-text">Developer</p>
<p class="black-text">A mysterious guy, he helped out Titan with his Python coding skills, he helped primarely with the Emoji parsers and the Discord Bot commands system.</p>
<p class="black-text flow-text">Head Developer</p>
<p class="black-text">Have you heard of JustMaffie? He has done quite some developing for Titan.</p>
</div>
</div>
</div>
</div>
<div class="col s12 m6">
<div class="col s12 m6 person">
<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 person">
<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/selina.png" alt="Selina" class="circle responsive-img">
</div>
<div class="col s10">
<h5 class="black-text card-title">Selina</h5>
<p class="black-text flow-text">Server Witch</p>
<p class="black-text">This gal manages our server and figures out how to keep the hamsters in the basement in line! Without her they might all run away!</p>
</div>
</div>
</div>
</div>
<div class="col s12 m6 person">
<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="col s12 m6 person">
<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/js.png') }}" alt=".JS" class="circle responsive-img">
<img src="/static/img/people/dotjs.jpg" alt="dotJS" class="circle responsive-img">
</div>
<div class="col s10">
<h5 class="black-text card-title">.JS</h5><!-- Yes this dot is intentional -->
<p class="black-text flow-text">Embed Theme Architect</p>
<p class="black-text">.JS didn't make the designs in JavaScript, he helped out Titan with his CSS skillz.</p>
<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="col s12 m6 person">
<div class="card-panel indigo lighten-5 z-depth-1 no-height-padding">
<div class="row valign-wrapper">
<div class="col s2">
@ -135,8 +150,8 @@ etc.</p>
</div>
<div class="col s10">
<h5 class="black-text card-title">Semic</h5>
<p class="black-text flow-text">Logo/Asset Designer</p>
<p class="black-text">Semic made most of our graphics such as the Logo and moooooore.</p>
<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>
@ -181,22 +196,88 @@ etc.</p>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-1">
<div class="row">
<div class="col s12 l2">
<img src="{{ url_for('static', filename='img/partners/reddit_tech.png') }}" alt="" class="circle responsive-img">
<img src="{{ url_for('static', filename='img/partners/everybot.jpg') }}" alt="" class="circle responsive-img">
</div>
<div class="col s12 l10">
<h5 class="black-text card-title">Reddit Tech</h5>
<p class="black-text flow-text">Reddit Technology Discussions</p>
<p class="black-text">Are you interested in technology? Want to discuss your PC build with others, or anything else tech related? Just want to hang out with fellow PCMasterRace people? Then Reddit Tech is the place for you!</p>
<a href="https://discord.gg/WS5DadN" class="waves-effect btn" target="_blank">Discord Server</a>
<h5 class="black-text card-title">Everybot</h5>
<p class="black-text flow-text">Multipurpose Bot</p>
<p class="black-text">Hey, thank you for reading this, Everybot is a Discord Bot created by JustMaffie, we aim to bring fun and moderation to Discord servers using our commands.
We also want to provide a nice community at our Discord Server.</p>
<a href="https://discord.gg/7creW7n" class="waves-effect btn" target="_blank">Discord Server</a>
<a href="https://discordapp.com/oauth2/authorize?client_id=343764326751928320&scope=bot&permissions=8" class="waves-effect btn" target="_blank">Bot Invite</a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-1">
<div class="row">
<div class="col s12 l2">
<img src="{{ url_for('static', filename='img/partners/ping_salar_emote.png') }}" alt="" class="circle responsive-img">
</div>
<div class="col s12 l10">
<h5 class="black-text card-title">Ping and Salar's Emote List</h5>
<p class="black-text flow-text">Discord Global Emojis Listing</p>
<p class="black-text">Do you want to use global emotes, but can't afford Nitro? Well look no further! The Discord Universe contains a few servers, which have emotes you can use EVERYWHERE! We're a community about discovering those servers, and sharing them with you! All you have to do is click the link below and go to the #links channel! From there, you can select the servers you wish to get emotes from and use them on all the servers you want! AND IT'S FREE!</p>
<a href="https://discord.gg/kwCUFja" class="waves-effect btn" target="_blank">Discord Server</a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-1">
<div class="row">
<div class="col s12 l2">
<img src="{{ url_for('static', filename='img/partners/streamers_connected.png') }}" alt="" class="circle responsive-img">
</div>
<div class="col s12 l10">
<h5 class="black-text card-title">Streamers Connected</h5>
<p class="black-text flow-text">Community for all Content Creators, Alike</p>
<p class="black-text">Streamers Connected is a community for content creators across all platforms of any genre and size. We strive to provide a place for creators to network, grow, and find any resources they may need to produce the best content they can.
We want to see all of our members rise to their potential and find success in their passion to create and entertain. We work with large and small developers to bring our streamers games at discounted prices, and fund giveaways.</p>
<a href="https://www.streamersconnected.tv/" class="waves-effect btn" target="_blank">Website</a>
<a href="https://discord.gg/StreamersConnected" class="waves-effect btn" target="_blank">Discord Server</a>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-1">
<div class="row">
<div class="col s12 l2">
<img src="{{ url_for('static', filename='img/partners/lgbtq_lounge.jpg') }}" alt="" class="circle responsive-img">
</div>
<div class="col s12 l10">
<h5 class="black-text card-title">LGBTQ+</h5>
<p class="black-text flow-text">A Welcoming Community for People of All Sexuality</p>
<p class="black-text">LGBTQ+ Lounge is a community that welcomes all, regardless of gender or sexuality, come to our server to enjoy yourself and make new friends and involve yourself with some of the events and activities that the staff will organise.</p>
<a href="https://discord.io/lgbtq-lounge" class="waves-effect btn" target="_blank">Discord Server</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.0/masonry.pkgd.min.js" integrity="sha256-YFADoQJIYFj+isdXssMGUrmsVNbVDfN5m8jPgVN+9m4=" crossorigin="anonymous"></script>
<script>
$(function() {
var $container = $('#cool-people');
$container.masonry({
itemSelector: '.person'
});
});
</script>
{% endblock %}

View File

@ -1,10 +1,10 @@
{% extends 'site_layout.html.j2' %}
{% block title %}Adding bot to server{% endblock %}
{% set title="Adding bot to server" %}
{% block content %}
<h1>Would you like to invite Titan to your server?</h1>
<p class="flow-text">Please keep in mind that <strong>Titan currently requires Administrator</strong> permissions to function.<br>
This is strictly enforced for the bot to send messages to all the channels, read role permissions, access banned users list, etc.</p>
<p class="flow-text">Please keep in mind that <strong>Titan works best with Administrator</strong> permissions to function.<br>
However this is not required. For those who do not want to give Titan Administrator, we've handpicked the permissions to give Titan for the best experience.</p>
<div class="row">
<div class="col s12">
<div id="invite-initial" class="card-panel indigo lighten-5 z-depth-3 hoverable">
@ -32,8 +32,7 @@ This is strictly enforced for the bot to send messages to all the channels, read
<span class="black-text center-align">
<h3>Step 2</h3>
<h5>Oops!</h5>
<p class="flow-text">There seems to be a problem processing the invite.<br>
Please make sure that the bot is given Administrator permission in the server.</p>
<p class="flow-text">There seems to be a problem processing the invite.</p>
<hr>
<p>You may try adding the bot to the server again, or report the bug on our
Discord server.</p>

View File

@ -0,0 +1,80 @@
{% extends 'site_layout.html.j2' %}
{% set title="Editing User Cosmetics Privilages" %}
{% block content %}
<h1>Administrating User Cosmetics Privilages</h1>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<p class="flow-text">New Entry</p>
<table class="bordered striped">
<thead>
<tr>
<th>User ID</th>
<th>CSS</th>
<th>Submit</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="input-field inline">
<input id="new_user_id" placeholder="User ID">
</div>
</td>
<td>
<div class="switch">
<label>
Off
<input type="checkbox" id="new_css_switch">
<span class="lever"></span>
On
</label>
</div>
</td>
<td>
<a class="waves-effect waves-light btn" id="new_submit">Submit</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<table class="bordered striped">
<thead>
<tr>
<th>Remove</th>
<th>User ID</th>
<th>CSS</th>
</tr>
</thead>
<tbody>
{% for cosmetic in cosmetics %}
<tr>
<td><a class="waves-effect waves-light btn red" id="new_submit" onclick="delete_user('{{ cosmetic.user_id }}');">Remove</a></td>
<td>{{ cosmetic.user_id }}</td>
<td>
<div class="switch">
<label>
Off
<input type="checkbox" id="new_css_switch" {% if cosmetic.css %}checked{% endif %} onchange="update_css_switch('{{ cosmetic.user_id }}', this)">
<span class="lever"></span>
On
</label>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='js/admin_cosmetics.js') }}"></script>
{% endblock %}

View File

@ -0,0 +1,32 @@
{% extends 'site_layout.html.j2' %}
{% set title="Manage Guilds as Administrator" %}
{% block content %}
<h1>Manage Guilds (Admin)</h1>
<p class="flow-text">Select a server to configure Titan Embeds. <strong>(Total server count: {{ servers|length }})</strong></p>
<div class="row">
{% for server in servers %}
<div class="col l4 m6 s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable">
<div class="row valign-wrapper">
<div class="col s3">
{% if server.icon %}
<img src="{{ icon_generate(server.guild_id, server.icon) }}" alt="" class="circle responsive-img">
{% else %}
<span class="black-text">No icon :(</span>
{% endif %}
</div>
<div class="col s7">
<span class="black-text">
<p class="flow-text truncate">{{ server.name }}</p>
<br>
<a class="waves-effect waves-light btn" href="{{url_for('admin.administrate_guild', guild_id=server['guild_id'])}}">Modify</a>
</span>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,41 @@
{% extends 'site_layout.html.j2' %}
{% set title="Admin" %}
{% block content %}
<h1>Administrate Titan Embeds</h1>
<p class="flow-text">Select an action.</p>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>Cosmetics</h4>
<p class="flow-text">Give or revoke special <em>cosmetics privilages</em> for users.</p>
<a class="waves-effect waves-light btn" href="{{ url_for('admin.cosmetics') }}">Manage</a>
</div>
</div>
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>Guilds</h4>
<p class="flow-text">Manage any guild in Titan, this will give you the normal dashboard.</p>
<a class="waves-effect waves-light btn" href="{{ url_for('admin.guilds') }}">Manage</a>
</div>
</div>
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>Titan Tokens</h4>
<p class="flow-text">View transactions and modify Titan Tokens per user.</p>
<a class="waves-effect waves-light btn" href="{{ url_for('admin.manage_titan_tokens') }}">Manage</a>
</div>
</div>
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>Run a Database Cleanup</h4>
<p class="flow-text">Clears the keyval caches and purges the old messages. (Hit once, and wait a minute)</p>
<a class="waves-effect waves-light btn" id="db_cleanup_btn">Run DB Cleanup Task</a>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='js/admin_index.js') }}"></script>
{% endblock %}

View File

@ -0,0 +1,105 @@
{% extends 'site_layout.html.j2' %}
{% set title="Editing User Titan Tokens" %}
{% block content %}
<h1>Administrating Titan Tokens</h1>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<p class="flow-text">New Entry</p>
<table class="bordered striped">
<thead>
<tr>
<th>User ID</th>
<th>Starting Balance</th>
<th>Submit</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="input-field inline">
<input id="new_user_id" placeholder="User ID">
</div>
</td>
<td>
<div class="input-field inline">
<input id="new_user_token" placeholder="Starting Balance" type="number">
</div>
</td>
<td>
<a class="waves-effect waves-light btn" id="new_submit">Submit</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<p class="flow-text">View Transactions and Modify User Tokens</p>
<ul class="collapsible" data-collapsible="accordion">
{% for don in donators %}
<li>
<div class="collapsible-header">{{ don.user_id }}</div>
<div class="collapsible-body">
<table class="bordered striped">
<thead>
<tr>
<th>Modify Amount</th>
<th>Submit</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="input-field inline">
<input placeholder="Modify Amount" type="number" id="input_{{ don.user_id }}">
</div>
<p>(Place a subtract sign in the front to remove tokens. Otherwise, it will add the amount)</p>
</td>
<td>
<a class="waves-effect waves-light btn" onclick="submit_modify_user('{{ don.user_id }}')">Submit</a>
</td>
</tr>
</tbody>
</table>
<h4>Balance: <strong>{{ don.tokens }}</strong> Tokens</h4>
<table class="striped">
<thead>
<tr>
<th>Trans #</th>
<th>Timestamp</th>
<th>Action</th>
<th>Change</th>
<th>Starting Bal</th>
<th>Ending Bal</th>
</tr>
</thead>
<tbody>
{% for trans in don.transactions %}
<tr>
<td>{{ trans.id }}</td>
<td>{{ trans.timestamp }}</td>
<td>{{ trans.action }}.</td>
<td>{{ trans.net_tokens }}</td>
<td>{{ trans.start_tokens }}</td>
<td>{{ trans.end_tokens }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='js/admin_token_transactions.js') }}"></script>
{% endblock %}

View File

@ -1,5 +1,5 @@
{% extends 'site_layout.html.j2' %}
{% block title %}Administrate Guild: {{ guild['name'] }}{% endblock %}
{% set title="Administrate Guild: " + guild['name'] %}
{% block additional_head_elements %}
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/administrate_guild.css') }}">
@ -51,6 +51,32 @@
<br>
<p class="flow-text">Toggle Visitor Mode</p>
<p>Visitors are able to view the channels that @everyone has access to. However, they are not able to send messages until they login using the usual methods.</p>
<div class="switch">
<label>
Disable
<input type="checkbox" id="visitor_view" name="visitor_view" {% if guild['visitor_view'] %}checked{% endif %} >
<span class="lever"></span>
Enable
</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>
<div class="switch">
<label>
@ -160,6 +186,11 @@
{% endif %}
</div>
<br><hr>
{% include 'card_commands.html.j2' %}
{% include 'card_queryparams.html.j2' %}
{% endblock %}
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='js/administrate_guild.js') }}"></script>

View File

@ -0,0 +1,18 @@
<h3>Commands</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">All commands start by <strong>mentioning</strong> the bot user, <em>@Titan</em>.</p>
<h4>Guest User Moderation</h4>
<p>All guest users are denoted by <strong>square brackets</strong> (or Titan's logo as avatar if enabled Webhook Messages) around their username in the Discord channel, when sending messages.</p>
<ul class="collection">
<li class="collection-item"><strong>ban &lt;username-query&gt;[#&lt;discriminator&gt;]</strong> <br> Bans the user by the username. The username does not need to be the full string. The discriminator is optional. <br> <em>Eg: ban Titan#0001</em></li>
<li class="collection-item"><strong>kick &lt;username-query&gt;[#&lt;discriminator&gt;]</strong> <br> Kicks the user by the username. The username does not need to be the full string. The discriminator is optional. <br> <em>Eg: kick Titan#0001</em></li>
</ul>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,43 @@
<h3>Query Parameters</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">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.com/embed/1234567890<strong>?css=1&defaultchannel=81387914189078528&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">
<strong>css=&lt;integer&gt;</strong> <br>
Styles the embed's theme according to the unique custom CSS ID. Custom CSS may be managed from the user dashboard page. <br>
<em>Eg: css=1</em>
</li>
<li class="collection-item">
<strong>defaultchannel=&lt;snowflake&gt;</strong> <br>
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>theme=&lt;string&gt;</strong> <br>
Want your embed to use one of our premade themes? Look no further! <br>
<hr>
<strong>Avaliable Options:</strong>
<ul class="browser-default">
<li><strong>BetterTitan</strong></li>
<li><strong>DiscordDark</strong></li>
</ul>
<hr>
<em>Eg: theme=DiscordDark</em>
</li>
<li class="collection-item">
<strong>username=&lt;string&gt;</strong> <br>
Prefills the guest username field with the given username. <br>
<em>Eg: username=Rainbow%20Dash</em>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
{% extends 'site_layout.html.j2' %}
{% block title %}Dashboard{% endblock %}
{% set title="Dashboard" %}
{% block content %}
<h1>User Dashboard</h1>
@ -60,4 +60,17 @@
{% endfor %}
</div>
{% endif %}
<hr>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>Donations!</h4>
<p class="flow-text">Would you like to support the Titan Embeds project?</p>
<p>You currently have <strong>{{ session["tokens"] }}</strong> Titan Tokens.</p>
<a class="waves-effect waves-light btn" href="{{ url_for('user.donate_get') }}">Donate!!</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,51 @@
{% extends 'site_layout.html.j2' %}
{% set title="Donate" %}
{% block content %}
<h1>Donate and Support Titan Embeds</h1>
<p class="flow-text">Contributing to the Titan project has never been so easy! Donate to support our project development and hosting.</p>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>The Name-Your-Price Tool</h4>
<p class="flow-text">Currently if you donate, we cannot give much back in return, yet. However, we do have some donatator features up our sleeves and will be implemented.</p>
<p class="flow-text">For now, you will receive <strong>Titan Tokens&trade;</strong> (to be spent on donator features) and a <strong>supporter role</strong> on our support server.</p>
<p class="range-field">
<input type="range" id="token-slider" min="1" max="100" value="5" />
</p>
<p class="flow-text">$<span id="money-display">5</span> for <strong><span id="token-display">500</span> tokens</strong>!</p>
<a class="waves-effect waves-light btn" id="donate-btn">Donate</a>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script type="text/javascript" src="{{ url_for('static', filename='js/donate.js') }}"></script>
{% endblock %}
{% block additional_head_elements %}
<style>
input[type=range]::-webkit-slider-thumb {
background-color: #303f9f;
}
input[type=range]::-moz-range-thumb {
background-color: #303f9f;
}
input[type=range]::-ms-thumb {
background-color: #303f9f;
}
/***** These are to edit the thumb and the text inside the thumb *****/
input[type=range] + .thumb {
background-color: #dedede;
}
input[type=range] + .thumb.active .value {
font-size: 12pt;
color: #303f9f;
}
input[type=range] + .thumb.active .value::before {
content: "$";
}
</style>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends 'site_layout.html.j2' %}
{% set title="Thanks for Donating" %}
{% block content %}
<h1>Thank you for Donating and Supporting the Titan Embeds project!</h1>
<div class="row">
<div class="col s12">
<div class="card-panel indigo lighten-5 z-depth-3 hoverable black-text">
<h4>You're officially one step closer to becoming a True Titan!</h4>
<p class="flow-text">You now have <strong>{{ tokens }}</strong> tokens!</p>
<p>Please visit our support server and contact a True Titan (Administrators Role) to claim your Supporter role, if you haven't done so already. Mention the transaction ID of <strong>{{ transaction }}</strong>.</p>
<a class="waves-effect waves-light btn" href="https://discord.io/Titan" target="_blank">Support Server</a>
<p><em>Have a nice day!</em></p>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html prefix="og: http://ogp.me/ns#">
<head>
<!--Import Google Icon Font-->
<link href="//fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
@ -12,6 +12,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
{% include 'seo_meta.html.j2' %}
{% with title="Visit " + guild['name'] + " embed", description="Visit " + guild['name'] + " on Titan Embeds and chat with your friends from the comfort of your own website. This page is 100% embeddable, iFrameable and looks good on any webpages. Titan is hassle free and designed as easy to setup!", image=generate_guild_icon( guild['id'], guild['icon']) %}
{% include "opengraph_tags.html.j2" %}
{% endwith %}
<title>{{ guild['name'] }} - Embed - Titan Embeds for Discord</title>
{% include 'google_analytics.html.j2' %}
@ -21,6 +24,7 @@
{% endif %}
</head>
<body>
{% include 'nobot_header.html.j2' %}
<div class="navbar-fixed">
<nav>
<div class="nav-wrapper">
@ -39,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>
@ -76,6 +78,7 @@
<div id="loginmodal" class="modal">
<div class="modal-content">
{% include 'nobot_header.html.j2' %}
<h4>{{ login_greeting }}</h4>
<div id="loginmodal-maincontent" class="row valign-wrap">
<div id="modal_guildinfobox" class="col m3 s12 center-align">
@ -112,15 +115,18 @@
</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 %}
<h4>Change Username</h4>
<div class="row">
<div>
<p>(Guests Accounts Only)</p>
<input id="change_username_field" type="text" {% if session.unauthenticated and session.username %}value="{{ session['username'] }}"{% else %}disabled{% endif %}>
<label class="active" for="change_username_field">Change your username (Hit ENTER/RETURN key to confirm)</label>
</div>
</div>
{% endif %}
<h4>Theme</h4>
<div class="row">
<div class="input-field col s12">
@ -129,11 +135,24 @@
<option value="DiscordDark">DiscordDark</option>
<option value="BetterTitan">BetterTitan</option>
</select>
<p>
<input type="checkbox" class="filled-in" id="overwrite_theme_custom_css_checkbox" checked="checked" />
<label for="overwrite_theme_custom_css_checkbox">Overwrite Current Embed Theme w/ User CSS</label>
</p>
</div>
</div>
</div>
</div>
<div id="emoji-picker">
<div id="emoji-picker-content">
<div class="row">
<h6>Server Emoji</h6>
<div id="emoji-picker-emojis"></div>
</div>
</div>
</div>
<footer id="footer" class="footer">
<div id="fetching-indicator" class="preloader-wrapper small active" style="display: none;">
<div class="spinner-layer spinner-blue-only">
@ -146,12 +165,18 @@
</div>
</div>
</div>
<div id="emoji-tray-toggle">
<a class="btn-floating btn-large waves-effect waves-light"><i class="material-icons">tag_faces</i></a>
</div>
<div id="footercontainer" class="footercontainer">
<div class="currentuserchip left" id="nameplate">
<div class="left"><img id="currentuserimage" src="" class="circle left currentuserimage" style="display: none;"></div>
<div id="currentusername" class="currentusername left"><span id="curuser_name">Titan</span><span id="curuser_discrim">#0001</span></div>
</div>
<div id="messageboxouter" class="input-field inline"><textarea placeholder="Enter message" id="messagebox" type="text" class="materialize-textarea" rows="1"></textarea></div>
<div id="messageboxouter" class="input-field inline">
<textarea placeholder="Enter message" id="messagebox" type="text" class="materialize-textarea" rows="1"></textarea>
<span id="visitor_mode_message" style="display:none;"><span id="visitor_mode_message_note">Please login to post a message.</span> <a id="visitor_login_btn" class="waves-effect waves-light btn">Login</a></span>
</div>
</div>
</footer>
@ -162,6 +187,9 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js" integrity="sha256-1hjUhpc44NwiNg8OwMu2QzJXhD8kcj+sJA3aCQZoUjg=" crossorigin="anonymous"></script>
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twemoji/2.5.0/2/twemoji.min.js" integrity="sha256-t5bxASdQ5tDbKQZy330h/YufCiZg82xG8PqIYzFpwhU=" crossorigin="anonymous"></script>
{% raw %}
<script id="mustache_channellistings" type="text/template">
@ -177,17 +205,22 @@
</script>
<script id="mustache_usermessage" type="text/template">
<p><span id="discordmessage_{{id}}" title="{{full_timestamp}}" class="chattimestamp">{{time}}</span> <span class="chatusername">{{username}}#{{discriminator}}</span> <span class="chatmessage">{{{content}}}</span></p>
<p><span class="chatusername">{{username}}#{{discriminator}}</span> <span id="discordmessage_{{id}}" title="{{full_timestamp}}" class="chattimestamp">{{time}}</span> <span class="chatmessage">{{{content}}}</span></p>
</script>
<script id="mustache_memberrole" type="text/template">
<li><a class="subheader role-title">{{name}}</a></li>
</script>
<script id="mustache_message_emoji" type="text/template">
<img class="message_emoji tooltipped" src='https://cdn.discordapp.com/emojis/{{id}}.png' data-position="top" data-delay="200" data-tooltip=":{{name}}:" />
</script>
{% endraw %}
<script>
const guild_id = "{{ guild_id }}";
const bot_client_id = "{{ client_id }}";
const visitors_enabled = {% if visitors_enabled %}true{% else %}false{% endif %};
</script>
<script type="text/javascript" src="{{ url_for('static', filename='js/embed.js') }}"></script>

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

@ -1,5 +1,5 @@
{% extends 'site_layout.html.j2' %}
{% block title %}Index{% endblock %}
{% set title="Index" %}
{% block content %}
<h1 class="center-align">Embed Discord like a<br><strong>true Titan</strong></h1>

View File

@ -0,0 +1,10 @@
{% if not bot_status["status"] %}
<div style="border: solid 3px red; background-color: yellow; color: black;">
<p>
<strong>NOTICE!</strong>
The bot is <strong>currently not online</strong> or has <strong>lost the connection</strong> to the webserver.
If you see this header, please <a href="https://discord.io/titan" target="_blank" style="background-color: orange; color: blue;">notify us</a> as soon as possible for us to fix this issue.
Down since approximately <code>{{ bot_status["formatted_utc"] }}</code> UTC (<code>{{ bot_status["epoch_seconds"] }} epoch seconds</code>).
</p>
</div>
{% endif %}

View File

@ -0,0 +1,5 @@
<meta property="og:title" content="{{ title }} page on Titan Embeds for Discord" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ request.base_url }}" />
<meta property="og:image" content="{{ url_for('static', filename='img/titanembeds_round.png', _external=True) if not image else image }}" />
<meta property="og:description" content="{{ description }}" />

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html prefix="og: http://ogp.me/ns#">
<head>
<!--Import Google Icon Font-->
<link href="//fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
@ -10,20 +10,31 @@
<!--Let browser know website is optimized for mobile-->
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{% block title %}{% endblock %} - Titan Embeds for Discord</title>
<title>{{ title }} - Titan Embeds for Discord</title>
{% include 'seo_meta.html.j2' %}
{% with title=title, description="Embed your Discord server in any website. Titan is hassle free and designed as easy to setup." %}
{% include "opengraph_tags.html.j2" %}
{% endwith %}
{% block additional_head_elements %}{% endblock %}
{% include 'google_analytics.html.j2' %}
</head>
<body>
{% include 'nobot_header.html.j2' %}
<main>
{% if session['unauthenticated'] is defined and not session['unauthenticated'] %}
<ul id="menu_dropdown" class="dropdown-content">
<li><a href="{{ url_for('user.dashboard') }}">Dashboard</a></li>
<li class="divider"></li>
<li><a href="{{ url_for('user.donate_get') }}"><i class="material-icons">monetization_on</i></nbr>{{ session["tokens"] }}</a></li>
{% if session['user_id'] is defined and session['user_id'] in devs %}
<li class="divider"></li>
<li><a href="{{ url_for('admin.index') }}">Admin</a></li>
{% endif %}
<li class="divider"></li>
<li><a href="{{ url_for('user.logout') }}">Logout</a></li>
</ul>
{% endif %}
@ -59,7 +70,7 @@
<div class="footer-copyright">
<div class="container">
A project by EndenDragon
<a class="grey-text text-lighten-4 right" href="https://github.com/EndenDragon/Titan">GitHub Repo</a>
<a class="grey-text text-lighten-4 right" href="https://github.com/TitanEmbeds/Titan">GitHub Repo</a>
</div>
</div>
</footer>

View File

@ -1,5 +1,9 @@
{% extends 'site_layout.html.j2' %}
{% block title %}{% if new %}New{% else %}Editing {{ css.name }} -{% endif %} User CSS{% endblock %}
{% if new %}
{% set title="New - User CSS" %}
{% else %}
{% set title="Editing " + css.name + " - User CSS" %}
{% endif %}
{% block content %}
<h1>{% if new %}New{% else %}Editing {{ css.name }}{% endif %} - User Defined CSS</h1>
@ -14,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>
@ -80,7 +84,7 @@ will have CSS cosmetic privilages removed, if caught. Please don't, we check the
<div class="col s12">
<p class="flow-text">Edit your CSS code here</p>
<div style="position: relative; height: 40vh;">
<div id="css_editor">{% if new %}/* Enter your CSS code here! */{% else %}{{ css.css }}{% endif %}</div>
<div id="css_editor">{% if new %}/* Enter your CSS code here! */{% else %}{{ css.css|e }}{% endif %}</div>
</div>
<br>
</div>

View File

@ -0,0 +1,216 @@
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 config import config
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)
bot_member_roles = get_member_roles(guild_id, config["client-id"])
if guild_id not in bot_member_roles:
bot_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 = get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_roles, session.get("user_id"), force_everyone)
bot_result = get_channel_permission(channel, guild_id, guild_owner, guild_roles, bot_member_roles, config["client-id"], False)
if not bot_result["read"]:
result["read"] = False
if not bot_result["write"]:
result["write"] = False
if not bot_result["mention_everyone"]:
result["mention_everyone"] = False
result_channels.append(result)
return sorted(result_channels, key=lambda k: k['channel']['position'])
def get_channel_permission(channel, guild_id, guild_owner, guild_roles, member_roles, user_id=None, force_everyone=False):
result = {"channel": channel, "read": False, "write": False, "mention_everyone": False}
if not user_id:
user_id = session.get("user_id")
if guild_owner == user_id:
result["read"] = True
result["write"] = True
result["mention_everyone"] = True
return result
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
return result
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
return result
def bot_can_create_webhooks(guild):
perm = 0
guild_roles = json.loads(guild.roles)
# @everyone
for role in guild_roles:
if role["id"] == guild.guild_id:
perm |= role["permissions"]
continue
member_roles = get_member_roles(guild.guild_id, config["client-id"])
# User Guild Roles
for m_role in member_roles:
for g_role in guild_roles:
if g_role["id"] == m_role:
perm |= g_role["permissions"]
continue
return user_has_permission(perm, 29)
def guild_webhooks_enabled(guild_id):
dbguild = db.session.query(Guilds).filter(Guilds.guild_id == guild_id).first()
if not dbguild.webhook_messages:
return False
return bot_can_create_webhooks(dbguild)

View File

@ -1,10 +1,12 @@
from titanembeds.database import db, Guilds, KeyValueProperties
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
import hashlib
import time
from titanembeds.discordrest import DiscordREST
@ -15,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
@ -30,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()
@ -65,8 +67,27 @@ def check_guild_existance(guild_id):
else:
return True
def guild_accepts_visitors(guild_id):
dbGuild = Guilds.query.filter_by(guild_id=guild_id).first()
return dbGuild.visitor_view
def guild_query_unauth_users_bool(guild_id):
dbGuild = db.session.query(Guilds).filter(Guilds.guild_id==guild_id).first()
return dbGuild.unauth_users
def bot_alive():
results = {"status": False, "formatted_utc": "Never", "epoch_seconds": None}
epoch = get_keyvalproperty("bot_heartbeat")
if not epoch:
return results
epoch = float(epoch)
utc = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(epoch))
results["formatted_utc"] = utc
results["epoch_seconds"] = epoch
now = time.time()
if now - epoch < 60 * 5:
results["status"] = True
return results
rate_limiter = Limiter(key_func=get_client_ipaddr) # Default limit by ip address
socketio = SocketIO()