SeaCogs/pterodactyl/pterodactyl.py

141 lines
6.7 KiB
Python

import json
import logging
import discord
import websockets
from pydactyl import PterodactylClient, exceptions
from redbot.core import Config, commands
from redbot.core.bot import Red
class Pterodactyl(commands.Cog):
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
def __init__(self, bot: Red):
self.bot = bot
self.config = Config.get_conf(self, identifier=457581387213637448123567, force_registration=True)
self.config.register_global(
base_url=None,
api_key=None,
server_id=None,
console_channel=None,
startup_jar=None,
startup_arguments=None,
power_action_in_progress=False
)
self.logger = logging.getLogger('red.sea.pterodactyl')
self.client = None
self.task = None
self.websocket = None
async def establish_websocket_connection(self):
self.logger.debug("Establishing WebSocket connection")
base_url = await self.config.base_url()
api_key = await self.config.api_key()
server_id = await self.config.server_id()
try:
client = PterodactylClient(base_url, api_key, debug=True).client
self.client = client
websocket_credentials = client.servers.get_websocket(server_id)
self.logger.debug("""Websocket connection details retrieved:
Socket: %s
Token: %s...""",
websocket_credentials['data']['socket'],
websocket_credentials['data']['token'][:20]
)
#NOTE - The token is truncated to prevent it from being logged in its entirety, for security reasons
except exceptions.ClientConfigError as e:
self.logger.error('Failed to initialize Pterodactyl client: %s', e)
return
except exceptions.PterodactylApiError as e:
self.logger.error('Failed to retrieve Pterodactyl websocket: %s', e)
return
async for websocket in websockets.connect(websocket_credentials['data']['socket'], origin=base_url):
try:
self.logger.debug("WebSocket connection established")
auth_message = json.dumps({"event": "auth", "args": [websocket_credentials['data']['token']]})
await websocket.send(auth_message)
self.logger.debug("Authentication message sent")
self.websocket = websocket
while True:
message = await websocket.recv()
if json.loads(message)['event'] in ['token expiring', 'token expired']:
self.logger.debug("Received token expiring/expired event. Refreshing token.")
websocket_credentials = client.servers.get_websocket(server_id)
auth_message = json.dumps({"event": "auth", "args": [websocket_credentials['data']['token']]})
await websocket.send(auth_message)
self.logger.debug("Authentication message sent")
if json.loads(message)['event'] == 'auth success':
self.logger.debug("Authentication successful")
# if json.loads(message)['event'] == 'console output' and await self.config.console_channel() is not None:
# channel = self.bot.get_channel(await self.config.console_channel)
# if channel is not None:
# await channel.send(json.loads(message)['args'][0][:1900])
#TODO - Add pagification for long messages to prevent Discord API errors
except websockets.exceptions.ConnectionClosed as e:
self.logger.debug("WebSocket connection closed: %s", e)
websocket_credentials = client.servers.get_websocket(server_id)
continue
def get_task(self):
return self.bot.loop.create_task(self.establish_websocket_connection(), name="Pterodactyl Websocket Connection")
async def cog_load(self):
self.task = self.get_task()
async def cog_unload(self):
self.task.cancel()
await self.client._session.close() # pylint: disable=protected-access
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
if message.channel.id == await self.config.console_channel():
await self.websocket.send(json.dumps({"event": "send command", "args": [message.content]}))
@commands.group(autohelp = True, name = "pterodactyl", aliases = ["ptero"])
async def pterodactyl(self, ctx: commands.Context):
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
@pterodactyl.group(autohelp = True, name = "config", aliases = ["settings", "set"])
async def pterodactyl_config(self, ctx: commands.Context):
"""Configure Pterodactyl settings."""
@pterodactyl_config.command(name = "url")
async def pterodactyl_config_base_url(self, ctx: commands.Context, base_url: str):
"""Set the base URL of your Pterodactyl Panel. Please include the protocol (http/https)."""
await self.config.base_url.set(base_url)
await ctx.send(f"Base URL set to {base_url}")
self.logger.debug("Configuration value set: base_url = %s\nRestarting task...", base_url)
self.task.cancel()
self.task = self.get_task()
@pterodactyl_config.command(name = "apikey")
async def pterodactyl_config_api_key(self, ctx: commands.Context, api_key: str):
"""Set the API key for your Pterodactyl Panel."""
await self.config.api_key.set(api_key)
await ctx.send(f"API key set to `{api_key[:5]}...{api_key[-4:]}`")
self.logger.debug("Configuration value set: api_key = %s\nRestarting task...", api_key)
self.task.cancel()
self.task = self.get_task()
@pterodactyl_config.command(name = "serverid")
async def pterodactyl_config_server_id(self, ctx: commands.Context, server_id: str):
"""Set the server ID for your Pterodactyl Panel."""
await self.config.server_id.set(server_id)
await ctx.send(f"Server ID set to {server_id}")
self.logger.debug("Configuration value set: server_id = %s\nRestarting task...", server_id)
self.task.cancel()
self.task = self.get_task()
@pterodactyl_config.command(name = "consolechannel")
async def pterodactyl_config_console_channel(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel to send console output to."""
await self.config.console_channel.set(channel.id)
await ctx.send(f"Console channel set to {channel.mention}")