Quotes data and cog skeleton.

This commit is contained in:
2025-08-25 19:33:48 +10:00
parent 9deb51af66
commit 45e6ed4e29
10 changed files with 199 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
BEGIN;
INSERT INTO version_history (component, from_version, to_version, author)
VALUES ('QUOTES', 0, 1, 'Initial Creation');
CREATE TABLE quotes(
quoteid 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TRIGGER quotes_timestamp BEFORE UPDATE ON quotes
FOR EACH ROW EXECUTE FUNCTION update_timestamp_column();
CREATE VIEW
quotes_info
AS
SELECT
*,
row_number() + 1 OVER (PARITION BY communityid ORDER BY created_at ASC)
FROM
quotes
ORDER BY (communityid, created_at);
COMMIT;

View File

5
quotes/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
import logging
logger = logging.getLogger(__name__)
from .discord import setup

28
quotes/data.py Normal file
View File

@@ -0,0 +1,28 @@
from data import Registry, RowModel, Table
from data.columns import Integer, Timestamp, String
from weakref import WeakValueDictionary
class Quote(RowModel):
_tablename_ = 'quotes'
_cache_ = WeakValueDictionary()
quoteid = Integer(primary=True)
communityid = Integer()
content = String()
created_by = Integer()
created_at = Timestamp()
_timestamp = Timestamp()
class QuoteInfo(Quote):
_tablename_ = 'quotes_info'
_readonly_ = True
_cache_ = WeakValueDictionary()
quotelabel = Integer()
class QuotesData(Registry):
quotes = Quote.table
quotes_info = QuoteInfo.table

View File

@@ -0,0 +1,5 @@
from .. import logger
async def setup(bot):
from .cog import QuoteCog
await bot.add_cog(QuoteCog(bot))

92
quotes/discord/cog.py Normal file
View File

@@ -0,0 +1,92 @@
from typing import Optional
import asyncio
import discord
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 . import logger
from ..data import QuotesData
from ..quotes import QuoteRegistry
class QuoteCog(LionCog):
def __init__(self, bot: LionBot):
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):
# TODO
...
# ----- Commands ------
@cmds.hybrid_command(
name='quote',
description="Display a random quote."
)
async def quote_cmd(self, ctx: LionContext):
# TODO
...
@cmds.hybrid_group(
name='quotes',
description="Base command group for quotes management.",
)
async def quotes_grp(self, ctx: LionContext):
# TODO
# Call the quotes list command
...
@cmds.hybrid_command(
name='addquote',
description="Create a new quote. Use without arguments to add a multiline quote."
)
@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"
)
async def quotes_add_cmd(self, ctx: LionContext, content: Optional[str]):
# TODO
...
@quotes_grp.command(
name='del',
description="Delete a saved quote (WARNING: This will change all quote numbers.)"
)
@appcmds.describe(
quote="Select the quote to delete, or write the number."
)
async def quotes_del_cmd(self, ctx: LionContext, quote: str):
# TODO
...
@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
...
@quotes_grp.command(
name='edit',
description="Edit a saved quote."
)
@appcmds.describe(
quote="Select the quote to delete, or write the number."
)
async def quotes_edit_cmd(self, ctx: LionContext, quote: str):
# TODO: Move quote to QuoteConverter?
# TODO
...

41
quotes/quotes.py Normal file
View File

@@ -0,0 +1,41 @@
from typing import Optional
from .data import Quote, QuoteInfo, QuotesData
class QuoteRegistry:
def __init__(self, data: QuotesData):
self.data = data
# TODO: For efficiency we could have a cache here
# Caching quotesinfo by community.
# Particularly efficient for autocomplete.
async def init(self):
await self.data.init()
async def get_community_quotes(self, communityid: int) -> list[QuoteInfo]:
return await QuoteInfo.fetch_where(communityid=communityid)
async def get_quoteinfo(self, quoteid: int) -> Optional[QuoteInfo]:
return await QuoteInfo.fetch(quoteid)
async def get_quote(self, quoteid: int) -> Optional[Quote]:
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)
return results[0] if results else None
async def create_quote(
self,
communityid: int,
content: str,
created_by: Optional[int] = None
) -> QuoteInfo:
quote = await Quote.create(
content=content,
communityid=communityid,
created_by=created_by,
)
info = await QuoteInfo.fetch(quote.quoteid)
assert info is not None
return info