diff --git a/pterodactyl/pterodactyl.py b/pterodactyl/pterodactyl.py index 2b9f5be..675984f 100644 --- a/pterodactyl/pterodactyl.py +++ b/pterodactyl/pterodactyl.py @@ -9,6 +9,7 @@ import websockets from pydactyl import PterodactylClient, exceptions from redbot.core import Config, commands from redbot.core.bot import Red +from redbot.core.utils.chat_formatting import box, pagify class Pterodactyl(commands.Cog): @@ -26,6 +27,7 @@ class Pterodactyl(commands.Cog): startup_arguments=None, power_action_in_progress=False, 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", chat_channel=None ) @@ -34,7 +36,7 @@ class Pterodactyl(commands.Cog): self.task = None self.websocket = None - async def establish_websocket_connection(self): + async def establish_websocket_connection(self) -> None: self.logger.debug("Establishing WebSocket connection") base_url = await self.config.base_url() 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 except exceptions.ClientConfigError as e: - self.logger.error('Failed to initialize Pterodactyl client: %s', e) - return + return self.logger.error('Failed to initialize Pterodactyl client: %s', e) except exceptions.PterodactylApiError as e: - self.logger.error('Failed to retrieve Pterodactyl websocket: %s', e) - return + return self.logger.error('Failed to retrieve Pterodactyl websocket: %s', e) async for websocket in websockets.connect(websocket_credentials['data']['socket'], origin=base_url, ping_timeout=60): try: - self.logger.debug("WebSocket connection established") + self.logger.info("WebSocket connection established") auth_message = json.dumps({"event": "auth", "args": [websocket_credentials['data']['token']]}) await websocket.send(auth_message) @@ -79,7 +79,7 @@ class Pterodactyl(commands.Cog): self.logger.debug("Authentication message sent") 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 current_status == 'running' or current_status == 'offline' or current_status == '': @@ -87,21 +87,25 @@ class Pterodactyl(commands.Cog): if channel is not None: content = self.remove_ansi_escape_codes(json.loads(message)['args'][0][:1900]) if content.startswith('['): - await channel.send(content=content) - #TODO - Add pagification for long messages to prevent Discord API errors + content = pagify(content, delims=[" ", "\n"]) + for page in content: + await channel.send(content=page) chat_message = await self.check_if_chat_message(content) if chat_message: info = await self.get_info(chat_message['username']) if info is not None: 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': current_status = json.loads(message)['args'][0] - console = self.bot.get_channel(await self.config.console_channel()) - if console is not None: - await console.send(f"Server status changed! `{json.loads(message)['args'][0]}`") + if await self.config.console_channel() is not None: + console = self.bot.get_channel(await self.config.console_channel()) + if console is not None: + await console.send(f"Server status changed! `{json.loads(message)['args'][0]}`") 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) continue @@ -146,8 +150,8 @@ class Pterodactyl(commands.Cog): else: 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): - return f'tellraw @a ["",{{"text":"{username} ","color":"{str(color)}"}},{{"text":" (DISCORD): ","color":"blue"}},{{"text":"{message}","color":"white"}}]' + async def get_tellraw_string(self, username: str, message: str, color: discord.Color): + return await self.config.tellraw_json().replace(".$U", username).replace(".$M", message).replace(".$C", str(color)) def get_task(self): 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()) if channel: 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) await self.websocket.send(msg) @@ -183,40 +187,72 @@ class Pterodactyl(commands.Cog): """Configure Pterodactyl settings.""" @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).""" + 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 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 = self.get_task() @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.""" 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.logger.info("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): + async def pterodactyl_config_server_id(self, ctx: commands.Context, *, server_id: str) -> None: """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.logger.info("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): + async def pterodactyl_config_console_channel(self, ctx: commands.Context, channel: discord.TextChannel) -> None: """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}") @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.""" await self.config.chat_channel.set(channel.id) 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')}")