From 0bccd9a677e8571c8ea26e1cb5fae5524d37c23f Mon Sep 17 00:00:00 2001
From: Jakub Kuczys <me@jacken.men>
Date: Sat, 28 Dec 2024 17:39:40 +0100
Subject: [PATCH] Single-source supported Java versions in Audio code

---
 redbot/cogs/audio/core/commands/llset.py      | 17 +++++++--
 redbot/cogs/audio/core/events/dpy.py          |  9 ++++-
 redbot/cogs/audio/managed_node/__init__.py    |  5 +--
 .../cogs/audio/managed_node/version_pins.py   | 14 ++++++-
 redbot/cogs/audio/manager.py                  | 38 +++++++++++++------
 5 files changed, 62 insertions(+), 21 deletions(-)

diff --git a/redbot/cogs/audio/core/commands/llset.py b/redbot/cogs/audio/core/commands/llset.py
index d91b99f78f6..f11ddb2bb9c 100644
--- a/redbot/cogs/audio/core/commands/llset.py
+++ b/redbot/cogs/audio/core/commands/llset.py
@@ -10,10 +10,11 @@
 from redbot.core import commands
 from redbot.core.data_manager import cog_data_path
 from redbot.core.i18n import Translator
-from redbot.core.utils.chat_formatting import box, inline
+from redbot.core.utils.chat_formatting import box, humanize_list, inline
 
 from ..abc import MixinMeta
 from ..cog_utils import CompositeMetaClass
+from ...managed_node import version_pins
 from ...utils import (
     MAX_JAVA_RAM,
     DEFAULT_LAVALINK_YAML,
@@ -29,6 +30,16 @@
 _ = Translator("Audio", Path(__file__))
 
 
+class LavalinkSetupJavaCommand(commands.Command):
+    def format_text_for_context(self, ctx: commands.Context, text: str) -> str:
+        text = super().format_text_for_context(ctx, text)
+        return text.format(
+            supported_java_versions=humanize_list(
+                list(map(str, version_pins.SUPPORTED_JAVA_VERSIONS))
+            ),
+        )
+
+
 class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):
     @commands.group(name="llset")
     @commands.is_owner()
@@ -43,7 +54,7 @@ async def command_llset(self, ctx: commands.Context):
         All the commands in here have the potential to break the Audio cog.
         """
 
-    @command_llset.command(name="java")
+    @command_llset.command(name="java", cls=LavalinkSetupJavaCommand)
     @has_managed_server()
     async def command_llset_java(self, ctx: commands.Context, *, java_path: str = "java"):
         """Change your Java executable path.
@@ -51,7 +62,7 @@ async def command_llset_java(self, ctx: commands.Context, *, java_path: str = "j
         This command shouldn't need to be used most of the time, and is only useful if the host machine has conflicting Java versions.
 
         If changing this make sure that the Java executable you set is supported by Audio.
-        The current supported versions are Java 17 and 11.
+        The current supported versions are Java {supported_java_versions}.
 
         Enter nothing or "java" to reset it back to default.
         """
diff --git a/redbot/cogs/audio/core/events/dpy.py b/redbot/cogs/audio/core/events/dpy.py
index 7627dc42da7..405104bb0d0 100644
--- a/redbot/cogs/audio/core/events/dpy.py
+++ b/redbot/cogs/audio/core/events/dpy.py
@@ -23,6 +23,7 @@
 from redbot.core.utils.chat_formatting import box, humanize_list, underline, bold
 
 from ...errors import TrackEnqueueError, AudioError
+from ...managed_node import version_pins
 from ..abc import MixinMeta
 from ..cog_utils import CompositeMetaClass
 
@@ -87,7 +88,7 @@
         "This command will change the executable path of Java, "
         "this is useful if you have multiple installations of Java and the default one is causing issues. "
         "Please don't change this unless you are certain that the Java version you are specifying is supported by Red. "
-        "The default and supported versions are currently Java 17 and 11."
+        "The supported versions are currently Java {supported_java_versions}."
     ),
     "command_llset_heapsize": _(
         "This command will change the maximum RAM allocation for the managed Lavalink node, "
@@ -279,7 +280,11 @@ async def cog_before_invoke(self, ctx: commands.Context) -> None:
                     "If you wish to continue, enter this case sensitive token without spaces as your next message."
                     "\n\n{confirm_token}"
                 ).format(
-                    template=_(DANGEROUS_COMMANDS[ctx.command.callback.__name__]),
+                    template=_(DANGEROUS_COMMANDS[ctx.command.callback.__name__]).format(
+                        supported_java_versions=humanize_list(
+                            list(map(str, version_pins.SUPPORTED_JAVA_VERSIONS))
+                        ),
+                    ),
                     confirm_token=box(confirm_token, lang="py"),
                 )
                 sent = await ctx.send(message)
diff --git a/redbot/cogs/audio/managed_node/__init__.py b/redbot/cogs/audio/managed_node/__init__.py
index 52d0a443b99..8311c7a1879 100644
--- a/redbot/cogs/audio/managed_node/__init__.py
+++ b/redbot/cogs/audio/managed_node/__init__.py
@@ -3,7 +3,7 @@
 
 from .ll_server_config import generate_server_config, get_default_server_config
 from .ll_version import LAVALINK_BUILD_LINE, LavalinkOldVersion, LavalinkVersion
-from .version_pins import JAR_VERSION, YT_PLUGIN_VERSION
+from . import version_pins
 
 __all__ = (
     "generate_server_config",
@@ -11,6 +11,5 @@
     "LAVALINK_BUILD_LINE",
     "LavalinkOldVersion",
     "LavalinkVersion",
-    "JAR_VERSION",
-    "YT_PLUGIN_VERSION",
+    "version_pins",
 )
diff --git a/redbot/cogs/audio/managed_node/version_pins.py b/redbot/cogs/audio/managed_node/version_pins.py
index aa4003fadc6..50745081d5a 100644
--- a/redbot/cogs/audio/managed_node/version_pins.py
+++ b/redbot/cogs/audio/managed_node/version_pins.py
@@ -1,9 +1,19 @@
-from typing import Final
+from typing import Final, Tuple
 
 from .ll_version import LavalinkVersion
 
-__all__ = ("JAR_VERSION", "YT_PLUGIN_VERSION")
+__all__ = (
+    "JAR_VERSION",
+    "YT_PLUGIN_VERSION",
+    "SUPPORTED_JAVA_VERSIONS",
+    "LATEST_SUPPORTED_JAVA_VERSION",
+    "OLDER_SUPPORTED_JAVA_VERSIONS",
+)
 
 
 JAR_VERSION: Final[LavalinkVersion] = LavalinkVersion(3, 7, 12, red=1)
 YT_PLUGIN_VERSION: Final[str] = "1.11.2"
+# keep this sorted from oldest to latest
+SUPPORTED_JAVA_VERSIONS: Final[Tuple[int, ...]] = (11, 17)
+LATEST_SUPPORTED_JAVA_VERSION: Final = SUPPORTED_JAVA_VERSIONS[-1]
+OLDER_SUPPORTED_JAVA_VERSIONS: Final[Tuple[int, ...]] = SUPPORTED_JAVA_VERSIONS[:-1]
diff --git a/redbot/cogs/audio/manager.py b/redbot/cogs/audio/manager.py
index 4e750da26b7..bd7e635376b 100644
--- a/redbot/cogs/audio/manager.py
+++ b/redbot/cogs/audio/manager.py
@@ -21,6 +21,7 @@
 
 from redbot.core import data_manager, Config
 from redbot.core.i18n import Translator
+from redbot.core.utils.chat_formatting import humanize_list
 
 from . import managed_node
 from .errors import (
@@ -36,6 +37,7 @@
     NoProcessFound,
     NodeUnhealthy,
 )
+from .managed_node import version_pins
 from .managed_node.ll_version import LAVALINK_BUILD_LINE, LavalinkVersion, LavalinkOldVersion
 from .utils import (
     get_max_allocation_size,
@@ -109,7 +111,7 @@
 class ServerManager:
     LAVALINK_DOWNLOAD_URL: Final[str] = (
         "https://github.com/Cog-Creators/Lavalink-Jars/releases/download/"
-        f"{managed_node.JAR_VERSION}/"
+        f"{version_pins.JAR_VERSION}/"
         "Lavalink.jar"
     )
 
@@ -254,15 +256,29 @@ async def _get_jar_args(self) -> Tuple[List[str], Optional[str]]:
             if self._java_version is None:
                 extras = ""
             else:
-                extras = f" however you have version {self._java_version} (executable: {self._java_exc})"
+                version = ".".join(map(str, self._java_version))
+                extras = f" however you have version {version} (executable: {self._java_exc})"
+            supported_versions = humanize_list(
+                list(map(str, version_pins.SUPPORTED_JAVA_VERSIONS)),
+                locale="en-US",
+                style="or-short",
+            )
+            latest_version = str(version_pins.LATEST_SUPPORTED_JAVA_VERSION)
+            older_versions = humanize_list(
+                list(map(str, reversed(version_pins.OLDER_SUPPORTED_JAVA_VERSIONS))),
+                locale="en-US",
+                style="or-short",
+            )
             raise UnsupportedJavaException(
                 await replace_p_with_prefix(
                     self.cog.bot,
-                    f"The managed Lavalink node requires Java 17 or 11 to run{extras};\n"
-                    "Either install version 17 (or 11) and restart the bot or connect to an external Lavalink node "
-                    "(https://docs.discord.red/en/stable/install_guides/index.html)\n"
-                    "If you already have Java 17 or 11 installed then then you will need to specify the executable path, "
-                    "use '[p]llset java' to set the correct Java 17 or 11 executable.",
+                    f"The managed Lavalink node requires Java {supported_versions} to run{extras};\n"
+                    f"Either install version {latest_version} (or {older_versions})"
+                    " and restart the bot or connect to an external Lavalink node"
+                    " (https://docs.discord.red/en/stable/install_guides/index.html)\n"
+                    f"If you already have Java {supported_versions} installed"
+                    " then you will need to specify the executable path,"
+                    f" use '[p]llset java' to set the correct Java {supported_versions} executable.",
                 )  # TODO: Replace with Audio docs when they are out
             )
         java_xms, java_xmx = list((await self._config.java.all()).values())
@@ -300,7 +316,7 @@ async def _has_java(self) -> Tuple[bool, Optional[Tuple[int, int]]]:
             self._java_version = None
         else:
             self._java_version = await self._get_java_version()
-            self._java_available = self._java_version[0] in (11, 17)
+            self._java_available = self._java_version[0] in version_pins.SUPPORTED_JAVA_VERSIONS
             self._java_exc = java_exec
         return self._java_available, self._java_version
 
@@ -386,7 +402,7 @@ async def _download_jar(self) -> None:
                     # A 404 means our LAVALINK_DOWNLOAD_URL is invalid, so likely the jar version
                     # hasn't been published yet
                     raise LavalinkDownloadFailed(
-                        f"Lavalink jar version {managed_node.JAR_VERSION}"
+                        f"Lavalink jar version {version_pins.JAR_VERSION}"
                         " hasn't been published yet",
                         response=response,
                         should_retry=False,
@@ -474,7 +490,7 @@ async def _is_up_to_date(self):
         self._jvm = java["jvm"].decode()
         self._lavaplayer = lavaplayer["lavaplayer"].decode()
         self._buildtime = date
-        self._up_to_date = self._lavalink_version >= managed_node.JAR_VERSION
+        self._up_to_date = self._lavalink_version >= version_pins.JAR_VERSION
         return self._up_to_date
 
     async def maybe_download_jar(self):
@@ -494,7 +510,7 @@ async def maybe_download_jar(self):
             log.info(
                 "Lavalink version outdated, triggering update from %s to %s...",
                 self._lavalink_version,
-                managed_node.JAR_VERSION,
+                version_pins.JAR_VERSION,
             )
             await self._download_jar()
         else: