Compare commits

..

5 Commits
18.2 ... 18.3

6 changed files with 95 additions and 47 deletions

View File

@ -7,6 +7,7 @@ from profiles.models import UserProfile
class UserProfileAdmin(admin.ModelAdmin): class UserProfileAdmin(admin.ModelAdmin):
date_hierarchy = "created" date_hierarchy = "created"
ordering = ("-created",) ordering = ("-created",)
readonly_fields = ("timezone_change_log",)
exclude = ( exclude = (
"twitch_token", "twitch_token",
"twitch_client_secret", "twitch_client_secret",

View File

@ -16,6 +16,7 @@ BNULL = {"blank": True, "null": True}
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class UserProfile(TimeStampedModel): class UserProfile(TimeStampedModel):
user = models.OneToOneField( user = models.OneToOneField(
User, on_delete=models.CASCADE, related_name="profile" User, on_delete=models.CASCADE, related_name="profile"
@ -72,7 +73,10 @@ class UserProfile(TimeStampedModel):
old_instance = UserProfile.objects.get(pk=self.pk) old_instance = UserProfile.objects.get(pk=self.pk)
is_timezone_change = self.timezone != old_instance.timezone is_timezone_change = self.timezone != old_instance.timezone
if is_timezone_change: if is_timezone_change:
logger.info("Updating timezone changelog for user", extra={"profile_id": self.id}) logger.info(
"Updating timezone changelog for user",
extra={"profile_id": self.id},
)
previous_changes = old_instance.timezone_change_log previous_changes = old_instance.timezone_change_log
now = timezone.now().replace(microsecond=0) now = timezone.now().replace(microsecond=0)
new_log = f"{self.timezone} - {now}" new_log = f"{self.timezone} - {now}"
@ -98,7 +102,7 @@ class UserProfile(TimeStampedModel):
change_list = self.historic_timezone_changes change_list = self.historic_timezone_changes
for idx, start in enumerate(change_list): for idx, start in enumerate(change_list):
try: try:
end = change_list[idx+1] end = change_list[idx + 1]
except IndexError: except IndexError:
end = None end = None

View File

@ -62,20 +62,20 @@ def start_of_year(dt, profile) -> datetime:
def one_off_fix_colins_profile(profile): def one_off_fix_colins_profile(profile):
home_tz = "America/New_York" home_tz = "America/New_York"
europe = "2022-10-15" europe = "2022-10-15 06:00:00"
europe_end = "2023-12-16 12:00:00"
europe_tz = "Europe/Paris" europe_tz = "Europe/Paris"
europe_end = "2023-12-15"
washington = "2023-04-28" washington = "2023-04-28 06:00:00"
washington_end = "2023-05-04 12:00:00"
washington_tz = "America/Los_Angeles" washington_tz = "America/Los_Angeles"
washington_end = "2023-05-04"
camp = "2024-08-04" camp = "2024-08-04 17:00:00"
camp_end = "2024-08-10" camp_end = "2024-08-10 12:00:00"
camp_tz = "America/Halifax" camp_tz = "America/Halifax"
summer = "2025-07-09 12:00:00" summer = "2025-07-09 06:00:00"
summer_end = "2025-07-13 20:30:00" summer_end = "2025-07-11 23:30:00"
summer_tz = "America/Los_Angeles" summer_tz = "America/Los_Angeles"
profile.timezone_change_log = "" profile.timezone_change_log = ""

View File

@ -50,20 +50,22 @@ class LastFM:
enrich=True, enrich=True,
) )
timezone = settings.TIME_ZONE timestamp = self.vrobbler_user.profile.get_timestamp_with_tz(
if self.vrobbler_user.profile: lfm_scrobble.get("timestamp")
timezone = self.vrobbler_user.profile.timezone )
stop_timestamp = timestamp + timedelta(
timestamp = lfm_scrobble.get("timestamp") seconds=track.run_time_seconds
)
new_scrobble = Scrobble( new_scrobble = Scrobble(
user=self.vrobbler_user, user=self.vrobbler_user,
timestamp=timestamp, timestamp=timestamp,
stop_timestamp=stop_timestamp,
source=source, source=source,
track=track, track=track,
timezone=timezone,
played_to_completion=True, played_to_completion=True,
in_progress=False, in_progress=False,
media_type=Scrobble.MediaType.TRACK, media_type=Scrobble.MediaType.TRACK,
timezone=timestamp.tzinfo.name,
) )
# Vrobbler scrobbles on finish, LastFM scrobbles on start # Vrobbler scrobbles on finish, LastFM scrobbles on start
seconds_eariler = timestamp - timedelta(seconds=20) seconds_eariler = timestamp - timedelta(seconds=20)

View File

@ -1087,6 +1087,7 @@ class Scrobble(TimeStampedModel):
key = media_class_to_foreign_key(media.__class__.__name__) key = media_class_to_foreign_key(media.__class__.__name__)
media_query = models.Q(**{key: media}) media_query = models.Q(**{key: media})
scrobble_data[key + "_id"] = media.id scrobble_data[key + "_id"] = media.id
skip_in_progress_check = kwargs.get("skip_in_progress_check", False)
# Find our last scrobble of this media item (track, video, etc) # Find our last scrobble of this media item (track, video, etc)
scrobble = ( scrobble = (
@ -1110,27 +1111,31 @@ class Scrobble(TimeStampedModel):
) )
return scrobble return scrobble
logger.info( if not skip_in_progress_check:
f"[create_or_update] check for existing scrobble to update ", logger.info(
extra={ f"[create_or_update] check for existing scrobble to update ",
"scrobble_id": scrobble.id if scrobble else None, extra={
"media_type": mtype, "scrobble_id": scrobble.id if scrobble else None,
"media_id": media.id, "media_type": mtype,
"scrobble_data": scrobble_data, "media_id": media.id,
}, "scrobble_data": scrobble_data,
) },
scrobble_data["playback_status"] = scrobble_data.pop("status", None) )
# If it's marked as stopped, send it through our update mechanism, which will complete it scrobble_data["playback_status"] = scrobble_data.pop(
if scrobble and ( "status", None
scrobble.can_be_updated )
or scrobble_data["playback_status"] == "stopped" # If it's marked as stopped, send it through our update mechanism, which will complete it
): if scrobble and (
if "log" in scrobble_data.keys() and scrobble.log: scrobble.can_be_updated
scrobble_data["log"] = scrobble.log | scrobble_data["log"] or scrobble_data["playback_status"] == "stopped"
return scrobble.update(scrobble_data) ):
if "log" in scrobble_data.keys() and scrobble.log:
scrobble_data["log"] = scrobble.log | scrobble_data["log"]
return scrobble.update(scrobble_data)
# Discard status before creating
scrobble_data.pop("playback_status")
# Discard status before creating
scrobble_data.pop("playback_status")
logger.info( logger.info(
f"[scrobbling] creating new scrobble", f"[scrobbling] creating new scrobble",
extra={ extra={

View File

@ -2,6 +2,7 @@ import logging
import re import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Optional from typing import Any, Optional
from zoneinfo import ZoneInfo
import pendulum import pendulum
import pytz import pytz
@ -18,6 +19,7 @@ from music.models import Track
from people.models import Person from people.models import Person
from podcasts.models import PodcastEpisode from podcasts.models import PodcastEpisode
from podcasts.utils import parse_mopidy_uri from podcasts.utils import parse_mopidy_uri
from profiles.models import UserProfile
from puzzles.models import Puzzle from puzzles.models import Puzzle
from scrobbles.constants import ( from scrobbles.constants import (
JELLYFIN_AUDIO_ITEM_TYPES, JELLYFIN_AUDIO_ITEM_TYPES,
@ -390,10 +392,25 @@ def email_scrobble_board_game(
locations[location_dict.get("id")] = location locations[location_dict.get("id")] = location
scrobbles_created = [] scrobbles_created = []
second = 0
for play_dict in bgstat_data.get("plays", []): for play_dict in bgstat_data.get("plays", []):
hour = None
minute = None
second = None
if "comments" in play_dict.keys(): if "comments" in play_dict.keys():
if "Learning to play" in play_dict.get("comments"): for line in play_dict.get("comments", "").split("\n"):
log_data["learning"] = True if "Learning to play" in line:
log_data["learning"] = True
if "Start time:" in line:
start_time = line.split(": ")[1]
pieces = start_time.split(":")
hour = int(pieces[0])
minute = int(pieces[1])
try:
second = int(pieces[2])
except IndexError:
second = 0
log_data["details"] = play_dict.get("comments") log_data["details"] = play_dict.get("comments")
log_data["expansion_ids"] = [] log_data["expansion_ids"] = []
try: try:
@ -402,7 +419,6 @@ def email_scrobble_board_game(
try: try:
base_game = expansions[play_dict.get("gameRefId")] base_game = expansions[play_dict.get("gameRefId")]
except KeyError: except KeyError:
print(play_dict)
logger.info( logger.info(
"Skipping scrobble of play, can't find game", "Skipping scrobble of play, can't find game",
extra={"play_dict": play_dict}, extra={"play_dict": play_dict},
@ -440,31 +456,51 @@ def email_scrobble_board_game(
} }
) )
start = parse(play_dict.get("playDate")) timestamp = parse(play_dict.get("playDate"))
if hour and minute:
logger.info(f"Scrobble playDate has manual start time {timestamp}")
timestamp = timestamp.replace(
hour=hour, minute=minute, second=second or 0
)
logger.info(f"Update to {timestamp}")
profile = UserProfile.objects.filter(user_id=user_id).first()
timestamp = profile.get_timestamp_with_tz(timestamp)
if play_dict.get("durationMin") > 0: if play_dict.get("durationMin") > 0:
duration_seconds = play_dict.get("durationMin") * 60 duration_seconds = play_dict.get("durationMin") * 60
else: else:
duration_seconds = base_game.run_time_seconds duration_seconds = base_game.run_time_seconds
stop = start + timedelta(seconds=duration_seconds) stop_timestamp = timestamp + timedelta(seconds=duration_seconds)
logger.info(f"Creating scrobble for {base_game} at {timestamp}")
scrobble_dict = { scrobble_dict = {
"user_id": user_id, "user_id": user_id,
"timestamp": start, "timestamp": timestamp,
"playback_position_seconds": duration_seconds, "playback_position_seconds": duration_seconds,
"source": "BG Stats", "source": "BG Stats",
"log": log_data, "log": log_data,
} }
scrobble = Scrobble.objects.filter( scrobble = None
board_game=base_game, user_id=user_id, timestamp=start if timestamp.year > 2023:
).first() logger.info(
"Scrobbles older than 2024 likely have no time associated just create it"
)
scrobble = Scrobble.objects.filter(
board_game=base_game, user_id=user_id, timestamp=timestamp
).first()
if scrobble: if scrobble:
logger.info( logger.info(
"Scrobble already exists, skipping", "Scrobble already exists, skipping",
extra={"scrobble_dict": scrobble_dict, "user_id": user_id}, extra={"scrobble_dict": scrobble_dict, "user_id": user_id},
) )
continue continue
scrobble = Scrobble.create_or_update(base_game, user_id, scrobble_dict) scrobble = Scrobble.create_or_update(
scrobble.stop_timestamp = stop base_game, user_id, scrobble_dict, skip_in_progress_check=True
)
scrobble.timezone = timestamp.tzinfo.name
scrobble.stop_timestamp = stop_timestamp
scrobble.in_progress = False scrobble.in_progress = False
scrobble.played_to_completion = True scrobble.played_to_completion = True
scrobble.save() scrobble.save()