Implement quotes.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from .quotes import setup
|
||||
|
||||
@@ -8,6 +8,7 @@ CREATE TABLE quotes(
|
||||
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()
|
||||
);
|
||||
@@ -19,7 +20,8 @@ CREATE VIEW
|
||||
AS
|
||||
SELECT
|
||||
*,
|
||||
row_number() + 1 OVER (PARITION BY communityid ORDER BY created_at ASC)
|
||||
(deleted_at is not NULL) AS is_deleted,
|
||||
(row_number() OVER (PARTITION BY communityid ORDER BY created_at ASC)) as quotelabel
|
||||
FROM
|
||||
quotes
|
||||
ORDER BY (communityid, created_at);
|
||||
@@ -1,5 +1,5 @@
|
||||
from data import Registry, RowModel, Table
|
||||
from data.columns import Integer, Timestamp, String
|
||||
from data.columns import Bool, Integer, Timestamp, String
|
||||
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
@@ -10,6 +10,7 @@ class Quote(RowModel):
|
||||
quoteid = Integer(primary=True)
|
||||
communityid = Integer()
|
||||
content = String()
|
||||
deleted_at = Timestamp()
|
||||
created_by = Integer()
|
||||
created_at = Timestamp()
|
||||
_timestamp = Timestamp()
|
||||
@@ -21,6 +22,7 @@ class QuoteInfo(Quote):
|
||||
_cache_ = WeakValueDictionary()
|
||||
|
||||
quotelabel = Integer()
|
||||
is_deleted = Bool()
|
||||
|
||||
|
||||
class QuotesData(Registry):
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
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 ..data import QuotesData
|
||||
from ..quotes import QuoteRegistry
|
||||
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)
|
||||
|
||||
@@ -24,31 +31,106 @@ class QuoteCog(LionCog):
|
||||
|
||||
# ----- API -----
|
||||
async def quote_acmpl(self, interaction: discord.Interaction, partial: str):
|
||||
# TODO
|
||||
...
|
||||
"""
|
||||
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):
|
||||
# TODO
|
||||
...
|
||||
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):
|
||||
# TODO
|
||||
# Call the quotes list command
|
||||
...
|
||||
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"
|
||||
)
|
||||
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."
|
||||
@@ -56,37 +138,121 @@ class QuoteCog(LionCog):
|
||||
@appcmds.describe(
|
||||
content="Content of the quote to add"
|
||||
)
|
||||
async def quotes_add_cmd(self, ctx: LionContext, content: Optional[str]):
|
||||
# TODO
|
||||
...
|
||||
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 (WARNING: This will change all quote numbers.)"
|
||||
description="Delete a saved quote."
|
||||
)
|
||||
@appcmds.describe(
|
||||
quote="Select the quote to delete, or write the number."
|
||||
quotestr="Select the quote to delete, or write the number."
|
||||
)
|
||||
async def quotes_del_cmd(self, ctx: LionContext, quote: str):
|
||||
# TODO
|
||||
...
|
||||
@appcmds.rename(quotestr='quote')
|
||||
async def quotes_del_cmd(self, ctx: LionContext, quotestr: str):
|
||||
# TODO: Double check group permission inheritance
|
||||
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."
|
||||
)
|
||||
async def quotes_list_cmd(self, ctx: LionContext):
|
||||
# TODO
|
||||
...
|
||||
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(
|
||||
quote="Select the quote to delete, or write the number."
|
||||
quotestr="Select the quote to edit, or write the number.",
|
||||
new_content="New content for the quote. Leave unselected to edit in multiline modal."
|
||||
)
|
||||
async def quotes_edit_cmd(self, ctx: LionContext, quote: str):
|
||||
# TODO: Move quote to QuoteConverter?
|
||||
# TODO
|
||||
...
|
||||
@appcmds.rename(quotestr='quote')
|
||||
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)
|
||||
|
||||
1
quotes/discord/ui/__init__.py
Normal file
1
quotes/discord/ui/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .quotelist import QuoteListUI
|
||||
272
quotes/discord/ui/quotelist.py
Normal file
272
quotes/discord/ui/quotelist.py
Normal file
@@ -0,0 +1,272 @@
|
||||
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 ...quotes import QuoteRegistry
|
||||
from ...lib import minify
|
||||
|
||||
class QuoteListUI(MessageUI):
|
||||
block_len = 10
|
||||
def __init__(self, bot: LionBot, quotes: QuoteRegistry, communityid: int, profileid: int, callerid: int, **kwargs):
|
||||
super().__init__(callerid=callerid, **kwargs)
|
||||
|
||||
self.bot = bot
|
||||
self.quotes = quotes
|
||||
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 Quote",
|
||||
style=ButtonStyle.green
|
||||
)
|
||||
async def new_quote_button(self, press: discord.Interaction, pressed: Button):
|
||||
# Show the create quote modal
|
||||
# Create quote flow.
|
||||
try:
|
||||
interaction, to_create = await input(
|
||||
interaction=press,
|
||||
title="Create Quote",
|
||||
question="Quote 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)
|
||||
|
||||
quote = await self.quotes.create_quote(
|
||||
communityid=self.communityid,
|
||||
content=to_create,
|
||||
created_by=self.caller_profileid
|
||||
)
|
||||
self.pagen = -1
|
||||
await self.refresh(thinking=interaction)
|
||||
|
||||
# Quote edit and delete selectors
|
||||
async def make_page_options(self):
|
||||
options = []
|
||||
for quote in self.current_page:
|
||||
labelstr = f"#{quote.quotelabel}: "
|
||||
minified = minify(quote.content, 100 - len(labelstr) - 1, strip=' >')
|
||||
option = SelectOption(
|
||||
label=f"{labelstr} {minified}",
|
||||
value=str(quote.quoteid)
|
||||
)
|
||||
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:
|
||||
quoteid = int(selected.values[0])
|
||||
await self.quotes.delete_quote(quoteid)
|
||||
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:
|
||||
quoteid = int(selected.values[0])
|
||||
quote = await self.quotes.get_quote(quoteid)
|
||||
assert quote is not None
|
||||
try:
|
||||
interaction, new_content = await input(
|
||||
interaction=selection,
|
||||
title="Edit Quote",
|
||||
question="Quote Content",
|
||||
timeout=300,
|
||||
style=TextStyle.long,
|
||||
default=quote.content,
|
||||
min_length=1,
|
||||
max_length=2000 - 8
|
||||
)
|
||||
await interaction.response.defer(thinking=True, ephemeral=True)
|
||||
except asyncio.TimeoutError:
|
||||
raise SafeCancellation
|
||||
|
||||
await quote.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 quote in self.current_page:
|
||||
labelstr = f"**#{quote.quotelabel}:** "
|
||||
minified = minify(quote.content, 100 - len(labelstr) - 1, strip=' >')
|
||||
lines.append(f"{labelstr} {minified}")
|
||||
embed = discord.Embed(
|
||||
title="Community Quotes",
|
||||
description='\n'.join(lines)
|
||||
)
|
||||
else:
|
||||
embed = discord.Embed(
|
||||
title="Community Quotes",
|
||||
description="No quotes have been created! Use `/addquote` or the 'New Quote' button below to make a quote!"
|
||||
)
|
||||
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_quote_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_quote_button,
|
||||
self.jump_button,
|
||||
self.quit_button),
|
||||
(self.edit_menu,),
|
||||
(self.delete_menu,),
|
||||
)
|
||||
else:
|
||||
# Add Close
|
||||
self.set_layout(
|
||||
(self.new_quote_button,
|
||||
self.jump_button),
|
||||
)
|
||||
|
||||
async def reload(self):
|
||||
quotes = await self.quotes.get_community_quotes(self.communityid)
|
||||
blocks = [
|
||||
quotes[i:i+self.block_len]
|
||||
for i in range(0, len(quotes), self.block_len)
|
||||
]
|
||||
self.blocks = blocks or [[]]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Optional
|
||||
from .data import Quote, QuoteInfo, QuotesData
|
||||
from .lib import utc_now
|
||||
|
||||
|
||||
class QuoteRegistry:
|
||||
@@ -13,7 +14,7 @@ class QuoteRegistry:
|
||||
await self.data.init()
|
||||
|
||||
async def get_community_quotes(self, communityid: int) -> list[QuoteInfo]:
|
||||
return await QuoteInfo.fetch_where(communityid=communityid)
|
||||
return await QuoteInfo.fetch_where(communityid=communityid, is_deleted=False)
|
||||
|
||||
async def get_quoteinfo(self, quoteid: int) -> Optional[QuoteInfo]:
|
||||
return await QuoteInfo.fetch(quoteid)
|
||||
@@ -22,7 +23,7 @@ class QuoteRegistry:
|
||||
return await Quote.fetch(quoteid)
|
||||
|
||||
async def get_quote_label(self, communityid: int, label: int) -> Optional[QuoteInfo]:
|
||||
results = await QuoteInfo.fetch_where(communityid=communityid, quotelabel=label)
|
||||
results = await QuoteInfo.fetch_where(communityid=communityid, quotelabel=label, is_deleted=False)
|
||||
return results[0] if results else None
|
||||
|
||||
async def create_quote(
|
||||
@@ -39,3 +40,6 @@ class QuoteRegistry:
|
||||
info = await QuoteInfo.fetch(quote.quoteid)
|
||||
assert info is not None
|
||||
return info
|
||||
|
||||
async def delete_quote(self, quoteid: int):
|
||||
await self.data.quotes.update_where(quoteid=quoteid).set(deleted_at=utc_now())
|
||||
|
||||
1
src/modules/profiles
Submodule
1
src/modules/profiles
Submodule
Submodule src/modules/profiles added at fc3bdcbb5c
Reference in New Issue
Block a user