263 lines
8.9 KiB
Python
263 lines
8.9 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 QuoteInfo, QuotesData
|
|
from ..quotes import QuoteRegistry
|
|
from ..lib import minify
|
|
|
|
from . import logger
|
|
from .ui import QuoteListUI
|
|
|
|
|
|
class QuoteCog(LionCog):
|
|
def __init__(self, bot: LionBot):
|
|
self.bot = bot
|
|
self.data = bot.db.load_registry(QuotesData())
|
|
self.quotes = QuoteRegistry(self.data)
|
|
|
|
async def cog_load(self):
|
|
await self.data.init()
|
|
await self.quotes.init()
|
|
|
|
# ----- API -----
|
|
async def quote_acmpl(self, interaction: discord.Interaction, partial: str):
|
|
"""
|
|
Autocomplete for a community-local quote selection.
|
|
"""
|
|
if not interaction.guild:
|
|
return []
|
|
|
|
choices = []
|
|
community = await self.bot.profiles.fetch_community(
|
|
interaction.guild, interaction=interaction,
|
|
touch=False
|
|
)
|
|
quotes = await self.quotes.get_community_quotes(community.communityid)
|
|
if not quotes:
|
|
nullchoice = appcmds.Choice(
|
|
name="No quotes have been created!",
|
|
value="0",
|
|
)
|
|
choices.append(nullchoice)
|
|
else:
|
|
for quote in quotes:
|
|
labelstr = f"#{quote.quotelabel}:"
|
|
if partial.lower() in labelstr + quote.content.lower():
|
|
minified = minify(quote.content, 100 - len(labelstr) - 1, strip=' >')
|
|
displayed = f"{labelstr} {minified}"
|
|
choice = appcmds.Choice(
|
|
name=displayed,
|
|
value=str(quote.quotelabel),
|
|
)
|
|
choices.append(choice)
|
|
if not choices:
|
|
nullchoice = appcmds.Choice(
|
|
name="No quotes matching your input!",
|
|
value="0",
|
|
)
|
|
choices.append(nullchoice)
|
|
|
|
return choices
|
|
|
|
async def resolve_quote(self, ctx: LionContext, quotestr: str) -> QuoteInfo:
|
|
"""
|
|
Resolve a quote string provided as an argument.
|
|
|
|
Essentially only accepts integer quote labels.
|
|
"""
|
|
quotestr = quotestr.strip('# ')
|
|
if not quotestr.isdigit():
|
|
raise UserInputError(
|
|
"Could not parse desired quote! Please enter the number or select from autocomplete options."
|
|
)
|
|
elif (label := int(quotestr)) == 0:
|
|
raise UserInputError(
|
|
"Invalid option selected!"
|
|
)
|
|
else:
|
|
quote = await self.quotes.get_quote_label(ctx.community.communityid, label)
|
|
if not quote:
|
|
raise UserInputError(
|
|
f"Quote #{label} does not exist!"
|
|
)
|
|
else:
|
|
return quote
|
|
|
|
# ----- Commands ------
|
|
@cmds.hybrid_command(
|
|
name='quote',
|
|
description="Display a random quote."
|
|
)
|
|
@cmds.guild_only()
|
|
async def quote_cmd(self, ctx: LionContext):
|
|
quotes = await self.quotes.get_community_quotes(ctx.community.communityid)
|
|
if quotes:
|
|
# Select a random quote
|
|
quote = random.choice(quotes)
|
|
if '\n' in quote.content:
|
|
formatted = f"**#{quote.quotelabel}:**\n{quote.content}"
|
|
else:
|
|
formatted = f"**#{quote.quotelabel}:** {quote.content}"
|
|
await ctx.reply(formatted)
|
|
else:
|
|
await ctx.reply("There are no quotes to display!")
|
|
|
|
@cmds.hybrid_group(
|
|
name='quotes',
|
|
description="Base command group for quotes management.",
|
|
)
|
|
@cmds.has_permissions(manage_guild=True)
|
|
@cmds.guild_only()
|
|
async def quotes_grp(self, ctx: LionContext):
|
|
await self.quotes_list_cmd(ctx)
|
|
|
|
@cmds.hybrid_command(
|
|
name='addquote',
|
|
description="Create a new quote. Use without arguments to add a multiline quote."
|
|
)
|
|
@appcmds.describe(
|
|
content="Content of the quote to add"
|
|
)
|
|
@cmds.has_permissions(manage_guild=True)
|
|
async def addquote_cmd(self, ctx: LionContext, *, content: Optional[str] = None):
|
|
await self.quotes_add_cmd(ctx, content=content)
|
|
|
|
@quotes_grp.command(
|
|
name='add',
|
|
description="Create a new quote. Use without arguments to add a multiline quote."
|
|
)
|
|
@appcmds.describe(
|
|
content="Content of the quote to add"
|
|
)
|
|
@cmds.has_permissions(manage_guild=True)
|
|
async def quotes_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 Quote",
|
|
question="Quote 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
|
|
|
|
quote = await self.quotes.create_quote(
|
|
communityid=ctx.community.communityid,
|
|
content=to_create,
|
|
created_by=ctx.profile.profileid,
|
|
)
|
|
await sender(f"Quote #{quote.quotelabel} created!")
|
|
|
|
@quotes_grp.command(
|
|
name='del',
|
|
description="Delete a saved quote."
|
|
)
|
|
@appcmds.describe(
|
|
quotestr="Select the quote to delete, or write the number."
|
|
)
|
|
@cmds.has_permissions(manage_guild=True)
|
|
@appcmds.rename(quotestr='quote')
|
|
async def quotes_del_cmd(self, ctx: LionContext, quotestr: str):
|
|
quote = await self.resolve_quote(ctx, quotestr)
|
|
label = quote.quotelabel
|
|
await self.quotes.delete_quote(quote.quoteid)
|
|
await ctx.reply(f"Quote #{label} was deleted.")
|
|
|
|
quotes_del_cmd.autocomplete('quotestr')(quote_acmpl)
|
|
|
|
@quotes_grp.command(
|
|
name='list',
|
|
description="Display the community quotes. Quotes may also be added/edited/deleted here."
|
|
)
|
|
@cmds.has_permissions(manage_guild=True)
|
|
async def quotes_list_cmd(self, ctx: LionContext):
|
|
view = QuoteListUI(self.bot, self.quotes, 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()
|
|
|
|
@quotes_grp.command(
|
|
name='edit',
|
|
description="Edit a saved quote."
|
|
)
|
|
@appcmds.describe(
|
|
quotestr="Select the quote to edit, or write the number.",
|
|
new_content="New content for the quote. Leave unselected to edit in multiline modal."
|
|
)
|
|
@appcmds.rename(quotestr='quote')
|
|
@cmds.has_permissions(manage_guild=True)
|
|
async def quotes_edit_cmd(self, ctx: LionContext, quotestr: str, *, new_content: Optional[str] = None):
|
|
quote = await self.resolve_quote(ctx, quotestr)
|
|
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} <quoteid> <new content>".format(
|
|
prefix=ctx.prefix,
|
|
cmd=invoked
|
|
)
|
|
)
|
|
raise SafeCancellation
|
|
else:
|
|
try:
|
|
interaction, new_content = await input(
|
|
interaction=ctx.interaction,
|
|
title="Edit Quote",
|
|
question="Quote Content",
|
|
timeout=300,
|
|
style=TextStyle.long,
|
|
default=quote.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.quotes.update_where(quoteid=quote.quoteid).set(content=new_content)
|
|
await sender(f"Quote #{quote.quotelabel} Updated!")
|
|
|
|
quotes_edit_cmd.autocomplete('quotestr')(quote_acmpl)
|