This repository has been archived on 2025-02-16. You can view files and clone it, but cannot push or open issues or pull requests.
Spiffo/utils/models.py
Corban-Lee Jones 55304170b1
All checks were successful
Build and Push Docker Image / build (push) Successful in 13s
player sessions and coordinate models
2024-12-11 21:41:21 +00:00

228 lines
7.0 KiB
Python

"""
Database schemas.
"""
import logging
from datetime import datetime, timedelta
import httpx
from tortoise import fields
from tortoise.functions import Sum
from tortoise.expressions import F
from tortoise.models import Model, Q
from discord import Embed
log = logging.getLogger(__name__)
class SteamProfileSummary(Model):
player = fields.ForeignKeyField(
model_name="models.Player",
on_delete=fields.CASCADE
)
steam_id = fields.CharField(max_length=20, unique=True)
profile_name = fields.CharField(max_length=32)
url = fields.CharField(max_length=128)
avatar_url = fields.CharField(max_length=128)
last_update = fields.DatetimeField(auto_now=True)
class Meta:
table = "steam_profile_summary"
class Coordinates(Model):
x = fields.IntField()
y = fields.IntField()
z = fields.IntField()
class Meta:
table = "ingame_coordinates"
class PlayerDeath(Model):
player = fields.ForeignKeyField(
model_name="models.Player",
on_delete=fields.CASCADE
)
cause = fields.CharField(max_length=32)
timestamp = fields.DatetimeField(auto_now_add=True)
coordinates = fields.ForeignKeyField(
model_name="models.Coordinates",
on_delete=fields.CASCADE
)
class Meta:
table = "player_deaths"
class PlayerSession(Model):
player = fields.ForeignKeyField(
model_name="models.Player",
on_delete=fields.CASCADE
)
connected_at = fields.DatetimeField()
disconnected_at = fields.DatetimeField(null=False)
connected_coords = fields.ForeignKeyField(
model_name="models.Coordinates",
on_delete=fields.CASCADE
)
disconnected_coords = fields.ForeignKeyField(
model_name="models.Coordinates",
on_delete=fields.CASCADE,
null=True
)
class Meta:
table = "player_session"
@property
def playtime(self) -> timedelta:
if not self.disconnected_at:
return datetime.now() - self.connected_at
return self.disconnected_at - self.connected_at
class Player(Model):
"""
"""
username = fields.CharField(max_length=20, unique=True)
is_dead = fields.BooleanField(default=False)
class Meta:
table = "players"
async def get_playtime(self) -> timedelta:
playtime = await PlayerSession.filter(player=self).exclude(disconnected_at=None).annotate(
total_playtime=Sum(F("disconnected_at") - F("connected_at"))
).values()
return timedelta(seconds=playtime[0]["total_playtime"].total_seconds() if playtime else timedelta())
async def get_deaths(self) -> list[PlayerDeath]:
return await PlayerDeath.filter(player=self)
async def add_death(
self,
coord_x: str | int,
coord_y: str | int,
coord_z: str | int,
cause: str,
timestamp: datetime
) -> PlayerDeath:
"""
"""
log.debug("Assigning death to player: %s", self.username)
self.is_dead = True
await self.save()
coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z)
return await PlayerDeath.create(
player=self,
cause=cause,
timestamp=timestamp,
coordinates=coordinates
)
async def get_latest_session(self, ignore_closed_sessions: bool = False) -> PlayerSession:
queryset = PlayerSession.filter(player=self)
if ignore_closed_sessions:
queryset = queryset.filter(disconnected_at=None)
return await queryset.last()
async def open_session(
self,
timestamp: datetime,
coord_x: str | int,
coord_y: str | int,
coord_z: str | int,
) -> PlayerSession:
log.debug("creating session for player: %s", self.username)
existing_session = await self.get_latest_session(ignore_closed_sessions=True)
if existing_session:
raise ValueError("Tried to open session while an open one exists.")
coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z)
return await PlayerSession.create(
player=self,
connected_at=timestamp,
connected_coords=coordinates
)
async def close_session(
self,
timestamp: datetime,
coord_x: str | int,
coord_y: str | int,
coord_z: str | int,
) -> PlayerSession:
log.debug("closing session for player: %s", self.username)
current_session = await self.get_latest_session(ignore_closed_sessions=True)
if not current_session:
raise ValueError("Tried to close session that doesn't exist.")
coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z)
current_session.disconnected_coords = coordinates
current_session.disconnected_at = timestamp
await current_session.save()
async def get_steam_summary(self) -> SteamProfileSummary | None:
"""
"""
return await SteamProfileSummary.get_or_none(player=self)
async def update_steam_summary(self, steam_id: str | int, steam_api_key: str) -> SteamProfileSummary:
"""
"""
log.debug("Updating Steam summary for player: %s", self.username)
if not steam_api_key:
raise ValueError("No Steam API key provided, can't get profile summary.")
async with httpx.AsyncClient() as client:
response = await client.get(url=f"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key={steam_api_key}&steamids={steam_id}")
response.raise_for_status()
data = response.json()
profiles = data.get("response", {}).get("players", [])
if not profiles:
raise ValueError("No profiles found in response")
profile = profiles[0]
summary, created = await SteamProfileSummary.get_or_create(
steam_id=steam_id,
defaults={
"player": self,
"profile_name": profile.get("personaname"),
"url": profile.get("profileurl"),
"avatar_url": profile.get("avatarfull")
}
)
if not created:
summary.profile_name = profile.get("personaname")
summary.url = profile.get("profileurl")
summary.avatar_url = profile.get("avatarfull")
await summary.save()
return summary
async def get_embed(self) -> Embed:
summary = await self.get_steam_summary()
if not summary:
raise ValueError("You must fetch the steam_profile_summary before creating an embed.")
death_count = len(await self.get_deaths())
playtime = str(await self.get_playtime())
embed = Embed(
title="Player",
description=(
f"{self.username} ([{summary.profile_name}]({summary.url}))\n"
f"Deaths: {death_count}\n"
f"Playtime: {playtime}"
)
)
embed.set_thumbnail(url=summary.avatar_url)
return embed