Files
sideeyes-koans-plugin/koans/discord/cog.py

252 lines
8.4 KiB
Python

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} <content>".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} <koanid> <new content>".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)