Add entire goal list to payload. Add contributions leaderboard to payload. Add raw sub point total to payload. Add raw bits total to payload. Add subpoint and bits total computations to ActiveSubathon.
244 lines
7.4 KiB
Python
244 lines
7.4 KiB
Python
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:
|
|
return await SubathonContribution.create(
|
|
subathon_id=self.subathondata.subathon_id,
|
|
profileid=profileid,
|
|
score=score,
|
|
event_id=event_id,
|
|
)
|
|
|
|
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(
|
|
subathoid=self.subathondata.subathon_id
|
|
)
|
|
.join(
|
|
"subscribe_events",
|
|
using=("eventid",),
|
|
join_type=JOINTYPE.INNER,
|
|
)
|
|
.select(total=f"SUM({pointcol})")
|
|
.with_no_adapter()
|
|
)
|
|
|
|
total_points += result["total"]
|
|
|
|
result = await (
|
|
SubathonContribution.table.select_where(
|
|
subathoid=self.subathondata.subathon_id
|
|
)
|
|
.join(
|
|
"subscribe_message_events",
|
|
using=("eventid",),
|
|
join_type=JOINTYPE.INNER,
|
|
)
|
|
.select(total=f"SUM({pointcol})")
|
|
.with_no_adapter()
|
|
)
|
|
|
|
total_points += result["total"]
|
|
|
|
result = await (
|
|
SubathonContribution.table.select_where(
|
|
subathoid=self.subathondata.subathon_id
|
|
)
|
|
.join(
|
|
"subscribe_gift_events",
|
|
using=("eventid",),
|
|
join_type=JOINTYPE.INNER,
|
|
)
|
|
.select(total=f"SUM(gifted_count * ({pointcol}))")
|
|
.with_no_adapter()
|
|
)
|
|
|
|
total_points += result["total"]
|
|
|
|
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(
|
|
subathoid=self.subathondata.subathon_id
|
|
)
|
|
.join(
|
|
"bits_events",
|
|
using=("eventid",),
|
|
join_type=JOINTYPE.INNER,
|
|
)
|
|
.select(total="SUM(bits)")
|
|
.with_no_adapter()
|
|
)
|
|
return result["total"]
|
|
|
|
|
|
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
|