From 299618ab1db62974cdec9124d52177a02e658d5b Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 30 Jul 2026 12:26:13 +0300 Subject: [PATCH 1/6] fix: Rename logged_messageid column --- data/pluscampaign-v1.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/pluscampaign-v1.sql b/data/pluscampaign-v1.sql index 4df05c3..279e984 100644 --- a/data/pluscampaign-v1.sql +++ b/data/pluscampaign-v1.sql @@ -38,7 +38,7 @@ CREATE TABLE campaign_rewards_earned( fulfilled_note TEXT, modnote TEXT, reference TEXT, - logged_messageid BIGINT, + log_messageid BIGINT, earned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), earned_from TEXT NOT NULL, _timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() From e700628403038477b3524d4d6dd08d911f9a5a44 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 30 Jul 2026 12:26:35 +0300 Subject: [PATCH 2/6] tweak: Log formatting --- plugin/campaign.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugin/campaign.py b/plugin/campaign.py index 932d947..0dbc95e 100644 --- a/plugin/campaign.py +++ b/plugin/campaign.py @@ -125,7 +125,8 @@ class RewardCampaign: embed.add_field( name="User Information", value=( - f"Twitch user `{reward.twitch_user_name or 'Unknown'}` `(ID: {reward.twitch_user_id or 'Unknown'})`.\n" + f"`{reward.twitch_user_name or 'Unknown'}`\n" + f"`(ID: {reward.twitch_user_id or 'Unknown'})`.\n" f"Internal ID `{reward.profileid}`" ), inline=True, @@ -135,8 +136,8 @@ class RewardCampaign: embed.add_field( name="Reward Earned", value=( - f"Earned campaign reward at {discord.utils.format_dt(reward.earned_at, 'F')}.\n" - f"Earned reason: '{reward.earned_from}'" + f"{discord.utils.format_dt(reward.earned_at, 'F')}.\n" + f"{reward.earned_from}" ), inline=True, ) From 1cc93d732e97aa55bfd0dc9609363cb22a228689 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 30 Jul 2026 12:26:53 +0300 Subject: [PATCH 3/6] fix: Reward formatting and acmpl --- plugin/discord/cog.py | 10 ++-- plugin/discord/ui/rewards.py | 88 +++++++++++++++++------------------- 2 files changed, 48 insertions(+), 50 deletions(-) diff --git a/plugin/discord/cog.py b/plugin/discord/cog.py index b63c20a..66ab6b6 100644 --- a/plugin/discord/cog.py +++ b/plugin/discord/cog.py @@ -369,11 +369,11 @@ class CampaignCog(LionCog): # Reward has been unfluffed update_args["fulfilled_at"] = None - new_ref_value = modal.reference.value or None + new_ref_value = modal.reference.component.value or None if new_ref_value != reward.reference: update_args["reference"] = new_ref_value - new_notes_value = modal.notes.value or None + new_notes_value = modal.notes.component.value or None if new_notes_value != reward.modnote: update_args["modnote"] = new_notes_value @@ -394,7 +394,11 @@ class CampaignCog(LionCog): #100: username in campaign """ - ... + return "#{rewardid}: {username} in {cname}".format( + rewardid=reward.earned_id, + username=reward.twitch_user_name or reward.twitch_user_id or "Unknown", + cname=campaign.row.campaign_name, + ) @campaign_editreward_cmd.autocomplete("rewardid") async def _reward_acmpl( diff --git a/plugin/discord/ui/rewards.py b/plugin/discord/ui/rewards.py index c336c7f..5dc6a2d 100644 --- a/plugin/discord/ui/rewards.py +++ b/plugin/discord/ui/rewards.py @@ -29,15 +29,13 @@ class RewardEditor(FastModal): # Title is the reward we are editing # Block of text with dates and user info - blurb = discord.ui.TextDisplay(content='placeholder') + blurb = discord.ui.TextDisplay(content="placeholder") # Fulfilled is a checkbox, and supports a fulfilled note flufbox = discord.ui.Label( text="Fulfilled", description="Whether this reward has been completed", - component=discord.ui.Checkbox( - default=False - ) + component=discord.ui.Checkbox(default=False), ) # Reference and further notes need to be editable, @@ -47,6 +45,7 @@ class RewardEditor(FastModal): description="Reference URL or other information", component=discord.ui.TextInput( style=discord.TextStyle.long, + required=False, ), ) notes = discord.ui.Label( @@ -54,6 +53,7 @@ class RewardEditor(FastModal): description="Further notes for this user/reward", component=discord.ui.TextInput( style=discord.TextStyle.long, + required=False, ), ) @@ -64,19 +64,18 @@ class RewardEditor(FastModal): # blurb table = { - 'Earned At': discord.utils.format_dt(reward.earned_at, 'F'), - 'Earned From': reward.earned_from, + "Earned At": discord.utils.format_dt(reward.earned_at, "F"), + "Earned From": reward.earned_from, } - prop_table = '\n'.join(tabulate(*table.items())) + prop_table = "\n".join(tabulate(*table.items())) self.blurb.content = prop_table - # Fulfilled - self.flufbox.component.default = (reward.fulfilled_at is not None) + self.flufbox.component.default = reward.fulfilled_at is not None # Notes - self.reference.component.default = reward.reference or '' - self.notes.component.default = reward.modnote or '' + self.reference.component.default = reward.reference or "" + self.notes.component.default = reward.modnote or "" return self @@ -134,7 +133,7 @@ class RewardList(MessageUI): reward = next(r for r in self._rewards if r.earned_id == value) modal = RewardEditor.from_reward(reward) - currently_fluffed = (reward.fulfilled_at is not None) + currently_fluffed = reward.fulfilled_at is not None @modal.submit_callback() async def on_editor_submit(interaction: discord.Interaction): @@ -142,18 +141,18 @@ class RewardList(MessageUI): if modal.flufbox.component.value and not currently_fluffed: # Reward has been fluffed - update_args['fulfilled_at'] = utc_now() + update_args["fulfilled_at"] = utc_now() elif currently_fluffed and not modal.flufbox.component.value: # Reward has been unfluffed - update_args['fulfilled_at'] = None + update_args["fulfilled_at"] = None - new_ref_value = modal.reference.value or None + new_ref_value = modal.reference.component.value or None if new_ref_value != reward.reference: - update_args['reference'] = new_ref_value + update_args["reference"] = new_ref_value - new_notes_value = modal.notes.value or None + new_notes_value = modal.notes.component.value or None if new_notes_value != reward.modnote: - update_args['modnote'] = new_notes_value + update_args["modnote"] = new_notes_value if update_args: await interaction.response.defer(thinking=True, ephemeral=True) @@ -162,7 +161,6 @@ class RewardList(MessageUI): else: await interaction.response.defer(thinking=False) - await selection.response.send_modal(modal) await self.refresh() @@ -171,17 +169,13 @@ class RewardList(MessageUI): async def reward_menu_refresh(self): menu = self.reward_menu - rewards = self.page + rewards = self.page if rewards: - menu.options = [ - self._format_reward_option(r) for r in rewards - ] - menu.disabled = False + menu.options = [self._format_reward_option(r) for r in rewards] + menu.disabled = False else: - menu.options = [ - SelectOption(label='DUMMY') - ] - menu.disabled = True + menu.options = [SelectOption(label="DUMMY")] + menu.disabled = True # Meta buttons @button(emoji=conf.emojis.getemoji("forward")) @@ -217,23 +211,23 @@ class RewardList(MessageUI): name = f"Reward #{reward.earned_id} earned by {reward.twitch_user_name or reward.twitch_user_id}" if reward.fulfilled_at is not None: - fat = discord.utils.format_dt(reward.fulfilled_at, 'F') + fat = discord.utils.format_dt(reward.fulfilled_at, "F") if reward.fulfilled_note is not None: fluf = f"{fat} ({reward.fulfilled_note})" else: - fluf = fat + fluf = fat else: fluf = "*Not Fulfilled*" table = { - 'Reward': "Plus Campaign Sketch", - 'Earned At': discord.utils.format_dt(reward.earned_at, 'F'), - 'Earned From': reward.earned_from, - 'Fulfilled At': fluf, - 'Reference': reward.reference or "*No reference set*", - 'Further notes': reward.modnote or "*No notes*", + "Reward": "Plus Campaign Sketch", + "Earned At": discord.utils.format_dt(reward.earned_at, "F"), + "Earned From": reward.earned_from, + "Fulfilled At": fluf, + "Reference": reward.reference or "*No reference set*", + "Further notes": reward.modnote or "*No notes*", } - prop_table = '\n'.join(tabulate(*table.items())) + prop_table = "\n".join(tabulate(*table.items())) return (name, prop_table) @@ -246,14 +240,10 @@ class RewardList(MessageUI): name = f"Reward #{reward.earned_id} earned by {reward.twitch_user_name or reward.twitch_user_id}" value = reward.earned_id - return SelectOption( - label=name, value=str(value) - ) + return SelectOption(label=name, value=str(value)) async def refresh_layout(self): - to_refresh = ( - self.reward_menu_refresh(), - ) + to_refresh = (self.reward_menu_refresh(),) await asyncio.gather(*to_refresh) if self.page_count <= 1: @@ -264,13 +254,18 @@ class RewardList(MessageUI): self.next_page_button.disabled = False self.set_layout( - (self.prev_page_button, self.refresh_button, self.next_page_button, self.quit_button), + ( + self.prev_page_button, + self.refresh_button, + self.next_page_button, + self.quit_button, + ), (self.reward_menu,), ) async def make_message(self) -> MessageArgs: embed = discord.Embed(title=f"{self.campaign.row.campaign_name} Reward List") - embed.set_footer(text='Last Update') + embed.set_footer(text="Last Update") embed.timestamp = utc_now() campaign = self.campaign @@ -284,7 +279,6 @@ class RewardList(MessageUI): embed.description = description - for reward in self.page: name, value = self._format_reward_section(reward) embed.add_field(name=name, value=value, inline=False) @@ -296,6 +290,6 @@ class RewardList(MessageUI): # TODO: Consider filter by unfulfilled or reward type self.reward_blocks = [ - rewards[i:i+self.blocklen] + rewards[i : i + self.blocklen] for i in range(0, len(rewards), self.blocklen) ] or [[]] From 99d1193be67ec971a2708520e7255e05c9060ef1 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 30 Jul 2026 12:42:53 +0300 Subject: [PATCH 4/6] fix: Add edit support to ThreadedWebhook --- plugin/lib.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugin/lib.py b/plugin/lib.py index f232fa3..e326d1c 100644 --- a/plugin/lib.py +++ b/plugin/lib.py @@ -17,22 +17,25 @@ def LOWER(expression: Expression) -> RawExpr: return RawExpr(final_expr, final_values) + def asexpr(value: Any) -> RawExpr: """ Turn a value into an expression. """ return RawExpr(sql.Placeholder(), (value,)) + async def fire_and_forget(awaitable, do_in=1, ignorable=(discord.HTTPException)): await asyncio.sleep(do_in) try: - await awaitable + await awaitable except ignorable: pass except Exception as e: # TODO: Log unexpected exceptions pass + class ThreadedWebhook(discord.Webhook): __slots__ = ("thread_id",) @@ -55,10 +58,14 @@ class ThreadedWebhook(discord.Webhook): kwargs.setdefault("thread", discord.Object(self.thread_id)) return await super().send(*args, **kwargs) + async def edit_message(self, *args, **kwargs): + if self.thread_id is not None: + kwargs.setdefault("thread", discord.Object(self.thread_id)) + return await super().edit_message(*args, **kwargs) + async def test_webhook(self): embed = discord.Embed( - title="Testing", - description="Testing logging webhook, feel free to delete." + title="Testing", description="Testing logging webhook, feel free to delete." ) result = await self.send(embed=embed, wait=True, silent=True) asyncio.create_task(fire_and_forget(result.delete())) From f61d0519ee84ad171348d978e3ee868ec168f2f8 Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 30 Jul 2026 12:43:18 +0300 Subject: [PATCH 5/6] fix(discord): Actually send edit modal --- plugin/discord/cog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/discord/cog.py b/plugin/discord/cog.py index 66ab6b6..6c24a6c 100644 --- a/plugin/discord/cog.py +++ b/plugin/discord/cog.py @@ -386,6 +386,8 @@ class CampaignCog(LionCog): else: await interaction.response.defer(thinking=False) + await ctx.interaction.response.send_modal(modal) + def _reward_acmpl_format( self, campaign: RewardCampaign, reward: EarnedReward ) -> str: From 87aec03e57e75f9ed1b33479f9665b01f08d8d0e Mon Sep 17 00:00:00 2001 From: Interitio Date: Thu, 30 Jul 2026 13:39:06 +0300 Subject: [PATCH 6/6] fix: Correct campaign condition --- plugin/campaign.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugin/campaign.py b/plugin/campaign.py index 0dbc95e..971cc8b 100644 --- a/plugin/campaign.py +++ b/plugin/campaign.py @@ -204,15 +204,17 @@ class CampaignRegistry: condition = Campaign.communityid == cid if active is not None: - active_condition = ( - Campaign.started_at != NULL and Campaign.completed_at == NULL + active_condition = (Campaign.started_at != NULL) & ( + Campaign.completed_at == NULL ) if active: - condition = condition and active_condition + condition = condition & active_condition else: - condition = condition and ~active_condition + condition = condition & ~active_condition - rows = await Campaign.fetch_where(condition) + rows = await Campaign.fetch_where( + condition, + ) campaigns = [RewardCampaign(row, session=self._session) for row in rows] return campaigns