Add Pterodactyl cog #19
1 changed files with 60 additions and 24 deletions
|
@ -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')}")
|
||||||
|
|
Loading…
Reference in a new issue