Compare commits

..

5 Commits

Author SHA1 Message Date
conatum f61d0519ee fix(discord): Actually send edit modal 2026-07-30 12:43:18 +03:00
conatum 99d1193be6 fix: Add edit support to ThreadedWebhook 2026-07-30 12:42:53 +03:00
conatum 1cc93d732e fix: Reward formatting and acmpl 2026-07-30 12:26:53 +03:00
conatum e700628403 tweak: Log formatting 2026-07-30 12:26:35 +03:00
conatum 299618ab1d fix: Rename logged_messageid column 2026-07-30 12:26:13 +03:00
5 changed files with 65 additions and 57 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ CREATE TABLE campaign_rewards_earned(
fulfilled_note TEXT, fulfilled_note TEXT,
modnote TEXT, modnote TEXT,
reference TEXT, reference TEXT,
logged_messageid BIGINT, log_messageid BIGINT,
earned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), earned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
earned_from TEXT NOT NULL, earned_from TEXT NOT NULL,
_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW() _timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
+4 -3
View File
@@ -125,7 +125,8 @@ class RewardCampaign:
embed.add_field( embed.add_field(
name="User Information", name="User Information",
value=( 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}`" f"Internal ID `{reward.profileid}`"
), ),
inline=True, inline=True,
@@ -135,8 +136,8 @@ class RewardCampaign:
embed.add_field( embed.add_field(
name="Reward Earned", name="Reward Earned",
value=( value=(
f"Earned campaign reward at {discord.utils.format_dt(reward.earned_at, 'F')}.\n" f"{discord.utils.format_dt(reward.earned_at, 'F')}.\n"
f"Earned reason: '{reward.earned_from}'" f"{reward.earned_from}"
), ),
inline=True, inline=True,
) )
+9 -3
View File
@@ -369,11 +369,11 @@ class CampaignCog(LionCog):
# Reward has been unfluffed # 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: 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: if new_notes_value != reward.modnote:
update_args["modnote"] = new_notes_value update_args["modnote"] = new_notes_value
@@ -386,6 +386,8 @@ class CampaignCog(LionCog):
else: else:
await interaction.response.defer(thinking=False) await interaction.response.defer(thinking=False)
await ctx.interaction.response.send_modal(modal)
def _reward_acmpl_format( def _reward_acmpl_format(
self, campaign: RewardCampaign, reward: EarnedReward self, campaign: RewardCampaign, reward: EarnedReward
) -> str: ) -> str:
@@ -394,7 +396,11 @@ class CampaignCog(LionCog):
#100: username in campaign #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") @campaign_editreward_cmd.autocomplete("rewardid")
async def _reward_acmpl( async def _reward_acmpl(
+41 -47
View File
@@ -29,15 +29,13 @@ class RewardEditor(FastModal):
# Title is the reward we are editing # Title is the reward we are editing
# Block of text with dates and user info # 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 # Fulfilled is a checkbox, and supports a fulfilled note
flufbox = discord.ui.Label( flufbox = discord.ui.Label(
text="Fulfilled", text="Fulfilled",
description="Whether this reward has been completed", description="Whether this reward has been completed",
component=discord.ui.Checkbox( component=discord.ui.Checkbox(default=False),
default=False
)
) )
# Reference and further notes need to be editable, # Reference and further notes need to be editable,
@@ -47,6 +45,7 @@ class RewardEditor(FastModal):
description="Reference URL or other information", description="Reference URL or other information",
component=discord.ui.TextInput( component=discord.ui.TextInput(
style=discord.TextStyle.long, style=discord.TextStyle.long,
required=False,
), ),
) )
notes = discord.ui.Label( notes = discord.ui.Label(
@@ -54,6 +53,7 @@ class RewardEditor(FastModal):
description="Further notes for this user/reward", description="Further notes for this user/reward",
component=discord.ui.TextInput( component=discord.ui.TextInput(
style=discord.TextStyle.long, style=discord.TextStyle.long,
required=False,
), ),
) )
@@ -64,19 +64,18 @@ class RewardEditor(FastModal):
# blurb # blurb
table = { table = {
'Earned At': discord.utils.format_dt(reward.earned_at, 'F'), "Earned At": discord.utils.format_dt(reward.earned_at, "F"),
'Earned From': reward.earned_from, "Earned From": reward.earned_from,
} }
prop_table = '\n'.join(tabulate(*table.items())) prop_table = "\n".join(tabulate(*table.items()))
self.blurb.content = prop_table self.blurb.content = prop_table
# Fulfilled # Fulfilled
self.flufbox.component.default = (reward.fulfilled_at is not None) self.flufbox.component.default = reward.fulfilled_at is not None
# Notes # Notes
self.reference.component.default = reward.reference or '' self.reference.component.default = reward.reference or ""
self.notes.component.default = reward.modnote or '' self.notes.component.default = reward.modnote or ""
return self return self
@@ -134,7 +133,7 @@ class RewardList(MessageUI):
reward = next(r for r in self._rewards if r.earned_id == value) reward = next(r for r in self._rewards if r.earned_id == value)
modal = RewardEditor.from_reward(reward) modal = RewardEditor.from_reward(reward)
currently_fluffed = (reward.fulfilled_at is not None) currently_fluffed = reward.fulfilled_at is not None
@modal.submit_callback() @modal.submit_callback()
async def on_editor_submit(interaction: discord.Interaction): async def on_editor_submit(interaction: discord.Interaction):
@@ -142,18 +141,18 @@ class RewardList(MessageUI):
if modal.flufbox.component.value and not currently_fluffed: if modal.flufbox.component.value and not currently_fluffed:
# Reward has been 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: elif currently_fluffed and not modal.flufbox.component.value:
# Reward has been unfluffed # 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: 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: if new_notes_value != reward.modnote:
update_args['modnote'] = new_notes_value update_args["modnote"] = new_notes_value
if update_args: if update_args:
await interaction.response.defer(thinking=True, ephemeral=True) await interaction.response.defer(thinking=True, ephemeral=True)
@@ -162,7 +161,6 @@ class RewardList(MessageUI):
else: else:
await interaction.response.defer(thinking=False) await interaction.response.defer(thinking=False)
await selection.response.send_modal(modal) await selection.response.send_modal(modal)
await self.refresh() await self.refresh()
@@ -171,17 +169,13 @@ class RewardList(MessageUI):
async def reward_menu_refresh(self): async def reward_menu_refresh(self):
menu = self.reward_menu menu = self.reward_menu
rewards = self.page rewards = self.page
if rewards: if rewards:
menu.options = [ menu.options = [self._format_reward_option(r) for r in rewards]
self._format_reward_option(r) for r in rewards menu.disabled = False
]
menu.disabled = False
else: else:
menu.options = [ menu.options = [SelectOption(label="DUMMY")]
SelectOption(label='DUMMY') menu.disabled = True
]
menu.disabled = True
# Meta buttons # Meta buttons
@button(emoji=conf.emojis.getemoji("forward")) @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}" name = f"Reward #{reward.earned_id} earned by {reward.twitch_user_name or reward.twitch_user_id}"
if reward.fulfilled_at is not None: 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: if reward.fulfilled_note is not None:
fluf = f"{fat} ({reward.fulfilled_note})" fluf = f"{fat} ({reward.fulfilled_note})"
else: else:
fluf = fat fluf = fat
else: else:
fluf = "*Not Fulfilled*" fluf = "*Not Fulfilled*"
table = { table = {
'Reward': "Plus Campaign Sketch", "Reward": "Plus Campaign Sketch",
'Earned At': discord.utils.format_dt(reward.earned_at, 'F'), "Earned At": discord.utils.format_dt(reward.earned_at, "F"),
'Earned From': reward.earned_from, "Earned From": reward.earned_from,
'Fulfilled At': fluf, "Fulfilled At": fluf,
'Reference': reward.reference or "*No reference set*", "Reference": reward.reference or "*No reference set*",
'Further notes': reward.modnote or "*No notes*", "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) 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}" name = f"Reward #{reward.earned_id} earned by {reward.twitch_user_name or reward.twitch_user_id}"
value = reward.earned_id value = reward.earned_id
return SelectOption( return SelectOption(label=name, value=str(value))
label=name, value=str(value)
)
async def refresh_layout(self): async def refresh_layout(self):
to_refresh = ( to_refresh = (self.reward_menu_refresh(),)
self.reward_menu_refresh(),
)
await asyncio.gather(*to_refresh) await asyncio.gather(*to_refresh)
if self.page_count <= 1: if self.page_count <= 1:
@@ -264,13 +254,18 @@ class RewardList(MessageUI):
self.next_page_button.disabled = False self.next_page_button.disabled = False
self.set_layout( 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,), (self.reward_menu,),
) )
async def make_message(self) -> MessageArgs: async def make_message(self) -> MessageArgs:
embed = discord.Embed(title=f"{self.campaign.row.campaign_name} Reward List") 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() embed.timestamp = utc_now()
campaign = self.campaign campaign = self.campaign
@@ -284,7 +279,6 @@ class RewardList(MessageUI):
embed.description = description embed.description = description
for reward in self.page: for reward in self.page:
name, value = self._format_reward_section(reward) name, value = self._format_reward_section(reward)
embed.add_field(name=name, value=value, inline=False) embed.add_field(name=name, value=value, inline=False)
@@ -296,6 +290,6 @@ class RewardList(MessageUI):
# TODO: Consider filter by unfulfilled or reward type # TODO: Consider filter by unfulfilled or reward type
self.reward_blocks = [ self.reward_blocks = [
rewards[i:i+self.blocklen] rewards[i : i + self.blocklen]
for i in range(0, len(rewards), self.blocklen) for i in range(0, len(rewards), self.blocklen)
] or [[]] ] or [[]]
+10 -3
View File
@@ -17,22 +17,25 @@ def LOWER(expression: Expression) -> RawExpr:
return RawExpr(final_expr, final_values) return RawExpr(final_expr, final_values)
def asexpr(value: Any) -> RawExpr: def asexpr(value: Any) -> RawExpr:
""" """
Turn a value into an expression. Turn a value into an expression.
""" """
return RawExpr(sql.Placeholder(), (value,)) return RawExpr(sql.Placeholder(), (value,))
async def fire_and_forget(awaitable, do_in=1, ignorable=(discord.HTTPException)): async def fire_and_forget(awaitable, do_in=1, ignorable=(discord.HTTPException)):
await asyncio.sleep(do_in) await asyncio.sleep(do_in)
try: try:
await awaitable await awaitable
except ignorable: except ignorable:
pass pass
except Exception as e: except Exception as e:
# TODO: Log unexpected exceptions # TODO: Log unexpected exceptions
pass pass
class ThreadedWebhook(discord.Webhook): class ThreadedWebhook(discord.Webhook):
__slots__ = ("thread_id",) __slots__ = ("thread_id",)
@@ -55,10 +58,14 @@ class ThreadedWebhook(discord.Webhook):
kwargs.setdefault("thread", discord.Object(self.thread_id)) kwargs.setdefault("thread", discord.Object(self.thread_id))
return await super().send(*args, **kwargs) 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): async def test_webhook(self):
embed = discord.Embed( embed = discord.Embed(
title="Testing", title="Testing", description="Testing logging webhook, feel free to delete."
description="Testing logging webhook, feel free to delete."
) )
result = await self.send(embed=embed, wait=True, silent=True) result = await self.send(embed=embed, wait=True, silent=True)
asyncio.create_task(fire_and_forget(result.delete())) asyncio.create_task(fire_and_forget(result.delete()))