Compare commits

...

4 Commits

Author SHA1 Message Date
41b50e352f detailed debug llog for file reader
All checks were successful
Build and Push Docker Image / build (push) Successful in 33s
2024-12-11 22:55:55 +00:00
1ff4f4e831 get total playtime from player 2024-12-11 22:55:39 +00:00
b2847ebe95 user log files only and handle task around build 2024-12-11 22:55:24 +00:00
825843ed86 remove that stupid package aerich, it's terrible! 2024-12-11 22:55:08 +00:00
8 changed files with 42 additions and 19 deletions

View File

@ -11,7 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Debug environment variable flag.
- Command to build player data from existing log files.
- Database migration management with the aerich package.
### Changed

View File

@ -13,4 +13,4 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY . /app/
CMD ["sh", "-c", "aerich upgrade && python bot.py"]
CMD ["python", "bot.py"]

2
bot.py
View File

@ -25,7 +25,7 @@ TORTOISE_ORM = {
"connections": { "default": f"sqlite://{str(DATA_DIR)}/db.sqlite" },
"apps": {
"models": {
"models": ["utils.models", "aerich.models"],
"models": ["utils.models"],
"default_connection": "default"
}
}

View File

@ -45,17 +45,19 @@ class PlayersCog(commands.Cog):
Build player data from existing and older log files.
"""
await inter.response.defer()
self.listen_for_changes.stop()
log.info("Building player data from logs.")
# Delete the existing data, as we will reconstruct it.
await Player.all().delete()
for log_file in LOGS_FOLDER_PATH.glob("**/*.txt"):
for log_file in LOGS_FOLDER_PATH.glob("**/*_user.txt"):
log.debug("building from log file: %s", str(log_file))
file_handler = LogFileReader(log_file, track_from_start=True)
for line in await file_handler.read():
await self.process_log_line(line, alert=False)
self.listen_for_changes.start()
await inter.followup.send("Completed")
@tasks.loop(seconds=3)

View File

@ -3,21 +3,30 @@ from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "players" (
CREATE TABLE IF NOT EXISTS "ingame_coordinates" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"x" INT NOT NULL,
"y" INT NOT NULL,
"z" INT NOT NULL
);
CREATE TABLE IF NOT EXISTS "players" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"username" VARCHAR(20) NOT NULL UNIQUE,
"last_connection" TIMESTAMP,
"last_disconnection" TIMESTAMP,
"play_time_seconds" INT NOT NULL DEFAULT 0,
"is_dead" INT NOT NULL DEFAULT 0
) /* */;
CREATE TABLE IF NOT EXISTS "player_deaths" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"coordinate_x" INT NOT NULL,
"coordinate_y" INT NOT NULL,
"coordinate_z" INT NOT NULL,
"cause" VARCHAR(32) NOT NULL,
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"coordinates_id" INT NOT NULL REFERENCES "ingame_coordinates" ("id") ON DELETE CASCADE,
"player_id" INT NOT NULL REFERENCES "players" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "player_session" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"connected_at" TIMESTAMP NOT NULL,
"disconnected_at" TIMESTAMP NOT NULL,
"connected_coords_id" INT NOT NULL REFERENCES "ingame_coordinates" ("id") ON DELETE CASCADE,
"disconnected_coords_id" INT REFERENCES "ingame_coordinates" ("id") ON DELETE CASCADE,
"player_id" INT NOT NULL REFERENCES "players" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "steam_profile_summary" (

View File

@ -1,4 +1,3 @@
aerich==0.8.0
aiofiles==24.1.0
aiohappyeyeballs==2.4.4
aiohttp==3.11.9

View File

@ -4,6 +4,7 @@ Database schemas.
import logging
from datetime import datetime, timedelta
from pytz import timezone
import httpx
from tortoise import fields
@ -61,13 +62,15 @@ class PlayerSession(Model):
on_delete=fields.CASCADE
)
connected_at = fields.DatetimeField()
disconnected_at = fields.DatetimeField(null=False)
disconnected_at = fields.DatetimeField(null=True)
connected_coords = fields.ForeignKeyField(
model_name="models.Coordinates",
related_name="connected_coords",
on_delete=fields.CASCADE
)
disconnected_coords = fields.ForeignKeyField(
model_name="models.Coordinates",
related_name="disconnected_coords",
on_delete=fields.CASCADE,
null=True
)
@ -93,10 +96,18 @@ class Player(Model):
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())
sessions = await PlayerSession.filter(player=self)
total_playtime = timedelta()
now = datetime.now()
utc = timezone("UTC")
# I know this is terrible efficiency-wise, but the tortoise docs
# are so bad and the annotations don't work like Django's models. Deal with it!
for session in sessions:
disconnected_at = session.disconnected_at or now
total_playtime += disconnected_at.astimezone(utc) - session.connected_at.astimezone(utc)
return total_playtime
async def get_deaths(self) -> list[PlayerDeath]:
return await PlayerDeath.filter(player=self)
@ -139,7 +150,8 @@ class Player(Model):
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.")
log.debug("deleting an unfinished session to open a new one")
await existing_session.delete()
coordinates = await Coordinates.create(x=coord_x, y=coord_y, z=coord_z)
return await PlayerSession.create(
@ -213,7 +225,8 @@ class Player(Model):
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())
playtime = str(await self.get_playtime()).split(".")[0] # remove the miliseconds
log.debug("death count is: %s and playtime is: %s", death_count, playtime)
embed = Embed(
title="Player",

View File

@ -28,6 +28,7 @@ class LogFileReader:
raise FileNotFoundError(self.file_path)
async with aiofiles.open(self.file_path, "r", encoding="utf-8") as file:
log.debug("file open, and jumping to line: %s", self._last_line_number)
if self._last_line_number == 0 and not self.track_from_start:
await file.seek(0, 2)
self._last_line_number = await file.tell()