generated from HoloTech/holotech-plugin-template
Compare commits
2 Commits
97bdf8e8e3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 08fb0084fa | |||
| 1c5eb6ceeb |
+145
-15
@@ -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
|
||||||
@@ -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,14 +62,18 @@ 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:
|
for campaign in campaigns:
|
||||||
reward_progress = len(await campaign.get_rewards())
|
rewards = await campaign.get_rewards()
|
||||||
|
reward_progress = len(rewards)
|
||||||
|
|
||||||
|
existing = any(reward.profileid == pid for reward in rewards)
|
||||||
|
if existing:
|
||||||
|
continue
|
||||||
|
|
||||||
if (
|
if (
|
||||||
campaign.row.target_rewards is None
|
campaign.row.target_rewards is None
|
||||||
@@ -74,12 +82,14 @@ 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=event_row["profileid"],
|
||||||
earned_from=f"(SUB) User subscribed for {duration} months at tier {tier}",
|
earned_from=f"(SUB NOTICE) {data.system_message}",
|
||||||
event_id=event_row["event_id"],
|
event_id=event_row["event_id"],
|
||||||
twitch_user_id=data["chatter_user_id"],
|
twitch_user_id=data.chatter.id,
|
||||||
twitch_user_name=data["chatter_user_name"],
|
twitch_user_name=data.chatter.name,
|
||||||
)
|
)
|
||||||
await self.dispatch_update(campaign)
|
await self.dispatch_update(campaign)
|
||||||
|
else:
|
||||||
|
logger.info(f"Campaigns ignoring sub notice event: {event_row}")
|
||||||
|
|
||||||
# @cmds.Component.listener()
|
# @cmds.Component.listener()
|
||||||
async def event_custom_redemption_add(self, payload):
|
async def event_custom_redemption_add(self, payload):
|
||||||
@@ -145,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.
|
||||||
@@ -154,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): ...
|
||||||
@@ -163,7 +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)
|
||||||
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)
|
||||||
@@ -196,12 +271,19 @@ 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,
|
||||||
)
|
)
|
||||||
@@ -344,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