From fda6847671223ccb640f458979a74d5cb126c066 Mon Sep 17 00:00:00 2001 From: Interitio Date: Wed, 3 Sep 2025 23:43:12 +1000 Subject: [PATCH] Refactor with registry. --- __init__.py | 1 + data/koans.sql | 20 ++++++++++ koans/__init__.py | 4 +- koans/data.py | 32 ++++++++++++++++ koans/koans.py | 34 +++++++++++++++++ koans/twitch/__init__.py | 5 +++ koans/twitch/component.py | 80 +++++++++++++++++---------------------- 7 files changed, 127 insertions(+), 49 deletions(-) create mode 100644 koans/twitch/__init__.py diff --git a/__init__.py b/__init__.py index e69de29..d8e9d4a 100644 --- a/__init__.py +++ b/__init__.py @@ -0,0 +1 @@ +from .koans import * diff --git a/data/koans.sql b/data/koans.sql index e69de29..2447861 100644 --- a/data/koans.sql +++ b/data/koans.sql @@ -0,0 +1,20 @@ +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 ... + +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() +); +CREATE TRIGGER koans_timestamp BEFORE UPDATE ON koans + FOR EACH ROW EXECUTE FUNCTION update_timestamp_column(); + +-- }}} +COMMIT; diff --git a/koans/__init__.py b/koans/__init__.py index 4070a96..81d6555 100644 --- a/koans/__init__.py +++ b/koans/__init__.py @@ -2,6 +2,4 @@ import logging logger = logging.getLogger(__name__) -async def setup(bot): - from .component import KoanComponent - await bot.add_component(KoanComponent(bot)) +from .twitch import setup as twitch_setup diff --git a/koans/data.py b/koans/data.py index e69de29..2dbe9ab 100644 --- a/koans/data.py +++ b/koans/data.py @@ -0,0 +1,32 @@ +from data import Registry, RowModel +from data.columns import String, Timestamp, Integer + + +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_ = {} + + koanid = Integer(primary=True) + communityid = Integer() + name = String() + message = String() + created_at = Timestamp() + _timestamp = Timestamp() + + +class KoanData(Registry): + VERSION = ('KOANS', 1) + + koans = Koan.table diff --git a/koans/koans.py b/koans/koans.py index e69de29..41c00d5 100644 --- a/koans/koans.py +++ b/koans/koans.py @@ -0,0 +1,34 @@ +from typing import Optional +from .data import Koan, KoanData + + +class KoanRegistry: + def __init__(self, data: KoanData): + 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_koan(self, koanid: int) -> Optional[Koan]: + return await Koan.fetch(koanid) + + 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) + koan = await Koan.create( + communityid=communityid, + name=name, + message=message + ) + return koan diff --git a/koans/twitch/__init__.py b/koans/twitch/__init__.py new file mode 100644 index 0000000..5f86c4c --- /dev/null +++ b/koans/twitch/__init__.py @@ -0,0 +1,5 @@ +from .. import logger + +async def setup(bot): + from .component import KoanComponent + await bot.add_component(KoanComponent(bot)) diff --git a/koans/twitch/component.py b/koans/twitch/component.py index b0fb7ce..38eecee 100644 --- a/koans/twitch/component.py +++ b/koans/twitch/component.py @@ -3,14 +3,23 @@ import random import twitchio from twitchio.ext import commands as cmds -from datamodels import Koan, Communities +from meta import Bot, Context from . import logger +from ..koans import KoanRegistry +from ..data import KoanData class KoanComponent(cmds.Component): - def __init__(self, bot): + def __init__(self, bot: Bot): self.bot = bot + self.data = bot.dbconn.load_registry(KoanData()) + self.koans = KoanRegistry(self.data) + + async def component_load(self): + await self.data.init() + await self.bot.version_check(*self.data.VERSION) + await self.koans.init() @cmds.Component.listener() async def event_message(self, payload: twitchio.ChatMessage) -> None: @@ -23,10 +32,11 @@ class KoanComponent(cmds.Component): !koans """ - community = await Communities.fetch_or_create(twitchid=ctx.channel.id, name=ctx.channel.name) + community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid - koans = await Koan.fetch_where(communityid=cid) + koans = await self.koans.get_community_koans(cid) + if koans: names = ', '.join(koan.name for koan in koans) await ctx.reply( @@ -36,61 +46,43 @@ class KoanComponent(cmds.Component): 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): """ Add or overwrite a koan to this channel. !koans add wind This is a wind koan """ - community = await Communities.fetch_or_create(twitchid=ctx.channel.id, name=ctx.channel.name) + community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid name = name.lower() + existing = await self.koans.get_koan_named(cid, name) + await self.koans.create_koan(cid, name, text) - assert isinstance(ctx.author, twitchio.Chatter) - if (ctx.author.moderator or ctx.author.broadcaster): - # Delete the koan with this name if it exists - existing = await Koan.table.delete_where( - communityid=cid, - name=name, - ) - - # Insert the new koan - await Koan.create( - communityid=cid, - name=name, - message=text - ) - - # Ack - if existing: - await ctx.reply(f"Updated the koan '{name}'") - else: - await ctx.reply(f"Created the new koan '{name}'") + if existing: + await ctx.reply(f"Updated the koan '{name}'") + else: + await ctx.reply(f"Created the new koan '{name}'") @koans.command(name='del', aliases=['delete', 'rm', 'remove']) + @cmds.is_moderator() async def koans_del(self, ctx: cmds.Context, name: str): """ Remove a koan from this channel by name. !koans del wind """ - community = await Communities.fetch_or_create(twitchid=ctx.channel.id, name=ctx.channel.name) + community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid name = name.lower() - - assert isinstance(ctx.author, twitchio.Chatter) - if (ctx.author.moderator or ctx.author.broadcaster): - # Delete the koan with this name if it exists - existing = await Koan.table.delete_where( - communityid=cid, - name=name, - ) - if existing: - await ctx.reply(f"Deleted the koan '{name}'") - else: - await ctx.reply(f"The koan '{name}' does not exist to delete!") + koan = await self.koans.get_koan_named(cid, name) + if koan: + await koan.delete() + await ctx.reply(f"Deleted the koan '{name}'") + else: + await ctx.reply(f"The koan '{name}' does not exist to delete!") @cmds.command(name='koan') async def koan(self, ctx: cmds.Context, name: Optional[str] = None): @@ -100,22 +92,18 @@ class KoanComponent(cmds.Component): !koan !koan wind """ - community = await Communities.fetch_or_create(twitchid=ctx.channel.id, name=ctx.channel.name) + community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid if name is not None: name = name.lower() - koans = await Koan.fetch_where( - communityid=cid, - name=name - ) - if koans: - koan = koans[0] + koan = await self.koans.get_koan_named(cid, name) + 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.") else: - koans = await Koan.fetch_where(communityid=cid) + koans = await self.koan.get_community_koans(communityid=cid) if koans: koan = random.choice(koans) await ctx.reply(koan.message)