Rough Campaign module skeleton.

This commit is contained in:
2026-07-22 18:42:08 +03:00
parent b8ffacd916
commit 67a04ef82f
10 changed files with 237 additions and 50 deletions
-11
View File
@@ -1,11 +0,0 @@
BEGIN;
-- Version dependency checks
DO $$
ASSERT current_module_version('PROFILES') = 1, 'Dependency version mismatch: PROFILES';
$$ LANGUAGE plpgsql;
-- Plugin version history
INSERT INTO version_history (component, from_version, to_version, author) VALUES ('AWESOME_PLUGIN', 0, 1, 'Initial Creation');
COMMIT;
+41
View File
@@ -0,0 +1,41 @@
BEGIN;
-- Version dependency checks
DO $$
ASSERT current_module_version('PROFILES') = 1, 'Dependency version mismatch: PROFILES';
ASSERT current_module_version('EVENT_TRACKER') = 2, 'Dependency version mismatch: EVENT_TRACKER';
$$ LANGUAGE plpgsql;
-- Plugin version history
INSERT INTO version_history (component, from_version, to_version, author) VALUES ('PLUSCAMPAIGN', 0, 1, 'Initial Creation');
CREATE TABLE campaigns(
campaign_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
communityid INTEGER NOT NULL REFERENCES communities(communityid) ON DELETE CASCADE ON UPDATE CASCADE,
target_rewards INTEGER NOT NULL,
campaign_name TEXT NOT NULL,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE campaign_rewards_earned(
earned_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
campaign_id INTEGER NOT NULL REFERENCES campaigns ON DELETE CASCADE ON UPDATE CASCADE,
profileid INTEGER NOT NULL REFERENCES user_profiles(profileid) ON DELETE CASCADE ON UPDATE CASCADE,
event_id INTEGER REFERENCES events ON DELETE SET NULL ON UPDATE CASCADE,
twitch_user_id TEXT,
twitch_user_name TEXT,
fulfilled_at TIMESTAMPTZ,
fulfilled_note TEXT,
earned_at TIMESTAMPTZ NOT NULL,
modnote TEXT,
earned_from TEXT NOT NULL,
_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- TODO: Possibly reference table for form destination?
COMMIT;
-15
View File
@@ -1,15 +0,0 @@
from typing import Optional
from .data import (
AwesomeData,
AwesomeTable,
)
class AwesomeRegistry:
VERSION = AwesomeData.VERSION
def __init__(self, data: AwesomeData):
self.data = data
async def init(self):
await self.data.init()
+16
View File
@@ -0,0 +1,16 @@
from typing import Optional
from .data import (
CampaignData,
Campaign,
EarnedReward,
)
class CampaignRegistry:
VERSION = CampaignData.VERSION
def __init__(self, data: CampaignData):
self.data = data
async def init(self):
await self.data.init()
+124
View File
@@ -0,0 +1,124 @@
from typing import Optional, TypeAlias, TypedDict
import json
from collections import defaultdict
from datetime import datetime, timedelta
from dataclasses import dataclass
from data.queries import JOINTYPE, ORDER
from meta.sockets import Channel
from utils.lib import utc_now
from modules.profiles.profiles.profiles import ProfilesRegistry
from . import logger
from .data import (
Campaign,
CampaignData,
EarnedReward,
)
from .campaign import CampaignRegistry
# ISO formatted timestamp
ISOTimestamp: TypeAlias = str
async def prepare_campaign(
profiler: ProfilesRegistry, campaign: Campaign
):
return {}
class CampaignChannel(Channel):
name = "PlusCampaign"
def __init__(
self, profiler: ProfilesRegistry, campaigns: CampaignRegistry, **kwargs
):
super().__init__(**kwargs)
self.profiler: ProfilesRegistry = profiler
self.campaigns: CampaignRegistry = campaigns
# Map of communities to webhooks listening for this community
self.communities = defaultdict(
set
) # Map of communityid -> listening websockets
async def on_connection(self, websocket, event):
if not (cidstr := event.get("community")):
logger.warning("Campaign connection missing communityid")
await super().on_connection(websocket, event)
await self.send_sample(websocket=websocket)
return
elif not cidstr.isdigit():
raise ValueError("Community id provided is not an integer")
cid = int(cidstr)
community = await self.profiler.get_community(cid)
if community is None:
raise ValueError("Unknown community provided.")
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)
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)
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
):
for ws in (websocket,) if websocket else self.communities[communityid]:
await self.send_event(
{
"type": "DO",
"method": "setTimer",
"args": payload,
},
websocket=ws,
)
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(
{
"type": "DO",
"method": "endTimer",
"args": payload,
},
websocket=ws,
)
async def send_no_campaign(self, communityid: int, websocket=None):
for ws in (websocket,) if websocket else self.communities[communityid]:
await self.send_event(
{
"type": "DO",
"method": "noTimer",
"args": {},
},
websocket=ws,
)
async def send_event(self, event, **kwargs):
logger.info(f"Sending websocket event: {json.dumps(event, indent=1)}")
await super().send_event(event, **kwargs)
+40 -8
View File
@@ -2,16 +2,48 @@ from data import Registry, RowModel, Table
from data.columns import String, Timestamp, Integer, Bool
class AwesomeTable(RowModel):
_tablename_ = "awesome_table"
_cache_ = {}
class Campaign(RowModel):
_tablename_ = "campaigns"
# _cache_ = {}
campaign_id = Integer(primary=True)
communityid = Integer()
target_rewards = Integer()
campaign_name = String()
started_at = Timestamp()
completed_at = Timestamp()
created_at = Timestamp()
_timestamp = Timestamp()
class EarnedReward(RowModel):
_tablename_ = "campaign_rewards_earned"
# _cache_ = {}
earned_id = Integer(primary=True)
campaign_id = Integer()
profileid = Integer()
event_id = Integer()
twitch_user_id = String()
twitch_user_name = String()
fulfilled_at = Timestamp()
fulfilled_note = String()
earned_at = Timestamp()
earned_from = String()
modnote = String()
userid = String(primary=True)
_timestamp = Timestamp()
class AwesomeData(Registry):
VERSION = ("AWESOME", 1)
AwesomeTable = AwesomeTable
awesome_table = AwesomeTable.table
class CampaignData(Registry):
VERSION = ("CAMPAIGN", 1)
Campaign = Campaign
campaigns = Campaign.table
EarnedReward = EarnedReward
campaign_rewards_earned = EarnedReward.table
+2 -2
View File
@@ -2,6 +2,6 @@ from .. import logger
async def setup(bot):
from .cog import AwesomeCog
from .cog import CampaignCog
await bot.add_cog(AwesomeCog(bot))
await bot.add_cog(CampaignCog(bot))
+6 -6
View File
@@ -9,18 +9,18 @@ from meta import LionBot, LionCog, LionContext
from meta.logger import log_wrap
from utils.lib import utc_now
from ..data import AwesomeData
from ..awesome import AwesomeRegistry
from ..data import CampaignData
from ..campaigns import CampaignRegistry
class AwesomeCog(LionCog):
class CampaignCog(LionCog):
def __init__(self, bot: LionBot):
self.bot = bot
self.data = bot.db.load_registry(AwesomeData())
self.profiles = AwesomeRegistry(self.data)
self.data = bot.db.load_registry(CampaignData())
self.campaigns = CampaignRegistry(self.data)
async def cog_load(self):
await self.data.init()
await self.bot.version_check(*self.data.VERSION)
await self.profiles.init()
await self.campaigns.init()
+2 -2
View File
@@ -6,6 +6,6 @@ if TYPE_CHECKING:
async def setup(bot: "Bot"):
from .component import AwesomeComponent
from .component import CampaignComponent
await bot.add_component(AwesomeComponent(bot))
await bot.add_component(CampaignComponent(bot))
+6 -6
View File
@@ -10,22 +10,22 @@ from utils.lib import utc_now
from . import logger
from ..data import AwesomeData
from ..awesome import AwesomeRegistry
from ..data import CampaignData
from ..campaign import CampaignRegistry
class AwesomeComponent(cmds.Component):
class CampaignComponent(cmds.Component):
def __init__(self, bot: Bot):
self.bot = bot
self.data = bot.dbconn.load_registry(AwesomeData())
self.awesome = AwesomeRegistry(self.data)
self.data = bot.dbconn.load_registry(CampaignData())
self.campaigns = CampaignRegistry(self.data)
# ----- API -----
async def component_load(self):
await self.data.init()
await self.bot.version_check(*self.data.VERSION)
await self.profiles.init()
await self.campaigns.init()
async def component_teardown(self):
pass