Compare commits
25 Commits
18f57d1800
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 11c77bcc89 | |||
| 89f9ce3ffa | |||
| 21cf65398c | |||
| 84791e6b91 | |||
| 7a9a47f11f | |||
| 0c5250f09e | |||
| aa87fd6f9f | |||
| 32b84625df | |||
| a19bb8a8cb | |||
| 7853d0c115 | |||
| c495e2387e | |||
| 7a9045d3dc | |||
| 891e121e99 | |||
| 0c4b2bfe32 | |||
| a82b58b2c4 | |||
| 21b1f9c3ab | |||
| 033fc5c1ae | |||
| 899f5e7292 | |||
| a5dd5ce6ad | |||
| 27103366aa | |||
| b10098d28f | |||
| a3a3b36230 | |||
| 0934ee3af4 | |||
| 2c8bc4ae24 | |||
| 37ecb2d7ee |
@@ -6,15 +6,17 @@ VALUES ('SUBATHON', 0, 1, 'Initial Creation');
|
|||||||
|
|
||||||
CREATE TABLE subathons(
|
CREATE TABLE subathons(
|
||||||
subathon_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
subathon_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
communityid INTEGER NOT NULL REFERENCES communities(communityid),
|
communityid INTEGER NOT NULL REFERENCES communities(communityid) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
initial_time INTEGER NOT NULL,
|
initial_time INTEGER NOT NULL,
|
||||||
|
name TEXT,
|
||||||
sub1_score NUMERIC NOT NULL DEFAULT 1,
|
sub1_score NUMERIC NOT NULL DEFAULT 1,
|
||||||
sub2_score NUMERIC NOT NULL DEFAULT 2,
|
sub2_score NUMERIC NOT NULL DEFAULT 2,
|
||||||
sub3_score NUMERIC NOT NULL DEFAULT 6,
|
sub3_score NUMERIC NOT NULL DEFAULT 6,
|
||||||
bit_score NUMERIC NOT NULL,
|
bit_score NUMERIC NOT NULL,
|
||||||
score_time NUMERIC NOT NULL,
|
score_time NUMERIC NOT NULL,
|
||||||
duration INTEGER NOT NULL DEFAULT 0,
|
duration INTEGER NOT NULL DEFAULT 0,
|
||||||
|
timecap INTEGER,
|
||||||
ended_at TIMESTAMPTZ
|
ended_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -25,8 +27,8 @@ CREATE TABLE running_subathons(
|
|||||||
|
|
||||||
CREATE TABLE subathon_contributions(
|
CREATE TABLE subathon_contributions(
|
||||||
contribution_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
contribution_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
subathon_id INTEGER NOT NULL REFERENCES subathons(subathon_id),
|
subathon_id INTEGER NOT NULL REFERENCES subathons(subathon_id) ON DELETE CASCADE,
|
||||||
profileid INTEGER REFERENCES user_profiles(profileid),
|
profileid INTEGER REFERENCES user_profiles(profileid) ON DELETE SET NULL,
|
||||||
score NUMERIC NOT NULL,
|
score NUMERIC NOT NULL,
|
||||||
event_id INTEGER REFERENCES events(event_id),
|
event_id INTEGER REFERENCES events(event_id),
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
@@ -34,7 +36,7 @@ CREATE TABLE subathon_contributions(
|
|||||||
|
|
||||||
CREATE TABLE subathon_goals(
|
CREATE TABLE subathon_goals(
|
||||||
goal_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
goal_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
subathon_id INTEGER NOT NULL REFERENCES subathons(subathon_id),
|
subathon_id INTEGER NOT NULL REFERENCES subathons(subathon_id) ON DELETE CASCADE,
|
||||||
required_score NUMERIC NOT NULL,
|
required_score NUMERIC NOT NULL,
|
||||||
description TEXT NOT NULL,
|
description TEXT NOT NULL,
|
||||||
notified BOOLEAN DEFAULT false,
|
notified BOOLEAN DEFAULT false,
|
||||||
|
|||||||
3
example-config/subathon.conf
Normal file
3
example-config/subathon.conf
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[SUBATHON]
|
||||||
|
timer_url = https://izashi.thewisewolf.dev/tracker/timer
|
||||||
|
|
||||||
261
subathon/channel.py
Normal file
261
subathon/channel.py
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
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 (
|
||||||
|
SubathonData,
|
||||||
|
Subathon,
|
||||||
|
RunningSubathon,
|
||||||
|
SubathonContribution,
|
||||||
|
SubathonGoal,
|
||||||
|
)
|
||||||
|
from .subathon import ActiveSubathon, SubathonRegistry
|
||||||
|
|
||||||
|
|
||||||
|
# ISO formatted timestamp
|
||||||
|
ISOTimestamp: TypeAlias = str
|
||||||
|
|
||||||
|
|
||||||
|
class GoalPayload(TypedDict):
|
||||||
|
required: float
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class ContributionPayload(TypedDict):
|
||||||
|
user_name: str
|
||||||
|
user_id: str | None
|
||||||
|
|
||||||
|
amount: float
|
||||||
|
seconds_added: int
|
||||||
|
timestamp: ISOTimestamp
|
||||||
|
|
||||||
|
|
||||||
|
class LeaderboardItemPayload(TypedDict):
|
||||||
|
user_name: str
|
||||||
|
user_id: str | None
|
||||||
|
amount: float
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreTablePayload(TypedDict):
|
||||||
|
bit_score: float # points per bit
|
||||||
|
t1_score: float # points per T1 sub
|
||||||
|
t2_score: float # points per T2 sub
|
||||||
|
t3_score: float # points per T3 sub
|
||||||
|
|
||||||
|
score_time: float # seconds added per point
|
||||||
|
|
||||||
|
|
||||||
|
class SubathonPayload(TypedDict):
|
||||||
|
end_at: ISOTimestamp
|
||||||
|
is_running: bool
|
||||||
|
score_table: ScoreTablePayload
|
||||||
|
name: str
|
||||||
|
|
||||||
|
total_contribution: float
|
||||||
|
total_subpoints: int
|
||||||
|
total_bits: int
|
||||||
|
|
||||||
|
goals_met: int
|
||||||
|
goals_total: int
|
||||||
|
last_goal: GoalPayload | None
|
||||||
|
next_goal: GoalPayload | None
|
||||||
|
goals: list[GoalPayload]
|
||||||
|
|
||||||
|
last_contribution: ContributionPayload | None
|
||||||
|
leaderboard: list[LeaderboardItemPayload]
|
||||||
|
|
||||||
|
|
||||||
|
async def prepare_subathon(
|
||||||
|
profiler: ProfilesRegistry, subathon: ActiveSubathon
|
||||||
|
) -> SubathonPayload:
|
||||||
|
total_score = await subathon.get_score()
|
||||||
|
|
||||||
|
# Build the goal data
|
||||||
|
goals = await subathon.get_goals()
|
||||||
|
goals_met = 0
|
||||||
|
last_goalp = None
|
||||||
|
next_goalp = None
|
||||||
|
goalps = []
|
||||||
|
total_goals = len(goals)
|
||||||
|
for goal in goals:
|
||||||
|
goalp = GoalPayload(
|
||||||
|
required=float(goal.required_score),
|
||||||
|
name=goal.description,
|
||||||
|
)
|
||||||
|
goalps.append(goalp)
|
||||||
|
if goal.required_score <= total_score:
|
||||||
|
last_goalp = goalp
|
||||||
|
goals_met += 1
|
||||||
|
elif next_goalp is None:
|
||||||
|
next_goalp = goalp
|
||||||
|
|
||||||
|
# Build the last contribution information
|
||||||
|
contribs = await subathon.fetch_contributions().limit(1)
|
||||||
|
last_contrib: ContributionPayload | None = None
|
||||||
|
if contribs:
|
||||||
|
contrib = contribs[0]
|
||||||
|
if contrib.profileid:
|
||||||
|
profile = await profiler.get_profile(contrib.profileid)
|
||||||
|
assert profile is not None
|
||||||
|
name = profile.nickname or "Unknown"
|
||||||
|
user_id = str(profile.profileid)
|
||||||
|
else:
|
||||||
|
name = "Anonymous"
|
||||||
|
user_id = None
|
||||||
|
last_contrib = ContributionPayload(
|
||||||
|
user_name=name,
|
||||||
|
user_id=user_id,
|
||||||
|
amount=float(contrib.score),
|
||||||
|
seconds_added=subathon.get_score_time(contrib.score),
|
||||||
|
timestamp=contrib.created_at.isoformat(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build the score table
|
||||||
|
score_table = ScoreTablePayload(
|
||||||
|
bit_score=float(subathon.subathondata.bit_score),
|
||||||
|
t1_score=float(subathon.subathondata.sub1_score),
|
||||||
|
t2_score=float(subathon.subathondata.sub2_score),
|
||||||
|
t3_score=float(subathon.subathondata.sub3_score),
|
||||||
|
score_time=int(subathon.subathondata.score_time),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build the contribution leaderboard
|
||||||
|
query = SubathonContribution.table.select_where(
|
||||||
|
subathon_id=subathon.subathondata.subathon_id
|
||||||
|
)
|
||||||
|
query.join("user_profiles", using=("profileid",), join_type=JOINTYPE.LEFT)
|
||||||
|
query.select("subathon_id", "profileid", total="SUM(score)")
|
||||||
|
query.order_by("total", direction=ORDER.DESC)
|
||||||
|
query.group_by("subathon_id", "profileid")
|
||||||
|
query.with_no_adapter()
|
||||||
|
results = await query
|
||||||
|
|
||||||
|
leaderboard = []
|
||||||
|
for row in results:
|
||||||
|
if pid := row["profileid"]:
|
||||||
|
profile = await profiler.get_profile(pid)
|
||||||
|
name = (profile.nickname if profile else None) or "Unknown"
|
||||||
|
else:
|
||||||
|
name = "Anonymous"
|
||||||
|
score = row["total"]
|
||||||
|
item = LeaderboardItemPayload(
|
||||||
|
user_name=name,
|
||||||
|
user_id=pid,
|
||||||
|
amount=float(score),
|
||||||
|
)
|
||||||
|
leaderboard.append(item)
|
||||||
|
|
||||||
|
# Build the raw totals
|
||||||
|
subpoints = await subathon.get_total_subs()
|
||||||
|
bits = await subathon.get_total_bits()
|
||||||
|
|
||||||
|
# Finally, put together the payload
|
||||||
|
payload = SubathonPayload(
|
||||||
|
name=subathon.subathondata.name,
|
||||||
|
end_at=(await subathon.get_ending()).isoformat(),
|
||||||
|
is_running=(subathon.running),
|
||||||
|
score_table=score_table,
|
||||||
|
total_contribution=float(await subathon.get_score()),
|
||||||
|
total_subpoints=subpoints,
|
||||||
|
total_bits=bits,
|
||||||
|
goals_met=goals_met,
|
||||||
|
goals_total=total_goals,
|
||||||
|
last_goal=last_goalp,
|
||||||
|
next_goal=next_goalp,
|
||||||
|
goals=goalps,
|
||||||
|
last_contribution=last_contrib,
|
||||||
|
leaderboard=leaderboard,
|
||||||
|
)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
class TimerChannel(Channel):
|
||||||
|
name = "SubTimer"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, profiler: ProfilesRegistry, subathons: SubathonRegistry, **kwargs
|
||||||
|
):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.profiler: ProfilesRegistry = profiler
|
||||||
|
self.subathons: SubathonRegistry = subathons
|
||||||
|
|
||||||
|
# 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")):
|
||||||
|
raise ValueError("Subathon timer connection missing communityid")
|
||||||
|
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)
|
||||||
|
|
||||||
|
subathon = await self.subathons.get_active_subathon(cid)
|
||||||
|
if subathon:
|
||||||
|
payload = await prepare_subathon(self.profiler, subathon)
|
||||||
|
await self.send_subathon_update(cid, payload, websocket)
|
||||||
|
else:
|
||||||
|
await self.send_no_subathon(cid, websocket)
|
||||||
|
|
||||||
|
async def del_connection(self, websocket):
|
||||||
|
for wss in self.communities.values():
|
||||||
|
wss.discard(websocket)
|
||||||
|
await super().del_connection(websocket)
|
||||||
|
|
||||||
|
async def send_subathon_update(
|
||||||
|
self, communityid: int, payload: SubathonPayload, 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_subathon_ended(
|
||||||
|
self, communityid: int, payload: SubathonPayload, 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_subathon(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)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
|||||||
from data import Registry, RowModel, Table
|
from data import Registry, RowModel, Table
|
||||||
from data.columns import String, Timestamp, Integer, Bool
|
from data.columns import String, Timestamp, Integer, Bool
|
||||||
|
|
||||||
# User contributed {} subs and added {}:{} to the timer! Thank you :love:
|
|
||||||
# We have reached goal #5: Karaoke !! Thank you everyone for your support <3
|
|
||||||
|
|
||||||
class Subathon(RowModel):
|
class Subathon(RowModel):
|
||||||
_tablename_ = 'subathons'
|
_tablename_ = 'subathons'
|
||||||
@@ -11,6 +9,7 @@ class Subathon(RowModel):
|
|||||||
subathon_id = Integer(primary=True)
|
subathon_id = Integer(primary=True)
|
||||||
communityid = Integer()
|
communityid = Integer()
|
||||||
started_at = Timestamp()
|
started_at = Timestamp()
|
||||||
|
name = String()
|
||||||
initial_time = Integer() # Initial number of seconds
|
initial_time = Integer() # Initial number of seconds
|
||||||
sub1_score = Integer()
|
sub1_score = Integer()
|
||||||
sub2_score = Integer()
|
sub2_score = Integer()
|
||||||
|
|||||||
245
subathon/subathon.py
Normal file
245
subathon/subathon.py
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
from asyncio import Event
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from data.queries import JOINTYPE
|
||||||
|
from utils.lib import utc_now, strfdelta
|
||||||
|
from modules.profiles.profiles.profiles import ProfilesRegistry
|
||||||
|
|
||||||
|
from . import logger
|
||||||
|
from .data import (
|
||||||
|
SubathonData,
|
||||||
|
Subathon,
|
||||||
|
RunningSubathon,
|
||||||
|
SubathonContribution,
|
||||||
|
SubathonGoal,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveSubathon:
|
||||||
|
def __init__(self, subathondata: Subathon, runningdata: RunningSubathon | None):
|
||||||
|
self.subathondata = subathondata
|
||||||
|
self.runningdata = runningdata
|
||||||
|
|
||||||
|
@property
|
||||||
|
def running(self):
|
||||||
|
return self.runningdata is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.subathondata.name or "Subathon"
|
||||||
|
|
||||||
|
async def check_cap(self):
|
||||||
|
if not (cap := self.subathondata.timecap):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
score = await self.get_score()
|
||||||
|
time_earned = self.get_score_time(score)
|
||||||
|
total_time = self.subathondata.initial_time + time_earned
|
||||||
|
return total_time >= cap
|
||||||
|
|
||||||
|
async def check_finished(self):
|
||||||
|
"""
|
||||||
|
Return True if the subathon duration has exceeded its earned time.
|
||||||
|
"""
|
||||||
|
return await self.get_remaining() <= 0
|
||||||
|
|
||||||
|
async def pause(self):
|
||||||
|
"""
|
||||||
|
Pause the active subathon.
|
||||||
|
If the subathon is in 'overtime', i.e. it has already finished,
|
||||||
|
then the total time is saved as the earned time not including the overtime.
|
||||||
|
"""
|
||||||
|
if not self.running:
|
||||||
|
raise ValueError("This subathon is not running!")
|
||||||
|
assert self.runningdata is not None
|
||||||
|
|
||||||
|
new_duration = await self.get_duration()
|
||||||
|
|
||||||
|
await self.subathondata.update(duration=new_duration)
|
||||||
|
await self.runningdata.delete()
|
||||||
|
self.runningdata = None
|
||||||
|
|
||||||
|
async def resume(self):
|
||||||
|
if self.running:
|
||||||
|
raise ValueError("This subathon is already running!")
|
||||||
|
self.runningdata = await RunningSubathon.create(
|
||||||
|
subathon_id=self.subathondata.subathon_id
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_score(self) -> float:
|
||||||
|
rows = await SubathonContribution.fetch_where(
|
||||||
|
subathon_id=self.subathondata.subathon_id
|
||||||
|
)
|
||||||
|
return sum(row.score for row in rows)
|
||||||
|
|
||||||
|
def get_score_time(self, score: float) -> int:
|
||||||
|
# Get time contributed by this score
|
||||||
|
return int(score * self.subathondata.score_time)
|
||||||
|
|
||||||
|
async def get_duration(self) -> int:
|
||||||
|
"""
|
||||||
|
Number of seconds that this subathon has run for.
|
||||||
|
Includes current running session if it exists.
|
||||||
|
|
||||||
|
If the subathon is already finished (in overtime)
|
||||||
|
returns the earned time instead of the true duration.
|
||||||
|
"""
|
||||||
|
duration = self.subathondata.duration
|
||||||
|
# Add running duration if required
|
||||||
|
if self.runningdata:
|
||||||
|
now = utc_now()
|
||||||
|
added = int((now - self.runningdata.last_started).total_seconds())
|
||||||
|
duration += added
|
||||||
|
earned = await self.get_earned()
|
||||||
|
|
||||||
|
return min(duration, earned)
|
||||||
|
|
||||||
|
async def get_earned(self) -> int:
|
||||||
|
"""
|
||||||
|
Number of seconds earned in the subathon so far.
|
||||||
|
Includes initial time.
|
||||||
|
Takes into account the cap.
|
||||||
|
"""
|
||||||
|
score = await self.get_score()
|
||||||
|
time_earned = self.get_score_time(score)
|
||||||
|
total_time = self.subathondata.initial_time + time_earned
|
||||||
|
if cap := self.subathondata.timecap:
|
||||||
|
total_time = min(total_time, cap)
|
||||||
|
return total_time
|
||||||
|
|
||||||
|
async def get_remaining(self) -> int:
|
||||||
|
"""
|
||||||
|
Number of seconds remaining on the subathon timer.
|
||||||
|
Will be 0 or slightly negative if finished.
|
||||||
|
"""
|
||||||
|
total_time = await self.get_earned()
|
||||||
|
return total_time - await self.get_duration() - 1
|
||||||
|
|
||||||
|
async def get_ending(self):
|
||||||
|
"""
|
||||||
|
Ending time of the subathon.
|
||||||
|
May be in the past if the subathon has already ended.
|
||||||
|
"""
|
||||||
|
now = utc_now()
|
||||||
|
remaining = await self.get_remaining()
|
||||||
|
return now + timedelta(seconds=remaining)
|
||||||
|
|
||||||
|
async def add_contribution(
|
||||||
|
self, profileid: int | None, score: float, event_id: int | None
|
||||||
|
) -> SubathonContribution:
|
||||||
|
row = await SubathonContribution.create(
|
||||||
|
subathon_id=self.subathondata.subathon_id,
|
||||||
|
profileid=profileid,
|
||||||
|
score=score,
|
||||||
|
event_id=event_id,
|
||||||
|
)
|
||||||
|
logger.info(f"Added contribution {row!r} to subathon {self.subathondata!r}")
|
||||||
|
return row
|
||||||
|
|
||||||
|
def fetch_contributions(self, **kwargs):
|
||||||
|
query = SubathonContribution.fetch_where(
|
||||||
|
subathon_id=self.subathondata.subathon_id, **kwargs
|
||||||
|
).order_by("created_at")
|
||||||
|
return query
|
||||||
|
|
||||||
|
async def get_goals(self) -> list[SubathonGoal]:
|
||||||
|
goals = await SubathonGoal.fetch_where(
|
||||||
|
subathon_id=self.subathondata.subathon_id
|
||||||
|
).order_by("required_score")
|
||||||
|
return goals
|
||||||
|
|
||||||
|
async def get_total_subs(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of sub points
|
||||||
|
contributed to this subathon.
|
||||||
|
"""
|
||||||
|
pointcol = (
|
||||||
|
"CASE "
|
||||||
|
"WHEN tier = 1000 THEN 1 "
|
||||||
|
"WHEN tier = 2000 THEN 2 "
|
||||||
|
"WHEN tier = 3000 THEN 6 "
|
||||||
|
"END"
|
||||||
|
)
|
||||||
|
total_points = 0
|
||||||
|
|
||||||
|
result = await (
|
||||||
|
SubathonContribution.table.select_where(
|
||||||
|
subathon_id=self.subathondata.subathon_id
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
"subscribe_events",
|
||||||
|
using=("event_id",),
|
||||||
|
join_type=JOINTYPE.INNER,
|
||||||
|
)
|
||||||
|
.select(total=f"SUM({pointcol})")
|
||||||
|
.with_no_adapter()
|
||||||
|
)
|
||||||
|
|
||||||
|
total_points += result[0]["total"] or 0
|
||||||
|
|
||||||
|
result = await (
|
||||||
|
SubathonContribution.table.select_where(
|
||||||
|
subathon_id=self.subathondata.subathon_id
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
"subscribe_message_events",
|
||||||
|
using=("event_id",),
|
||||||
|
join_type=JOINTYPE.INNER,
|
||||||
|
)
|
||||||
|
.select(total=f"SUM({pointcol})")
|
||||||
|
.with_no_adapter()
|
||||||
|
)
|
||||||
|
|
||||||
|
total_points += result[0]["total"] or 0
|
||||||
|
|
||||||
|
result = await (
|
||||||
|
SubathonContribution.table.select_where(
|
||||||
|
subathon_id=self.subathondata.subathon_id
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
"gift_events",
|
||||||
|
using=("event_id",),
|
||||||
|
join_type=JOINTYPE.INNER,
|
||||||
|
)
|
||||||
|
.select(total=f"SUM(gifted_count * ({pointcol}))")
|
||||||
|
.with_no_adapter()
|
||||||
|
)
|
||||||
|
|
||||||
|
total_points += result[0]["total"] or 0
|
||||||
|
|
||||||
|
return total_points
|
||||||
|
|
||||||
|
async def get_total_bits(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the total number of bits
|
||||||
|
contributed to this subathon.
|
||||||
|
"""
|
||||||
|
result = await (
|
||||||
|
SubathonContribution.table.select_where(
|
||||||
|
subathon_id=self.subathondata.subathon_id
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
"bits_events",
|
||||||
|
using=("event_id",),
|
||||||
|
join_type=JOINTYPE.INNER,
|
||||||
|
)
|
||||||
|
.select(total="SUM(bits)")
|
||||||
|
.with_no_adapter()
|
||||||
|
)
|
||||||
|
return result[0]["total"] or 0
|
||||||
|
|
||||||
|
|
||||||
|
class SubathonRegistry:
|
||||||
|
def __init__(self, data: SubathonData, profiler: ProfilesRegistry):
|
||||||
|
self.data = data
|
||||||
|
self.profiler = profiler
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
await self.data.init()
|
||||||
|
|
||||||
|
async def get_active_subathon(self, communityid: int) -> ActiveSubathon | None:
|
||||||
|
rows = await Subathon.fetch_where(communityid=communityid, ended_at=None)
|
||||||
|
if rows:
|
||||||
|
subathondata = rows[0]
|
||||||
|
running = await RunningSubathon.fetch(subathondata.subathon_id)
|
||||||
|
subba = ActiveSubathon(subathondata, running)
|
||||||
|
return subba
|
||||||
Reference in New Issue
Block a user