Add Pterodactyl cog #19

Merged
cswimr merged 139 commits from pterodactyl into main 2024-03-02 00:07:42 -05:00
Showing only changes of commit 76aa99c3f9 - Show all commits

View file

@ -9,6 +9,7 @@ import websockets
from pydactyl import PterodactylClient, exceptions from pydactyl import PterodactylClient, exceptions
from redbot.core import Config, commands from redbot.core import Config, commands
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import box, pagify
class Pterodactyl(commands.Cog): class Pterodactyl(commands.Cog):
@ -26,6 +27,7 @@ class Pterodactyl(commands.Cog):
startup_arguments=None, startup_arguments=None,
power_action_in_progress=False, power_action_in_progress=False,
chat_regex=r"\[(\d{2}:\d{2}:\d{2})\sINFO\]:\s<(\w+)>\s(.*)", chat_regex=r"\[(\d{2}:\d{2}:\d{2})\sINFO\]:\s<(\w+)>\s(.*)",
tellraw_json='tellraw @a ["",{{"text":".$U ","color":".$C"}},{{"text":" (DISCORD): ","color":"blue"}},{{"text":".$M","color":"white"}}]',
api_endpoint="minecraft", api_endpoint="minecraft",
chat_channel=None chat_channel=None
) )
@ -34,7 +36,7 @@ class Pterodactyl(commands.Cog):
self.task = None self.task = None
self.websocket = None self.websocket = None
async def establish_websocket_connection(self): async def establish_websocket_connection(self) -> None:
self.logger.debug("Establishing WebSocket connection") self.logger.debug("Establishing WebSocket connection")
base_url = await self.config.base_url() base_url = await self.config.base_url()
api_key = await self.config.api_key() api_key = await self.config.api_key()
@ -52,15 +54,13 @@ class Pterodactyl(commands.Cog):
) )
#NOTE - The token is truncated to prevent it from being logged in its entirety, for security reasons #NOTE - The token is truncated to prevent it from being logged in its entirety, for security reasons
except exceptions.ClientConfigError as e: except exceptions.ClientConfigError as e:
self.logger.error('Failed to initialize Pterodactyl client: %s', e) return self.logger.error('Failed to initialize Pterodactyl client: %s', e)
return
except exceptions.PterodactylApiError as e: except exceptions.PterodactylApiError as e:
self.logger.error('Failed to retrieve Pterodactyl websocket: %s', e) return self.logger.error('Failed to retrieve Pterodactyl websocket: %s', e)
return
async for websocket in websockets.connect(websocket_credentials['data']['socket'], origin=base_url, ping_timeout=60): async for websocket in websockets.connect(websocket_credentials['data']['socket'], origin=base_url, ping_timeout=60):
try: try:
self.logger.debug("WebSocket connection established") self.logger.info("WebSocket connection established")
auth_message = json.dumps({"event": "auth", "args": [websocket_credentials['data']['token']]}) auth_message = json.dumps({"event": "auth", "args": [websocket_credentials['data']['token']]})
await websocket.send(auth_message) await websocket.send(auth_message)
@ -79,7 +79,7 @@ class Pterodactyl(commands.Cog):
self.logger.debug("Authentication message sent") self.logger.debug("Authentication message sent")
if json.loads(message)['event'] == 'auth success': if json.loads(message)['event'] == 'auth success':
self.logger.debug("Authentication successful") self.logger.info("WebSocket authentication successful")
if json.loads(message)['event'] == 'console output' and await self.config.console_channel() is not None: if json.loads(message)['event'] == 'console output' and await self.config.console_channel() is not None:
if current_status == 'running' or current_status == 'offline' or current_status == '': if current_status == 'running' or current_status == 'offline' or current_status == '':
@ -87,21 +87,25 @@ class Pterodactyl(commands.Cog):
if channel is not None: if channel is not None:
content = self.remove_ansi_escape_codes(json.loads(message)['args'][0][:1900]) content = self.remove_ansi_escape_codes(json.loads(message)['args'][0][:1900])
if content.startswith('['): if content.startswith('['):
await channel.send(content=content) content = pagify(content, delims=[" ", "\n"])
#TODO - Add pagification for long messages to prevent Discord API errors for page in content:
await channel.send(content=page)
chat_message = await self.check_if_chat_message(content) chat_message = await self.check_if_chat_message(content)
if chat_message: if chat_message:
info = await self.get_info(chat_message['username']) info = await self.get_info(chat_message['username'])
if info is not None: if info is not None:
await self.send_chat_discord(chat_message['username'], chat_message['message'], info['data']['player']['avatar']) await self.send_chat_discord(chat_message['username'], chat_message['message'], info['data']['player']['avatar'])
else:
await self.send_chat_discord(chat_message['username'], chat_message['message'], 'https://seafsh.cc/u/j3AzqQ.png')
if json.loads(message)['event'] == 'status': if json.loads(message)['event'] == 'status':
current_status = json.loads(message)['args'][0] current_status = json.loads(message)['args'][0]
console = self.bot.get_channel(await self.config.console_channel()) if await self.config.console_channel() is not None:
if console is not None: console = self.bot.get_channel(await self.config.console_channel())
await console.send(f"Server status changed! `{json.loads(message)['args'][0]}`") if console is not None:
await console.send(f"Server status changed! `{json.loads(message)['args'][0]}`")
except websockets.exceptions.ConnectionClosed as e: except websockets.exceptions.ConnectionClosed as e:
self.logger.debug("WebSocket connection closed: %s", e) self.logger.info("WebSocket connection closed: %s", e)
websocket_credentials = client.servers.get_websocket(server_id) websocket_credentials = client.servers.get_websocket(server_id)
continue continue
@ -146,8 +150,8 @@ class Pterodactyl(commands.Cog):
else: else:
self.logger.debug("Chat channel not set. Skipping sending chat message to Discord") self.logger.debug("Chat channel not set. Skipping sending chat message to Discord")
def get_tellraw_string(self, username: str, message: str, color: discord.Color): async def get_tellraw_string(self, username: str, message: str, color: discord.Color):
return f'tellraw @a ["",{{"text":"{username} ","color":"{str(color)}"}},{{"text":" (DISCORD): ","color":"blue"}},{{"text":"{message}","color":"white"}}]' return await self.config.tellraw_json().replace(".$U", username).replace(".$M", message).replace(".$C", str(color))
def get_task(self): def get_task(self):
return self.bot.loop.create_task(self.establish_websocket_connection(), name="Pterodactyl Websocket Connection") return self.bot.loop.create_task(self.establish_websocket_connection(), name="Pterodactyl Websocket Connection")
@ -170,7 +174,7 @@ class Pterodactyl(commands.Cog):
channel = self.bot.get_channel(await self.config.console_channel()) channel = self.bot.get_channel(await self.config.console_channel())
if channel: if channel:
await channel.send(f"Received chat message from {message.author.id}: {message.content[:1900]}") await channel.send(f"Received chat message from {message.author.id}: {message.content[:1900]}")
msg = json.dumps({"event": "send command", "args": [self.get_tellraw_string(message.author.display_name, message.content, message.author.color)]}) msg = json.dumps({"event": "send command", "args": [await self.get_tellraw_string(message.author.display_name, message.content, message.author.color)]})
self.logger.debug("Sending chat message to server:\n%s", msg) self.logger.debug("Sending chat message to server:\n%s", msg)
await self.websocket.send(msg) await self.websocket.send(msg)
@ -183,40 +187,72 @@ class Pterodactyl(commands.Cog):
"""Configure Pterodactyl settings.""" """Configure Pterodactyl settings."""
@pterodactyl_config.command(name = "url") @pterodactyl_config.command(name = "url")
async def pterodactyl_config_base_url(self, ctx: commands.Context, base_url: str): async def pterodactyl_config_base_url(self, ctx: commands.Context, *, base_url: str = None) -> None:
"""Set the base URL of your Pterodactyl Panel. Please include the protocol (http/https).""" """Set the base URL of your Pterodactyl Panel. Please include the protocol (http/https)."""
if base_url is None:
base_url = await self.config.base_url()
return await ctx.send(f"Base URL is currently set to {base_url}")
await self.config.base_url.set(base_url) await self.config.base_url.set(base_url)
await ctx.send(f"Base URL set to {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.logger.info("Configuration value set: base_url = %s\nRestarting task...", base_url)
self.task.cancel() self.task.cancel()
self.task = self.get_task() self.task = self.get_task()
@pterodactyl_config.command(name = "apikey") @pterodactyl_config.command(name = "apikey")
async def pterodactyl_config_api_key(self, ctx: commands.Context, api_key: str): async def pterodactyl_config_api_key(self, ctx: commands.Context, *, api_key: str) -> None:
"""Set the API key for your Pterodactyl Panel.""" """Set the API key for your Pterodactyl Panel."""
await self.config.api_key.set(api_key) await self.config.api_key.set(api_key)
await ctx.send(f"API key set to `{api_key[:5]}...{api_key[-4:]}`") 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.logger.info("Configuration value set: api_key = %s\nRestarting task...", api_key)
self.task.cancel() self.task.cancel()
self.task = self.get_task() self.task = self.get_task()
@pterodactyl_config.command(name = "serverid") @pterodactyl_config.command(name = "serverid")
async def pterodactyl_config_server_id(self, ctx: commands.Context, server_id: str): async def pterodactyl_config_server_id(self, ctx: commands.Context, *, server_id: str) -> None:
"""Set the server ID for your Pterodactyl Panel.""" """Set the server ID for your Pterodactyl Panel."""
await self.config.server_id.set(server_id) await self.config.server_id.set(server_id)
await ctx.send(f"Server ID set to {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.logger.info("Configuration value set: server_id = %s\nRestarting task...", server_id)
self.task.cancel() self.task.cancel()
self.task = self.get_task() self.task = self.get_task()
@pterodactyl_config.command(name = "consolechannel") @pterodactyl_config.command(name = "consolechannel")
async def pterodactyl_config_console_channel(self, ctx: commands.Context, channel: discord.TextChannel): async def pterodactyl_config_console_channel(self, ctx: commands.Context, channel: discord.TextChannel) -> None:
"""Set the channel to send console output to.""" """Set the channel to send console output to."""
await self.config.console_channel.set(channel.id) await self.config.console_channel.set(channel.id)
await ctx.send(f"Console channel set to {channel.mention}") await ctx.send(f"Console channel set to {channel.mention}")
@pterodactyl_config.command(name = "chatchannel") @pterodactyl_config.command(name = "chatchannel")
async def pterodactyl_config_chat_channel(self, ctx: commands.Context, channel: discord.TextChannel): async def pterodactyl_config_chat_channel(self, ctx: commands.Context, channel: discord.TextChannel) -> None:
"""Set the channel to send chat output to.""" """Set the channel to send chat output to."""
await self.config.chat_channel.set(channel.id) await self.config.chat_channel.set(channel.id)
await ctx.send(f"Chat channel set to {channel.mention}") await ctx.send(f"Chat channel set to {channel.mention}")
@pterodactyl_config.group(name = "chat")
async def pterodactyl_config_chat(self, ctx: commands.Context):
"""Configure chat settings."""
@pterodactyl_config_chat.command(name = "regex")
async def pterodactyl_config_chat_regex(self, ctx: commands.Context, *, regex: str = None) -> None:
"""Set the regex pattern to match chat messages.
See [documentation]() for more information."""
#TODO - fix this link
if regex is None:
regex = await self.config.chat_regex()
return await ctx.send(f"Chat regex is currently set to:\n{box(regex, 'regex')}")
await self.config.chat_regex.set(regex)
await ctx.send(f"Chat regex set to:\n{box(regex, 'regex')}")
@pterodactyl_config_chat.command(name = "tellraw")
async def pterodactyl_config_chat_tellraw(self, ctx: commands.Context, *, tellraw: str = None) -> None:
"""Set the tellraw JSON to send chat messages to Discord.
Required placeholders: `.$U` (username), `.$M` (message), `.$C` (color)
See [documentation]() for more information."""
#TODO - fix this link
if tellraw is None:
tellraw = await self.config.tellraw_json()
return await ctx.send(f"Tellraw JSON is currently set to:\n{box(tellraw, 'json')}")
await self.config.tellraw_json.set(tellraw)
await ctx.send(f"Tellraw JSON set to:\n{box(tellraw, 'json')}")