From 5e6ed4d6cc00d6de308f316c6566856e0d53ccee Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Thu, 29 Oct 2026 23:12:16 +0100 Subject: [PATCH] add /versjon showing the branch and commit the bot runs Co-Authored-By: Claude Opus 5.5 --- src/cogs/botinfo.py | 48 ++++++++++++++++++++++++++++++++++++ src/cogs/utils/misc_utils.py | 41 ++++++++++++++++++++++++++++++ src/run.py | 3 +++ 3 files changed, 92 insertions(+) diff --git a/src/cogs/botinfo.py b/src/cogs/botinfo.py index ffcfff1..7093d89 100644 --- a/src/cogs/botinfo.py +++ b/src/cogs/botinfo.py @@ -8,6 +8,9 @@ from discord import app_commands from discord.ext import commands from psutil import Process +from cogs.utils import embed_templates +from cogs.utils import misc_utils + class BotInfo(commands.Cog): """View information about the bot""" @@ -83,6 +86,8 @@ class BotInfo(commands.Cog): embed.add_field(name="Kernel", value=f"{platform.system()} {platform.release()}") if exists("/.dockerenv"): embed.add_field(name="Docker", value="✔️") + if version := self.bot.version: + embed.add_field(name="Versjon", value=f"`{version.branch}`\n{self.commit_link(version)}") embed.add_field( name=f"Brukere ({len(total_members)})", value=f'{self.bot.emoji["online"]}{len(online_members)}\n' @@ -128,6 +133,49 @@ class BotInfo(commands.Cog): embed.add_field(name="📶 Ping", value=f"* **Websocket ping:** {self.get_ping()} ms\n* **I hodet:** ∞") await interaction.response.send_message(embed=embed, content=None) + @app_commands.checks.bot_has_permissions(embed_links=True) + @app_commands.checks.cooldown(1, 2) + @app_commands.command(name="versjon", description="Se hvilken branch og commit botten kjører") + async def version(self, interaction: discord.Interaction): + """ + View the branch and commit the bot is running + + Parameters + ---------- + interaction (discord.Interaction): The slash command interaction + """ + + if not (version := self.bot.version): + embed = embed_templates.error_warning("Fant ikke versjonen. Botten kjører kanskje ikke fra et git-repo") + return await interaction.response.send_message(embed=embed) + + embed = discord.Embed(color=interaction.client.user.color, title="Versjon") + embed.add_field(name="Branch", value=f"`{version.branch}`") + embed.add_field(name="Commit", value=self.commit_link(version)) + embed.add_field(name="Dato", value=discord.utils.format_dt(version.committed, style="f")) + embed.add_field(name="Melding", value=discord.utils.escape_markdown(version.subject), inline=False) + if version.dirty: + embed.set_footer(text="Kjører med endringer som ikke er committet") + await interaction.response.send_message(embed=embed) + + def commit_link(self, version: misc_utils.GitVersion) -> str: + """ + Format the commit as a link to it in the source code repository + + Parameters + ---------- + version (misc_utils.GitVersion): The version the bot runs + + Returns + ---------- + (str): The short commit hash, linked if the source code url is configured + """ + + short = f"`{version.commit[:7]}`" + if source_code := self.bot.misc.get("source_code"): + return f"[{short}]({source_code}/commit/{version.commit})" + return short + def get_ping(self) -> int: """ Get the bot's ping in milliseconds diff --git a/src/cogs/utils/misc_utils.py b/src/cogs/utils/misc_utils.py index 5c566d5..49883f7 100644 --- a/src/cogs/utils/misc_utils.py +++ b/src/cogs/utils/misc_utils.py @@ -1,7 +1,9 @@ import asyncio import datetime import functools +import subprocess import textwrap +from dataclasses import dataclass from math import ceil import aiohttp @@ -33,6 +35,45 @@ def localize(dt: datetime.datetime, tz: datetime.tzinfo | None) -> datetime.date return dt.replace(tzinfo=tz) if tz else dt.astimezone() +@dataclass +class GitVersion: + """The branch and commit the code is checked out at""" + + branch: str + commit: str + subject: str + committed: datetime.datetime + dirty: bool + + +def git_version() -> GitVersion | None: + """ + Get the branch and latest commit of the git repository the bot runs from + + Returns + ---------- + (GitVersion | None): The branch and commit. None if git or the repository isn't available + """ + + def git(*args: str) -> str: + return subprocess.run(["git", *args], capture_output=True, text=True, check=True).stdout.strip() + + try: + branch = git("rev-parse", "--abbrev-ref", "HEAD") + commit, timestamp, subject = git("log", "-1", "--format=%H%n%ct%n%s").split("\n", 2) + dirty = bool(git("status", "--porcelain", "--untracked-files=no")) + except (OSError, subprocess.CalledProcessError, ValueError): + return None + + return GitVersion( + branch=branch, + commit=commit, + subject=subject, + committed=datetime.datetime.fromtimestamp(int(timestamp), datetime.UTC), + dirty=dirty, + ) + + # The exceptions discord.py's tasks.loop retries instead of stopping on RETRIED_TASK_EXCEPTIONS = ( OSError, diff --git a/src/run.py b/src/run.py index 4e3b06c..87588c1 100644 --- a/src/run.py +++ b/src/run.py @@ -106,6 +106,9 @@ class Bot(commands.Bot): midnight_tz = datetime.UTC misc_utils.MIDNIGHT = datetime.time(tzinfo=midnight_tz) + # Read once, so it shows the code that's running even if the repository is updated without a restart + self.version = misc_utils.git_version() + async def setup_hook(self): """ Open the HTTP session and database pool, load the cogs and sync the slash commands