From 84de4a77647f2ff10b86cf2c1c0ca26a07e3ead7 Mon Sep 17 00:00:00 2001 From: Interitio Date: Sat, 25 Jul 2026 10:50:03 +0300 Subject: [PATCH] Add rudimentary websocket channel. --- plugin/channel.py | 68 +++++++++++++++++++++++++++----------- plugin/twitch/component.py | 44 +++++++++++++++++------- 2 files changed, 80 insertions(+), 32 deletions(-) diff --git a/plugin/channel.py b/plugin/channel.py index 171e555..e08460c 100644 --- a/plugin/channel.py +++ b/plugin/channel.py @@ -16,17 +16,41 @@ from .data import ( CampaignData, EarnedReward, ) -from .campaign import CampaignRegistry +from .campaign import CampaignRegistry, RewardCampaign # ISO formatted timestamp ISOTimestamp: TypeAlias = str -async def prepare_campaign( - profiler: ProfilesRegistry, campaign: Campaign -): - return {} +class CampaignPayload(TypedDict): + communityid: int + campaign_id: int + campaign_name: str + + started_at: ISOTimestamp | None + completed_at: ISOTimestamp | None + + reward_progress: int + reward_cap: int | None + + +async def prepare_campaign(campaign: RewardCampaign) -> CampaignPayload: + reward_progress = len(await campaign.get_rewards()) + + return CampaignPayload( + communityid=campaign.row.communityid, + campaign_id=campaign.row.campaign_id, + campaign_name=campaign.row.campaign_name, + started_at=campaign.row.started_at.isoformat() + if campaign.row.started_at + else None, + completed_at=campaign.row.completed_at.isoformat() + if campaign.row.completed_at + else None, + reward_progress=reward_progress, + reward_cap=campaign.row.target_rewards, + ) class CampaignChannel(Channel): @@ -61,30 +85,36 @@ class CampaignChannel(Channel): await super().on_connection(websocket, event) self.communities[cid].add(websocket) - # TODO: Prepare campaign for sending - if campaign: - payload = await prepare_campaign(self.profiler, campaign) + # Fetch active campaign for this cid and send if it exists + active = await self.campaigns.fetch_campaigns(cid) + if active: + # TODO: Presumes only one active campaign + campaign = active[0] + payload = await prepare_campaign(campaign) await self.send_campaign_update(cid, payload, websocket) else: await self.send_no_campaign(cid, websocket) async def send_sample(self, websocket): - import json import random - with open("sample-payload.json") as f: - payload = json.load(f) - ending = utc_now() + timedelta(seconds=10) - payload['args']['end_at'] = ending.isoformat() - await self.send_event(payload, websocket=websocket) + + payload = CampaignPayload( + communityid=1, + campaign_id=1, + campaign_name="PartnerPlus2026", + started_at=utc_now().isoformat(), + completed_at=None, + reward_progress=random.randint(0, 105), + reward_cap=150, + ) + await self.send_event(payload, websocket=websocket) async def del_connection(self, websocket): for wss in self.communities.values(): wss.discard(websocket) await super().del_connection(websocket) - async def send_campaign_update( - self, communityid: int, payload, websocket=None - ): + async def send_campaign_update(self, communityid: int, payload, websocket=None): for ws in (websocket,) if websocket else self.communities[communityid]: await self.send_event( { @@ -95,9 +125,7 @@ class CampaignChannel(Channel): websocket=ws, ) - async def send_campaign_ended( - self, communityid: int, payload, websocket=None - ): + async def send_campaign_ended(self, communityid: int, payload, websocket=None): for ws in (websocket,) if websocket else self.communities[communityid]: await self.send_event( { diff --git a/plugin/twitch/component.py b/plugin/twitch/component.py index 9f2a50c..b7ae14e 100644 --- a/plugin/twitch/component.py +++ b/plugin/twitch/component.py @@ -11,7 +11,8 @@ from utils.lib import utc_now from . import logger from ..data import CampaignData -from ..campaign import CampaignRegistry +from ..campaign import CampaignRegistry, RewardCampaign +from ..channel import CampaignPayload, prepare_campaign, CampaignChannel class CampaignComponent(cmds.Component): @@ -20,6 +21,9 @@ class CampaignComponent(cmds.Component): self.data = bot.dbconn.load_registry(CampaignData()) self.campaigns = CampaignRegistry(self.data) + self.channel = CampaignChannel(self.bot.profiles.profiles, self.campaigns) + + register_channel("Campaign", self.channel) # ----- API ----- async def component_load(self): @@ -30,6 +34,11 @@ class CampaignComponent(cmds.Component): async def component_teardown(self): pass + async def dispatch_update(self, campaign: RewardCampaign): + cid = campaign.row.communityid + payload = await prepare_campaign(campaign) + await self.channel.send_campaign_update(cid, payload) + # ------ Event Handlers ----- @cmds.Component.listener() async def event_safe_event_chat_notice_sub(self, payload): @@ -55,16 +64,23 @@ class CampaignComponent(cmds.Component): campaigns = await self.campaigns.fetch_campaigns(event_row["communityid"]) for campaign in campaigns: - # Add a reward to the database with the correct info. - await campaign.add_reward( - profileid=event_row["profileid"], - earned_reason=f"(SUB NOTICE): User subscribed for {duration} months at tier {tier}", - event_id=event_row["event_id"], - twitch_user_id=data["chatter_user_id"], - twitch_user_name=data["chatter_user_name"], - ) - # TODO: Webhook logging maybe.. - # Or just general logging. + reward_progress = len(await campaign.get_rewards()) + + 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_reason=f"(SUB NOTICE): User subscribed for {duration} months at tier {tier}", + event_id=event_row["event_id"], + twitch_user_id=data["chatter_user_id"], + twitch_user_name=data["chatter_user_name"], + ) + await self.dispatch_update(campaign) + # TODO: Webhook logging maybe.. + # Or just general logging. @cmds.Component.listener() async def event_safe_event_chat_notice_resub(self, payload): @@ -178,6 +194,7 @@ class CampaignComponent(cmds.Component): f"Success! Your campaign '{name}' has been created and started. " "Best of luck!" ) + await self.dispatch_update(campaign) @group_campaign.command(name="finish", aliases=["stop", "complete", "end"]) @cmds.is_moderator() @@ -221,7 +238,9 @@ class CampaignComponent(cmds.Component): given=rewards_earned, cap=reward_cap, ) - await ctx.reply(formatted) + await ctx.reply(formatte + await self.dispatch_update(campaign) +) @group_campaign.command(name="reward") @cmds.is_moderator() @@ -258,3 +277,4 @@ class CampaignComponent(cmds.Component): await ctx.reply( f"Successfully added campaign reward to {user.mention}'s account." ) + await self.dispatch_update(campaign)