From 58684d5f3420fe2ade444f2328d993afe981226c Mon Sep 17 00:00:00 2001 From: Rene Arumetsa Date: Sun, 3 May 2026 12:02:19 +0300 Subject: [PATCH] Add patch notes to bot --- bot.py | 3 + commands/info_commands.py | 148 ++++++++++++++++++++++++++++++++++++++ docs/PATCHNOTES.md | 8 +++ strings.py | 15 ++++ 4 files changed, 174 insertions(+) create mode 100644 commands/info_commands.py create mode 100644 docs/PATCHNOTES.md diff --git a/bot.py b/bot.py index 5e33873..3703330 100644 --- a/bot.py +++ b/bot.py @@ -35,6 +35,7 @@ from commands.economy_profile_commands import register_economy_profile_commands from commands.economy_support_commands import register_economy_support_commands from commands.ops_channel_commands import register_ops_channel_commands from commands.ops_admin_commands import register_ops_admin_commands +from commands.info_commands import register_info_commands # --------------------------------------------------------------------------- # Logging @@ -483,6 +484,8 @@ register_ops_channel_commands( set_allowed_channels=_set_allowed_channels, ) +register_info_commands(tree, bot, log) + @tree.command(name="ping", description=S.CMD["ping"]) async def cmd_ping(interaction: discord.Interaction): diff --git a/commands/info_commands.py b/commands/info_commands.py new file mode 100644 index 0000000..df23a2e --- /dev/null +++ b/commands/info_commands.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import logging +import re +from pathlib import Path + +import discord +from discord import app_commands + +import strings as S + + +_PATCHNOTES_PATH = Path(__file__).resolve().parent.parent / "docs" / "PATCHNOTES.md" +_VERSION_RE = re.compile(r"^##\s+(.+?)\s*$") +_EMBED_DESC_MAX = 4096 +_SELECT_OPTIONS_MAX = 25 + + +def _load_versions() -> list[tuple[str, str]]: + try: + text = _PATCHNOTES_PATH.read_text(encoding="utf-8") + except FileNotFoundError: + return [] + versions: list[tuple[str, str]] = [] + cur_header: str | None = None + cur_body: list[str] = [] + for line in text.splitlines(): + m = _VERSION_RE.match(line) + if m: + if cur_header is not None: + versions.append((cur_header, "\n".join(cur_body).strip())) + cur_header = m.group(1).strip() + cur_body = [] + elif cur_header is not None: + cur_body.append(line) + if cur_header is not None: + versions.append((cur_header, "\n".join(cur_body).strip())) + return versions + + +def _build_embed(versions: list[tuple[str, str]], idx: int) -> discord.Embed: + header, body = versions[idx] + if len(body) > _EMBED_DESC_MAX: + body = body[: _EMBED_DESC_MAX - 1] + "…" + embed = discord.Embed( + title=S.PATCHNOTES_UI["title"].format(version=header), + description=body or S.PATCHNOTES_UI["empty_version"], + color=0x5865F2, + ) + embed.set_footer( + text=S.PATCHNOTES_UI["footer"].format(idx=idx + 1, total=len(versions)) + ) + return embed + + +def register_info_commands( + tree: app_commands.CommandTree, + bot: discord.Client, + log: logging.Logger, +) -> None: + @tree.command(name="patchnotes", description=S.CMD["patchnotes"]) + async def cmd_patchnotes(interaction: discord.Interaction): + versions = _load_versions() + if not versions: + await interaction.response.send_message( + S.PATCHNOTES_UI["empty_file"], ephemeral=True + ) + return + + invoker_id = interaction.user.id + + class PatchNotesView(discord.ui.View): + def __init__(self, idx: int = 0): + super().__init__(timeout=180) + self.idx = idx + self._rebuild() + + def _rebuild(self): + self.clear_items() + newer_btn = discord.ui.Button( + label=S.PATCHNOTES_UI["btn_newer"], + style=discord.ButtonStyle.secondary, + disabled=self.idx <= 0, + ) + older_btn = discord.ui.Button( + label=S.PATCHNOTES_UI["btn_older"], + style=discord.ButtonStyle.secondary, + disabled=self.idx >= len(versions) - 1, + ) + newer_btn.callback = self._make_step_cb(-1) + older_btn.callback = self._make_step_cb(+1) + self.add_item(newer_btn) + self.add_item(older_btn) + + opts: list[discord.SelectOption] = [] + for i, (hdr, _) in enumerate(versions[:_SELECT_OPTIONS_MAX]): + opts.append( + discord.SelectOption( + label=hdr[:100], + value=str(i), + default=(i == self.idx), + ) + ) + if len(opts) > 1: + select = discord.ui.Select( + placeholder=S.PATCHNOTES_UI["select_placeholder"], + options=opts, + min_values=1, + max_values=1, + ) + select.callback = self._make_select_cb(select) + self.add_item(select) + + def _make_step_cb(self, delta: int): + async def _cb(interaction: discord.Interaction): + if interaction.user.id != invoker_id: + await interaction.response.send_message( + S.ERR["not_your_menu"], ephemeral=True + ) + return + self.idx = max(0, min(len(versions) - 1, self.idx + delta)) + self._rebuild() + await interaction.response.edit_message( + embed=_build_embed(versions, self.idx), view=self + ) + + return _cb + + def _make_select_cb(self, select: discord.ui.Select): + async def _cb(interaction: discord.Interaction): + if interaction.user.id != invoker_id: + await interaction.response.send_message( + S.ERR["not_your_menu"], ephemeral=True + ) + return + self.idx = int(select.values[0]) + self._rebuild() + await interaction.response.edit_message( + embed=_build_embed(versions, self.idx), view=self + ) + + return _cb + + view = PatchNotesView(0) + await interaction.response.send_message( + embed=_build_embed(versions, 0), view=view, ephemeral=True + ) + log.info("/patchnotes by %s (%d versions)", interaction.user, len(versions)) diff --git a/docs/PATCHNOTES.md b/docs/PATCHNOTES.md new file mode 100644 index 0000000..c454d8d --- /dev/null +++ b/docs/PATCHNOTES.md @@ -0,0 +1,8 @@ +# TipiBOTi muudatuste logi + +Siit leiad ülevaate TipiBOTi uuendustest. Uusimad muudatused on üleval. +Vorminda iga versioon `## ` peakirjaga (nt `## v0.1.0 — 2026-05-03`). + +## v0.1.0 — 2026-05-03 + +- Lisatud `/patchnotes` diff --git a/strings.py b/strings.py index cb5cfd5..4275ee7 100644 --- a/strings.py +++ b/strings.py @@ -171,6 +171,7 @@ CMD: dict[str, str] = { "fish": "Mine kalastama (interaktiivne mäng, 2min ooteaeg)", "fishbook": "Vaata oma kalakogu ja kogutud kalaliike", "fishsell": "Müü kalu oma inventarist", + "patchnotes": "Vaata TipiBOTi viimaseid muudatusi ja uuendusi", } # --------------------------------------------------------------------------- @@ -752,6 +753,20 @@ SEND_UI: dict[str, str] = { "forbidden": "❌ Mul pole õigust kanalisse {channel} kirjutada.", } +# --------------------------------------------------------------------------- +# /patchnotes UI strings +# --------------------------------------------------------------------------- + +PATCHNOTES_UI: dict[str, str] = { + "title": "📝 Muudatuste logi — {version}", + "footer": "Versioon {idx}/{total}", + "btn_newer": "◀ Uuem", + "btn_older": "Vanem ▶", + "select_placeholder": "Vali versioon…", + "empty_file": "ℹ️ Muudatuste logi on hetkel tühi.", + "empty_version": "_(selle versiooni kohta märkmeid pole)_", +} + # --------------------------------------------------------------------------- # /allowchannel /denychannel /channels UI strings # ---------------------------------------------------------------------------