Updates to stuff lol
This commit is contained in:
parent
b3eefa1266
commit
27231fda7b
@ -57,6 +57,8 @@ import json # noqa: E402
|
|||||||
logger.info('JSON Imported')
|
logger.info('JSON Imported')
|
||||||
import aiohttp # noqa: E402
|
import aiohttp # noqa: E402
|
||||||
logger.info('aiohttp Imported')
|
logger.info('aiohttp Imported')
|
||||||
|
import redis # noqa: E402
|
||||||
|
logger.info('redis Imported')
|
||||||
logger.info(f'Misc Libs Import Complete - Took {(datetime.utcnow() - start).total_seconds()} seconds')
|
logger.info(f'Misc Libs Import Complete - Took {(datetime.utcnow() - start).total_seconds()} seconds')
|
||||||
# noinspection PyRedeclaration
|
# noinspection PyRedeclaration
|
||||||
# start = datetime.utcnow()
|
# start = datetime.utcnow()
|
||||||
@ -78,8 +80,11 @@ class Geeksbot(commands.Bot):
|
|||||||
self.config_dir = 'geeksbot/config/'
|
self.config_dir = 'geeksbot/config/'
|
||||||
self.config_file = 'bot_config.json'
|
self.config_file = 'bot_config.json'
|
||||||
self.extension_dir = 'exts'
|
self.extension_dir = 'exts'
|
||||||
|
self.cache = redis.Redis(host=os.environ['REDIS_HOST'], port=os.environ['REDIS_PORT'], db=1, charset="utf-8", decode_responses=True)
|
||||||
self.api_token = os.environ['API_TOKEN']
|
self.api_token = os.environ['API_TOKEN']
|
||||||
self.aio_session = aiohttp.ClientSession(loop=self.loop)
|
self.aio_session = aiohttp.ClientSession(loop=self.loop)
|
||||||
|
self.auth_header = {'Authorization': f'Token {self.api_token}'}
|
||||||
|
self.api_base = 'https://geeksbot.app/api'
|
||||||
with open(f'{self.config_dir}{self.config_file}') as f:
|
with open(f'{self.config_dir}{self.config_file}') as f:
|
||||||
self.bot_config = json.load(f)
|
self.bot_config = json.load(f)
|
||||||
self.embed_color = discord.Colour.from_rgb(49, 107, 111)
|
self.embed_color = discord.Colour.from_rgb(49, 107, 111)
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"load_list": [
|
"load_list": [
|
||||||
"admin"
|
"admin",
|
||||||
|
"exec",
|
||||||
|
"message_events"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
99
geeksbot/exts/message_events.py
Normal file
99
geeksbot/exts/message_events.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
message_logger = logging.getLogger('MessageEvents')
|
||||||
|
|
||||||
|
|
||||||
|
class MessageEvents(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
def get_message_data(self, message: discord.Message):
|
||||||
|
data = {
|
||||||
|
'id': message.id,
|
||||||
|
'author': message.author.id,
|
||||||
|
'guild': message.guild.id,
|
||||||
|
'channel': message.channel.id,
|
||||||
|
'created_at': message.created_at.timestamp(),
|
||||||
|
'tagged_everyone': message.mention_everyone,
|
||||||
|
'content': message.content,
|
||||||
|
'embeds': [e.to_dict() for e in message.embeds],
|
||||||
|
'tagged_users': [user.id for user in message.mentions],
|
||||||
|
'tagged_roles': [role.id for role in message.role_mentions],
|
||||||
|
'tagged_channels': [channel.id for channel in message.channel_mentions],
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
@commands.Cog.listener(name='on_message')
|
||||||
|
async def on_message(self, message):
|
||||||
|
message_logger.info(f'Got Message')
|
||||||
|
r = await self.bot.aio_session.get(f'{self.bot.api_base}/users/{message.author.id}/',
|
||||||
|
headers=self.bot.auth_header)
|
||||||
|
if r.status != 200:
|
||||||
|
message_logger.warning(f'User not found: {message.author.id} Status: {r.status}')
|
||||||
|
return
|
||||||
|
|
||||||
|
user = await r.json()
|
||||||
|
if user.get('logging_enabled'):
|
||||||
|
message_data = self.get_message_data(message)
|
||||||
|
r = await self.bot.aio_session.post(f'{self.bot.api_base}/messages/',
|
||||||
|
headers=self.bot.auth_header,
|
||||||
|
json=message_data)
|
||||||
|
message_logger.info(f'Storing Message:\nStatus: {r.status}\n{await r.json()}')
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_raw_message_delete(self, payload):
|
||||||
|
data = {
|
||||||
|
'deleted_at': datetime.utcnow().timestamp()
|
||||||
|
}
|
||||||
|
r = await self.bot.aio_session.put(f'{self.bot.api_base}/messages/{payload.message_id}',
|
||||||
|
headers=self.bot.auth_header,
|
||||||
|
json=data)
|
||||||
|
message_logger.info(f'Deleting Message {payload.message_id}:\nStatus: {r.status}\n{await r.json()}')
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_raw_bulk_message_delete(self, payload):
|
||||||
|
data = {
|
||||||
|
'deleted_at': datetime.utcnow().timestamp()
|
||||||
|
}
|
||||||
|
for id in payload.message_ids:
|
||||||
|
r = await self.bot.aio_session.put(f'{self.bot.api_base}/messages/{id}',
|
||||||
|
headers=self.bot.auth_header,
|
||||||
|
json=data)
|
||||||
|
message_logger.info(f'Deleting Message {id}:\nStatus: {r.status}\n{await r.json()}')
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_raw_message_edit(self, payload):
|
||||||
|
if payload.data.get('mentions'):
|
||||||
|
tagged_users = [user.id for user in payload.data.get('mentions')]
|
||||||
|
else:
|
||||||
|
tagged_users = None
|
||||||
|
if payload.data.get('mention_roles'):
|
||||||
|
tagged_roles = [role.id for role in payload.data.get('mention_roles')]
|
||||||
|
else:
|
||||||
|
tagged_roles = None
|
||||||
|
if payload.data.get('mention_channels'):
|
||||||
|
tagged_channels = [channel.id for channel in payload.data.get('mention_channels')]
|
||||||
|
else:
|
||||||
|
tagged_channels = None
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'modified_at': datetime.utcnow().timestamp(),
|
||||||
|
'content': payload.data.get('content'),
|
||||||
|
'embeds': payload.data.get('embeds'),
|
||||||
|
'tagged_everyone': payload.data.get('mention_everyone'),
|
||||||
|
'tagged_users': tagged_users,
|
||||||
|
'tagged_roles': tagged_roles,
|
||||||
|
'tagged_channels': tagged_channels
|
||||||
|
}
|
||||||
|
r = await self.bot.aio_session.put(f'{self.bot.api_base}/messages/{payload.message_id}/',
|
||||||
|
headers=self.bot.auth_header,
|
||||||
|
json=data)
|
||||||
|
message_logger.info(f'Editing Message {payload.message_id}\nStatus: {r.status}\n{await r.json()}')
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot):
|
||||||
|
bot.add_cog(MessageEvents(bot))
|
||||||
948
geeksbot/exts/rcon.py
Normal file
948
geeksbot/exts/rcon.py
Normal file
@ -0,0 +1,948 @@
|
|||||||
|
import discord
|
||||||
|
from discord.ext.commands import Cog
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import asyncio
|
||||||
|
import typing
|
||||||
|
from geeksbot.imports import arcon
|
||||||
|
from geeksbot.imports import utils
|
||||||
|
from geeksbot.imports import checks
|
||||||
|
|
||||||
|
rcon_log = logging.getLogger('rcon')
|
||||||
|
|
||||||
|
|
||||||
|
class Rcon(Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
async def connect_rcon_server(self, *, name: str, ip: str, port: int, password: str,
|
||||||
|
single_packet: bool = False, force: bool = False, monitor_chat: bool = False,
|
||||||
|
server_chat_channel: int = None, server_messages_channel: int = None) \
|
||||||
|
-> typing.Union[int, str]:
|
||||||
|
|
||||||
|
if name.replace('_', ' ').title() in self.bot.connected_ark_servers.keys() \
|
||||||
|
and isinstance(self.bot.connected_ark_servers[name], arcon.ARKServer) \
|
||||||
|
and self.bot.connected_ark_servers[name].writer \
|
||||||
|
and not force:
|
||||||
|
rcon_log.info(f"{name.replace('_', ' ').title()} is already connected.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
server = arcon.ARKServer(host=ip, port=port, password=password, single_packet=single_packet,
|
||||||
|
monitor_chat=monitor_chat, server_chat_channel=server_chat_channel,
|
||||||
|
server_messages_channel=server_messages_channel)
|
||||||
|
rcon_log.info(f'{name.replace("_", " ").title()} configured.')
|
||||||
|
connected = await server.connect()
|
||||||
|
if connected == 1:
|
||||||
|
rcon_log.info(f'{name.replace("_", " ").title()} connected.')
|
||||||
|
self.bot.connected_ark_servers[name.replace('_', ' ').title()] = server
|
||||||
|
return 1
|
||||||
|
elif connected == -1:
|
||||||
|
rcon_log.info(f'{name.replace("_", " ").title()} timeout error.')
|
||||||
|
return 'Timeout Error'
|
||||||
|
elif connected == 0:
|
||||||
|
rcon_log.info(f'{name.replace("_", " ").title()} auth error.')
|
||||||
|
return 'Authentication Failed'
|
||||||
|
else:
|
||||||
|
rcon_log.info(f'{name.replace("_", " ").title()} unknown error.')
|
||||||
|
return 'Unknown Error'
|
||||||
|
|
||||||
|
async def get_rcon_server_by_name(self, *, guild_config: dict, name: str) \
|
||||||
|
-> typing.Union[dict, arcon.ARKServer, None]:
|
||||||
|
guild_servers = guild_config.get('rcon_connections')
|
||||||
|
if guild_servers:
|
||||||
|
if name == '*':
|
||||||
|
server_list = {}
|
||||||
|
for server, info in guild_servers.items():
|
||||||
|
if server == name.replace('_', ' ').title():
|
||||||
|
if server not in self.bot.connected_ark_servers.keys():
|
||||||
|
connection_result = await self.connect_rcon_server(
|
||||||
|
name=server,
|
||||||
|
ip=info['ip'],
|
||||||
|
port=info['port'],
|
||||||
|
password=info['password'],
|
||||||
|
monitor_chat=info.get('monitoring_chat', None),
|
||||||
|
server_chat_channel=info.get('game_chat_chan_id', None),
|
||||||
|
server_messages_channel=info.get('msg_chan_id', None)
|
||||||
|
)
|
||||||
|
if connection_result != 1:
|
||||||
|
return None
|
||||||
|
return self.bot.connected_ark_servers[server]
|
||||||
|
elif name == '*':
|
||||||
|
if server not in self.bot.connected_ark_servers.keys():
|
||||||
|
connection_result = await self.connect_rcon_server(
|
||||||
|
name=server,
|
||||||
|
ip=info['ip'],
|
||||||
|
port=info['port'],
|
||||||
|
password=info['password'],
|
||||||
|
monitor_chat=info.get('monitoring_chat', None),
|
||||||
|
server_chat_channel=info.get('game_chat_chan_id', None),
|
||||||
|
server_messages_channel=info.get('msg_chan_id', None)
|
||||||
|
)
|
||||||
|
if connection_result != 1:
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
server_list[server] = f'Cannot connect to the {server} server.\n{connection_result}'
|
||||||
|
try:
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
server_list[server] = self.bot.connected_ark_servers[server]
|
||||||
|
except KeyError:
|
||||||
|
rcon_log.warning(server_list[server])
|
||||||
|
del server_list[server]
|
||||||
|
else:
|
||||||
|
if name == '*':
|
||||||
|
return server_list
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def admin(bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||||||
|
player = msg.split(' ||| ')[1].split(' (')[0]
|
||||||
|
rcon_log.info(f'{player} requested admin assistance')
|
||||||
|
admin_roles = guild_config.get('admin_roles')
|
||||||
|
if admin_roles:
|
||||||
|
for role in admin_roles:
|
||||||
|
msg = '{0} {1}'.format(msg, discord.utils.get(guild.roles, id=admin_roles[role]).mention)
|
||||||
|
await server_con.server_chat_to_player_name(player, 'GeeksBot: Admin Geeks have been notified you need '
|
||||||
|
'assistance. Please be patient.')
|
||||||
|
return msg
|
||||||
|
|
||||||
|
async def dinowipe(self, bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||||||
|
player = msg.split(' ||| ')[1].split(' (')[0]
|
||||||
|
steam_ref = self.bot.fs_db.collection(f'users').where('steam_name', '==', player)
|
||||||
|
user_info = await self.bot.loop.run_in_executor(self.bot.tpe, steam_ref.get)
|
||||||
|
steamid = None
|
||||||
|
user = None
|
||||||
|
for user in user_info:
|
||||||
|
if user:
|
||||||
|
steamid = user.to_dict().get('steam_id')
|
||||||
|
break
|
||||||
|
if steamid:
|
||||||
|
if not self.bot.dino_wipe_request.get(server_name):
|
||||||
|
if user:
|
||||||
|
player = await patron.Patron.from_id(bot, steamid, discord_id=int(user.id))
|
||||||
|
rcon_log.info(f'{player.steam_name} requested a wild dino wipe')
|
||||||
|
member = guild.get_member(player.discord_id)
|
||||||
|
self.bot.loop.create_task(self.request_dinowipe(member, server_name, server_con))
|
||||||
|
else:
|
||||||
|
await server_con.server_chat_to_player_name(player,
|
||||||
|
'Sorry, an error has occurred please try again.')
|
||||||
|
else:
|
||||||
|
await server_con.server_chat_to_player_name(player, 'A Wild Dino Wipe request is already in progress.')
|
||||||
|
else:
|
||||||
|
await server_con.server_chat_to_player_name(player, 'Sorry. You are not registered to run commands via the '
|
||||||
|
'in-game chat. Please send a chat message in-game '
|
||||||
|
'$register steamid=your_steam_id and follow the '
|
||||||
|
'instructions so I can link your character with your '
|
||||||
|
'Discord account. Thanks')
|
||||||
|
return msg
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def delaywipe(bot, guild: discord.Guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||||||
|
player = msg.split(' ||| ')[1].split(' (')[0]
|
||||||
|
if bot.dino_wipe_request.get(server_name):
|
||||||
|
bot.dino_wipe_request[server_name] = False
|
||||||
|
await server_con.broadcast(f'Wild Dino Wipe has been delayed by {player}. '
|
||||||
|
f'Please watch chat for timing updates')
|
||||||
|
elif bot.dino_wipe_request.get(server_name) is None:
|
||||||
|
await server_con.server_chat_to_player_name(player, 'There are no Wild Dino Wipes pending.')
|
||||||
|
else:
|
||||||
|
await server_con.server_chat_to_player_name(player, 'The dino wipe has already been delayed.')
|
||||||
|
return msg
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def register(bot, guild, msg, server_name, server_con: arcon.ARKServer, guild_config: dict):
|
||||||
|
player = msg.split(' ||| ')[1].split(' (')[0]
|
||||||
|
steamid = msg.split(' ||| ')[1].split('steamid=')[1].split(' ')[0].replace('`', '').replace("'", '').strip()
|
||||||
|
rcon_log.info(f'{player} - {steamid}')
|
||||||
|
try:
|
||||||
|
int(steamid)
|
||||||
|
except ValueError:
|
||||||
|
await server_con.server_chat_to_player_name(player, 'That is not a valid SteamID')
|
||||||
|
return msg
|
||||||
|
else:
|
||||||
|
identifier = randint(10000, 99999)
|
||||||
|
prefix = guild_config.get("prefixes", bot.default_prefix)
|
||||||
|
await server_con.server_chat_to_player_name(player,
|
||||||
|
'Your SteamID has been noted, to finish registering '
|
||||||
|
'please run the following command in the discord '
|
||||||
|
f'channel. You will not be able to run commands '
|
||||||
|
f'in-game until your registration is completed... '
|
||||||
|
f'{prefix[0] if isinstance(prefix, list) else prefix}'
|
||||||
|
f'register {identifier}')
|
||||||
|
bot.pending_registrations[identifier] = (player, steamid)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
@commands.command(name='register')
|
||||||
|
async def register_discord_account(self, ctx, identifier: int=None):
|
||||||
|
user_ref = self.bot.fs_db.document(f'users/{ctx.author.id}')
|
||||||
|
user_info = (await self.bot.loop.run_in_executor(self.bot.tpe, user_ref.get)).to_dict()
|
||||||
|
if user_info.get('steam_id') and user_info.get('steam_name'):
|
||||||
|
await ctx.send('You are already registered to run commands in-game')
|
||||||
|
return
|
||||||
|
|
||||||
|
if identifier not in self.bot.pending_registrations.keys():
|
||||||
|
await ctx.send('That identifier is not in the pending registrations')
|
||||||
|
return
|
||||||
|
|
||||||
|
steam_info = self.bot.pending_registrations[identifier]
|
||||||
|
|
||||||
|
await self.bot.loop.run_in_executor(self.bot.tpe, user_ref.update, {
|
||||||
|
'steam_name': steam_info[0],
|
||||||
|
'steam_id': steam_info[1]
|
||||||
|
})
|
||||||
|
await ctx.send('Registration complete. You are now authorized to run commands in-game on the ARK servers.')
|
||||||
|
del self.bot.pending_registrations[identifier]
|
||||||
|
|
||||||
|
async def create_server_chat_chan(self, guild: discord.Guild, server_name: str,
|
||||||
|
server_con: arcon.ARKServer, guild_config: dict):
|
||||||
|
rcon_log.info(f'Creating channel for {server_name}')
|
||||||
|
category = discord.utils.get(guild.categories, name='Server Chats')
|
||||||
|
if category is None:
|
||||||
|
overrides = {guild.default_role: discord.PermissionOverwrite(read_messages=False)}
|
||||||
|
category = await guild.create_category('Server Chats', overwrites=overrides)
|
||||||
|
rcon_log.info(category)
|
||||||
|
channels = guild.channels
|
||||||
|
if category:
|
||||||
|
for channel in category.channels:
|
||||||
|
if server_con.server_chat_channel == channel.id:
|
||||||
|
return channel
|
||||||
|
else:
|
||||||
|
rcon_log.info(f'Creating {server_name}')
|
||||||
|
chan = await guild.create_text_channel(f'{server_name}', category=category)
|
||||||
|
server_con.server_chat_channel = chan.id
|
||||||
|
if guild_config.get('rcon_connections'):
|
||||||
|
guild_config['rcon_connections'][server_name]['game_chat_chan_id'] = chan.id
|
||||||
|
guild_ref = self.bot.fs_db.document(f'guilds/{guild.id}')
|
||||||
|
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, guild_config)
|
||||||
|
return chan
|
||||||
|
|
||||||
|
async def _monitor_chat(self, guild, server_name, server_con, guild_config: dict):
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
async def start_monitor_chat(bot, guild, *, server_name: str,
|
||||||
|
server_con: arcon.ARKServer, guild_config: dict):
|
||||||
|
while server_con.monitor_chat:
|
||||||
|
messages = await server_con.getchat()
|
||||||
|
rcon_log.debug('Got chat from {0}.'.format(server_name))
|
||||||
|
for message in [msg.strip() for msg in messages.split('\n')
|
||||||
|
if msg.strip() != 'Server received, But no response!!']:
|
||||||
|
rcon_log.info(message)
|
||||||
|
message_out = f'```{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} ||| {message}```'
|
||||||
|
for command in self.bot.game_commands:
|
||||||
|
prefix_command = '{0}{1}'.format(self.bot.game_prefix, command)
|
||||||
|
if message.split('): ')[-1].startswith(prefix_command):
|
||||||
|
try:
|
||||||
|
func = getattr(self, command)
|
||||||
|
except AttributeError:
|
||||||
|
rcon_log.warning('Function not found "{0}"'.format(command))
|
||||||
|
else:
|
||||||
|
rcon_log.info(f'Sending to {command}')
|
||||||
|
message_out = await func(bot, guild, message_out, server_name, server_con, guild_config)
|
||||||
|
|
||||||
|
await guild.get_channel(server_con.server_chat_channel).send(message_out)
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
await guild.get_channel(server_con.server_chat_channel).send('Monitoring Stopped')
|
||||||
|
|
||||||
|
rcon_log.info(server_con.server_chat_channel)
|
||||||
|
if server_con.server_chat_channel:
|
||||||
|
channel = self.bot.get_channel(server_con.server_chat_channel)
|
||||||
|
if not channel:
|
||||||
|
channel = await self.create_server_chat_chan(guild, server_name, server_con, guild_config)
|
||||||
|
else:
|
||||||
|
channel = await self.create_server_chat_chan(guild, server_name, server_con, guild_config)
|
||||||
|
rcon_log.info(channel)
|
||||||
|
self.bot.loop.create_task(start_monitor_chat(self.bot, guild, server_name=server_name,
|
||||||
|
server_con=server_con, guild_config=guild_config))
|
||||||
|
await channel.send('Started monitoring on the {0} server.'.format(server_name))
|
||||||
|
rcon_log.debug('Started monitoring on the {0} server.'.format(server_name))
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def monitor_chat(self, ctx, *, server=None):
|
||||||
|
"""Begins monitoring the specified ARK server for chat messages and other events.
|
||||||
|
The specified server must already be in the current guild\'s configuration.
|
||||||
|
To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
|
||||||
|
The server argument is not case sensitive and if the server name has two
|
||||||
|
words it can be in one of the following forms:
|
||||||
|
first last
|
||||||
|
first_last
|
||||||
|
"first last"
|
||||||
|
To view all the valid ARK servers for this guild see list_ark_servers."""
|
||||||
|
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if server is not None:
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name=server)
|
||||||
|
if server_con:
|
||||||
|
server_con.monitor_chat = True
|
||||||
|
ctx.guild_config['rcon_connections'][server]["monitoring_chat"] = 1
|
||||||
|
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||||||
|
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||||||
|
await self._monitor_chat(ctx.guild, server, server_con, ctx.guild_config)
|
||||||
|
await ctx.message.add_reaction('✅')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'Server not found: {server}')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You must include a server in this command.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def end_monitor_chat(self, ctx, *, server=None):
|
||||||
|
"""Ends chat monitoring on the specified server.
|
||||||
|
Context is the same as monitor_chat"""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if server is not None:
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name=server)
|
||||||
|
if server_con:
|
||||||
|
server_con.monitor_chat = False
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{server} is not connected currently.')
|
||||||
|
if server in ctx.guild_config.get('rcon_connections', []):
|
||||||
|
ctx.guild_config['rcon_connections'][server]["monitoring_chat"] = 0
|
||||||
|
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||||||
|
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||||||
|
else:
|
||||||
|
await ctx.send(f'Server not found in config: {server}')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You must include a server in this command.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def listplayers(self, ctx, *, server_name=None):
|
||||||
|
"""Lists the players currently connected to the specified ARK server.
|
||||||
|
The specified server must already be in the current guild\'s configuration.
|
||||||
|
To add and remove ARK servers from the guild see add_rcon_server and remove_rcon_server.
|
||||||
|
The server argument is not case sensitive and if the server name has two
|
||||||
|
words it can be in one of the following forms:
|
||||||
|
first last
|
||||||
|
first_last
|
||||||
|
"first last"
|
||||||
|
To view all the valid ARK servers for this guild see list_ark_servers."""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
|
||||||
|
if server_name:
|
||||||
|
server_name = server_name.replace('_', ' ').title()
|
||||||
|
server: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name=server_name)
|
||||||
|
if server:
|
||||||
|
msg = await ctx.send(f'**Getting Data for the {server_name} server**')
|
||||||
|
await ctx.channel.trigger_typing()
|
||||||
|
|
||||||
|
message = await server.listplayers()
|
||||||
|
await ctx.channel.trigger_typing()
|
||||||
|
await msg.delete()
|
||||||
|
await ctx.send(f'**Players currently on the {server_name} server:**\n{message}')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
|
||||||
|
f'"{server_name}" "ip" port "password" if you would like to get info from it.')
|
||||||
|
else:
|
||||||
|
futures = []
|
||||||
|
guild_servers: dict = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name='*'
|
||||||
|
)
|
||||||
|
if guild_servers is not None:
|
||||||
|
for name, server in guild_servers.items():
|
||||||
|
if not isinstance(server, arcon.ARKServer):
|
||||||
|
await ctx.send(f'__**{server}**__')
|
||||||
|
else:
|
||||||
|
msg = await ctx.send(f'**Getting Data for the {name} server**')
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
async def _listplayers(*, name, server_con: arcon.ARKServer, mess: discord.Message):
|
||||||
|
name = name.replace('_', ' ').title()
|
||||||
|
|
||||||
|
response = await server_con.listplayers()
|
||||||
|
await mess.edit(content=f'**Players currently on the {name} server:**\n{response}')
|
||||||
|
|
||||||
|
futures.append(_listplayers(mess=msg, server_con=server, name=name))
|
||||||
|
if futures:
|
||||||
|
asyncio.ensure_future(asyncio.gather(*futures))
|
||||||
|
else:
|
||||||
|
await ctx.send('There are no available servers for this guild.')
|
||||||
|
else:
|
||||||
|
await ctx.send('There are no available servers for this guild.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def add_rcon_server(self, ctx, server, ip, port, password):
|
||||||
|
"""Adds the specified server to the current guild\'s rcon config.
|
||||||
|
All multi-word strings (<server>, <ip>, <password>) must be contained inside double quotes."""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
if ctx.guild_config.get('rcon_connections'):
|
||||||
|
if server not in ctx.guild_config['rcon_connections']:
|
||||||
|
ctx.guild_config['rcon_connections'][server] = {
|
||||||
|
'ip': ip,
|
||||||
|
'port': port,
|
||||||
|
'password': password,
|
||||||
|
'game_chat_chan_id': 0,
|
||||||
|
'msg_chan_id': 0,
|
||||||
|
'monitoring_chat': 0
|
||||||
|
}
|
||||||
|
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||||||
|
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||||||
|
await ctx.send('{0} server has been added to my configuration.'.format(server))
|
||||||
|
else:
|
||||||
|
await ctx.send('This server name is already in my configuration. Please choose another.')
|
||||||
|
else:
|
||||||
|
ctx.guild_config['rcon_connections'] = {server: {
|
||||||
|
'ip': ip,
|
||||||
|
'port': port,
|
||||||
|
'password': password,
|
||||||
|
'game_chat_chan_id': 0,
|
||||||
|
'msg_chan_id': 0,
|
||||||
|
'monitoring_chat': 0
|
||||||
|
}}
|
||||||
|
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||||||
|
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||||||
|
await ctx.send('{0} server has been added to my configuration.'.format(server))
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
await ctx.message.delete()
|
||||||
|
await ctx.send('Command deleted to prevent password leak.')
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def remove_rcon_server(self, ctx, *, server: str):
|
||||||
|
"""removes the specified server from the current guild\'s rcon config."""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
if server in ctx.guild_config.get('rcon_connections', []):
|
||||||
|
del ctx.guild_config['rcon_connections'][server]
|
||||||
|
guild_ref = self.bot.fs_db.document(f'guilds/{ctx.guild.id}')
|
||||||
|
await self.bot.loop.run_in_executor(self.bot.tpe, guild_ref.update, ctx.guild_config)
|
||||||
|
await ctx.send('{0} has been removed from my configuration.'.format(server))
|
||||||
|
else:
|
||||||
|
await ctx.send('{0} is not in my configuration.'.format(server))
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _whitelist(*, server_name: str, server_con: arcon.ARKServer,
|
||||||
|
player: patron.Patron, message: discord.Message, message_lock: asyncio.Lock):
|
||||||
|
result = await server_con.whitelist(player.steam_id)
|
||||||
|
if result == f'{player.steam_id} Allow Player To Join No Check':
|
||||||
|
with await message_lock:
|
||||||
|
message = await message.channel.get_message(message.id)
|
||||||
|
await message.edit(content=f'{message.content}\n{server_name.replace("_", " ").title()}'
|
||||||
|
f' Done!')
|
||||||
|
else:
|
||||||
|
with await message_lock:
|
||||||
|
message = await message.channel.get_message(message.id)
|
||||||
|
await message.edit(content=f'{message.content}\n{server_name.replace("_", " ").title()}'
|
||||||
|
f' Failed!')
|
||||||
|
|
||||||
|
@commands.command(name='add_whitelist')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def add_whitelist(self, ctx, *, members: str=None):
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if members:
|
||||||
|
futures = []
|
||||||
|
members = members.replace(', ', '').split(',')
|
||||||
|
converter = commands.MemberConverter()
|
||||||
|
for member in members:
|
||||||
|
try:
|
||||||
|
member = await converter.convert(ctx, member)
|
||||||
|
except commands.errors.BadArgument:
|
||||||
|
try:
|
||||||
|
member = int(member)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f'Member {member} can\'t be found and is not a valid Steam64 ID.')
|
||||||
|
|
||||||
|
if isinstance(member, discord.Member):
|
||||||
|
player = await patron.Patron.from_name(self.bot, discord_name=member)
|
||||||
|
else:
|
||||||
|
player = await patron.Patron.from_id(self.bot, steam_id=member)
|
||||||
|
|
||||||
|
if player == -1:
|
||||||
|
await ctx.send(f'{ctx.author.mention} I Cannot find a player with a discord name/steam id of '
|
||||||
|
f'{member} in the current whitelist sheet. Did you forget to '
|
||||||
|
f'move them to the correct sheet?')
|
||||||
|
else:
|
||||||
|
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name='*')
|
||||||
|
if rcon_connections:
|
||||||
|
msg = await ctx.send(f'**Whitelisting {player.discord_name} on all servers**')
|
||||||
|
lock = asyncio.Lock()
|
||||||
|
for server_name, server_con in rcon_connections.items():
|
||||||
|
futures.append(self._whitelist(server_name=server_name, server_con=server_con,
|
||||||
|
player=player, message=msg, message_lock=lock))
|
||||||
|
|
||||||
|
if futures:
|
||||||
|
asyncio.ensure_future(asyncio.gather(*futures), loop=self.bot.loop)
|
||||||
|
else:
|
||||||
|
await ctx.send('Nothing for me to do')
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
await ctx.send('I need a list of members to whitelist.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command(name='new_patron')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def register_new_patron(self, ctx, *, members: str=None):
|
||||||
|
"""Adds the included Steam 64 IDs to the running whitelist on all the ARK servers in the current guild\'s rcon config.
|
||||||
|
Steam 64 IDs should be a comma separated list of IDs.
|
||||||
|
Example: 76561198024193239,76561198024193239,76561198024193239"""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if members is not None:
|
||||||
|
async with ctx.typing():
|
||||||
|
members = members.replace(', ', ',').split(',')
|
||||||
|
converter = commands.MemberConverter()
|
||||||
|
members = [await converter.convert(ctx, m) for m in members]
|
||||||
|
futures = []
|
||||||
|
patrons = []
|
||||||
|
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name='*')
|
||||||
|
|
||||||
|
if not isinstance(members, list):
|
||||||
|
members = [members, ]
|
||||||
|
for member in members:
|
||||||
|
member: discord.Member
|
||||||
|
player = await patron.Patron.from_name(self.bot, discord_name=member)
|
||||||
|
if player == -1:
|
||||||
|
await ctx.send(f'{ctx.author.mention} I Cannot find a player with a discord name of '
|
||||||
|
f'{member.display_name} in the current whitelist sheet. Did you forget to '
|
||||||
|
f'move them to the correct sheet?')
|
||||||
|
else:
|
||||||
|
if rcon_connections:
|
||||||
|
msg = await ctx.send(f'**Whitelisting {player.discord_name} on all servers**')
|
||||||
|
lock = asyncio.Lock()
|
||||||
|
for server_name, server_con in rcon_connections.items():
|
||||||
|
futures.append(self._whitelist(server_name=server_name, server_con=server_con,
|
||||||
|
player=player, message=msg, message_lock=lock))
|
||||||
|
|
||||||
|
roles = []
|
||||||
|
patron_roles = ctx.guild_config.get('patreon_tiers')
|
||||||
|
creator_roles = ctx.guild_config.get('patreon_creators')
|
||||||
|
if creator_roles:
|
||||||
|
rcon_log.info(f'patron_of {player.patron_of}')
|
||||||
|
if 'both' in player.patron_of.casefold():
|
||||||
|
rcon_log.info('found both')
|
||||||
|
for role_id in creator_roles.values():
|
||||||
|
|
||||||
|
roles.append(ctx.guild.get_role(int(role_id)))
|
||||||
|
else:
|
||||||
|
roles.append(ctx.guild.get_role(
|
||||||
|
int(creator_roles[player.patron_of + '_Patron'])
|
||||||
|
))
|
||||||
|
if patron_roles:
|
||||||
|
roles.append(ctx.guild.get_role(
|
||||||
|
int(patron_roles[player.patreon_tier.strip().title()])
|
||||||
|
))
|
||||||
|
if roles:
|
||||||
|
role_str = '\n'.join([role.name for role in roles])
|
||||||
|
await ctx.send(f'Adding {player.discord_name} to the following roles:\n'
|
||||||
|
f'{role_str}')
|
||||||
|
await ctx.guild.get_member(int(player.discord_id)).add_roles(*roles)
|
||||||
|
|
||||||
|
patrons.append(player)
|
||||||
|
|
||||||
|
if futures:
|
||||||
|
asyncio.ensure_future(asyncio.gather(*futures), loop=self.bot.loop)
|
||||||
|
else:
|
||||||
|
await ctx.send('Nothing for me to do')
|
||||||
|
return
|
||||||
|
|
||||||
|
if patrons:
|
||||||
|
new_patron_message = ctx.guild_config.get('new_patron_message')
|
||||||
|
new_patron_channel = ctx.guild_config.get('new_patron_channel')
|
||||||
|
if new_patron_message and new_patron_channel:
|
||||||
|
channel = ctx.guild.get_channel(int(new_patron_channel))
|
||||||
|
if channel:
|
||||||
|
patron_mentions = []
|
||||||
|
for p in patrons:
|
||||||
|
member = ctx.guild.get_member(int(p.discord_id))
|
||||||
|
patron_mentions.append(member.mention)
|
||||||
|
prelude = new_patron_message.get('prelude')
|
||||||
|
message = ''
|
||||||
|
if prelude:
|
||||||
|
if len(patron_mentions) > 1:
|
||||||
|
message = prelude.get('plural')
|
||||||
|
else:
|
||||||
|
message = prelude.get('singular')
|
||||||
|
body = new_patron_message.get('body')
|
||||||
|
if body:
|
||||||
|
server_info_channel = ctx.guild.get_channel(int(
|
||||||
|
ctx.guild_config.get('server_info')
|
||||||
|
))
|
||||||
|
message += ' ' + body.format(users=', '.join(patron_mentions),
|
||||||
|
server_info=server_info_channel.mention if
|
||||||
|
server_info_channel else "server_info")
|
||||||
|
await channel.send(message)
|
||||||
|
else:
|
||||||
|
await ctx.send('I need a list of members to add roles to and whitelist.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def saveworld(self, ctx, *, server=None):
|
||||||
|
"""Runs SaveWorld on the specified ARK server.
|
||||||
|
If a server is not specified it will default to running saveworld on all servers in the guild\'s config.
|
||||||
|
Will print out "World Saved" for each server when the command completes successfully."""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
async def _saveworld(ctx, server_name: str, server_con: arcon.ARKServer):
|
||||||
|
response = await server_con.saveworld()
|
||||||
|
if response == 'World Saved':
|
||||||
|
await ctx.send(f'{server_name} Saved')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'Failed to save {server_name}')
|
||||||
|
|
||||||
|
futures = []
|
||||||
|
async with ctx.typing():
|
||||||
|
if server is None:
|
||||||
|
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name='*')
|
||||||
|
if rcon_connections:
|
||||||
|
for server_name, server_con in rcon_connections.items():
|
||||||
|
futures.append(_saveworld(ctx, server_name, server_con))
|
||||||
|
self.bot.loop.create_task(asyncio.gather(*futures))
|
||||||
|
else:
|
||||||
|
await ctx.send('There are no available servers for this guild.')
|
||||||
|
else:
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name=server
|
||||||
|
)
|
||||||
|
if server_con:
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
await _saveworld(ctx, server, server_con)
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{server} is not currently in the configuration for this guild.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.group(case_insensitive=True)
|
||||||
|
async def broadcast(self, ctx):
|
||||||
|
"""Run help broadcast for more info"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@broadcast.command(name='all', aliases=['a'])
|
||||||
|
@commands.guild_only()
|
||||||
|
async def broadcast_all(self, ctx, *, message=None):
|
||||||
|
"""Sends a broadcast message to all servers in the guild config.
|
||||||
|
The message will be prefixed with the Discord name of the person running the command.
|
||||||
|
Will print "Success" for each server once the broadcast is sent."""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if message is not None:
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
async def _broadcast(*, message: str, server_con: arcon.ARKServer, server_name: str,
|
||||||
|
msg: discord.Message, message_lock: asyncio.Lock):
|
||||||
|
print(server_con.host, server_con.port)
|
||||||
|
response = await server_con.broadcast(message)
|
||||||
|
if response == 'Server received, But no response!!':
|
||||||
|
with await message_lock:
|
||||||
|
msg = await msg.channel.get_message(msg.id)
|
||||||
|
await msg.edit(content=f'{msg.content}\n{server_name} Success')
|
||||||
|
else:
|
||||||
|
with await message_lock:
|
||||||
|
msg = await msg.channel.get_message(msg.id)
|
||||||
|
await msg.edit(content=f'{msg.content}\n{server_name} Failed')
|
||||||
|
futures = []
|
||||||
|
rcon_connections: dict = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name='*')
|
||||||
|
if rcon_connections:
|
||||||
|
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||||||
|
msg = await ctx.send(f'Broadcasting "{message}" to all servers.')
|
||||||
|
lock = asyncio.Lock()
|
||||||
|
for server_name, server_con in rcon_connections.items():
|
||||||
|
futures.append(_broadcast(message=message, server_con=server_con, server_name=server_name,
|
||||||
|
msg=msg, message_lock=lock))
|
||||||
|
self.bot.loop.create_task(asyncio.gather(*futures))
|
||||||
|
await ctx.message.add_reaction('✅')
|
||||||
|
else:
|
||||||
|
await ctx.send('There are no available servers for this guild.')
|
||||||
|
else:
|
||||||
|
await ctx.send('You must include a message with this command.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@broadcast.command(name='server')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def broadcast_server(self, ctx, server, *, message=None):
|
||||||
|
"""Sends a broadcast message to the specified server that is in the guild's config.
|
||||||
|
The message will be prefixed with the Discord name of the person running the command.
|
||||||
|
If <server> has more than one word in it's name it will either need to be surrounded
|
||||||
|
by double quotes or the words separated by _"""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if server is not None:
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
if message is not None:
|
||||||
|
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name=server
|
||||||
|
)
|
||||||
|
if server_con:
|
||||||
|
msg = await ctx.send(f'Broadcasting "{message}" to {server}.')
|
||||||
|
response = await server_con.broadcast(message)
|
||||||
|
if response == 'Server received, But no response!!':
|
||||||
|
await msg.add_reaction(self.bot.unicode_emojis['y'])
|
||||||
|
else:
|
||||||
|
await msg.add_reaction(self.bot.unicode_emojis['x'])
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{server} is not in the config for this guild')
|
||||||
|
else:
|
||||||
|
await ctx.send('You must include a message with this command.')
|
||||||
|
else:
|
||||||
|
await ctx.send('You must include a server with this command')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command(aliases=['servers', 'list_servers'])
|
||||||
|
@commands.guild_only()
|
||||||
|
@commands.check(checks.is_restricted_chan)
|
||||||
|
async def list_ark_servers(self, ctx):
|
||||||
|
"""Returns a list of all the ARK servers in the current guild\'s config."""
|
||||||
|
servers = ctx.guild_config.get('rcon_connections', [])
|
||||||
|
em = discord.Embed(style='rich',
|
||||||
|
title=f'__**There are currently {len(servers)} ARK servers in my config:**__',
|
||||||
|
color=discord.Colour.green()
|
||||||
|
)
|
||||||
|
if ctx.guild.icon:
|
||||||
|
em.set_thumbnail(url=f'{ctx.guild.icon_url}')
|
||||||
|
for server in servers:
|
||||||
|
description = f"""
|
||||||
|
឵ **IP:** {servers[server]['ip']}:{servers[server]['port']}
|
||||||
|
឵ **Steam Connect:** [steam://connect/{servers[server]['ip']}:{servers[server]['port']}]\
|
||||||
|
(steam://connect/{servers[server]['ip']}:{servers[server]['port']})"""
|
||||||
|
em.add_field(name=f'__***{server}***__', value=description, inline=False)
|
||||||
|
await ctx.send(embed=em)
|
||||||
|
|
||||||
|
@commands.command(name='server_chat')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def send_chat_to_server(self, ctx, server: str=None, *, message: str=None):
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if server is not None:
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(guild_config=ctx.guild_config,
|
||||||
|
name=server)
|
||||||
|
if server_con:
|
||||||
|
if message is not None:
|
||||||
|
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||||||
|
msg = await ctx.send(f'Sending "{message}" to {server}\'s chat')
|
||||||
|
response = await server_con.serverchat(message)
|
||||||
|
if response == 'Server received, But no response!!':
|
||||||
|
await msg.add_reaction(self.bot.unicode_emojis['y'])
|
||||||
|
else:
|
||||||
|
await msg.add_reaction(self.bot.unicode_emojis['x'])
|
||||||
|
else:
|
||||||
|
await ctx.send('You must include a message with this command.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
|
||||||
|
f'"{server}" "ip" port "password" if you would like to get info from it.')
|
||||||
|
else:
|
||||||
|
await ctx.send('You must include a server with this command')
|
||||||
|
else:
|
||||||
|
await ctx.send('You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command(name='restart_server', aliases=['restart'])
|
||||||
|
@commands.guild_only()
|
||||||
|
async def restart_rcon_server(self, ctx, message: str, server_name: str=None,
|
||||||
|
delay_time: int=15, sleep_time: int=60):
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
|
||||||
|
if server_name is None or server_name == 'all':
|
||||||
|
futures = []
|
||||||
|
rcon_servers: dict = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name='*'
|
||||||
|
)
|
||||||
|
for server_name, server_con in rcon_servers.items():
|
||||||
|
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||||||
|
futures.append(utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
|
||||||
|
message=message, delay=delay_time, sleep=sleep_time))
|
||||||
|
self.bot.loop.create_task(asyncio.gather(*futures))
|
||||||
|
|
||||||
|
elif server_name.startswith('exclude='):
|
||||||
|
futures = []
|
||||||
|
exclude_servers = server_name.split('exclude=')[1].replace('_', ' ').title().split(',')
|
||||||
|
rcon_servers: dict = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name='*'
|
||||||
|
)
|
||||||
|
for server_name, server_con in rcon_servers.items():
|
||||||
|
if server_name not in exclude_servers:
|
||||||
|
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||||||
|
futures.append(utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
|
||||||
|
message=message, delay=delay_time, sleep=sleep_time))
|
||||||
|
self.bot.loop.create_task(asyncio.gather(*futures))
|
||||||
|
|
||||||
|
else:
|
||||||
|
async with ctx.typing():
|
||||||
|
server_name = server_name.replace('_', ' ').title()
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name=server_name
|
||||||
|
)
|
||||||
|
if server_con:
|
||||||
|
message = ''.join(i for i in f'{ctx.author.display_name}: {message}' if ord(i) < 128)
|
||||||
|
await utils.restart_rcon_server(ctx, server_name=server_name, server_con=server_con,
|
||||||
|
message=message, delay=delay_time, sleep=sleep_time)
|
||||||
|
else:
|
||||||
|
await ctx.send(f'That server is not in my configuration.\nPlease add it via !add_rcon_server '
|
||||||
|
f'"{server_name}" "ip" port "password" if you would like to get info from it.')
|
||||||
|
else:
|
||||||
|
await ctx.send('You are not authorized to run this command.')
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
@staticmethod
|
||||||
|
async def start_dinowipe(channel, server_name: str, server_con: arcon.ARKServer):
|
||||||
|
msg = await channel.send(f'Wild dinos will be wiped on {server_name} in 2 minutes.')
|
||||||
|
await server_con.broadcast(f'Wild dino wipe incoming in 2 minutes, expect a small amount of lag while '
|
||||||
|
f'the dinos repopulate.')
|
||||||
|
await asyncio.sleep(90)
|
||||||
|
await server_con.serverchat(f'Wild Dino Wipe in 30 seconds')
|
||||||
|
await asyncio.sleep(26)
|
||||||
|
await server_con.serverchat(f'Wild Dino Wipe in 3')
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
await server_con.serverchat(f'Wild Dino Wipe in 2')
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
await server_con.serverchat(f'Wild Dino Wipe in 1')
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
response = await server_con.destroy_wild_dinos()
|
||||||
|
if response == 'All Wild Dinos Destroyed':
|
||||||
|
await msg.edit(content=f'Wild Dinos Wiped on {server_name}')
|
||||||
|
await server_con.serverchat(f'Wild dinos have been wiped.')
|
||||||
|
else:
|
||||||
|
await msg.edit(content=f'Failed to wipe wild dinos on {server_name}')
|
||||||
|
await server_con.serverchat(f'Wild Dino Wipe failed, Please let the Admin know of this issue')
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames,PyUnresolvedReferences
|
||||||
|
async def request_dinowipe(self, requester, server_name: str, server_con: arcon.ARKServer):
|
||||||
|
self.bot.dino_wipe_request[server_name] = True
|
||||||
|
if server_con.server_messages_channel and self.bot.get_channel(int(server_con.server_messages_channel)):
|
||||||
|
channel = self.bot.get_channel(int(server_con.server_messages_channel))
|
||||||
|
else:
|
||||||
|
channel = self.bot.get_channel(server_con.server_chat_channel)
|
||||||
|
msg = False
|
||||||
|
if channel:
|
||||||
|
msg = await channel.send(f'@here {requester.mention} has requested wild dinos be wiped on the '
|
||||||
|
f'{server_name} server\n'
|
||||||
|
f'If you are in the middle of taming or have some other reason to '
|
||||||
|
f'delay the wipe please react to this message with '
|
||||||
|
f'{self.bot.unicode_emojis["x"]}.\n'
|
||||||
|
f'Doing so will delay the wipe for 10 minutes.')
|
||||||
|
await server_con.broadcast(f'{requester.display_name} has requested wild dinos be wiped.\n'
|
||||||
|
f'if you are in the middle of taming or have some other reason to delay the wipe '
|
||||||
|
f'please send a chat message containing "$delaywipe" here in-game.\n'
|
||||||
|
f'Doing so will delay the wipe for 10 minutes.')
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
await server_con.serverchat('The wipe will continue in 2 minutes if there are no requests to delay. '
|
||||||
|
'Send a message containing $delaywipe to delay for 10 minutes.')
|
||||||
|
rcon_log.info('Dino Wipe messages sent')
|
||||||
|
if msg:
|
||||||
|
def check(reaction, user):
|
||||||
|
return str(reaction.emoji) == self.bot.unicode_emojis['x'] and reaction.message.id == msg.id and \
|
||||||
|
user != self.bot.user
|
||||||
|
|
||||||
|
user = None
|
||||||
|
await msg.add_reaction(self.bot.unicode_emojis['x'])
|
||||||
|
try:
|
||||||
|
reaction, user = await self.bot.wait_for('reaction_add', check=check, timeout=120)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
rcon_log.info('Message timed out...')
|
||||||
|
if self.bot.dino_wipe_request.get(server_name):
|
||||||
|
rcon_log.info('Starting dino wipe')
|
||||||
|
await self.start_dinowipe(channel, server_name, server_con)
|
||||||
|
rcon_log.info('Wild Dino Wipe Completed')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
rcon_log.info('Dino Wipe Delayed.')
|
||||||
|
await server_con.broadcast(f'Wild Dino Wipe has been delayed by {user.display_name}. '
|
||||||
|
f'Please watch chat for timing updates')
|
||||||
|
await channel.send(f'Dino wipe has been delayed by {user.mention if user else "Game Chat"}...')
|
||||||
|
for i in range(10):
|
||||||
|
await asyncio.sleep(60)
|
||||||
|
if i >= 5 or i == 0:
|
||||||
|
await server_con.serverchat(f'The Wild Dino Wipe process will continue in {9 - i} '
|
||||||
|
f'{"minute" if 9 - i == 1 else "minutes"}')
|
||||||
|
await self.request_dinowipe(requester, server_name, server_con)
|
||||||
|
else:
|
||||||
|
rcon_log.info('No channels configured, running wipe.')
|
||||||
|
await asyncio.sleep(120)
|
||||||
|
if not self.bot.dino_wipe_request.get(server_name):
|
||||||
|
rcon_log.info('Wipe Delay requested')
|
||||||
|
for i in range(10):
|
||||||
|
await asyncio.sleep(60)
|
||||||
|
await server_con.serverchat(f'The Wino Dino Wipe process will continue in {9 - i} '
|
||||||
|
f'{"minute" if 9 - i == 1 else "minutes"}')
|
||||||
|
await self.request_dinowipe(requester, server_name, server_con)
|
||||||
|
|
||||||
|
@commands.command(name='dinowipe')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def run_dino_wipe(self, ctx, *, server=None):
|
||||||
|
"""Runs DestroyWildDinos on the specified ARK server.
|
||||||
|
If a server is not specified it will default to wiping the wild dinos on all servers in the guild\'s config.
|
||||||
|
Will print out "Wild Dinos Wiped on <server name>" for each server when the command completes successfully."""
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
futures = []
|
||||||
|
async with ctx.typing():
|
||||||
|
if server is None:
|
||||||
|
rcon_connections: dict = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name='*'
|
||||||
|
)
|
||||||
|
if rcon_connections:
|
||||||
|
for server_name, server_con in rcon_connections.items():
|
||||||
|
futures.append(self.start_dinowipe(ctx.channel, server_name, server_con))
|
||||||
|
self.bot.loop.create_task(asyncio.gather(*futures))
|
||||||
|
else:
|
||||||
|
await ctx.send('There are no available servers for this guild.')
|
||||||
|
else:
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name=server
|
||||||
|
)
|
||||||
|
if server_con:
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
await self.start_dinowipe(ctx.channel, server, server_con)
|
||||||
|
else:
|
||||||
|
await ctx.send(f'{server} is not currently in the configuration for this guild.')
|
||||||
|
else:
|
||||||
|
await ctx.send(f'You are not authorized to run this command.')
|
||||||
|
|
||||||
|
@commands.command(name='run_command', aliases=['run'])
|
||||||
|
@commands.guild_only()
|
||||||
|
async def run_rcon_command(self, ctx, server=None, *, command=None):
|
||||||
|
if await checks.is_rcon_admin(self.bot, ctx):
|
||||||
|
if not server:
|
||||||
|
await ctx.send('You must include a server when running this command.')
|
||||||
|
return
|
||||||
|
async with ctx.typing():
|
||||||
|
server = server.replace('_', ' ').title()
|
||||||
|
server_con: arcon.ARKServer = await self.get_rcon_server_by_name(
|
||||||
|
guild_config=ctx.guild_config, name=server
|
||||||
|
)
|
||||||
|
if server_con:
|
||||||
|
if not command:
|
||||||
|
await ctx.send('You must include a command to run on the server.')
|
||||||
|
return
|
||||||
|
|
||||||
|
command = command.split(' ')
|
||||||
|
|
||||||
|
if ctx.guild_config.get('allowed_rcon_commands'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = await server_con.run_command(command=' '.join(command), multi_packet=True)
|
||||||
|
if isinstance(response, str):
|
||||||
|
await ctx.send(response)
|
||||||
|
else:
|
||||||
|
body = response.body
|
||||||
|
pag = utils.Paginator(bot=self.bot)
|
||||||
|
pag.add(body)
|
||||||
|
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
|
||||||
|
await book.create_book()
|
||||||
|
else:
|
||||||
|
await ctx.send('That server was not found in the config for this guild.')
|
||||||
|
|
||||||
|
def setup(bot):
|
||||||
|
bot.add_cog(Rcon(bot))
|
||||||
1
geeksbot/exts/tickets.py
Normal file
1
geeksbot/exts/tickets.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
import discord
|
||||||
118
geeksbot/imports/arcon.py
Normal file
118
geeksbot/imports/arcon.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
from . import rcon
|
||||||
|
import asyncio
|
||||||
|
from typing import Union
|
||||||
|
import logging
|
||||||
|
|
||||||
|
arcon_log = logging.getLogger('arcon_lib')
|
||||||
|
|
||||||
|
|
||||||
|
class ARKServer(rcon.RCONConnection):
|
||||||
|
def __init__(self, *args, monitor_chat: bool = False, server_chat_channel: int = None,
|
||||||
|
server_messages_channel: int = None, **kwargs):
|
||||||
|
self.monitor_chat = monitor_chat
|
||||||
|
self.server_chat_channel = server_chat_channel
|
||||||
|
self.server_messages_channel = server_messages_channel
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
async def run_command(self, command: str, multi_packet: bool = False, reconnect_counter: int = 0) \
|
||||||
|
-> Union[rcon.RCONPacket, str]:
|
||||||
|
arcon_log.debug(f'Command requested: {command}')
|
||||||
|
if self.authenticated:
|
||||||
|
packet = rcon.RCONPacket(next(self.packet_id), rcon.SERVERDATA_EXECCOMMAND, command)
|
||||||
|
with await self.lock:
|
||||||
|
try:
|
||||||
|
arcon_log.debug(f'Sending packet {packet.packet_id}')
|
||||||
|
await self.send_packet(packet)
|
||||||
|
arcon_log.debug(f'Packet Sent.')
|
||||||
|
except ConnectionResetError:
|
||||||
|
arcon_log.info(f'Connection to {self.host}:{self.port} lost, Reconnecting...')
|
||||||
|
self.lock.release()
|
||||||
|
await self._reconnect_and_resend(packet)
|
||||||
|
await self.lock.acquire()
|
||||||
|
finally:
|
||||||
|
arcon_log.debug(f'Waiting for response to packet {packet.packet_id}')
|
||||||
|
try:
|
||||||
|
response = await self.read(packet, multi_packet=multi_packet)
|
||||||
|
except asyncio.TimeoutError as e:
|
||||||
|
if reconnect_counter > 5:
|
||||||
|
return 'Reached max reconnects. Closing connection.'
|
||||||
|
arcon_log.warning(f'No response received: {e}\nAttempting to reconnect #{reconnect_counter}')
|
||||||
|
self.lock.release()
|
||||||
|
await self._reconnect()
|
||||||
|
await self.lock.acquire()
|
||||||
|
response = await self.run_command(command=command, multi_packet=multi_packet,
|
||||||
|
reconnect_counter=reconnect_counter + 1)
|
||||||
|
arcon_log.debug(f'Response Received:\n{response.packet_type}:{response.packet_id}:{response.body}')
|
||||||
|
response.body = response.body.strip('\x00\x00').strip()
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return 'Server is not Authenticated. Please let the Admin know of this issue.'
|
||||||
|
|
||||||
|
async def getchat(self) -> str:
|
||||||
|
response = await self.run_command(command='getchat', multi_packet=True)
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def saveworld(self) -> str:
|
||||||
|
response = await self.run_command(command='saveworld')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def serverchat(self, message: str) -> str:
|
||||||
|
response = await self.run_command(command=f'serverchat {message}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def broadcast(self, message: str) -> str:
|
||||||
|
response = await self.run_command(command=f'broadcast {message}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def listplayers(self) -> str:
|
||||||
|
response = await self.run_command(command=f'listplayers')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def whitelist(self, steam_id: str) -> str:
|
||||||
|
response = await self.run_command(command=f'AllowPlayerToJoinNoCheck {steam_id}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def ban_player(self, steam_id: int) -> str:
|
||||||
|
response = await self.run_command(command=f'BanPlayer {steam_id}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def unban_player(self, steam_id: int) -> str:
|
||||||
|
response = await self.run_command(command=f'UnbanPlayer {steam_id}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def kick_player(self, steam_id: int) -> str:
|
||||||
|
response = await self.run_command(command=f'KickPlayer {steam_id}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def stop_server(self) -> int:
|
||||||
|
saved = await self.saveworld()
|
||||||
|
if saved == 'World Saved':
|
||||||
|
await self.serverchat(saved)
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
response = await self.run_command(command='DoExit')
|
||||||
|
if response.body == 'Exiting...':
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 2
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
async def get_logs(self):
|
||||||
|
response = await self.run_command(command=f'GetGameLog', multi_packet=True)
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def server_chat_to_steam_id(self, steam_id: int, message: str) -> str:
|
||||||
|
response = await self.run_command(command=f'ServerChatTo {steam_id} {message}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def server_chat_to_player_name(self, player_name: str, message: str) -> str:
|
||||||
|
response = await self.run_command(command=f'ServerChatToPlayer "{player_name}" {message}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def set_time_of_day(self, hour: int, minute: int = 00, seconds: int = 00) -> str:
|
||||||
|
response = await self.run_command(command=f'SetTimeOfDay {hour}:{minute}:{seconds}')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
|
|
||||||
|
async def destroy_wild_dinos(self):
|
||||||
|
response = await self.run_command(command='DestroyWildDinos')
|
||||||
|
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
import discord
|
||||||
|
|
||||||
|
def is_rcon_admin(bot, ctx):
|
||||||
|
|
||||||
183
geeksbot/imports/rcon.py
Normal file
183
geeksbot/imports/rcon.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import itertools
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# Packet types
|
||||||
|
SERVERDATA_AUTH = 3
|
||||||
|
SERVERDATA_AUTH_RESPONSE = 2
|
||||||
|
SERVERDATA_EXECCOMMAND = 2
|
||||||
|
SERVERDATA_RESPONSE_VALUE = 0
|
||||||
|
|
||||||
|
__all__ = ['RCONPacket', 'RCONConnection']
|
||||||
|
|
||||||
|
rcon_log = logging.getLogger('rcon_lib')
|
||||||
|
|
||||||
|
|
||||||
|
class RCONPacket:
|
||||||
|
def __init__(self, packet_id: int = 0, packet_type: int = -1, body: str = ''):
|
||||||
|
self.packet_id = packet_id
|
||||||
|
self.packet_type = packet_type
|
||||||
|
self.body = body
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Return the body of the packet"""
|
||||||
|
return self.body
|
||||||
|
|
||||||
|
def size(self):
|
||||||
|
"""Return the size of the packet"""
|
||||||
|
return len(self.body) + 10
|
||||||
|
|
||||||
|
def pack(self):
|
||||||
|
"""Return the packed packet"""
|
||||||
|
return struct.pack(f'<3i{len(self.body) + 2}s',
|
||||||
|
self.size(),
|
||||||
|
self.packet_id,
|
||||||
|
self.packet_type,
|
||||||
|
bytearray(self.body, 'utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
class RCONConnection:
|
||||||
|
"""Connection to an RCON server"""
|
||||||
|
|
||||||
|
def __init__(self, host: str, port: int, password: str = '', single_packet: bool = False):
|
||||||
|
"""Create a New RCON Connection
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
host (str): The hostname or IP address of the server to connect to
|
||||||
|
port (int): The port to connect to on the server
|
||||||
|
password (str): The password to authenticate with the server
|
||||||
|
single_packet (bool): True for servers who don't give 0 length SERVERDATA_RESPONSE_VALUE requests
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.password = password
|
||||||
|
self.single_packet = single_packet
|
||||||
|
self.packet_id = itertools.count(1)
|
||||||
|
self.loop = asyncio.get_event_loop()
|
||||||
|
self.reader = None
|
||||||
|
self.writer = None
|
||||||
|
self.lock = asyncio.Lock()
|
||||||
|
self.authenticated = False
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
"""Returns -1 if connection times out
|
||||||
|
Returns 1 if connection and auth are successful
|
||||||
|
Returns 0 if auth fails"""
|
||||||
|
try:
|
||||||
|
rcon_log.debug(f'Connecting to {self.host}:{self.port}...')
|
||||||
|
self.reader, self.writer = await asyncio.open_connection(self.host, self.port, loop=self.loop)
|
||||||
|
except TimeoutError as e:
|
||||||
|
rcon_log.error(f'Timeout error: {e}')
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
rcon_log.debug('Connected. Attempting to Authenticate...')
|
||||||
|
auth_packet = RCONPacket(next(self.packet_id), SERVERDATA_AUTH, self.password)
|
||||||
|
with await self.lock:
|
||||||
|
await self.send_packet(auth_packet)
|
||||||
|
response = await self.read()
|
||||||
|
if response.packet_type == SERVERDATA_AUTH_RESPONSE and response.packet_id != -1:
|
||||||
|
rcon_log.debug(f'Authorized {response.packet_type}:{response.packet_id}:{response.body}')
|
||||||
|
self.authenticated = True
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
rcon_log.debug(f'Not Authorized {response.packet_type}:{response.packet_id}:{response.body}')
|
||||||
|
self.authenticated = False
|
||||||
|
return 0
|
||||||
|
|
||||||
|
async def _reconnect(self):
|
||||||
|
self.writer = None
|
||||||
|
self.reader = None
|
||||||
|
connected = await self.connect()
|
||||||
|
rcon_log.info(f'Connection completed with a return of {connected}')
|
||||||
|
if connected != -1:
|
||||||
|
rcon_log.info('Connected')
|
||||||
|
else:
|
||||||
|
rcon_log.warning('Connection Failed')
|
||||||
|
return connected
|
||||||
|
|
||||||
|
async def _reconnect_and_resend(self, packet):
|
||||||
|
connected = await self._reconnect()
|
||||||
|
if connected != -1:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
rcon_log.info(f'Re-sending packet {packet.packet_id}')
|
||||||
|
await self.send_packet(packet)
|
||||||
|
rcon_log.info(f'Packet Sent.')
|
||||||
|
return connected
|
||||||
|
else:
|
||||||
|
return connected
|
||||||
|
|
||||||
|
async def keep_alive(self):
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(60)
|
||||||
|
ka_packet = RCONPacket(next(self.packet_id), SERVERDATA_EXECCOMMAND, '')
|
||||||
|
try:
|
||||||
|
with await self.lock:
|
||||||
|
await asyncio.wait_for(self.send_packet(ka_packet), 10, loop=self.loop)
|
||||||
|
await asyncio.wait_for(self.read(ka_packet), 10, loop=self.loop)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
self.reader = None
|
||||||
|
self.writer = None
|
||||||
|
await self.connect()
|
||||||
|
|
||||||
|
async def send_packet(self, packet):
|
||||||
|
if packet.size() > 4096:
|
||||||
|
rcon_log.error('Packet Size is larger than 4096 bytes. Cannot send packet.')
|
||||||
|
raise RuntimeWarning('Packet Size is larger than 4096 bytes. Cannot send packet.')
|
||||||
|
if self.writer is None:
|
||||||
|
await self.connect()
|
||||||
|
rcon_log.debug(f'Sending Packet {packet.packet_id}: {packet.pack() if packet.packet_type is not SERVERDATA_AUTH else "Censored for Password Security."}')
|
||||||
|
self.writer.write(packet.pack())
|
||||||
|
await self.writer.drain()
|
||||||
|
rcon_log.debug(f'Packet {packet.packet_id} Sent.')
|
||||||
|
|
||||||
|
async def read(self, request: RCONPacket = None, multi_packet=False) -> RCONPacket:
|
||||||
|
rcon_log.debug(f'Waiting to receive response to packet {request.packet_id if request else None}')
|
||||||
|
response = RCONPacket()
|
||||||
|
try:
|
||||||
|
if request:
|
||||||
|
while response.packet_id != request.packet_id and response.packet_id < request.packet_id:
|
||||||
|
if multi_packet:
|
||||||
|
if request is None:
|
||||||
|
rcon_log.warning('A request packet is required to receive a multi packet response')
|
||||||
|
raise ValueError('A request packet is required to receive a multi packet response')
|
||||||
|
await asyncio.sleep(.01)
|
||||||
|
response = await self._receive_multi_packet()
|
||||||
|
rcon_log.debug(f'Received Multi-Packet response to packet {request.packet_id}:\n'
|
||||||
|
f'{response.packet_type}:{response.packet_id}:{response.body}')
|
||||||
|
else:
|
||||||
|
response = await self.receive_packet()
|
||||||
|
rcon_log.debug(f'Received Single-Packet response to packet {request.packet_id}:\n'
|
||||||
|
f'{response.packet_type}:{response.packet_id}:{response.body}')
|
||||||
|
else:
|
||||||
|
response = await self.receive_packet()
|
||||||
|
rcon_log.debug(f'Received Single-Packet response:\n'
|
||||||
|
f'{response.packet_type}:{response.packet_id}:{response.body}')
|
||||||
|
except struct.error as e:
|
||||||
|
rcon_log.error(f'Struct Error: {e}')
|
||||||
|
response = RCONPacket(body='Error receiving data from the server. Attempting to reconnect. '
|
||||||
|
'Please try again in a little bit.')
|
||||||
|
self.lock.release()
|
||||||
|
await self._reconnect()
|
||||||
|
await self.lock.acquire()
|
||||||
|
except AttributeError as e:
|
||||||
|
rcon_log.error(f'Attribute Error: {e}')
|
||||||
|
response = RCONPacket(body='Error receiving data from the server. Attempting to reconnect. '
|
||||||
|
'Please try again in a little bit.')
|
||||||
|
self.lock.release()
|
||||||
|
await self._reconnect()
|
||||||
|
await self.lock.acquire()
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def receive_packet(self):
|
||||||
|
header = await self.reader.read(struct.calcsize('<3i'))
|
||||||
|
(packet_size, packet_id, packet_type) = struct.unpack('<3i', header)
|
||||||
|
body = await self.reader.read(packet_size - 8)
|
||||||
|
return RCONPacket(packet_id, packet_type, body.decode('ascii'))
|
||||||
|
|
||||||
|
async def _receive_multi_packet(self):
|
||||||
|
header = await self.reader.read(struct.calcsize('<3i'))
|
||||||
|
(packet_size, packet_id, packet_type) = struct.unpack('<3i', header)
|
||||||
|
body = await self.reader.readuntil(separator=b'\x00\x00')
|
||||||
|
return RCONPacket(packet_id, packet_type, body.decode('ascii'))
|
||||||
@ -3,6 +3,10 @@ import asyncio
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
async def get_guild_config(bot, guild_id):
|
||||||
|
guild_config = bot.cache.get()
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyDefaultArgument
|
# noinspection PyDefaultArgument
|
||||||
def to_list_of_str(items, out: list = list(), level=1, recurse=0):
|
def to_list_of_str(items, out: list = list(), level=1, recurse=0):
|
||||||
# noinspection PyShadowingNames
|
# noinspection PyShadowingNames
|
||||||
|
|||||||
9
geeksbot_v2/channels/api_urls.py
Normal file
9
geeksbot_v2/channels/api_urls.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import ChannelsAPI, ChannelDetail
|
||||||
|
|
||||||
|
app_name = "channels_api"
|
||||||
|
urlpatterns = [
|
||||||
|
path("", view=ChannelsAPI.as_view(), name="list"),
|
||||||
|
path("<str:id>/", view=ChannelDetail.as_view(), name='detail')
|
||||||
|
]
|
||||||
@ -1,5 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ChannelsConfig(AppConfig):
|
class ChannelsConfig(AppConfig):
|
||||||
name = 'channels'
|
name = 'geeksbot_v2.channels'
|
||||||
|
verbose_name = _("Channels")
|
||||||
|
|||||||
23
geeksbot_v2/channels/migrations/0001_initial.py
Normal file
23
geeksbot_v2/channels/migrations/0001_initial.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('guilds', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Channel',
|
||||||
|
fields=[
|
||||||
|
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||||
|
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
28
geeksbot_v2/channels/migrations/0002_auto_20190921_0250.py
Normal file
28
geeksbot_v2/channels/migrations/0002_auto_20190921_0250.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 2.2.4 on 2019-09-21 02:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('channels', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='channel',
|
||||||
|
name='admin',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='channel',
|
||||||
|
name='default',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='channel',
|
||||||
|
name='new_patron',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -12,6 +12,28 @@ from .utils import create_success_response
|
|||||||
class Channel(models.Model):
|
class Channel(models.Model):
|
||||||
id = models.CharField(max_length=30, primary_key=True)
|
id = models.CharField(max_length=30, primary_key=True)
|
||||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
||||||
|
default = models.BooleanField(default=False)
|
||||||
|
new_patron = models.BooleanField(default=False)
|
||||||
|
admin = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def update_channel(self, data):
|
||||||
|
if data.get('default'):
|
||||||
|
try:
|
||||||
|
existing_default = self.get_guild_channels(self.guild).get(default=True)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
existing_default.default = False
|
||||||
|
existing_default.save()
|
||||||
|
finally:
|
||||||
|
self.default = data.get('default')
|
||||||
|
if data.get('new_patron'):
|
||||||
|
self.new_patron = data.get('new_patron')
|
||||||
|
if data.get('admin'):
|
||||||
|
self.admin = data.get('admin')
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_new_channel(cls, data):
|
def add_new_channel(cls, data):
|
||||||
@ -21,7 +43,7 @@ class Channel(models.Model):
|
|||||||
status=status.HTTP_409_CONFLICT)
|
status=status.HTTP_409_CONFLICT)
|
||||||
guild_id = data.get('guild')
|
guild_id = data.get('guild')
|
||||||
if not (id and guild_id):
|
if not (id and guild_id):
|
||||||
return create_error_response('Id and Guild are required',
|
return create_error_response('ID and Guild are required',
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
guild = Guild.get_guild_by_id(guild_id)
|
guild = Guild.get_guild_by_id(guild_id)
|
||||||
if not isinstance(guild, Guild):
|
if not isinstance(guild, Guild):
|
||||||
@ -30,7 +52,10 @@ class Channel(models.Model):
|
|||||||
|
|
||||||
channel = cls(
|
channel = cls(
|
||||||
id=id,
|
id=id,
|
||||||
guild=guild
|
guild=guild,
|
||||||
|
default=data.get('default', False),
|
||||||
|
new_patron=data.get('new_patron', False),
|
||||||
|
admin=data.get('admin', False)
|
||||||
)
|
)
|
||||||
channel.save()
|
channel.save()
|
||||||
return create_success_response(channel, status.HTTP_201_CREATED, many=False)
|
return create_success_response(channel, status.HTTP_201_CREATED, many=False)
|
||||||
@ -42,5 +67,10 @@ class Channel(models.Model):
|
|||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_guild_channels(cls, guild):
|
||||||
|
if isinstance(guild, Guild):
|
||||||
|
return cls.objects.filter(guild=guild)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(id)
|
return str(id)
|
||||||
|
|||||||
@ -1,3 +1,69 @@
|
|||||||
from django.shortcuts import render
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||||
|
from .models import Channel
|
||||||
|
from .utils import create_error_response
|
||||||
|
from .utils import create_success_response
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
# API Views
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelsAPI(PaginatedAPIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, format=None):
|
||||||
|
guilds = Channel.objects.all()
|
||||||
|
page = self.paginate_queryset(guilds)
|
||||||
|
if page is not None:
|
||||||
|
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||||
|
|
||||||
|
return create_success_response(guilds, status.HTTP_200_OK, many=True)
|
||||||
|
|
||||||
|
def post(self, request, format=None):
|
||||||
|
data = dict(request.data)
|
||||||
|
return Channel.add_new_channel(data)
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelDetail(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, id, format=None):
|
||||||
|
try:
|
||||||
|
guild = Channel.objects.get(id=id)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return create_error_response("Channel Does not Exist",
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
else:
|
||||||
|
return create_success_response(guild,
|
||||||
|
status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def put(self, request, id, format=None):
|
||||||
|
channel = Channel.get_channel_by_id(id)
|
||||||
|
|
||||||
|
if channel:
|
||||||
|
data = dict(request.data)
|
||||||
|
channel = channel.update_channel(data)
|
||||||
|
return create_success_response(channel,
|
||||||
|
status=status.HTTP_202_ACCEPTED)
|
||||||
|
else:
|
||||||
|
return create_error_response('Channel Does Not Exist',
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def delete(self, request, id, format=None):
|
||||||
|
guild = Channel.get_guild_by_id(id)
|
||||||
|
|
||||||
|
if guild:
|
||||||
|
# data = dict(request.data)
|
||||||
|
# TODO Add a check to verify user is allowed to delete...
|
||||||
|
# Possibly in object permissions...
|
||||||
|
guild.delete()
|
||||||
|
return create_success_response(guild,
|
||||||
|
status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return create_error_response('Channel Does Not Exist',
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|||||||
@ -80,6 +80,7 @@ LOCAL_APPS = [
|
|||||||
"geeksbot_v2.dmessages.apps.MessagesConfig",
|
"geeksbot_v2.dmessages.apps.MessagesConfig",
|
||||||
"geeksbot_v2.patreon.apps.PatreonConfig",
|
"geeksbot_v2.patreon.apps.PatreonConfig",
|
||||||
"geeksbot_v2.rcon.apps.RconConfig",
|
"geeksbot_v2.rcon.apps.RconConfig",
|
||||||
|
"geeksbot_v2.channels.apps.ChannelsConfig",
|
||||||
# Your stuff: custom apps go here
|
# Your stuff: custom apps go here
|
||||||
]
|
]
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
@ -285,3 +286,5 @@ REST_FRAMEWORK = {
|
|||||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
||||||
"PAGE_SIZE": 100,
|
"PAGE_SIZE": 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SILENCED_SYSTEM_CHECKS = ["auth.W004"]
|
||||||
|
|||||||
@ -16,8 +16,10 @@ urlpatterns = [
|
|||||||
path("users/", include("geeksbot_v2.users.urls", namespace="users")),
|
path("users/", include("geeksbot_v2.users.urls", namespace="users")),
|
||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
# Your stuff: custom urls includes go here
|
# Your stuff: custom urls includes go here
|
||||||
path("api/users", include("geeksbot_v2.users.api_urls", namespace="users_api")),
|
path("api/users/", include("geeksbot_v2.users.api_urls", namespace="users_api")),
|
||||||
path("api/guilds", include("geeksbot_v2.guilds.api_urls", namespace="guilds_api"))
|
path("api/guilds/", include("geeksbot_v2.guilds.api_urls", namespace="guilds_api")),
|
||||||
|
path("api/channels/", include("geeksbot_v2.channels.api_urls", namespace="channels_api")),
|
||||||
|
path("api/messages/", include("geeksbot_v2.dmessages.api_urls", namespace="messages_api")),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|||||||
15
geeksbot_v2/dmessages/api_urls.py
Normal file
15
geeksbot_v2/dmessages/api_urls.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import MessageDetailAPI, MessagesAPI
|
||||||
|
from .views import RequestDetailAPI, RequestsAPI
|
||||||
|
from .views import CommentDetailAPI, CommentsAPI
|
||||||
|
|
||||||
|
app_name = "channels_api"
|
||||||
|
urlpatterns = [
|
||||||
|
path("", view=MessagesAPI.as_view(), name="message_list"),
|
||||||
|
path("<str:id>/", view=MessageDetailAPI.as_view(), name='message_detail'),
|
||||||
|
path("requests/", view=RequestsAPI.as_view(), name="requests_list"),
|
||||||
|
path("requests/<str:id>/", view=RequestDetailAPI.as_view(), name='request_detail'),
|
||||||
|
path("requests/<str:request_id>/comments/", view=CommentsAPI.as_view(), name="comments_list"),
|
||||||
|
path("requests/<str:request_id>/comments/<str:comment_id>/", view=CommentDetailAPI.as_view(), name='comment_detail'),
|
||||||
|
]
|
||||||
@ -1,9 +1,7 @@
|
|||||||
# Generated by Django 2.2.4 on 2019-09-17 19:31
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@ -11,29 +9,26 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('guilds', '0001_initial'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Message',
|
name='AdminComment',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('channel', models.CharField(max_length=30)),
|
('content', models.CharField(max_length=1000)),
|
||||||
('created_at', models.DateTimeField()),
|
('updated_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('modified_at', models.DateTimeField(null=True)),
|
],
|
||||||
('deleted_at', models.DateTimeField(null=True)),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdminRequest',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('completed', models.BooleanField(default=False)),
|
||||||
|
('requested_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('completed_at', models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
('completed_message', models.CharField(blank=True, default=None, max_length=1000, null=True)),
|
||||||
('content', models.CharField(max_length=2000)),
|
('content', models.CharField(max_length=2000)),
|
||||||
('previous_content', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=2000), size=None)),
|
|
||||||
('tagged_users', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)),
|
|
||||||
('tagged_channels', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)),
|
|
||||||
('tagged_roles', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)),
|
|
||||||
('tagged_everyone', models.BooleanField()),
|
|
||||||
('embeds', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
|
|
||||||
('previous_embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None), size=None)),
|
|
||||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@ -45,20 +40,20 @@ class Migration(migrations.Migration):
|
|||||||
('format', models.PositiveSmallIntegerField()),
|
('format', models.PositiveSmallIntegerField()),
|
||||||
('channel', models.CharField(max_length=30)),
|
('channel', models.CharField(max_length=30)),
|
||||||
('message_number', models.PositiveSmallIntegerField()),
|
('message_number', models.PositiveSmallIntegerField()),
|
||||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
|
||||||
('message', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message')),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='AdminRequest',
|
name='Message',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||||
('completed', models.BooleanField()),
|
('created_at', models.DateTimeField()),
|
||||||
('requested_at', models.DateTimeField()),
|
('modified_at', models.DateTimeField(blank=True, null=True)),
|
||||||
('completed_at', models.DateTimeField()),
|
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('content', models.CharField(max_length=2000)),
|
||||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
('previous_content', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=2000), default=list, size=None)),
|
||||||
('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message')),
|
('tagged_everyone', models.BooleanField()),
|
||||||
|
('embeds', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), default=list, size=None)),
|
||||||
|
('previous_embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None), default=list, size=None)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
95
geeksbot_v2/dmessages/migrations/0002_auto_20190920_2139.py
Normal file
95
geeksbot_v2/dmessages/migrations/0002_auto_20190920_2139.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('guilds', '0001_initial'),
|
||||||
|
('dmessages', '0001_initial'),
|
||||||
|
('channels', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='channel',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='channels.Channel'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='guild',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='tagged_channels',
|
||||||
|
field=models.ManyToManyField(related_name='_message_tagged_channels_+', to='channels.Channel'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='tagged_roles',
|
||||||
|
field=models.ManyToManyField(to='guilds.Role'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='tagged_users',
|
||||||
|
field=models.ManyToManyField(related_name='_message_tagged_users_+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guildinfo',
|
||||||
|
name='guild',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='guildinfo',
|
||||||
|
name='message',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adminrequest',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adminrequest',
|
||||||
|
name='channel',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='channels.Channel'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adminrequest',
|
||||||
|
name='completed_by',
|
||||||
|
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adminrequest',
|
||||||
|
name='guild',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adminrequest',
|
||||||
|
name='message',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='dmessages.Message'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='admincomment',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='admincomment',
|
||||||
|
name='request',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dmessages.AdminRequest'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
geeksbot_v2/dmessages/migrations/0003_auto_20190921_0721.py
Normal file
18
geeksbot_v2/dmessages/migrations/0003_auto_20190921_0721.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.4 on 2019-09-21 07:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dmessages', '0002_auto_20190920_2139'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='message',
|
||||||
|
name='content',
|
||||||
|
field=models.CharField(blank=True, max_length=2000, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -19,20 +19,20 @@ from .utils import create_comment_success_response
|
|||||||
|
|
||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
id = models.CharField(max_length=30, primary_key=True)
|
id = models.CharField(max_length=30, primary_key=True)
|
||||||
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
author = models.ForeignKey(User, related_name="+", on_delete=models.CASCADE)
|
||||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
||||||
channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
|
channel = models.ForeignKey(Channel, related_name="+", on_delete=models.CASCADE)
|
||||||
created_at = models.DateTimeField()
|
created_at = models.DateTimeField()
|
||||||
modified_at = models.DateTimeField(null=True, blank=True)
|
modified_at = models.DateTimeField(null=True, blank=True)
|
||||||
deleted_at = models.DateTimeField(null=True, blank=True)
|
deleted_at = models.DateTimeField(null=True, blank=True)
|
||||||
content = models.CharField(max_length=2000)
|
content = models.CharField(max_length=2000, null=True, blank=True)
|
||||||
previous_content = ArrayField(models.CharField(max_length=2000), default=[])
|
previous_content = ArrayField(models.CharField(max_length=2000), default=list)
|
||||||
tagged_users = models.ManyToManyField(User)
|
tagged_users = models.ManyToManyField(User, related_name="+")
|
||||||
tagged_channels = models.ManyToManyField(Channel)
|
tagged_channels = models.ManyToManyField(Channel, related_name="+")
|
||||||
tagged_roles = models.ManyToManyField(Role)
|
tagged_roles = models.ManyToManyField(Role)
|
||||||
tagged_everyone = models.BooleanField()
|
tagged_everyone = models.BooleanField()
|
||||||
embeds = ArrayField(models.TextField(), default=[])
|
embeds = ArrayField(models.TextField(), default=list)
|
||||||
previous_embeds = ArrayField(ArrayField(models.TextField()), default=[])
|
previous_embeds = ArrayField(ArrayField(models.TextField()), default=list)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_new_message(cls, data):
|
def add_new_message(cls, data):
|
||||||
@ -46,7 +46,7 @@ class Message(models.Model):
|
|||||||
created_at = data.get('created_at')
|
created_at = data.get('created_at')
|
||||||
content = data.get('content')
|
content = data.get('content')
|
||||||
tagged_everyone = data.get('tagged_everyone')
|
tagged_everyone = data.get('tagged_everyone')
|
||||||
if not (id and author_id and guild_id and channel_id and created_at and content and tagged_everyone):
|
if not (id and author_id and guild_id and channel_id and created_at and (tagged_everyone is not None)):
|
||||||
return create_error_response("One or more required fields are missing.",
|
return create_error_response("One or more required fields are missing.",
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
author = User.get_user_by_id(author_id)
|
author = User.get_user_by_id(author_id)
|
||||||
@ -97,9 +97,9 @@ class Message(models.Model):
|
|||||||
|
|
||||||
def update_message(self, data):
|
def update_message(self, data):
|
||||||
if data.get('modified_at'):
|
if data.get('modified_at'):
|
||||||
self.modified_at = data.get('modified_at')
|
self.modified_at = datetime.fromtimestamp(int(data.get('modified_at')))
|
||||||
if data.get('deleted_at'):
|
if data.get('deleted_at'):
|
||||||
self.modified_at = data.get('deleted_at')
|
self.deleted_at = datetime.fromtimestamp(int(data.get('deleted_at')))
|
||||||
if data.get('content'):
|
if data.get('content'):
|
||||||
content = data.get('content')
|
content = data.get('content')
|
||||||
if content != self.content:
|
if content != self.content:
|
||||||
@ -164,13 +164,15 @@ class GuildInfo(models.Model):
|
|||||||
|
|
||||||
class AdminRequest(models.Model):
|
class AdminRequest(models.Model):
|
||||||
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
|
||||||
author = models.ForeignKey(User, on_delete=models.DO_NOTHING)
|
author = models.ForeignKey(User, related_name="+", on_delete=models.DO_NOTHING)
|
||||||
message = models.ForeignKey(Message, on_delete=models.DO_NOTHING)
|
message = models.ForeignKey(Message, on_delete=models.DO_NOTHING)
|
||||||
channel = models.ForeignKey(Channel, on_delete=models.DO_NOTHING)
|
channel = models.ForeignKey(Channel, on_delete=models.DO_NOTHING, null=True)
|
||||||
completed = models.BooleanField(default=False)
|
completed = models.BooleanField(default=False)
|
||||||
requested_at = models.DateTimeField(auto_now_add=True, blank=True)
|
requested_at = models.DateTimeField(auto_now_add=True, blank=True)
|
||||||
completed_at = models.DateTimeField(null=True, blank=True, default=None)
|
completed_at = models.DateTimeField(null=True, blank=True, default=None)
|
||||||
completed_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, null=True, blank=True, default=None)
|
completed_by = models.ForeignKey(
|
||||||
|
User, related_name="+", on_delete=models.DO_NOTHING, null=True, blank=True, default=None
|
||||||
|
)
|
||||||
completed_message = models.CharField(max_length=1000, null=True, blank=True, default=None)
|
completed_message = models.CharField(max_length=1000, null=True, blank=True, default=None)
|
||||||
content = models.CharField(max_length=2000)
|
content = models.CharField(max_length=2000)
|
||||||
|
|
||||||
@ -179,7 +181,7 @@ class AdminRequest(models.Model):
|
|||||||
completed_by_id = data.get('completed_by')
|
completed_by_id = data.get('completed_by')
|
||||||
completed_message = data.get('message')
|
completed_message = data.get('message')
|
||||||
if not self.completed and completed:
|
if not self.completed and completed:
|
||||||
self.completed_at = datetime.now()
|
self.completed_at = datetime.utcnow()
|
||||||
self.completed_message = completed_message
|
self.completed_message = completed_message
|
||||||
user = User.get_user_by_id(completed_by_id)
|
user = User.get_user_by_id(completed_by_id)
|
||||||
if not isinstance(user, User):
|
if not isinstance(user, User):
|
||||||
@ -226,6 +228,10 @@ class AdminRequest(models.Model):
|
|||||||
request.save()
|
request.save()
|
||||||
return create_request_success_response(request, status.HTTP_201_CREATED, many=False)
|
return create_request_success_response(request, status.HTTP_201_CREATED, many=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_open_requests_by_guild(cls, guild_id):
|
||||||
|
return cls.objects.filter(guild__id=guild_id).filter(completed=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_request_by_id(cls, id):
|
def get_request_by_id(cls, id):
|
||||||
try:
|
try:
|
||||||
@ -244,8 +250,7 @@ class AdminComment(models.Model):
|
|||||||
updated_at = models.DateTimeField(auto_now_add=True, blank=True)
|
updated_at = models.DateTimeField(auto_now_add=True, blank=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_new_comment(cls, data):
|
def add_new_comment(cls, data, request_id):
|
||||||
request_id = data.get('request')
|
|
||||||
author_id = data.get('author')
|
author_id = data.get('author')
|
||||||
content = data.get('content')
|
content = data.get('content')
|
||||||
if not (request_id and author_id and content):
|
if not (request_id and author_id and content):
|
||||||
@ -274,3 +279,7 @@ class AdminComment(models.Model):
|
|||||||
return cls.objects.get(id=id)
|
return cls.objects.get(id=id)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_comments_by_request(cls, request):
|
||||||
|
return cls.objects.filter(request=request)
|
||||||
|
|||||||
@ -1,3 +1,124 @@
|
|||||||
from django.shortcuts import render
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
|
||||||
|
from .models import Message
|
||||||
|
from .models import AdminComment
|
||||||
|
from .models import AdminRequest
|
||||||
|
from .models import GuildInfo
|
||||||
|
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||||
|
from .utils import create_error_response
|
||||||
|
from .utils import create_success_response
|
||||||
|
from .utils import create_request_success_response
|
||||||
|
from .utils import create_comment_success_response
|
||||||
|
from .serializers import AdminRequestSerializer
|
||||||
|
from .serializers import AdminCommentSerializer
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
# API Views
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesAPI(PaginatedAPIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, format=None):
|
||||||
|
messages = Message.objects.all()
|
||||||
|
page = self.paginate_queryset(messages)
|
||||||
|
if page:
|
||||||
|
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||||
|
return create_success_response(messages, status.HTTP_200_OK, many=True)
|
||||||
|
|
||||||
|
def post(self, request, format=None):
|
||||||
|
data = dict(request.data)
|
||||||
|
return Message.add_new_message(data)
|
||||||
|
|
||||||
|
|
||||||
|
class MessageDetailAPI(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, id, format=None):
|
||||||
|
message = Message.get_message_by_id(id)
|
||||||
|
if message:
|
||||||
|
return create_success_response(message, status.HTTP_200_OK, many=False)
|
||||||
|
else:
|
||||||
|
return create_error_response("Message Does Not Exist",
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def put(self, request, id, format=None):
|
||||||
|
data = dict(request.data)
|
||||||
|
message = Message.get_message_by_id(id)
|
||||||
|
if message:
|
||||||
|
return message.update_message(data)
|
||||||
|
else:
|
||||||
|
return create_error_response('Message Does Not Exist',
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsAPI(PaginatedAPIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, guild, format=None):
|
||||||
|
requests = AdminRequest.get_open_requests_by_guild(guild)
|
||||||
|
page = self.paginate_queryset(requests)
|
||||||
|
if page is not None:
|
||||||
|
return create_request_success_response(page, status.HTTP_200_OK, many=True)
|
||||||
|
|
||||||
|
return create_request_success_response(requests, status.HTTP_200_OK, many=True)
|
||||||
|
|
||||||
|
def post(self, request, format=None):
|
||||||
|
data = dict(request.data)
|
||||||
|
return AdminRequest.add_new_request(data)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestDetailAPI(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, req, id, format=None):
|
||||||
|
req = AdminRequest.get_request_by_id(id)
|
||||||
|
if req:
|
||||||
|
comments = AdminComment.get_comments_by_request(req)
|
||||||
|
if comments:
|
||||||
|
data = AdminRequestSerializer(req).data
|
||||||
|
data['comments'] = AdminCommentSerializer(comments, many=True).data
|
||||||
|
return Response(data, status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return create_request_success_response(req, status.HTTP_200_OK, many=False)
|
||||||
|
else:
|
||||||
|
return create_error_response("That Request Does Not Exist",
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def put(self, request, id, format=None):
|
||||||
|
req = AdminRequest.get_request_by_id(id)
|
||||||
|
if req:
|
||||||
|
data = dict(request.data)
|
||||||
|
return req.update_request(data)
|
||||||
|
return create_error_response("That Request Does Not Exist",
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
class CommentsAPI(PaginatedAPIView):
|
||||||
|
permissions_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request, request_id, format=None):
|
||||||
|
data = dict(request.data)
|
||||||
|
return AdminComment.add_new_comment(data, request_id)
|
||||||
|
|
||||||
|
|
||||||
|
class CommentDetailAPI(APIView):
|
||||||
|
permissions_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, request_id, comment_id, format=None):
|
||||||
|
comment = AdminComment.get_comment_by_id(comment_id)
|
||||||
|
if comment:
|
||||||
|
if comment.request.id != request_id:
|
||||||
|
return create_error_response("That comment is not for this request",
|
||||||
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
return create_comment_success_response(comment, status.HTTP_200_OK, many=False)
|
||||||
|
else:
|
||||||
|
return create_error_response("Comment Does Not Exist",
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|||||||
@ -36,7 +36,7 @@ done
|
|||||||
>&2 echo 'PostgreSQL is available'
|
>&2 echo 'PostgreSQL is available'
|
||||||
|
|
||||||
python manage.py collectstatic --noinput
|
python manage.py collectstatic --noinput
|
||||||
python manage.py makemigrations
|
python manage.py makemigrations --noinput
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
|
||||||
/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import GuildsAPI, GuildDetail
|
from .views import GuildsAPI, GuildDetail
|
||||||
|
from .views import RolesAPI, RoleDetailAPI
|
||||||
|
|
||||||
app_name = "users_api"
|
app_name = "guilds_api"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("/", view=GuildsAPI.as_view(), name="list"),
|
path("", view=GuildsAPI.as_view(), name="list"),
|
||||||
path("/<str:id>/", view=GuildDetail.as_view(), name='detail')
|
path("<str:id>/", view=GuildDetail.as_view(), name='detail'),
|
||||||
|
path("<str:guild_id>/roles/", view=RolesAPI.as_view(), name="list"),
|
||||||
|
path("<str:guild_id>/roles/<str:id>/", view=RoleDetailAPI.as_view(), name='detail'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 2.2.4 on 2019-09-17 19:31
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
@ -28,7 +28,7 @@ class Migration(migrations.Migration):
|
|||||||
name='Role',
|
name='Role',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||||
('type', models.PositiveSmallIntegerField()),
|
('role_type', models.PositiveSmallIntegerField()),
|
||||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
25
geeksbot_v2/guilds/migrations/0002_auto_20190921_0250.py
Normal file
25
geeksbot_v2/guilds/migrations/0002_auto_20190921_0250.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 2.2.4 on 2019-09-21 02:50
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('guilds', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='guild',
|
||||||
|
name='admin_chat',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='guild',
|
||||||
|
name='default_channel',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='guild',
|
||||||
|
name='new_patron_channel',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -15,24 +15,15 @@ from .utils import create_role_success_response
|
|||||||
|
|
||||||
class Guild(models.Model):
|
class Guild(models.Model):
|
||||||
id = models.CharField(max_length=30, primary_key=True)
|
id = models.CharField(max_length=30, primary_key=True)
|
||||||
admin_chat = models.CharField(max_length=30, blank=True, null=True)
|
|
||||||
new_patron_message = models.TextField(max_length=1000, blank=True, null=True)
|
new_patron_message = models.TextField(max_length=1000, blank=True, null=True)
|
||||||
default_channel = models.CharField(max_length=30)
|
|
||||||
new_patron_channel = models.CharField(max_length=30, blank=True, null=True)
|
|
||||||
prefixes = ArrayField(models.CharField(max_length=10))
|
prefixes = ArrayField(models.CharField(max_length=10))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
def update_guild(self, data):
|
def update_guild(self, data):
|
||||||
if data.get('admin_chat'):
|
|
||||||
self.admin_chat = data.get('admin_chat')
|
|
||||||
if data.get('new_patron_message'):
|
if data.get('new_patron_message'):
|
||||||
self.new_patron_message = data.get('new_patron_message')
|
self.new_patron_message = data.get('new_patron_message')
|
||||||
if data.get('default_channel'):
|
|
||||||
self.default_channel = data.get('default_channel')
|
|
||||||
if data.get('new_patron_channel'):
|
|
||||||
self.new_patron_channel = data.get('new_patron_channel')
|
|
||||||
if data.get('add_prefix'):
|
if data.get('add_prefix'):
|
||||||
if data.get('add_prefix') not in self.prefixes:
|
if data.get('add_prefix') not in self.prefixes:
|
||||||
self.prefixes.append(data.get('add_prefix'))
|
self.prefixes.append(data.get('add_prefix'))
|
||||||
@ -55,9 +46,8 @@ class Guild(models.Model):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create_guild(cls, data):
|
def create_guild(cls, data):
|
||||||
id = data.get('id')
|
id = data.get('id')
|
||||||
default_channel = data.get('default_channel')
|
if not id:
|
||||||
if not (id and default_channel):
|
return create_error_response('ID is required',
|
||||||
return create_error_response('id and default_channel are required',
|
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
if cls.get_guild_by_id(id):
|
if cls.get_guild_by_id(id):
|
||||||
@ -66,11 +56,8 @@ class Guild(models.Model):
|
|||||||
|
|
||||||
guild = cls(
|
guild = cls(
|
||||||
id=id,
|
id=id,
|
||||||
default_channel=default_channel,
|
|
||||||
prefixes=data.get('prefixes'),
|
prefixes=data.get('prefixes'),
|
||||||
admin_chat=data.get('admin_chat'),
|
new_patron_message=data.get('new_patron_message')
|
||||||
new_patron_message=data.get('new_patron_message'),
|
|
||||||
new_patron_channel=data.get('new_patron_channel')
|
|
||||||
)
|
)
|
||||||
guild.save()
|
guild.save()
|
||||||
return create_success_response(guild, status.HTTP_201_CREATED, many=False)
|
return create_success_response(guild, status.HTTP_201_CREATED, many=False)
|
||||||
@ -89,11 +76,10 @@ class Role(models.Model):
|
|||||||
return create_role_success_response(self, status=status.HTTP_202_ACCEPTED, many=False)
|
return create_role_success_response(self, status=status.HTTP_202_ACCEPTED, many=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_new_role(cls, data):
|
def add_new_role(cls, guild_id, data):
|
||||||
id = data.get('id')
|
id = data.get('id')
|
||||||
guild_id = data.get('guild')
|
|
||||||
role_type = data.get('role_type')
|
role_type = data.get('role_type')
|
||||||
if not (id and guild_id and role_type):
|
if not (id and guild_id and (role_type is not None)):
|
||||||
return create_error_response("The Role ID, Guild, and Role Type are required",
|
return create_error_response("The Role ID, Guild, and Role Type are required",
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@ -132,5 +118,9 @@ class Role(models.Model):
|
|||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_guild_roles(cls, guild):
|
||||||
|
return cls.objects.filter(guild__id=guild)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.guild.id} | {self.id}"
|
return f"{self.guild.id} | {self.id}"
|
||||||
|
|||||||
@ -13,4 +13,4 @@ class GuildSerializer(serializers.ModelSerializer):
|
|||||||
class RoleSerializer(serializers.ModelSerializer):
|
class RoleSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = ["id", "guild", "type"]
|
fields = ["id", "guild", "role_type"]
|
||||||
|
|||||||
@ -5,8 +5,10 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||||||
|
|
||||||
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||||
from .models import Guild
|
from .models import Guild
|
||||||
|
from .models import Role
|
||||||
from .utils import create_error_response
|
from .utils import create_error_response
|
||||||
from .utils import create_success_response
|
from .utils import create_success_response
|
||||||
|
from .utils import create_role_success_response
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
@ -67,3 +69,45 @@ class GuildDetail(APIView):
|
|||||||
else:
|
else:
|
||||||
return create_error_response('Guild Does Not Exist',
|
return create_error_response('Guild Does Not Exist',
|
||||||
status=status.HTTP_404_NOT_FOUND)
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
class RolesAPI(PaginatedAPIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, guild_id, format=None):
|
||||||
|
roles = Role.get_guild_roles(guild_id)
|
||||||
|
page = self.paginate_queryset(roles)
|
||||||
|
if page is not None:
|
||||||
|
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||||
|
|
||||||
|
return create_success_response(roles, status.HTTP_200_OK, many=True)
|
||||||
|
|
||||||
|
def post(self, request, guild_id, format=None):
|
||||||
|
data = dict(request.data)
|
||||||
|
return Role.add_new_role(guild_id, data)
|
||||||
|
|
||||||
|
|
||||||
|
class RoleDetailAPI(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request, guild_id, id, format=None):
|
||||||
|
try:
|
||||||
|
role = Role.objects.get(id=id)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return create_error_response("Guild Does not Exist",
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
else:
|
||||||
|
return create_role_success_response(role,
|
||||||
|
status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def put(self, request, guild_id, id, format=None):
|
||||||
|
role = Role.get_role_by_id(id)
|
||||||
|
|
||||||
|
if role:
|
||||||
|
data = dict(request.data)
|
||||||
|
role = role.update_role(data)
|
||||||
|
return create_role_success_response(role,
|
||||||
|
status=status.HTTP_202_ACCEPTED)
|
||||||
|
else:
|
||||||
|
return create_error_response('Guild Does Not Exist',
|
||||||
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 2.2.4 on 2019-09-17 19:31
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
@ -16,10 +16,9 @@ class Migration(migrations.Migration):
|
|||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PatreonCreator',
|
name='PatreonCreator',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('creator', models.CharField(max_length=50, primary_key=True, serialize=False)),
|
||||||
('creator', models.CharField(max_length=50)),
|
('link', models.CharField(max_length=100, unique=True)),
|
||||||
('link', models.CharField(max_length=100)),
|
('guilds', models.ManyToManyField(to='guilds.Guild')),
|
||||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@ -30,7 +29,8 @@ class Migration(migrations.Migration):
|
|||||||
('description', models.TextField()),
|
('description', models.TextField()),
|
||||||
('amount', models.IntegerField(null=True)),
|
('amount', models.IntegerField(null=True)),
|
||||||
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='patreon.PatreonCreator')),
|
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='patreon.PatreonCreator')),
|
||||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
('guild', models.ManyToManyField(to='guilds.Guild')),
|
||||||
|
('next_lower_tier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='patreon.PatreonTier')),
|
||||||
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Role')),
|
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Role')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -70,7 +70,7 @@ class PatreonTier(models.Model):
|
|||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
role = models.ForeignKey(Role, on_delete=models.CASCADE)
|
role = models.ForeignKey(Role, on_delete=models.CASCADE)
|
||||||
amount = models.IntegerField(null=True)
|
amount = models.IntegerField(null=True)
|
||||||
next_lower_tier = models.ForeignKey('self', null=True, blank=True)
|
next_lower_tier = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
def update_tier(self, data):
|
def update_tier(self, data):
|
||||||
if data.get('guild'):
|
if data.get('guild'):
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
# Generated by Django 2.2.4 on 2019-09-17 19:31
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
@ -10,9 +9,9 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('dmessages', '0001_initial'),
|
|
||||||
('guilds', '0001_initial'),
|
('guilds', '0001_initial'),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
('dmessages', '0001_initial'),
|
||||||
|
('channels', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@ -25,13 +24,12 @@ class Migration(migrations.Migration):
|
|||||||
('port', models.PositiveIntegerField()),
|
('port', models.PositiveIntegerField()),
|
||||||
('password', models.CharField(max_length=50)),
|
('password', models.CharField(max_length=50)),
|
||||||
('monitor_chat', models.BooleanField()),
|
('monitor_chat', models.BooleanField()),
|
||||||
('monitor_chat_channel', models.CharField(blank=True, max_length=30)),
|
('alerts_channel', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='channels.Channel')),
|
||||||
('alerts_channel', models.CharField(blank=True, max_length=30)),
|
|
||||||
('info_channel', models.CharField(blank=True, max_length=30)),
|
|
||||||
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
|
||||||
('info_message', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dmessages.Message')),
|
('info_channel', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='channels.Channel')),
|
||||||
('settings_message', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dmessages.Message')),
|
('info_message', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='dmessages.Message')),
|
||||||
('whitelist', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
|
('monitor_chat_channel', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='channels.Channel')),
|
||||||
|
('settings_message', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='dmessages.Message')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
22
geeksbot_v2/rcon/migrations/0002_rconserver_whitelist.py
Normal file
22
geeksbot_v2/rcon/migrations/0002_rconserver_whitelist.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('rcon', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rconserver',
|
||||||
|
name='whitelist',
|
||||||
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -4,6 +4,6 @@ from geeksbot_v2.users.views import UsersAPI, UserDetail
|
|||||||
|
|
||||||
app_name = "users_api"
|
app_name = "users_api"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("/", view=UsersAPI.as_view(), name="list"),
|
path("", view=UsersAPI.as_view(), name="list"),
|
||||||
path("/<str:id>/", view=UserDetail.as_view(), name="detail"),
|
path("<str:id>/", view=UserDetail.as_view(), name="detail"),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 2.2.4 on 2019-09-17 19:38
|
# Generated by Django 2.2.4 on 2019-09-20 21:39
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
@ -25,7 +25,6 @@ class Migration(migrations.Migration):
|
|||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
|
||||||
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
|
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
|
||||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||||
@ -33,11 +32,12 @@ class Migration(migrations.Migration):
|
|||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')),
|
('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')),
|
||||||
|
('username', models.CharField(help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||||
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
|
||||||
('discord_username', models.CharField(max_length=100, null=True)),
|
('discord_username', models.CharField(max_length=100, null=True)),
|
||||||
('previous_discord_usernames', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), blank=True, null=True, size=None)),
|
('previous_discord_usernames', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), blank=True, null=True, size=None)),
|
||||||
('discriminator', models.IntegerField(null=True)),
|
('discriminator', models.CharField(max_length=4, null=True)),
|
||||||
('previous_discriminators', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), blank=True, null=True, size=None)),
|
('previous_discriminators', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=4), blank=True, null=True, size=None)),
|
||||||
('steam_id', models.CharField(blank=True, max_length=30, null=True)),
|
('steam_id', models.CharField(blank=True, max_length=30, null=True)),
|
||||||
('animated', models.BooleanField(blank=True, null=True)),
|
('animated', models.BooleanField(blank=True, null=True)),
|
||||||
('avatar', models.CharField(blank=True, max_length=100, null=True)),
|
('avatar', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
@ -61,9 +61,9 @@ class Migration(migrations.Migration):
|
|||||||
name='UserLog',
|
name='UserLog',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('time', models.DateTimeField()),
|
('time', models.DateTimeField(auto_now_add=True)),
|
||||||
('action', models.IntegerField()),
|
('action', models.IntegerField()),
|
||||||
('description', models.CharField(max_length=100)),
|
('description', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 2.2.4 on 2019-09-17 21:09
|
|
||||||
|
|
||||||
import django.contrib.auth.validators
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('users', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# Generated by Django 2.2.4 on 2019-09-18 05:54
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('users', '0002_auto_20190917_2109'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='discriminator',
|
|
||||||
field=models.CharField(max_length=4, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='previous_discriminators',
|
|
||||||
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=4), blank=True, null=True, size=None),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -6,3 +6,4 @@ psutil
|
|||||||
pytz
|
pytz
|
||||||
async_timeout
|
async_timeout
|
||||||
cached_property
|
cached_property
|
||||||
|
redis-py
|
||||||
Loading…
x
Reference in New Issue
Block a user