From 5ec3eefb99905d58a2627d0232d5ca20c0350523 Mon Sep 17 00:00:00 2001
From: NorbiPeti <norbipeti@norbipeti.eu>
Date: Mon, 17 Feb 2025 03:49:16 +0100
Subject: [PATCH] Improve /servers and add /server_info

It looks much nicer now
---
 bot.py              |  7 +++--
 cogs/info.py        | 74 +++++++++++++++++++++++++++++++++++++++++++--
 config.example.yaml |  1 +
 requirements.txt    |  1 +
 utils.py            |  2 +-
 5 files changed, 78 insertions(+), 7 deletions(-)

diff --git a/bot.py b/bot.py
index bf93035..88d3c44 100644
--- a/bot.py
+++ b/bot.py
@@ -1,7 +1,8 @@
+import logging
+
 import discord
 from discord.ext import commands
-from datetime import datetime
-import logging
+from discord.utils import utcnow
 
 logger = logging.getLogger(__name__)
 
@@ -22,7 +23,7 @@ intents = discord.Intents(
 class RoboBot(commands.Bot):
     def __init__(self, command_prefix, config, **options):
         self.config = config
-        self.uptime = datetime.utcnow()  # used for info :)
+        self.uptime = utcnow()  # used for info :)
         self.src = config.get("github_url")
         # Set up the command prefix and intents from commands.Bot
         super().__init__(command_prefix=command_prefix, intents=intents, **options)
diff --git a/cogs/info.py b/cogs/info.py
index a33edaa..83b185b 100644
--- a/cogs/info.py
+++ b/cogs/info.py
@@ -1,10 +1,13 @@
 import logging
 
+import discord
 from discord.ext import commands
 
 from utils import about_me_embed
 from utils import handle_api_request
 
+from table2ascii import table2ascii as t2a, PresetStyle
+
 logger = logging.getLogger(__name__)
 
 
@@ -27,14 +30,71 @@ class Info(commands.Cog):
         embed = about_me_embed(self.bot)
         await ctx.send(embed=embed, ephemeral=True)
 
+    @commands.hybrid_command(
+        name="server_info",
+        description="Displays information about a server.",
+        aliases=["server"],
+    )
+    async def server_info(self, ctx, *, server_name: str):
+        result, message = handle_api_request("servers")
+        if not result:
+            await ctx.send(f"Failed to retrieve server status: {message}", ephemeral=True)
+            return
+
+        for server in result:
+            if server_name.lower() in server["name"].lower():
+                found_server = server
+                break
+        else:
+            await ctx.send(f"Couldn't find server with name {server_name}", ephemeral=True)
+            return
+
+        game_map = server.get("mapAliasAndVersion", "Unknown")
+        map_alias, map_version = game_map.split('/')
+
+        result, message = handle_api_request("maps")
+        if not result:
+            await ctx.send(f"Failed to retrieve maps: {message}", ephemeral=True)
+            return
+
+        for game_map in result:
+            if map_alias == game_map["alias"]:
+                found_map = game_map
+                break
+        else:
+            await ctx.send(f"Couldn't find map with alias {map_alias}", ephemeral=True)
+            return
+
+        # make the embed
+        embed = discord.Embed(
+            title=f"{found_server['name']}",
+            description="",
+            color=discord.Color.dark_orange(),
+        )
+        # add fields
+        game_state = found_server["gameState"]
+        match game_state:
+            case "waiting":
+                game_state = "waiting for players"
+            case "running":
+                game_state = "game in progress"
+            case "ending":
+                game_state = "game over"
+        embed.add_field(name="Map", value=f"{found_map['name']}")
+        embed.add_field(name="Map version",value=f"{map_version}")
+        embed.add_field(name="Players", value=f"{found_server['onlinePlayers']}/{found_server['gameSettings']['maxPlayers']}")
+        embed.add_field(name="Status", value=f"{game_state}")
+
+        await ctx.send(embed=embed, ephemeral=True)
+
     @commands.hybrid_command(
         name="servers", description="Fetches info about the game servers",
         aliases=["server_list"]
     )
     async def servers(self, ctx):
         result, message = handle_api_request("servers")
+        table_body = []
         if result:
-            response = "Servers:"
             for server in result:
                 name = server.get("name", "Unknown")
                 online_players = server.get("onlinePlayers", -1)
@@ -42,8 +102,16 @@ class Info(commands.Cog):
                 map = server.get("mapAliasAndVersion", "Unknown")
                 game_settings = server.get("gameSettings", {})
                 max_players = game_settings.get("maxPlayers", -1)
-                response += f"\n* {name} - {game_state} - {map} - {online_players}/{max_players}"
-            await ctx.send(response, ephemeral=True)
+                table_body.append([name, map, game_state, f"{online_players}/{max_players}"])
+            output = t2a(
+                header=["Name", "Map", "State", "Players"],
+                body=table_body,
+                style=PresetStyle.thin_compact
+            )
+
+            await ctx.send(f"```\nServers:\n{output}\n```", ephemeral=True)
+        elif len(result) == 0:
+            await ctx.send("There are no servers online at the moment", ephemeral=True)
         else:
             await ctx.send(f"Failed to retrieve server status: {message}", ephemeral=True)
 
diff --git a/config.example.yaml b/config.example.yaml
index 99d57b9..438f766 100644
--- a/config.example.yaml
+++ b/config.example.yaml
@@ -18,6 +18,7 @@ routes:
   stop_server: "/server/stop"
   change_map: "/server/change_map"
   servers: "/servers"
+  maps: "/maps"
 
 # Mod IDs (for permissions)
 # Replicate if you want admins, gamemasters, etc. 
diff --git a/requirements.txt b/requirements.txt
index 0e0d007..bfc7b12 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ humanfriendly==10.0
 psutil==6.0.0
 PyYAML==6.0.2
 Requests==2.32.3
+table2ascii~=1.1.3
diff --git a/utils.py b/utils.py
index c928002..4cdd799 100644
--- a/utils.py
+++ b/utils.py
@@ -76,7 +76,7 @@ def mod_only():
 
 
 # Gross function, but it works.
-def handle_api_request(route_key, data=None) -> tuple[Union[dict, list, None]]:
+def handle_api_request(route_key, data=None) -> tuple[Union[dict, list, None], str]:
     # route_key is your YAML key
     # Data defaults to None so we don't have to set it