generated from HoloTech/holotech-plugin-template
Compare commits
16 Commits
87aec03e57
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 08fb0084fa | |||
| 1c5eb6ceeb | |||
| 97bdf8e8e3 | |||
| 14134d8831 | |||
| 1e8bab1334 | |||
| 252a8f1aa2 | |||
| da2122d375 | |||
| 6122603115 | |||
| a69d45c1c2 | |||
| e11fb8f071 | |||
| c54653322e | |||
| 8189eeec05 | |||
| f2e8852d45 | |||
| 5eb0c1a712 | |||
| 300f712bfd | |||
| 05c428bcc9 |
+24
-7
@@ -62,6 +62,7 @@ class RewardCampaign:
|
|||||||
twitch_user_id: Optional[str] = None,
|
twitch_user_id: Optional[str] = None,
|
||||||
twitch_user_name: Optional[str] = None,
|
twitch_user_name: Optional[str] = None,
|
||||||
earned_at: Optional[datetime] = None,
|
earned_at: Optional[datetime] = None,
|
||||||
|
**kwargs,
|
||||||
) -> EarnedReward:
|
) -> EarnedReward:
|
||||||
row = await EarnedReward.create(
|
row = await EarnedReward.create(
|
||||||
campaign_id=self.row.campaign_id,
|
campaign_id=self.row.campaign_id,
|
||||||
@@ -71,6 +72,7 @@ class RewardCampaign:
|
|||||||
twitch_user_id=twitch_user_id,
|
twitch_user_id=twitch_user_id,
|
||||||
twitch_user_name=twitch_user_name,
|
twitch_user_name=twitch_user_name,
|
||||||
earned_at=earned_at or utc_now(),
|
earned_at=earned_at or utc_now(),
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
await self.try_to_log_reward(row)
|
await self.try_to_log_reward(row)
|
||||||
|
|
||||||
@@ -85,12 +87,28 @@ class RewardCampaign:
|
|||||||
await reward.update(**kwargs)
|
await reward.update(**kwargs)
|
||||||
await self.try_to_log_reward(reward)
|
await self.try_to_log_reward(reward)
|
||||||
|
|
||||||
|
async def delete_reward(self, rewardid: int):
|
||||||
|
reward = await EarnedReward.fetch(rewardid)
|
||||||
|
if reward is None:
|
||||||
|
raise ValueError("Reward doesn't exist")
|
||||||
|
await self.try_to_unlog_reward(reward)
|
||||||
|
await reward.delete()
|
||||||
|
|
||||||
async def get_rewards(self) -> list[EarnedReward]:
|
async def get_rewards(self) -> list[EarnedReward]:
|
||||||
rows = await EarnedReward.fetch_where(
|
rows = await EarnedReward.fetch_where(
|
||||||
campaign_id=self.row.campaign_id
|
campaign_id=self.row.campaign_id
|
||||||
).order_by("earned_at", direction=ORDER.ASC)
|
).order_by("earned_at", direction=ORDER.ASC)
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
async def try_to_unlog_reward(self, reward: EarnedReward):
|
||||||
|
import discord
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.webhook and reward.log_messageid:
|
||||||
|
await self.webhook.delete_message(reward.log_messageid)
|
||||||
|
except discord.HTTPException:
|
||||||
|
pass
|
||||||
|
|
||||||
async def try_to_log_reward(self, reward: EarnedReward):
|
async def try_to_log_reward(self, reward: EarnedReward):
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@@ -121,12 +139,13 @@ class RewardCampaign:
|
|||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title=f"Reward #{reward.earned_id} in {self.row.campaign_name}",
|
title=f"Reward #{reward.earned_id} in {self.row.campaign_name}",
|
||||||
)
|
)
|
||||||
|
embed.description = f"> {reward.earned_from}"
|
||||||
# User Field
|
# User Field
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="User Information",
|
name="User Information",
|
||||||
value=(
|
value=(
|
||||||
f"`{reward.twitch_user_name or 'Unknown'}`\n"
|
f"`{reward.twitch_user_name or 'Unknown'}`\n"
|
||||||
f"`(ID: {reward.twitch_user_id 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,10 +154,7 @@ class RewardCampaign:
|
|||||||
# Earning field
|
# Earning field
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Reward Earned",
|
name="Reward Earned",
|
||||||
value=(
|
value=(f"{discord.utils.format_dt(reward.earned_at, 'F')}."),
|
||||||
f"{discord.utils.format_dt(reward.earned_at, 'F')}.\n"
|
|
||||||
f"{reward.earned_from}"
|
|
||||||
),
|
|
||||||
inline=True,
|
inline=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -154,12 +170,13 @@ class RewardCampaign:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Fulfilled field
|
# Fulfilled field
|
||||||
|
fluffed_emoji = "✅" if reward.fulfilled_at else "🔳"
|
||||||
if reward.fulfilled_at is not None:
|
if reward.fulfilled_at is not None:
|
||||||
fluffed = f"Fluffed at {discord.utils.format_dt(reward.fulfilled_at, 'F')}"
|
fluffed = f"{fluffed_emoji} Fluffed at {discord.utils.format_dt(reward.fulfilled_at, 'F')}"
|
||||||
if reward.fulfilled_note:
|
if reward.fulfilled_note:
|
||||||
fluffed += "\n" + "Fluff Note: " + reward.fulfilled_note
|
fluffed += "\n" + "Fluff Note: " + reward.fulfilled_note
|
||||||
else:
|
else:
|
||||||
fluffed = "Not yet fluffed"
|
fluffed = f"{fluffed_emoji} Not yet fluffed"
|
||||||
|
|
||||||
embed.add_field(name="Fulfilled", value=fluffed)
|
embed.add_field(name="Fulfilled", value=fluffed)
|
||||||
|
|
||||||
|
|||||||
+88
-7
@@ -7,6 +7,7 @@ from discord.ext import commands as cmds
|
|||||||
from discord import Forbidden, User, app_commands as appcmds
|
from discord import Forbidden, User, app_commands as appcmds
|
||||||
|
|
||||||
from meta import LionBot, LionCog, LionContext
|
from meta import LionBot, LionCog, LionContext
|
||||||
|
from meta import logger
|
||||||
from meta.errors import SafeCancellation, UserInputError
|
from meta.errors import SafeCancellation, UserInputError
|
||||||
from meta.logger import log_wrap
|
from meta.logger import log_wrap
|
||||||
from utils.lib import utc_now
|
from utils.lib import utc_now
|
||||||
@@ -79,6 +80,7 @@ class CampaignCog(LionCog):
|
|||||||
sorted_campaigns = sorted(
|
sorted_campaigns = sorted(
|
||||||
campaigns,
|
campaigns,
|
||||||
key=lambda camp: (camp.is_active, camp.row.started_at, camp.row.created_at),
|
key=lambda camp: (camp.is_active, camp.row.started_at, camp.row.created_at),
|
||||||
|
reverse=True,
|
||||||
)
|
)
|
||||||
names = [
|
names = [
|
||||||
camp.row.campaign_name
|
camp.row.campaign_name
|
||||||
@@ -258,7 +260,7 @@ class CampaignCog(LionCog):
|
|||||||
update_args["campaign_name"] = new_name
|
update_args["campaign_name"] = new_name
|
||||||
|
|
||||||
if new_cap is not None:
|
if new_cap is not None:
|
||||||
update_args["total_rewards"] = new_cap if new_cap > 0 else None
|
update_args["target_rewards"] = new_cap if new_cap > 0 else None
|
||||||
|
|
||||||
# If logging webhook is given, check that it works
|
# If logging webhook is given, check that it works
|
||||||
if logging_webhook is not None:
|
if logging_webhook is not None:
|
||||||
@@ -318,14 +320,18 @@ class CampaignCog(LionCog):
|
|||||||
campaign_rewards_cmd.autocomplete("campaign_name")(_campaign_acmpl)
|
campaign_rewards_cmd.autocomplete("campaign_name")(_campaign_acmpl)
|
||||||
|
|
||||||
@campaign_group.command(
|
@campaign_group.command(
|
||||||
name="editreward",
|
name="delreward",
|
||||||
description="Add or edit details for a given reward in a campaign (see also /campaign rewards)",
|
description="Remove a campaign reward",
|
||||||
)
|
)
|
||||||
@appcmds.describe(
|
@appcmds.describe(
|
||||||
rewardid="Earned reward to edit",
|
rewardid="Earned reward to delete",
|
||||||
)
|
)
|
||||||
@appcmds.rename(rewardid="reward")
|
@appcmds.rename(rewardid="reward")
|
||||||
async def campaign_editreward_cmd(self, ctx: LionContext, rewardid: str):
|
async def campaign_delreward_cmd(
|
||||||
|
self,
|
||||||
|
ctx: LionContext,
|
||||||
|
rewardid: str,
|
||||||
|
):
|
||||||
# For this we'll just open the reward editor
|
# For this we'll just open the reward editor
|
||||||
# Will need to identify the reward with autocomplete. Can use the reward id directly..
|
# Will need to identify the reward with autocomplete. Can use the reward id directly..
|
||||||
if not rewardid.isdigit():
|
if not rewardid.isdigit():
|
||||||
@@ -353,11 +359,84 @@ class CampaignCog(LionCog):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Okay, this is our reward, and author has permission to modify it. Spin the modal.
|
# Okay, they have permission, let's delete the reward.
|
||||||
modal = RewardEditor.from_reward(reward)
|
await campaign.delete_reward(reward.earned_id)
|
||||||
|
|
||||||
|
# Ack
|
||||||
|
await ctx.reply(content="Campaign reward deleted!", ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
|
@campaign_group.command(
|
||||||
|
name="editreward",
|
||||||
|
description="Add or edit details for a given reward in a campaign (see also /campaign rewards)",
|
||||||
|
)
|
||||||
|
@appcmds.describe(
|
||||||
|
rewardid="Earned reward to edit",
|
||||||
|
fulfilled="Whether this reward has been completed or not.",
|
||||||
|
reference="Reference information for this reward, e.g. image or message URL",
|
||||||
|
notes="Any additional notes",
|
||||||
|
)
|
||||||
|
@appcmds.rename(rewardid="reward")
|
||||||
|
async def campaign_editreward_cmd(
|
||||||
|
self,
|
||||||
|
ctx: LionContext,
|
||||||
|
rewardid: str,
|
||||||
|
fulfilled: Optional[bool] = None,
|
||||||
|
reference: Optional[str] = None,
|
||||||
|
notes: Optional[str] = None
|
||||||
|
):
|
||||||
|
# For this we'll just open the reward editor
|
||||||
|
# Will need to identify the reward with autocomplete. Can use the reward id directly..
|
||||||
|
if not rewardid.isdigit():
|
||||||
|
raise UserInputError(
|
||||||
|
"Please enter the reward number or select a reward from the argument menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
reward = await EarnedReward.fetch(int(rewardid))
|
||||||
|
if not reward:
|
||||||
|
raise UserInputError(
|
||||||
|
"Reward not found. Please enter the reward number or select a reward from the argument menu"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch campaign and then do a perm check
|
||||||
|
campaign = await self.campaigns.fetch_campaign(reward.campaign_id)
|
||||||
|
if campaign is None:
|
||||||
|
raise SafeCancellation("Something went wrong.. please try again soon.")
|
||||||
|
if campaign.row.communityid != ctx.community.communityid:
|
||||||
|
raise UserInputError("This reward doesn't belong to this community!")
|
||||||
|
|
||||||
|
if not await self.campaign_modcheck(campaign, ctx.author):
|
||||||
|
await ctx.interaction.response.send_message(
|
||||||
|
"You need to be an administrator or have the configured modrole to use that!",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Okay, this is our reward, and author has permission to modify it.
|
||||||
currently_fluffed = reward.fulfilled_at is not None
|
currently_fluffed = reward.fulfilled_at is not None
|
||||||
|
|
||||||
|
update_args = {}
|
||||||
|
if fulfilled is not None:
|
||||||
|
if fulfilled and not currently_fluffed:
|
||||||
|
# Reward has been fluffed
|
||||||
|
update_args["fulfilled_at"] = utc_now()
|
||||||
|
elif currently_fluffed and not fulfilled:
|
||||||
|
# Reward has been unfluffed
|
||||||
|
update_args["fulfilled_at"] = None
|
||||||
|
if reference is not None and reference != reward.reference:
|
||||||
|
update_args["reference"] = reference
|
||||||
|
if notes is not None and notes != reward.modnote:
|
||||||
|
update_args["modnote"] = notes
|
||||||
|
|
||||||
|
if update_args:
|
||||||
|
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
|
||||||
|
await campaign.update_reward(reward.earned_id, **update_args)
|
||||||
|
await ctx.interaction.followup.send(
|
||||||
|
content="Reward updated!"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
modal = RewardEditor.from_reward(reward)
|
||||||
|
|
||||||
@modal.submit_callback()
|
@modal.submit_callback()
|
||||||
async def on_editor_submit(interaction: discord.Interaction):
|
async def on_editor_submit(interaction: discord.Interaction):
|
||||||
update_args = {}
|
update_args = {}
|
||||||
@@ -402,6 +481,7 @@ class CampaignCog(LionCog):
|
|||||||
cname=campaign.row.campaign_name,
|
cname=campaign.row.campaign_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@campaign_delreward_cmd.autocomplete("rewardid")
|
||||||
@campaign_editreward_cmd.autocomplete("rewardid")
|
@campaign_editreward_cmd.autocomplete("rewardid")
|
||||||
async def _reward_acmpl(
|
async def _reward_acmpl(
|
||||||
self, interaction: discord.Interaction, partial: str
|
self, interaction: discord.Interaction, partial: str
|
||||||
@@ -427,6 +507,7 @@ class CampaignCog(LionCog):
|
|||||||
sorted_campaigns = sorted(
|
sorted_campaigns = sorted(
|
||||||
campaigns,
|
campaigns,
|
||||||
key=lambda camp: (camp.is_active, camp.row.started_at, camp.row.created_at),
|
key=lambda camp: (camp.is_active, camp.row.started_at, camp.row.created_at),
|
||||||
|
reverse=True,
|
||||||
)
|
)
|
||||||
for campaign in sorted_campaigns:
|
for campaign in sorted_campaigns:
|
||||||
rewards = await campaign.get_rewards()
|
rewards = await campaign.get_rewards()
|
||||||
|
|||||||
@@ -208,7 +208,6 @@ class RewardList(MessageUI):
|
|||||||
"""
|
"""
|
||||||
# Title is Reward #n earned by twitch_username
|
# Title is Reward #n earned by twitch_username
|
||||||
# Reward, Earned at, Earned by, Fulfilled at (not fulfilled/date), Ref, Added Notes
|
# Reward, Earned at, Earned by, Fulfilled at (not fulfilled/date), Ref, Added Notes
|
||||||
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")
|
||||||
@@ -219,17 +218,25 @@ class RewardList(MessageUI):
|
|||||||
else:
|
else:
|
||||||
fluf = "*Not Fulfilled*"
|
fluf = "*Not Fulfilled*"
|
||||||
|
|
||||||
|
fluffed_emoji = "✅" if reward.fulfilled_at else "🔳"
|
||||||
|
earned = discord.utils.format_dt(reward.earned_at, "d")
|
||||||
|
|
||||||
|
name = f"{fluffed_emoji} #{reward.earned_id} earned by {reward.twitch_user_name or reward.twitch_user_id} at {earned}"
|
||||||
|
|
||||||
table = {
|
table = {
|
||||||
"Reward": "Plus Campaign Sketch",
|
# "Reward": "Plus Campaign Sketch",
|
||||||
"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)
|
value = '\n'.join((
|
||||||
|
"> {reason}",
|
||||||
|
"{table}",
|
||||||
|
)).format(reason=reward.earned_from, table=prop_table)
|
||||||
|
return (name, value)
|
||||||
|
|
||||||
def _format_reward_option(self, reward: EarnedReward) -> SelectOption:
|
def _format_reward_option(self, reward: EarnedReward) -> SelectOption:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -63,6 +63,16 @@ class ThreadedWebhook(discord.Webhook):
|
|||||||
kwargs.setdefault("thread", discord.Object(self.thread_id))
|
kwargs.setdefault("thread", discord.Object(self.thread_id))
|
||||||
return await super().edit_message(*args, **kwargs)
|
return await super().edit_message(*args, **kwargs)
|
||||||
|
|
||||||
|
async def delete_message(self, *args, **kwargs):
|
||||||
|
if self.thread_id is not None:
|
||||||
|
kwargs.setdefault("thread", discord.Object(self.thread_id))
|
||||||
|
return await super().delete_message(*args, **kwargs)
|
||||||
|
|
||||||
|
async def fetch_message(self, *args, **kwargs):
|
||||||
|
if self.thread_id is not None:
|
||||||
|
kwargs.setdefault("thread", discord.Object(self.thread_id))
|
||||||
|
return await super().fetch_message(*args, **kwargs)
|
||||||
|
|
||||||
async def test_webhook(self):
|
async def test_webhook(self):
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Testing", description="Testing logging webhook, feel free to delete."
|
title="Testing", description="Testing logging webhook, feel free to delete."
|
||||||
|
|||||||
+191
-24
@@ -1,9 +1,13 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime as dt
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import twitchio
|
import twitchio
|
||||||
from twitchio.ext import commands as cmds
|
from twitchio.ext import commands as cmds
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
from data.queries import JOINTYPE, ORDER
|
||||||
from meta import Bot
|
from meta import Bot
|
||||||
from meta.logger import log_wrap
|
from meta.logger import log_wrap
|
||||||
from meta.sockets import Channel, register_channel
|
from meta.sockets import Channel, register_channel
|
||||||
@@ -24,7 +28,7 @@ class CampaignComponent(cmds.Component):
|
|||||||
self.campaigns = CampaignRegistry(self.data)
|
self.campaigns = CampaignRegistry(self.data)
|
||||||
self.channel = CampaignChannel(self.bot.profiles.profiles, self.campaigns)
|
self.channel = CampaignChannel(self.bot.profiles.profiles, self.campaigns)
|
||||||
|
|
||||||
register_channel("Campaign", self.channel)
|
register_channel(self.channel.name, self.channel)
|
||||||
|
|
||||||
# ----- API -----
|
# ----- API -----
|
||||||
async def component_load(self):
|
async def component_load(self):
|
||||||
@@ -42,7 +46,7 @@ class CampaignComponent(cmds.Component):
|
|||||||
|
|
||||||
# ------ Event Handlers -----
|
# ------ Event Handlers -----
|
||||||
@cmds.Component.listener()
|
@cmds.Component.listener()
|
||||||
async def event_safe_event_chat_notice_sub(self, payload):
|
async def event_safe_chat_notice_sub(self, payload):
|
||||||
"""
|
"""
|
||||||
This is our most important notice for this event.
|
This is our most important notice for this event.
|
||||||
|
|
||||||
@@ -58,11 +62,51 @@ class CampaignComponent(cmds.Component):
|
|||||||
|
|
||||||
# Check the tier and duration of the subscription.
|
# Check the tier and duration of the subscription.
|
||||||
# Continue if tier = 3000 and duration is at least 3 months
|
# Continue if tier = 3000 and duration is at least 3 months
|
||||||
if (tier := detail_row["tier"]) == 3000 and (
|
if detail_row["tier"] == 3000 and detail_row["duration_months"] >= 3:
|
||||||
duration := detail_row["duration_months"]
|
|
||||||
) >= 3:
|
|
||||||
# Check if there is an ongoing campaign
|
# Check if there is an ongoing campaign
|
||||||
campaigns = await self.campaigns.fetch_campaigns(event_row["communityid"])
|
campaigns = await self.campaigns.fetch_campaigns(event_row["communityid"])
|
||||||
|
pid = event_row["profileid"]
|
||||||
|
|
||||||
|
for campaign in campaigns:
|
||||||
|
rewards = await campaign.get_rewards()
|
||||||
|
reward_progress = len(rewards)
|
||||||
|
|
||||||
|
existing = any(reward.profileid == pid for reward in rewards)
|
||||||
|
if existing:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (
|
||||||
|
campaign.row.target_rewards is None
|
||||||
|
or reward_progress < campaign.row.target_rewards
|
||||||
|
):
|
||||||
|
# Add a reward to the database with the correct info.
|
||||||
|
await campaign.add_reward(
|
||||||
|
profileid=event_row["profileid"],
|
||||||
|
earned_from=f"(SUB NOTICE) {data.system_message}",
|
||||||
|
event_id=event_row["event_id"],
|
||||||
|
twitch_user_id=data.chatter.id,
|
||||||
|
twitch_user_name=data.chatter.name,
|
||||||
|
)
|
||||||
|
await self.dispatch_update(campaign)
|
||||||
|
else:
|
||||||
|
logger.info(f"Campaigns ignoring sub notice event: {event_row}")
|
||||||
|
|
||||||
|
# @cmds.Component.listener()
|
||||||
|
async def event_custom_redemption_add(self, payload):
|
||||||
|
if payload.reward.title not in (
|
||||||
|
"hi!",
|
||||||
|
"hydrate",
|
||||||
|
"stretch",
|
||||||
|
"save file",
|
||||||
|
"pet lilac",
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
community = await self.bot.profiles.fetch_community(payload.broadcaster)
|
||||||
|
cid = community.communityid
|
||||||
|
profile = await self.bot.profiles.fetch_profile(payload.user)
|
||||||
|
pid = profile.profileid
|
||||||
|
campaigns = await self.campaigns.fetch_campaigns(cid)
|
||||||
|
|
||||||
for campaign in campaigns:
|
for campaign in campaigns:
|
||||||
reward_progress = len(await campaign.get_rewards())
|
reward_progress = len(await campaign.get_rewards())
|
||||||
@@ -73,18 +117,20 @@ class CampaignComponent(cmds.Component):
|
|||||||
):
|
):
|
||||||
# Add a reward to the database with the correct info.
|
# Add a reward to the database with the correct info.
|
||||||
await campaign.add_reward(
|
await campaign.add_reward(
|
||||||
profileid=event_row["profileid"],
|
profileid=pid,
|
||||||
earned_from=f"(SUB) User subscribed for {duration} months at tier {tier}",
|
earned_from=f"(REDEEM) User redeemed {payload.reward.title}",
|
||||||
event_id=event_row["event_id"],
|
event_id=None,
|
||||||
twitch_user_id=data["chatter_user_id"],
|
twitch_user_id=payload.user.id,
|
||||||
twitch_user_name=data["chatter_user_name"],
|
twitch_user_name=payload.user.name,
|
||||||
|
reference=f"Redeem text: {payload.user_input}",
|
||||||
)
|
)
|
||||||
await self.dispatch_update(campaign)
|
await self.dispatch_update(campaign)
|
||||||
# TODO: Webhook logging maybe.. (do this in campaign)
|
|
||||||
# Or just general logging. (also do this in campaign, but we can do again here)
|
|
||||||
|
|
||||||
@cmds.Component.listener()
|
# @cmds.Component.listener()
|
||||||
async def event_message(self, payload):
|
async def event_message(self, payload):
|
||||||
|
if not payload.text.startswith("%reward%"):
|
||||||
|
return
|
||||||
|
|
||||||
community = await self.bot.profiles.fetch_community(payload.broadcaster)
|
community = await self.bot.profiles.fetch_community(payload.broadcaster)
|
||||||
cid = community.communityid
|
cid = community.communityid
|
||||||
profile = await self.bot.profiles.fetch_profile(payload.chatter)
|
profile = await self.bot.profiles.fetch_profile(payload.chatter)
|
||||||
@@ -109,7 +155,7 @@ class CampaignComponent(cmds.Component):
|
|||||||
await self.dispatch_update(campaign)
|
await self.dispatch_update(campaign)
|
||||||
|
|
||||||
@cmds.Component.listener()
|
@cmds.Component.listener()
|
||||||
async def event_safe_event_chat_notice_resub(self, payload):
|
async def event_safe_chat_notice_resub(self, payload):
|
||||||
# Check that the end of the sub is past the threshold by adding duration to the previous sub
|
# Check that the end of the sub is past the threshold by adding duration to the previous sub
|
||||||
# Threshold being now + 3 months, by calendar date.
|
# Threshold being now + 3 months, by calendar date.
|
||||||
# Or even if the sub ends on at least the month that is past the three month region.
|
# Or even if the sub ends on at least the month that is past the three month region.
|
||||||
@@ -118,7 +164,72 @@ class CampaignComponent(cmds.Component):
|
|||||||
# # TODO: This logic should be done for completeness, but will postpone for now
|
# # TODO: This logic should be done for completeness, but will postpone for now
|
||||||
# This could be done in subscription_message as well, but the
|
# This could be done in subscription_message as well, but the
|
||||||
# notice has slightly more self-contained metadata.
|
# notice has slightly more self-contained metadata.
|
||||||
...
|
event_row, detail_row, data = payload
|
||||||
|
|
||||||
|
# Check the tier and duration of the subscription.
|
||||||
|
# Continue if tier = 3000 and duration is at least 3 months
|
||||||
|
if detail_row["tier"] == 3000 and detail_row["duration_months"] >= 3:
|
||||||
|
# Check if there is an ongoing campaign
|
||||||
|
campaigns = await self.campaigns.fetch_campaigns(event_row["communityid"])
|
||||||
|
pid = event_row["profileid"]
|
||||||
|
|
||||||
|
# Get user's last sub date
|
||||||
|
last_sub_date = await self.get_sub_start(
|
||||||
|
channelid=event_row["channel_id"],
|
||||||
|
userid=event_row["user_id"],
|
||||||
|
tier=3000,
|
||||||
|
)
|
||||||
|
if last_sub_date is None:
|
||||||
|
logger.error(f"T3 rsub with no history: {event_row!r}")
|
||||||
|
return
|
||||||
|
forecast_end = last_sub_date + relativedelta(
|
||||||
|
months=detail_row["duration_months"]
|
||||||
|
)
|
||||||
|
if forecast_end < dt.datetime(2026, 11, 1, tzinfo=dt.UTC):
|
||||||
|
logger.warning(f"T3 sub with forecast end too short: {event_row!r}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for campaign in campaigns:
|
||||||
|
rewards = await campaign.get_rewards()
|
||||||
|
reward_progress = len(rewards)
|
||||||
|
|
||||||
|
existing = any(reward.profileid == pid for reward in rewards)
|
||||||
|
if existing:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (
|
||||||
|
campaign.row.target_rewards is None
|
||||||
|
or reward_progress < campaign.row.target_rewards
|
||||||
|
):
|
||||||
|
# Add a reward to the database with the correct info.
|
||||||
|
await campaign.add_reward(
|
||||||
|
profileid=event_row["profileid"],
|
||||||
|
earned_from=f"(RESUB NOTICE) {data.system_message}",
|
||||||
|
event_id=event_row["event_id"],
|
||||||
|
twitch_user_id=data.chatter.id,
|
||||||
|
twitch_user_name=data.chatter.name,
|
||||||
|
)
|
||||||
|
await self.dispatch_update(campaign)
|
||||||
|
else:
|
||||||
|
logger.info(f"Campaigns ignoring sub notice event: {event_row}")
|
||||||
|
|
||||||
|
async def get_sub_start(self, channelid, userid, tier=3000):
|
||||||
|
event_tracker = self.bot.get_component("TrackerComponent")
|
||||||
|
query = event_tracker.data.events.select_where(
|
||||||
|
channel_id=channelid,
|
||||||
|
user_id=userid,
|
||||||
|
tier=tier,
|
||||||
|
)
|
||||||
|
query.join("subscribe_events", using=("event_id",), join_type=JOINTYPE.INNER)
|
||||||
|
query.order_by("created_at", direction=ORDER.DESC)
|
||||||
|
query.select("created_at")
|
||||||
|
query.limit(1)
|
||||||
|
query.with_no_adapter()
|
||||||
|
rows = await query
|
||||||
|
if rows:
|
||||||
|
return rows[0]["created_at"]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@cmds.Component.listener()
|
@cmds.Component.listener()
|
||||||
async def event_safe_subscription(self, payload): ...
|
async def event_safe_subscription(self, payload): ...
|
||||||
@@ -127,8 +238,7 @@ class CampaignComponent(cmds.Component):
|
|||||||
async def event_safe_subscription_message(self, payload): ...
|
async def event_safe_subscription_message(self, payload): ...
|
||||||
|
|
||||||
# ------ Commands -----
|
# ------ Commands -----
|
||||||
@cmds.group(name="campaign", invoke_fallback=True)
|
@cmds.group(name="campaign", aliases=["ppp"], invoke_fallback=True)
|
||||||
@cmds.is_moderator()
|
|
||||||
async def group_campaign(self, ctx: cmds.Context, name: Optional[str] = None):
|
async def group_campaign(self, ctx: cmds.Context, name: Optional[str] = None):
|
||||||
"""Status of the current or named campaign."""
|
"""Status of the current or named campaign."""
|
||||||
community = await self.bot.profiles.fetch_community(ctx.broadcaster)
|
community = await self.bot.profiles.fetch_community(ctx.broadcaster)
|
||||||
@@ -161,16 +271,24 @@ class CampaignComponent(cmds.Component):
|
|||||||
rewards_earned = len(all_rewards)
|
rewards_earned = len(all_rewards)
|
||||||
reward_cap = campaign.row.target_rewards
|
reward_cap = campaign.row.target_rewards
|
||||||
|
|
||||||
if reward_cap is not None:
|
response = (
|
||||||
response = "{name}: {given} rewards earned out of {cap}!"
|
"event details can be found here: {event_link} "
|
||||||
else:
|
"there are {remaining} sketch slots remaining! ♡♡ "
|
||||||
response = "{name}: {given} rewards earned so far!"
|
)
|
||||||
|
|
||||||
|
# if reward_cap is not None:
|
||||||
|
# response = "{name}: {given} rewards earned out of {cap}!"
|
||||||
|
# else:
|
||||||
|
# response = "{name}: {given} rewards earned so far!"
|
||||||
formatted = response.format(
|
formatted = response.format(
|
||||||
name=campaign.row.campaign_name,
|
name=campaign.row.campaign_name,
|
||||||
|
event_link="https://lilac.thewisewolf.dev/provides/partner_plus_poster2.jpg",
|
||||||
|
remaining=reward_cap - rewards_earned,
|
||||||
given=rewards_earned,
|
given=rewards_earned,
|
||||||
cap=reward_cap,
|
cap=reward_cap,
|
||||||
)
|
)
|
||||||
await ctx.reply(formatted)
|
await ctx.reply(formatted)
|
||||||
|
await self.dispatch_update(campaign)
|
||||||
|
|
||||||
@group_campaign.command(name="setup", aliases=["start"])
|
@group_campaign.command(name="setup", aliases=["start"])
|
||||||
@cmds.is_moderator()
|
@cmds.is_moderator()
|
||||||
@@ -258,9 +376,9 @@ class CampaignComponent(cmds.Component):
|
|||||||
reward_cap = campaign.row.target_rewards
|
reward_cap = campaign.row.target_rewards
|
||||||
|
|
||||||
if reward_cap is not None:
|
if reward_cap is not None:
|
||||||
response = f"Completed {name}: {rewards_earned} rewards earned out of {reward_cap}!"
|
response = "Completed {name}: {given} rewards earned out of {cap}!"
|
||||||
else:
|
else:
|
||||||
response = f"Completed {name}: {rewards_earned} rewards earned so far!"
|
response = "Completed {name}: {given} rewards earned so far!"
|
||||||
formatted = response.format(
|
formatted = response.format(
|
||||||
name=campaign.row.campaign_name,
|
name=campaign.row.campaign_name,
|
||||||
given=rewards_earned,
|
given=rewards_earned,
|
||||||
@@ -274,7 +392,8 @@ class CampaignComponent(cmds.Component):
|
|||||||
async def cmd_campaign_reward(
|
async def cmd_campaign_reward(
|
||||||
self,
|
self,
|
||||||
ctx: cmds.Context,
|
ctx: cmds.Context,
|
||||||
user: twitchio.PartialUser,
|
user: twitchio.User,
|
||||||
|
*,
|
||||||
reward_reason: str,
|
reward_reason: str,
|
||||||
):
|
):
|
||||||
"""Manually reward a target user, adjusting their points."""
|
"""Manually reward a target user, adjusting their points."""
|
||||||
@@ -307,3 +426,51 @@ class CampaignComponent(cmds.Component):
|
|||||||
f"Successfully added campaign reward to {user.mention}'s account."
|
f"Successfully added campaign reward to {user.mention}'s account."
|
||||||
)
|
)
|
||||||
await self.dispatch_update(campaign)
|
await self.dispatch_update(campaign)
|
||||||
|
|
||||||
|
@group_campaign.command(name="test")
|
||||||
|
@cmds.is_moderator()
|
||||||
|
async def cmd_campaign_test(
|
||||||
|
self,
|
||||||
|
ctx: cmds.Context,
|
||||||
|
user: twitchio.User,
|
||||||
|
channel: twitchio.User,
|
||||||
|
tier: int = 3000,
|
||||||
|
):
|
||||||
|
community = await self.bot.profiles.fetch_community(ctx.broadcaster)
|
||||||
|
cid = community.communityid
|
||||||
|
profile = await self.bot.profiles.fetch_profile(user)
|
||||||
|
name = user.display_name or profile.nickname or "Unknown"
|
||||||
|
pid = profile.profileid
|
||||||
|
campaigns = await self.campaigns.fetch_campaigns(cid)
|
||||||
|
campaign = campaigns[0]
|
||||||
|
|
||||||
|
# Get user's last sub date
|
||||||
|
last_sub_date = await self.get_sub_start(
|
||||||
|
channelid=channel.id,
|
||||||
|
userid=user.id,
|
||||||
|
tier=tier,
|
||||||
|
)
|
||||||
|
lines = []
|
||||||
|
if last_sub_date is None:
|
||||||
|
subline = f"Last {tier} sub not found"
|
||||||
|
lines.append(subline)
|
||||||
|
else:
|
||||||
|
subline = f"Last {tier} sub at {last_sub_date}"
|
||||||
|
lines.append(subline)
|
||||||
|
forecast_end = last_sub_date + relativedelta(months=3)
|
||||||
|
lines.append(f"Three months after: {forecast_end}")
|
||||||
|
|
||||||
|
if forecast_end > dt.datetime(2026, 11, 1, tzinfo=dt.UTC):
|
||||||
|
lines.append("Which is after the end of the event")
|
||||||
|
else:
|
||||||
|
lines.append("Which is not after the end of the event")
|
||||||
|
|
||||||
|
rewards = await campaign.get_rewards()
|
||||||
|
|
||||||
|
existing = any(reward.profileid == pid for reward in rewards)
|
||||||
|
if existing:
|
||||||
|
lines.append(f"This user has already been rewarded.")
|
||||||
|
else:
|
||||||
|
lines.append(f"This user might yet earn a reward")
|
||||||
|
|
||||||
|
await ctx.reply("; ".join(lines))
|
||||||
|
|||||||
Reference in New Issue
Block a user