diff --git a/data/koans.sql b/data/koans.sql index 2447861..42f3803 100644 --- a/data/koans.sql +++ b/data/koans.sql @@ -1,20 +1,30 @@ BEGIN; --- Koan data {{{ -INSERT INTO version_history (component, from_version, to_version, author) - VALUES ('KOANS', 0, 1, 'Initial Creation'); ----- !koans lists koans. !koan gives a random koan. !koans add name ... !koans del name ... +INSERT INTO version_history (component, from_version, to_version, author) +VALUES ('KOANS', 0, 2, 'Initial Creation'); CREATE TABLE koans( - koanid INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - communityid INTEGER NOT NULL REFERENCES communities ON UPDATE CASCADE ON DELETE CASCADE, - name TEXT NOT NULL, - message TEXT NOT NULL, + koanid INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + communityid INTEGER NOT NULL REFERENCES communities(communityid) ON DELETE CASCADE ON UPDATE CASCADE, + content TEXT NOT NULL, + created_by INTEGER REFERENCES user_profiles(profileid) ON UPDATE CASCADE ON DELETE NO ACTION, + deleted_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), _timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TRIGGER koans_timestamp BEFORE UPDATE ON koans FOR EACH ROW EXECUTE FUNCTION update_timestamp_column(); --- }}} +CREATE VIEW + koans_info +AS + SELECT + *, + (deleted_at is not NULL) AS is_deleted, + (row_number() OVER (PARTITION BY communityid ORDER BY created_at ASC)) as koanlabel + FROM + koans + ORDER BY (communityid, created_at); + + COMMIT; diff --git a/koans/__init__.py b/koans/__init__.py index 81d6555..0cb2d20 100644 --- a/koans/__init__.py +++ b/koans/__init__.py @@ -3,3 +3,4 @@ import logging logger = logging.getLogger(__name__) from .twitch import setup as twitch_setup +from .discord import setup diff --git a/koans/data.py b/koans/data.py index 2dbe9ab..428df4a 100644 --- a/koans/data.py +++ b/koans/data.py @@ -1,32 +1,31 @@ -from data import Registry, RowModel -from data.columns import String, Timestamp, Integer +from data import Registry, RowModel, Table +from data.columns import Bool, Integer, Timestamp, String +from weakref import WeakValueDictionary class Koan(RowModel): - """ - Schema - ====== - CREATE TABLE koans( - koanid INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - communityid INTEGER NOT NULL REFERENCES communities ON UPDATE CASCADE ON DELETE CASCADE, - name TEXT NOT NULL, - message TEXT NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - _timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() - ); - """ _tablename_ = 'koans' - _cache_ = {} + _cache_ = WeakValueDictionary() koanid = Integer(primary=True) communityid = Integer() - name = String() - message = String() + content = String() + deleted_at = Timestamp() + created_by = Integer() created_at = Timestamp() _timestamp = Timestamp() -class KoanData(Registry): - VERSION = ('KOANS', 1) +class KoanInfo(Koan): + _tablename_ = 'koans_info' + _readonly_ = True + _cache_ = WeakValueDictionary() + + koanlabel = Integer() + is_deleted = Bool() + +class KoansData(Registry): + VERSION = ('KOANS', 2) koans = Koan.table + koans_info = KoanInfo.table diff --git a/koans/discord/__init__.py b/koans/discord/__init__.py new file mode 100644 index 0000000..7f424d7 --- /dev/null +++ b/koans/discord/__init__.py @@ -0,0 +1,5 @@ +from .. import logger + +async def setup(bot): + from .cog import KoanCog + await bot.add_cog(KoanCog(bot)) diff --git a/koans/discord/cog.py b/koans/discord/cog.py new file mode 100644 index 0000000..65d9c09 --- /dev/null +++ b/koans/discord/cog.py @@ -0,0 +1,251 @@ +from typing import Optional +import asyncio +import random + +import discord +from discord.enums import TextStyle +from discord.ext import commands as cmds +from discord import app_commands as appcmds + +from meta import LionBot, LionCog, LionContext +from meta.errors import ResponseTimedOut, SafeCancellation, UserInputError +from utils.ui import input + +from ..data import KoanInfo, KoansData +from ..koans import KoanRegistry +from ..lib import minify + +from . import logger +from .ui import KoanListUI + + +class KoanCog(LionCog): + def __init__(self, bot: LionBot): + self.bot = bot + self.data = bot.db.load_registry(KoansData()) + self.koans = KoanRegistry(self.data) + + async def cog_load(self): + await self.data.init() + await self.koans.init() + + # ----- API ----- + async def koan_acmpl(self, interaction: discord.Interaction, partial: str): + """ + Autocomplete for a community-local koan selection. + """ + if not interaction.guild: + return [] + + choices = [] + community = await self.bot.profiles.fetch_community( + interaction.guild, interaction=interaction, + touch=False + ) + koans = await self.koans.get_community_koans(community.communityid) + if not koans: + nullchoice = appcmds.Choice( + name="No koans have been created!", + value="0", + ) + choices.append(nullchoice) + else: + for koan in koans: + labelstr = f"#{koan.koanlabel}:" + if partial.lower() in labelstr + koan.content.lower(): + minified = minify(koan.content, 100 - len(labelstr) - 1, strip=' >') + displayed = f"{labelstr} {minified}" + choice = appcmds.Choice( + name=displayed, + value=str(koan.koanlabel), + ) + choices.append(choice) + if not choices: + nullchoice = appcmds.Choice( + name="No koans matching your input!", + value="0", + ) + choices.append(nullchoice) + + return choices + + async def resolve_koan(self, ctx: LionContext, koanstr: str) -> KoanInfo: + """ + Resolve a koan string provided as an argument. + + Essentially only accepts integer koan labels. + """ + koanstr = koanstr.strip('# ') + if not koanstr.isdigit(): + raise UserInputError( + "Could not parse desired koan! Please enter the number or select from autocomplete options." + ) + elif (label := int(koanstr)) == 0: + raise UserInputError( + "Invalid option selected!" + ) + else: + koan = await self.koans.get_koan_label(ctx.community.communityid, label) + if not koan: + raise UserInputError( + f"Koan #{label} does not exist!" + ) + else: + return koan + + # ----- Commands ------ + @cmds.hybrid_command( + name='koan', + description="Display a random koan." + ) + @cmds.guild_only() + async def koan_cmd(self, ctx: LionContext): + koans = await self.koans.get_community_koans(ctx.community.communityid) + if koans: + # Select a random koan + koan = random.choice(koans) + if '\n' in koan.content: + formatted = f"**#{koan.koanlabel}:**\n{koan.content}" + else: + formatted = f"**#{koan.koanlabel}:** {koan.content}" + await ctx.reply(formatted) + else: + await ctx.reply("There are no koans to display!") + + @cmds.hybrid_group( + name='koans', + description="Base command group for koans management.", + ) + @cmds.has_permissions(manage_guild=True) + @cmds.guild_only() + async def koans_grp(self, ctx: LionContext): + await self.koans_list_cmd(ctx) + + @koans_grp.command( + name='add', + description="Create a new koan. Use without arguments to add a multiline koan." + ) + @appcmds.describe( + content="Content of the koan to add" + ) + @cmds.has_permissions(manage_guild=True) + async def koans_add_cmd(self, ctx: LionContext, *, content: Optional[str] = None): + if content is not None: + interaction = ctx.interaction + to_create = content + elif not ctx.interaction: + invoked = ' '.join( + (*ctx.invoked_parents, ctx.invoked_with or '') + ) + await ctx.error_reply( + "**USAGE:** {prefix}{cmd} ".format( + prefix=ctx.prefix, + cmd=invoked + ) + ) + raise SafeCancellation + else: + try: + interaction, to_create = await input( + interaction=ctx.interaction, + title="Create Koan", + question="Koan Content", + timeout=300, + style=TextStyle.long, + min_length=1, + max_length=2000 - 8 + ) + except asyncio.TimeoutError: + raise SafeCancellation + + if interaction: + sender = interaction.response.send_message + else: + sender = ctx.reply + + koan = await self.koans.create_koan( + communityid=ctx.community.communityid, + content=to_create, + created_by=ctx.profile.profileid, + ) + await sender(f"Koan #{koan.koanlabel} created!") + + @koans_grp.command( + name='del', + description="Delete a saved koan." + ) + @appcmds.describe( + koanstr="Select the koan to delete, or write the number." + ) + @cmds.has_permissions(manage_guild=True) + @appcmds.rename(koanstr='koan') + async def koans_del_cmd(self, ctx: LionContext, koanstr: str): + koan = await self.resolve_koan(ctx, koanstr) + label = koan.koanlabel + await self.koans.delete_koan(koan.koanid) + await ctx.reply(f"Koan #{label} was deleted.") + + koans_del_cmd.autocomplete('koanstr')(koan_acmpl) + + @koans_grp.command( + name='list', + description="Display the community koans. Koans may also be added/edited/deleted here." + ) + @cmds.has_permissions(manage_guild=True) + async def koans_list_cmd(self, ctx: LionContext): + view = KoanListUI(self.bot, self.koans, ctx.community.communityid, ctx.profile.profileid, ctx.author.id) + if ctx.interaction is None: + await view.send(ctx.channel) + else: + await view.run(ctx.interaction) + await view.wait() + + @koans_grp.command( + name='edit', + description="Edit a saved koan." + ) + @appcmds.describe( + koanstr="Select the koan to edit, or write the number.", + new_content="New content for the koan. Leave unselected to edit in multiline modal." + ) + @appcmds.rename(koanstr='koan') + @cmds.has_permissions(manage_guild=True) + async def koans_edit_cmd(self, ctx: LionContext, koanstr: str, *, new_content: Optional[str] = None): + koan = await self.resolve_koan(ctx, koanstr) + if new_content is not None: + interaction = ctx.interaction + elif not ctx.interaction: + invoked = ' '.join( + (*ctx.invoked_parents, ctx.invoked_with or '') + ) + await ctx.error_reply( + "**USAGE:** {prefix}{cmd} ".format( + prefix=ctx.prefix, + cmd=invoked + ) + ) + raise SafeCancellation + else: + try: + interaction, new_content = await input( + interaction=ctx.interaction, + title="Edit Koan", + question="Koan Content", + timeout=300, + style=TextStyle.long, + default=koan.content, + min_length=1, + max_length=2000 - 8 + ) + except asyncio.TimeoutError: + raise SafeCancellation + + if interaction: + sender = interaction.response.send_message + else: + sender = ctx.reply + + await self.data.koans.update_where(koanid=koan.koanid).set(content=new_content) + await sender(f"Koan #{koan.koanlabel} Updated!") + + koans_edit_cmd.autocomplete('koanstr')(koan_acmpl) diff --git a/koans/discord/ui/__init__.py b/koans/discord/ui/__init__.py new file mode 100644 index 0000000..eccf03c --- /dev/null +++ b/koans/discord/ui/__init__.py @@ -0,0 +1 @@ +from .koanlist import KoanListUI diff --git a/koans/discord/ui/koanlist.py b/koans/discord/ui/koanlist.py new file mode 100644 index 0000000..56b8eaf --- /dev/null +++ b/koans/discord/ui/koanlist.py @@ -0,0 +1,271 @@ +import asyncio + +import discord +from discord.ui.select import select, Select +from discord.components import SelectOption +from discord.enums import ButtonStyle, TextStyle +from discord.ui.button import button, Button +from discord.ui.text_input import TextInput + +from meta import LionBot, conf +from meta.errors import SafeCancellation + +from utils.ui import MessageUI, input +from utils.lib import MessageArgs + +from .. import logger +from ...koans import KoanRegistry +from ...lib import minify + +class KoanListUI(MessageUI): + block_len = 10 + def __init__(self, bot: LionBot, koans: KoanRegistry, communityid: int, profileid: int, callerid: int, **kwargs): + super().__init__(callerid=callerid, **kwargs) + + self.bot = bot + self.koans = koans + self.communityid = communityid + self.caller_profileid = profileid + + # Paging state + self._pagen = 0 + self.blocks = [[]] + + @property + def page_count(self): + return len(self.blocks) + + @property + def pagen(self): + self._pagen = self._pagen % self.page_count + return self._pagen + + @pagen.setter + def pagen(self, value): + self._pagen = value % self.page_count + + @property + def current_page(self): + return self.blocks[self.pagen] + + # ----- API ----- + + # ----- UI Components ----- + @button( + label="New Koan", + style=ButtonStyle.green + ) + async def new_koan_button(self, press: discord.Interaction, pressed: Button): + # Show the create koan modal + # Create koan flow. + try: + interaction, to_create = await input( + interaction=press, + title="Create Koan", + question="Koan Content", + timeout=300, + style=TextStyle.long, + min_length=1, + max_length=2000 - 8 + ) + except asyncio.TimeoutError: + raise SafeCancellation + await interaction.response.defer(thinking=True, ephemeral=True) + + koan = await self.koans.create_koan( + communityid=self.communityid, + content=to_create, + created_by=self.caller_profileid + ) + self.pagen = -1 + await self.refresh(thinking=interaction) + + # Koan edit and delete selectors + async def make_page_options(self): + options = [] + for koan in self.current_page: + labelstr = f"#{koan.koanlabel}: " + minified = minify(koan.content, 100 - len(labelstr) - 1, strip=' >') + option = SelectOption( + label=f"{labelstr} {minified}", + value=str(koan.koanid) + ) + options.append(option) + return options + + @select( + cls=Select, + placeholder="Select to delete", + min_values=1, max_values=1 + ) + async def delete_menu(self, selection: discord.Interaction, selected: Select): + await selection.response.defer(thinking=True, ephemeral=True) + if selected.values: + koanid = int(selected.values[0]) + await self.koans.delete_koan(koanid) + await self.refresh(thinking=selection) + + async def refresh_delete_menu(self): + self.delete_menu.options = await self.make_page_options() + + @select( + cls=Select, + placeholder="Select to edit", + min_values=1, max_values=1 + ) + async def edit_menu(self, selection: discord.Interaction, selected: Select): + if selected.values: + koanid = int(selected.values[0]) + koan = await self.koans.get_koan(koanid) + assert koan is not None + try: + interaction, new_content = await input( + interaction=selection, + title="Edit Koan", + question="Koan Content", + timeout=300, + style=TextStyle.long, + default=koan.content, + min_length=1, + max_length=2000 - 8 + ) + await interaction.response.defer(thinking=True, ephemeral=True) + except asyncio.TimeoutError: + raise SafeCancellation + + await koan.update(content=new_content) + await self.refresh(thinking=interaction) + + async def refresh_edit_menu(self): + self.edit_menu.options = await self.make_page_options() + + # Backwards + @button(emoji=conf.emojis.backward, style=ButtonStyle.grey) + async def prev_button(self, press: discord.Interaction, pressed: Button): + await press.response.defer(thinking=True, ephemeral=True) + self.pagen -= 1 + await self.refresh(thinking=press) + + # Jump to page + @button(label="JUMP_PLACEHOLDER", style=ButtonStyle.blurple) + async def jump_button(self, press: discord.Interaction, pressed: Button): + """ + Jump-to-page button. + Loads a page-switch dialogue. + """ + t = self.bot.translator.t + try: + interaction, value = await input( + press, + title="Jump to page", + question="Page number to jump to" + ) + value = value.strip() + except asyncio.TimeoutError: + return + + if not value.lstrip('- ').isdigit(): + error_embed = discord.Embed( + title="Invalid page number, please try again!", + colour=discord.Colour.brand_red() + ) + await interaction.response.send_message(embed=error_embed, ephemeral=True) + else: + await interaction.response.defer(thinking=True) + pagen = int(value.lstrip('- ')) + if value.startswith('-'): + pagen = -1 * pagen + elif pagen > 0: + pagen = pagen - 1 + self.pagen = pagen + await self.refresh(thinking=interaction) + + async def jump_button_refresh(self): + component = self.jump_button + component.label = f"{self.pagen + 1}/{self.page_count}" + component.disabled = (self.page_count <= 1) + + # Forward + @button(emoji=conf.emojis.forward, style=ButtonStyle.grey) + async def next_button(self, press: discord.Interaction, pressed: Button): + await press.response.defer(thinking=True) + self.pagen += 1 + await self.refresh(thinking=press) + + # Quit + @button(emoji=conf.emojis.cancel, style=ButtonStyle.red) + async def quit_button(self, press: discord.Interaction, pressed: Button): + """ + Quit the UI. + """ + await press.response.defer() + await self.quit() + + # ----- UI Flow ----- + async def make_message(self) -> MessageArgs: + if self.current_page: + lines = [] + for koan in self.current_page: + labelstr = f"**#{koan.koanlabel}:** " + minified = minify(koan.content, 100 - len(labelstr) - 1, strip=' >') + lines.append(f"{labelstr} {minified}") + embed = discord.Embed( + title="Community Koans", + description='\n'.join(lines) + ) + else: + embed = discord.Embed( + title="Community Koans", + description="No koans have been created! Use `/koans add` or the 'New Koan' button below to make a koan!" + ) + return MessageArgs(embed=embed) + + async def refresh_layout(self): + # Refresh menus + to_refresh = ( + self.refresh_edit_menu(), + self.refresh_delete_menu(), + self.jump_button_refresh(), + ) + await asyncio.gather(*to_refresh) + + if self.page_count > 1: + # Multiple page case + # < Add Jump Close > + # edit + # delete + self.set_layout( + (self.prev_button, + self.new_koan_button, + self.jump_button, + self.quit_button, + self.next_button), + (self.edit_menu,), + (self.delete_menu,), + ) + elif self.current_page: + # Single page case + # Add Close + # Edit + # Delete + self.set_layout( + (self.new_koan_button, + self.quit_button), + (self.edit_menu,), + (self.delete_menu,), + ) + else: + # Add Close + self.set_layout( + (self.new_koan_button, + self.quit_button), + ) + + async def reload(self): + koans = await self.koans.get_community_koans(self.communityid) + blocks = [ + koans[i:i+self.block_len] + for i in range(0, len(koans), self.block_len) + ] + self.blocks = blocks or [[]] + diff --git a/koans/koans.py b/koans/koans.py index 41c00d5..05799d8 100644 --- a/koans/koans.py +++ b/koans/koans.py @@ -1,34 +1,42 @@ from typing import Optional -from .data import Koan, KoanData +from .data import Koan, KoanInfo, KoansData +from .lib import utc_now class KoanRegistry: - def __init__(self, data: KoanData): - self.data = data + def __init__(self, data: KoansData): + self.data = data async def init(self): await self.data.init() - async def get_community_koans(self, communityid: int) -> list[Koan]: - return await Koan.fetch_where(communityid=communityid) + async def get_community_koans(self, communityid: int) -> list[KoanInfo]: + return await KoanInfo.fetch_where(communityid=communityid, is_deleted=False) + + async def get_koaninfo(self, koanid: int) -> Optional[KoanInfo]: + return await KoanInfo.fetch(koanid) async def get_koan(self, koanid: int) -> Optional[Koan]: return await Koan.fetch(koanid) + + async def get_koan_label(self, communityid: int, label: int) -> Optional[KoanInfo]: + results = await KoanInfo.fetch_where(communityid=communityid, koanlabel=label, is_deleted=False) + return results[0] if results else None - async def get_koan_named(self, communityid: int, name: str) -> Optional[Koan]: - name = name.lower() - koans = await Koan.fetch_where(communityid=communityid, name=name) - if koans: - return koans[0] - else: - return None - - async def create_koan(self, communityid: int, name: str, message: str) -> Koan: - name = name.lower() - await Koan.table.delete_where(communityid=communityid, name=name) + async def create_koan( + self, + communityid: int, + content: str, + created_by: Optional[int] = None + ) -> KoanInfo: koan = await Koan.create( + content=content, communityid=communityid, - name=name, - message=message + created_by=created_by, ) - return koan + info = await KoanInfo.fetch(koan.koanid) + assert info is not None + return info + + async def delete_koan(self, koanid: int): + await self.data.koans.update_where(koanid=koanid).set(deleted_at=utc_now()) diff --git a/koans/lib.py b/koans/lib.py new file mode 100644 index 0000000..4217ef5 --- /dev/null +++ b/koans/lib.py @@ -0,0 +1,19 @@ +from typing import Optional +import datetime as dt +from datetime import datetime + + +def minify(content: str, maxlength: int, strip: Optional[str] = ' ', newlines: str = ' '): + content.replace('\n', newlines) + if strip: + content = content.strip(strip) + + if len(content) > maxlength: + new_content = content[maxlength-3] + '...' + else: + new_content = content + return content + + +def utc_now(): + return datetime.now(dt.UTC) diff --git a/koans/twitch/component.py b/koans/twitch/component.py index 38eecee..922fb7f 100644 --- a/koans/twitch/component.py +++ b/koans/twitch/component.py @@ -7,13 +7,14 @@ from meta import Bot, Context from . import logger from ..koans import KoanRegistry -from ..data import KoanData +from ..data import KoansData +from ..lib import minify class KoanComponent(cmds.Component): def __init__(self, bot: Bot): self.bot = bot - self.data = bot.dbconn.load_registry(KoanData()) + self.data = bot.dbconn.load_registry(KoansData()) self.koans = KoanRegistry(self.data) async def component_load(self): @@ -25,6 +26,17 @@ class KoanComponent(cmds.Component): async def event_message(self, payload: twitchio.ChatMessage) -> None: print(f"[{payload.broadcaster.name}] - {payload.chatter.name}: {payload.text}") + async def component_command_error(self, payload): + try: + raise payload.exception + except cmds.ArgumentError: + if cmd := payload.context.command: + usage = f"{cmd.qualified_name} {cmd.signature}" + await payload.context.reply(f"USAGE: {usage}") + return False + except Exception: + pass + @cmds.group(invoke_fallback=True) async def koans(self, ctx: cmds.Context) -> None: """ @@ -38,74 +50,100 @@ class KoanComponent(cmds.Component): koans = await self.koans.get_community_koans(cid) if koans: - names = ', '.join(koan.name for koan in koans) - await ctx.reply( - f"Koans: {names}" - ) + count = len(koans) + parts = [] + for koan in koans[-10:]: + minified = minify(koan.content, 20) + formatted = f"#{koan.koanlabel}: {minified}" + parts.append(formatted) + partstr = '; '.join(parts) + laststr = "Last 10: " if count > 10 else "" + message = f"We have {count} koans! {laststr}{partstr}" + await ctx.reply(message) else: await ctx.reply("No koans have been made in this channel!") @koans.command(name='add', aliases=['new', 'create']) @cmds.is_moderator() - async def koans_add(self, ctx: cmds.Context, name: str, *, text: str): + async def koans_add(self, ctx: cmds.Context, *, content: str): """ Add or overwrite a koan to this channel. - !koans add wind This is a wind koan + !koans add This is a wind koan """ community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid + profile = await self.bot.profiles.fetch_profile(ctx.chatter) + pid = profile.profileid - name = name.lower() - existing = await self.koans.get_koan_named(cid, name) - await self.koans.create_koan(cid, name, text) + koan = await self.koans.create_koan( + cid, + content, + created_by=pid, + ) - if existing: - await ctx.reply(f"Updated the koan '{name}'") + await ctx.reply(f"Koan #{koan.koanlabel} created!") + + # TODO: Error message on signature failure + + @koans.command(name='edit', aliases=['update',]) + @cmds.is_moderator() + async def koans_edit(self, ctx: cmds.Context, label: int, *, new_content: str): + community = await self.bot.profiles.fetch_community(ctx.broadcaster) + cid = community.communityid + + koan = await self.koans.get_koan_label(cid, label) + + if koan: + await self.data.koans.update_where(koanid=koan.koanid).set(content=new_content) + await ctx.reply(f"Updated koan #{label}") else: - await ctx.reply(f"Created the new koan '{name}'") + await ctx.reply(f"Koan #{label}' does not exist to delete!") + @koans.command(name='del', aliases=['delete', 'rm', 'remove']) @cmds.is_moderator() - async def koans_del(self, ctx: cmds.Context, name: str): + async def koans_del(self, ctx: cmds.Context, label: int): """ - Remove a koan from this channel by name. + Remove a koan from this channel by number. - !koans del wind + !koans del 5 """ community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid - name = name.lower() - koan = await self.koans.get_koan_named(cid, name) + koan = await self.koans.get_koan_label(cid, label) + if koan: await koan.delete() - await ctx.reply(f"Deleted the koan '{name}'") + await ctx.reply(f"Deleted koan #{label}") else: - await ctx.reply(f"The koan '{name}' does not exist to delete!") + await ctx.reply(f"Koan #{label}' does not exist to delete!") @cmds.command(name='koan') - async def koan(self, ctx: cmds.Context, name: Optional[str] = None): + async def koan(self, ctx: cmds.Context, label: Optional[int] = None): """ - Show a koan from this channel. Optionally by name. + Show a koan from this channel. Optionally by number. !koan - !koan wind + !koan 5 """ community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid - if name is not None: - name = name.lower() - koan = await self.koans.get_koan_named(cid, name) + if label is not None: + koan = await self.koans.get_koan_label(cid, label) if koan: await ctx.reply(koan.message) else: - await ctx.reply(f"The requested koan '{name}' does not exist! Use '{ctx.prefix}koans' to see all the koans.") + await ctx.reply(f"Koan #{label} does not exist!") else: koans = await self.koan.get_community_koans(communityid=cid) if koans: koan = random.choice(koans) - await ctx.reply(koan.message) + formatted = f"Koan #{koan.koanlabel}: {koan.message}" + parts = [formatted[i: i:500] for i in range(0, len(formatted), 500)] + for part in parts: + await ctx.reply(part) else: await ctx.reply("This channel doesn't have any koans!")