Merge pull request #48 from TitanEmbeds/master
Merge latest master to cssvars branch
31
CONTRIBUTING.md
Normal 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!
|
@ -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.
|
||||
|
@ -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 "------------------------------"
|
||||
@ -44,4 +42,4 @@ echo ""
|
||||
echo "After you finished editing those files, you may right click on run_c9.py and click run in the menu to start the webapp."
|
||||
echo "To run the discordbot, change your directory to discord bot: cd discordbot/"
|
||||
echo "and type the following command: python3.5 run.py"
|
||||
echo "------------------------------"
|
||||
echo "------------------------------"
|
||||
|
97
discordbot/cleanup_db_messages.py
Normal 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()
|
@ -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",
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
discord.py
|
||||
sqlalchemy
|
||||
asyncio_extras
|
@ -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:
|
||||
@ -31,6 +38,29 @@ class Titan(discord.Client):
|
||||
gathered.exception()
|
||||
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:
|
||||
@ -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)
|
||||
server_bans = await self.get_bans(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)
|
||||
banned = await self.get_bans(guild)
|
||||
for ban in banned:
|
||||
await self.database.update_guild_member(ban, False, True)
|
||||
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
|
||||
|
@ -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,77 +110,23 @@ class DatabaseInterface(object):
|
||||
gui = Guilds(
|
||||
guild.id,
|
||||
guild.name,
|
||||
json.dumps(self.get_roles_list(guild.roles)),
|
||||
json.dumps(self.get_channels_list(guild.channels)),
|
||||
json.dumps(self.get_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():
|
||||
@ -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()
|
||||
|
@ -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
|
||||
|
17
discordbot/titanembeds/database/keyvalue_properties.py
Normal 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
|
1
discordbot/titanembeds/socketio/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .socketiointerface import SocketIOInterface
|
174
discordbot/titanembeds/socketio/socketiointerface.py
Normal 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')
|
98
discordbot/titanembeds/utils.py
Normal 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
@ -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
|
@ -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)
|
@ -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():
|
||||
|
160
webapp/alembic/versions/2a2f32ac91d6_added_titan_tokens.py
Normal 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 ###
|
@ -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 ###
|
@ -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 ###
|
@ -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 ###
|
@ -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 ###
|
@ -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 ###
|
@ -4,9 +4,15 @@ config = {
|
||||
'client-id': "Your app client id",
|
||||
'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",
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
flask
|
||||
flask-sqlalchemy
|
||||
flask_limiter
|
||||
requests_oauthlib
|
||||
Flask-SSLify
|
@ -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)
|
||||
|
@ -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)
|
@ -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()}
|
1
webapp/titanembeds/blueprints/admin/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .admin import admin
|
227
webapp/titanembeds/blueprints/admin/admin.py
Normal 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)
|
@ -1 +1 @@
|
||||
from api import api
|
||||
from .api import api
|
||||
|
@ -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("<", "\<")
|
||||
@ -139,11 +49,16 @@ def format_post_content(guild_id, message):
|
||||
for match in all_mentions:
|
||||
mention = "<@" + match[2: len(match) - 1] + ">"
|
||||
message = message.replace(match, mention, 1)
|
||||
|
||||
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
|
||||
|
||||
if not guild_webhooks_enabled(guild_id):
|
||||
if (session['unauthenticated']):
|
||||
message = u"**[{}#{}]** {}".format(session['username'], session['user_id'], message)
|
||||
else:
|
||||
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,8 +236,30 @@ def post():
|
||||
if not chan.get("write"):
|
||||
status_code = 401
|
||||
elif not illegal_post:
|
||||
userid = session["user_id"]
|
||||
content = format_everyone_mention(chan, content)
|
||||
message = discord_api.create_message(channel_id, 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)
|
||||
response.status_code = status_code
|
||||
@ -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:
|
||||
channelsjson = json.loads(guild.channels)
|
||||
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
|
||||
db.session.commit()
|
||||
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)
|
||||
|
@ -1 +1 @@
|
||||
from embed import embed
|
||||
from .embed import embed
|
||||
|
@ -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)
|
||||
|
1
webapp/titanembeds/blueprints/gateway/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .gateway import Gateway
|
106
webapp/titanembeds/blueprints/gateway/gateway.py
Normal 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)
|
@ -1 +1 @@
|
||||
from user import user
|
||||
from .user import user
|
||||
|
@ -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)
|
@ -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
|
13
webapp/titanembeds/database/administrators.py
Normal 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
|
@ -4,4 +4,12 @@ class Cosmetics(db.Model):
|
||||
__tablename__ = "cosmetics"
|
||||
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
|
||||
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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
18
webapp/titanembeds/database/titan_tokens.py
Normal 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
|
21
webapp/titanembeds/database/token_transactions.py
Normal 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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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 {
|
||||
width: 85%;
|
||||
}
|
||||
.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;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* Adds a hyphen where the word breaks, if supported (No Blink) */
|
||||
-ms-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
#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;
|
||||
|
||||
-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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
@ -225,112 +361,124 @@ a {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
BIN
webapp/titanembeds/static/img/partners/everybot.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
webapp/titanembeds/static/img/partners/lgbtq_lounge.jpg
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
webapp/titanembeds/static/img/partners/ping_salar_emote.png
Normal file
After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 19 KiB |
BIN
webapp/titanembeds/static/img/partners/streamers_connected.png
Normal file
After Width: | Height: | Size: 202 KiB |
BIN
webapp/titanembeds/static/img/people/appledash.png
Normal file
After Width: | Height: | Size: 535 KiB |
BIN
webapp/titanembeds/static/img/people/dotjs.jpg
Normal file
After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 56 KiB |
BIN
webapp/titanembeds/static/img/people/selina.png
Normal file
After Width: | Height: | Size: 156 KiB |
BIN
webapp/titanembeds/static/img/webhook_comparison.png
Normal file
After Width: | Height: | Size: 22 KiB |
82
webapp/titanembeds/static/js/admin_cosmetics.js
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
30
webapp/titanembeds/static/js/admin_index.js
Normal 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);
|
||||
});
|
||||
}
|
||||
})();
|
56
webapp/titanembeds/static/js/admin_token_transactions.js
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
@ -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')
|
||||
|
19
webapp/titanembeds/static/js/donate.js
Normal 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();
|
||||
});
|
||||
})();
|
@ -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 <username-query>[#<discriminator>]</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 <username-query>[#<discriminator>]</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 %}
|
||||
|
@ -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>
|
||||
|
80
webapp/titanembeds/templates/admin_cosmetics.html.j2
Normal 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 %}
|
32
webapp/titanembeds/templates/admin_guilds.html.j2
Normal 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 %}
|
41
webapp/titanembeds/templates/admin_index.html.j2
Normal 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 %}
|
105
webapp/titanembeds/templates/admin_token_transactions.html.j2
Normal 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 %}
|
@ -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') }}">
|
||||
@ -48,6 +48,32 @@
|
||||
Enable
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
@ -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>
|
||||
|
18
webapp/titanembeds/templates/card_commands.html.j2
Normal 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 <username-query>[#<discriminator>]</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 <username-query>[#<discriminator>]</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>
|
43
webapp/titanembeds/templates/card_queryparams.html.j2
Normal 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=<integer></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=<snowflake></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=<string></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=<string></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>
|
@ -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 %}
|
||||
{% endblock %}
|
||||
|
||||
<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 %}
|
51
webapp/titanembeds/templates/donate.html.j2
Normal 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™</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 %}
|
18
webapp/titanembeds/templates/donate_thanks.html.j2
Normal 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 %}
|
@ -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,10 +135,23 @@
|
||||
<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;">
|
||||
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
10
webapp/titanembeds/templates/nobot_header.html.j2
Normal 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 %}
|
5
webapp/titanembeds/templates/opengraph_tags.html.j2
Normal 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 }}" />
|
@ -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>
|
||||
|
@ -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>
|
||||
|
216
webapp/titanembeds/userbookkeeping.py
Normal 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)
|
@ -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()
|