[boardgames] Adding email scrobbler for BG Stats

This commit is contained in:
2025-07-02 23:01:13 -04:00
parent 3548c29f97
commit 1590ce5f18
13 changed files with 469 additions and 49 deletions

View File

@ -1,12 +1,12 @@
import logging
import re
from datetime import datetime
from datetime import datetime, timedelta
from typing import Any, Optional
import pendulum
import pytz
from beers.models import Beer
from boardgames.models import BoardGame
from boardgames.models import BoardGame, BoardGameDesigner, BoardGameLocation
from books.models import Book
from bricksets.models import BrickSet
from dateutil.parser import parse
@ -15,6 +15,7 @@ from locations.constants import LOCATION_PROVIDERS
from locations.models import GeoLocation
from music.constants import JELLYFIN_POST_KEYS, MOPIDY_POST_KEYS
from music.models import Track
from people.models import Person
from podcasts.models import PodcastEpisode
from podcasts.utils import parse_mopidy_uri
from puzzles.models import Puzzle
@ -28,10 +29,10 @@ from scrobbles.utils import convert_to_seconds, extract_domain
from sports.models import SportEvent
from sports.thesportsdb import lookup_event_from_thesportsdb
from tasks.models import Task
from tasks.utils import get_title_from_labels
from videogames.howlongtobeat import lookup_game_from_hltb
from videogames.models import VideoGame
from videos.models import Video
from tasks.utils import get_title_from_labels
from webpages.models import WebPage
logger = logging.getLogger(__name__)
@ -311,26 +312,117 @@ def manual_scrobble_board_game(
return Scrobble.create_or_update(boardgame, user_id, scrobble_dict)
def find_and_enrich_board_game_data(game_dict: dict) -> BoardGame | None:
"""TODO Move this to a utility somewhere"""
game = BoardGame.find_or_create(game_dict.get("bggId"))
if game:
game.cooperative = game_dict.get("cooperative", False)
game.highest_wins = game_dict.get("highestWins", True)
game.no_points = game_dict.get("noPoints", False)
game.uses_teams = game_dict.get("useTeams", False)
if not game.rating:
game.rating = game_dict.get("rating") / 10
game.save()
if game_dict.get("designers"):
for designer_name in game_dict.get("designers", "").split(", "):
BoardGameDesigner.objects.get_or_create(name=designer_name)
return game
def email_scrobble_board_game(
bgstat_data: dict[str, Any], user_id: int
) -> Scrobble | None:
game_dict: dict[str, Any] = bgstat_data.get("games", [])[0]
if not game_dict.get("bggId", False):
player_dict = {}
for player in bgstat_data.get("players", []):
if player.get("isAnonymous"):
person, _created = Person.objects.get_or_create(name="Anonymous")
else:
person, _created = Person.objects.get_or_create(
bgstats_id=player.get("uuid")
)
if not person.name:
person.name = player.get("name", "")
person.save()
player_dict[player.get("id")] = person
game_list: list = bgstat_data.get("games", [])
if not game_list:
logger.info(
"Data from BG Stats JSON had not BGG ID, not scrobbling",
"No game data from BG Stats, not scrobbling",
extra={"bgstat_data": bgstat_data},
)
return
boardgame = BoardGame.find_or_create(game_dict.get("bggId"))
# TODO Enrich data from our bgstats data?
#
# TODO Build up board game meta data from the rest of the data
scrobble_dict = {}
players_list: list[dict[str, Any]] = bgstat_data.get("players", [])
location: dict[str, Any] = bgstat_data.get("locations", [])[0]
base_game = None
expansions = []
log_data = {}
for game in game_list:
logger.info(f"Finding and enriching {game.get('name')}")
enriched_game = find_and_enrich_board_game_data(game)
if game.get("isBaseGame"):
base_game = enriched_game
elif game.get("isExpansion"):
expansions.append(enriched_game)
return Scrobble.create_or_update(boardgame, user_id, scrobble_dict)
for expansion in expansions:
expansion.expansion_for_boardgame = base_game
expansion.save()
log_data["expansion_ids"] = [e.id for e in expansions]
location_dict: dict[str, Any] = bgstat_data.get("locations", [])[0]
location, _created = BoardGameLocation.objects.get_or_create(
bgstats_id=location_dict.get("uuid")
)
if not location.name:
location.name = location_dict.get("name")
geoloc = GeoLocation.objects.filter(
title__icontains=location.name
).first()
if geoloc:
location.geo_location = geoloc
location.save()
log_data["location_id"] = location.id
play_dict = bgstat_data.get("plays", [])[0]
if play_dict.get("rounds", False):
log_data["rounds"] = play_dict.get("rounds")
if play_dict.get("board", False):
log_data["board"] = play_dict.get("board")
log_data["players"] = []
for score_dict in play_dict.get("playerScores", []):
log_data["players"].append(
{
"person_id": player_dict[score_dict.get("playerRefId")].id,
"new": score_dict.get("newPlayer"),
"win": score_dict.get("winner"),
"score": score_dict.get("score"),
"rank": score_dict.get("rank"),
"seat_order": score_dict.get("seatOrder"),
"role": score_dict.get("role"),
}
)
start = parse(play_dict.get("playDate"))
duration_seconds = play_dict.get("durationMin") * 60
stop = start + timedelta(seconds=duration_seconds)
scrobble_dict = {
"user_id": user_id,
"timestamp": start,
"playback_position_seconds": duration_seconds,
"source": "BG Stats",
"log": log_data,
}
scrobble = Scrobble.create_or_update(base_game, user_id, scrobble_dict)
scrobble.stop_timestamp = stop
scrobble.in_progress = False
scrobble.played_to_completion = True
scrobble.save()
return scrobble
def manual_scrobble_from_url(