From 28103655880bba6c5cd1fa453aa721cfc5fdf01f Mon Sep 17 00:00:00 2001 From: Interitio Date: Sat, 28 Sep 2024 15:39:24 +1000 Subject: [PATCH] feat(counters): Dynamic counter aliases. --- src/modules/counters/cog.py | 159 ++++++++++++++++++++--------- src/modules/counters/data.py | 23 +++++ src/modules/counters/migration.sql | 7 ++ 3 files changed, 140 insertions(+), 49 deletions(-) diff --git a/src/modules/counters/cog.py b/src/modules/counters/cog.py index 0e99aba9..39ba1071 100644 --- a/src/modules/counters/cog.py +++ b/src/modules/counters/cog.py @@ -68,7 +68,30 @@ def counter_cmd_factory( user_total=user_total, ) ) - return counter_cmd + + async def lb_cmd(cog, ctx: commands.Context, *, args: str = ''): + user = await ctx.channel.user() + await ctx.reply(await cog.formatted_lb(counter, args, int(user.id))) + + async def undo_cmd(cog, ctx: commands.Context): + userid = int(ctx.author.id) + channelid = int((await ctx.channel.user()).id) + _counter = await cog.fetch_counter(counter) + query = cog.data.CounterEntry.fetch_where( + counterid=_counter.counterid, + userid=userid, + ) + query.order_by('created_at', direction=ORDER.DESC) + query.limit(1) + results = await query + if not results: + await ctx.reply("Nothing to delete!") + else: + row = results[0] + await row.delete() + await ctx.reply("Undo successful!") + + return (counter_cmd, lb_cmd, undo_cmd) class CounterCog(LionCog): @@ -84,6 +107,7 @@ class CounterCog(LionCog): async def cog_load(self): self._load_twitch_methods(self.crocbot) + await self.load_counter_commands() await self.data.init() await self.load_counters() @@ -92,6 +116,55 @@ class CounterCog(LionCog): async def cog_unload(self): self._unload_twitch_methods(self.crocbot) + async def load_counter_commands(self): + rows = await self.data.CounterCommand.fetch_where() + for row in rows: + counter = await self.data.Counter.fetch(row.counterid) + counter_cb, lb_cb, undo_cb = counter_cmd_factory( + counter.name, + row.response + ) + cmds = [] + main_cmd = commands.command(name=row.name)(counter_cb) + cmds.append(main_cmd) + if row.lbname: + lb_cmd = commands.command(name=row.lbname)(lb_cb) + cmds.append(lb_cmd) + if row.undoname: + undo_cmd = commands.command(name=row.undoname)(undo_cb) + cmds.append(undo_cmd) + + for cmd in cmds: + self.add_twitch_command(self.crocbot, cmd) + + logger.info(f"(Re)Loaded {len(rows)} counter commands!") + + + + # commands = { + # 'stuff': ( + # 'stuffcounter', + # 'stufflb', + # "Good luck with {detailsorname}! We have done {total} stuffs {period_name}." + # ), + # 'water': ( + # 'water', + # 'waterlb', + # "Good job hydrating! We have had {total} cups of tea {period_name}." + # ), + # 'coffee': ( + # 'coffee', + # 'coffeelb', + # "Enjoy your {detailsorname}! We have had {total} cups of coffee {period_name}." + # ), + # 'tea': ( + # 'tea', + # 'tealb', + # "Enjoy your {detailsorname}! We have had {total} cups of tea this {period_name}." + # ), + # } + + async def cog_check(self, ctx): return True @@ -207,8 +280,43 @@ class CounterCog(LionCog): elif subcmd == 'clear': await self.reset_counter(name) await ctx.reply(f"'{name}' counter reset.") + elif subcmd == 'alias': + splits = args.split(maxsplit=3) if args else [] + counter = await self.fetch_counter(name) + rows = await self.data.CounterCommand.fetch_where(counterid=counter.counterid) + existing = rows[0] if rows else None + if existing and not args: + # Show current alias + await ctx.reply( + f"Counter '{name}' aliases: '!{existing.name}' to add to counter; " + f"'!{existing.lbname}' to view counter leaderboard; " + f"'!{existing.undoname}' to undo (your) last addition." + ) + elif len(splits) < 4: + # Show usage + await ctx.reply( + "USAGE: !counter alias -- " + "Response accepts keywords {total}, {period}, {period_name}, {detailsorname}, {user_total}." + ) + else: + # Create new alias + cmdname, lbname, undoname, response = splits + # Remove any existing alias + await self.data.CounterCommand.table.delete_where(name=cmdname) + + alias = await self.data.CounterCommand.create( + name=cmdname, + counterid=counter.counterid, + lbname=lbname, undoname=undoname, response=response + ) + await self.load_counter_commands() + await ctx.reply( + f"Alias created for counter '{name}': '!{alias.name}' to add to counter; " + f"'!{alias.lbname}' to view counter leaderboard; " + f"'!{alias.undoname}' to undo (your) last addition." + ) else: - await ctx.reply(f"Unrecognised subcommand {subcmd}. Supported subcommands: 'show', 'add', 'lb', 'clear'.") + await ctx.reply(f"Unrecognised subcommand {subcmd}. Supported subcommands: 'show', 'add', 'lb', 'clear', 'alias'.") async def parse_period(self, userid: int, periodstr: str, default=PERIOD.STREAM): if periodstr: @@ -263,50 +371,3 @@ class CounterCog(LionCog): return f"{counter} {period.value[-1]} leaderboard --- {lbstr}" else: return f"{counter} {period.value[-1]} leaderboard is empty!" - - # Misc actual counter commands - # TODO: Factor this out to a different module... - # TODO: Probably make these all alias commands - - tea_cmd = commands.command(name='tea')( - counter_cmd_factory( - 'tea', - "Enjoy your {detailsorname}! We have had {total} cups of tea {period_name}." - ) - ) - - @commands.command() - async def tealb(self, ctx: commands.Context, *, args: str = ''): - user = await ctx.channel.user() - await ctx.reply(await self.formatted_lb('tea', args, int(user.id))) - - coffee_cmd = commands.command(name='coffee')( - counter_cmd_factory( - 'coffee', - "Enjoy your {detailsorname}! We have had {total} cups of coffee {period_name}." - ) - ) - - @commands.command() - async def coffeelb(self, ctx: commands.Context, *, args: str = ''): - user = await ctx.channel.user() - await ctx.reply(await self.formatted_lb('coffee', args, int(user.id))) - - water_cmd = commands.command(name='water')( - counter_cmd_factory( - 'water', - "Good job hydrating! We have had {total} cups of tea {period_name}." - ) - ) - - @commands.command() - async def waterlb(self, ctx: commands.Context, *, args: str = ''): - user = await ctx.channel.user() - await ctx.reply(await self.formatted_lb('water', args, int(user.id))) - - stuff_cmd = commands.command(name='stuffcounter')( - counter_cmd_factory( - 'stuff', - "Good luck with {detailsorname}! We have done {total} stuffs {period_name}." - ) - ) diff --git a/src/modules/counters/data.py b/src/modules/counters/data.py index a7405053..59fa313a 100644 --- a/src/modules/counters/data.py +++ b/src/modules/counters/data.py @@ -49,3 +49,26 @@ class CounterData(Registry): context_str = String() details = String() + class CounterCommand(RowModel): + """ + Schema + ------ + CREATE TABLE counter_commands( + name TEXT PRIMARY KEY, + counterid INTEGER NOT NULL REFERENCES counters (counterid) ON UPDATE CASCADE ON DELETE CASCADE, + lbname TEXT, + undoname TEXT, + response TEXT NOT NULL + ); + """ + # NOTE: This table will be replaced by aliases soon anyway + # So no need to worry about integrity or future-proofing + _tablename_ = 'counter_commands' + _cache_ = {} + + name = String(primary=True) + counterid = Integer() + lbname = String() + undoname = String() + response = String() + diff --git a/src/modules/counters/migration.sql b/src/modules/counters/migration.sql index 3a0220c0..d9f9f543 100644 --- a/src/modules/counters/migration.sql +++ b/src/modules/counters/migration.sql @@ -1,2 +1,9 @@ ALTER TABLE counters ADD COLUMN category TEXT; ALTER TABLE counter_log ADD COLUMN details TEXT; +CREATE TABLE counter_commands( + name TEXT PRIMARY KEY, + counterid INTEGER NOT NULL REFERENCES counters (counterid) ON UPDATE CASCADE ON DELETE CASCADE, + lbname TEXT, + undoname TEXT, + response TEXT NOT NULL +);