forked from sass/tipibot
Add patch notes to bot
This commit is contained in:
148
commands/info_commands.py
Normal file
148
commands/info_commands.py
Normal file
@@ -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))
|
||||
Reference in New Issue
Block a user