[brickset] Fix manual scrobbling and admin
This commit is contained in:
@ -16,7 +16,9 @@ class BrickSet(LongPlayScrobblableMixin):
|
||||
number = models.CharField(max_length=10, **BNULL)
|
||||
release_year = models.IntegerField(**BNULL)
|
||||
piece_count = models.IntegerField(**BNULL)
|
||||
brickset_rating = models.DecimalField(max_digits=3, decimal_places=1, **BNULL)
|
||||
brickset_rating = models.DecimalField(
|
||||
max_digits=3, decimal_places=1, **BNULL
|
||||
)
|
||||
lego_item_number = models.CharField(max_length=10, **BNULL)
|
||||
box_image = models.ImageField(upload_to="brickset/boxes/", **BNULL)
|
||||
box_image_small = ImageSpecField(
|
||||
@ -53,8 +55,12 @@ class BrickSet(LongPlayScrobblableMixin):
|
||||
return BrickSetLogData
|
||||
|
||||
@classmethod
|
||||
def find_or_create(cls, title: str) -> "BrickSet":
|
||||
return cls.objects.filter(title=title).first()
|
||||
def find_or_create(cls, brickset_id: str) -> "BrickSet":
|
||||
brickset = cls.objects.filter(number=brickset_id).first()
|
||||
if not brickset:
|
||||
# TODO: enrich this from the website
|
||||
brickset = cls.objects.create(number=brickset_id)
|
||||
return brickset
|
||||
|
||||
@property
|
||||
def primary_image_url(self) -> str:
|
||||
|
||||
@ -16,6 +16,7 @@ class AlbumAdmin(admin.ModelAdmin):
|
||||
"theaudiodb_mood",
|
||||
"musicbrainz_id",
|
||||
)
|
||||
raw_id_fields = ("album_artist",)
|
||||
list_filter = (
|
||||
"theaudiodb_score",
|
||||
"theaudiodb_genre",
|
||||
@ -53,6 +54,10 @@ class TrackAdmin(admin.ModelAdmin):
|
||||
"artist",
|
||||
"musicbrainz_id",
|
||||
)
|
||||
raw_id_fields = (
|
||||
"album",
|
||||
"artist",
|
||||
)
|
||||
list_filter = ("album", "artist")
|
||||
search_fields = ("title",)
|
||||
ordering = ("-created",)
|
||||
|
||||
@ -27,7 +27,7 @@ class ScrobbleInline(admin.TabularInline):
|
||||
"geo_location",
|
||||
"task",
|
||||
"mood",
|
||||
"brickset",
|
||||
"brick_set",
|
||||
"trail",
|
||||
"beer",
|
||||
"web_page",
|
||||
@ -122,9 +122,12 @@ class ScrobbleAdmin(admin.ModelAdmin):
|
||||
"video_game",
|
||||
"board_game",
|
||||
"geo_location",
|
||||
"puzzle",
|
||||
"paper",
|
||||
"food",
|
||||
"task",
|
||||
"mood",
|
||||
"brickset",
|
||||
"brick_set",
|
||||
"trail",
|
||||
"beer",
|
||||
"web_page",
|
||||
|
||||
@ -27,14 +27,14 @@ MEDIA_END_PADDING_SECONDS = {
|
||||
TODOIST_TASK_URL = "https://app.todoist.com/app/task/{id}"
|
||||
|
||||
SCROBBLE_CONTENT_URLS = {
|
||||
"-i": "https://www.imdb.com/title/",
|
||||
"-s": "https://www.thesportsdb.com/event/",
|
||||
"-g": "https://boardgamegeek.com/boardgame/",
|
||||
"-u": "https://untappd.com/",
|
||||
"-b": "https://www.amazon.com/",
|
||||
"-t": "https://app.todoist.com/app/task/{id}",
|
||||
"-i": "https://www.youtube.com/watch?v=",
|
||||
"-p": "https://www.ipdb.plus/IPDb/puzzle.php?id=",
|
||||
"-i": ["https://www.imdb.com/title/", "https://www.youtube.com/watch?v="],
|
||||
"-s": ["https://www.thesportsdb.com/event/"],
|
||||
"-g": ["https://boardgamegeek.com/boardgame/"],
|
||||
"-u": ["https://untappd.com/"],
|
||||
"-b": ["https://www.amazon.com/"],
|
||||
"-t": ["https://app.todoist.com/app/task/{id}"],
|
||||
"-p": ["https://www.ipdb.plus/IPDb/puzzle.php?id="],
|
||||
"-l": ["https://brickset.com/sets/"],
|
||||
}
|
||||
|
||||
EXCLUDE_FROM_NOW_PLAYING = ("GeoLocation",)
|
||||
@ -49,6 +49,7 @@ MANUAL_SCROBBLE_FNS = {
|
||||
"-w": "manual_scrobble_webpage",
|
||||
"-t": "manual_scrobble_task",
|
||||
"-p": "manual_scrobble_puzzle",
|
||||
"-l": "manual_scrobble_brickset",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.19 on 2025-06-09 14:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("scrobbles", "0069_scrobble_puzzle_alter_scrobble_media_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="scrobble",
|
||||
old_name="brickset",
|
||||
new_name="brick_set",
|
||||
),
|
||||
]
|
||||
@ -549,7 +549,7 @@ class Scrobble(TimeStampedModel):
|
||||
LifeEvent, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
mood = models.ForeignKey(Mood, on_delete=models.DO_NOTHING, **BNULL)
|
||||
brickset = models.ForeignKey(
|
||||
brick_set = models.ForeignKey(
|
||||
BrickSet, on_delete=models.DO_NOTHING, **BNULL
|
||||
)
|
||||
media_type = models.CharField(
|
||||
@ -1019,8 +1019,8 @@ class Scrobble(TimeStampedModel):
|
||||
media_obj = self.life_event
|
||||
if self.mood:
|
||||
media_obj = self.mood
|
||||
if self.brickset:
|
||||
media_obj = self.brickset
|
||||
if self.brick_set:
|
||||
media_obj = self.brick_set
|
||||
if self.trail:
|
||||
media_obj = self.trail
|
||||
if self.beer:
|
||||
|
||||
@ -8,6 +8,7 @@ import pytz
|
||||
from beers.models import Beer
|
||||
from boardgames.models import BoardGame
|
||||
from books.models import Book
|
||||
from bricksets.models import BrickSet
|
||||
from dateutil.parser import parse
|
||||
from django.utils import timezone
|
||||
from locations.constants import LOCATION_PROVIDERS
|
||||
@ -23,7 +24,7 @@ from scrobbles.constants import (
|
||||
SCROBBLE_CONTENT_URLS,
|
||||
)
|
||||
from scrobbles.models import Scrobble
|
||||
from scrobbles.utils import convert_to_seconds
|
||||
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
|
||||
@ -322,14 +323,12 @@ def manual_scrobble_from_url(
|
||||
we know about the content type, and routes it to the appropriate media
|
||||
scrobbler. Otherwise, return nothing."""
|
||||
content_key = ""
|
||||
try:
|
||||
domain = url.split("//")[-1].split("/")[0]
|
||||
except IndexError:
|
||||
domain = None
|
||||
domain = extract_domain(url)
|
||||
|
||||
for key, content_url in SCROBBLE_CONTENT_URLS.items():
|
||||
if domain in content_url:
|
||||
content_key = key
|
||||
for key, content_urls in SCROBBLE_CONTENT_URLS.items():
|
||||
for content_url in content_urls:
|
||||
if domain in content_url:
|
||||
content_key = key
|
||||
|
||||
item_id = None
|
||||
if not content_key:
|
||||
@ -343,8 +342,10 @@ def manual_scrobble_from_url(
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if content_key == "-i":
|
||||
if content_key == "-i" and "v=" in url:
|
||||
item_id = url.split("v=")[1].split("&")[0]
|
||||
elif content_key == "-i" and "title/tt" in url:
|
||||
item_id = "tt" + str(item_id)
|
||||
|
||||
scrobble_fn = MANUAL_SCROBBLE_FNS[content_key]
|
||||
return eval(scrobble_fn)(item_id, user_id, action=action)
|
||||
@ -853,3 +854,32 @@ def manual_scrobble_puzzle(
|
||||
|
||||
# TODO Kick out a process to enrich the media here, and in every scrobble event
|
||||
return Scrobble.create_or_update(puzzle, user_id, scrobble_dict)
|
||||
|
||||
|
||||
def manual_scrobble_brickset(
|
||||
brickset_id: str, user_id: int, action: Optional[str] = None
|
||||
):
|
||||
brickset = BrickSet.find_or_create(brickset_id)
|
||||
|
||||
if not brickset:
|
||||
logger.error(f"No brickset found for Brickset ID {brickset_id}")
|
||||
return
|
||||
|
||||
scrobble_dict = {
|
||||
"user_id": user_id,
|
||||
"timestamp": timezone.now(),
|
||||
"playback_position_seconds": 0,
|
||||
"source": "Vrobbler",
|
||||
}
|
||||
logger.info(
|
||||
"[vrobbler-scrobble] brickset scrobble request received",
|
||||
extra={
|
||||
"brickset_id": brickset.id,
|
||||
"user_id": user_id,
|
||||
"scrobble_dict": scrobble_dict,
|
||||
"media_type": Scrobble.MediaType.BRICKSET,
|
||||
},
|
||||
)
|
||||
|
||||
# TODO Kick out a process to enrich the media here, and in every scrobble event
|
||||
return Scrobble.create_or_update(brickset, user_id, scrobble_dict)
|
||||
|
||||
@ -3,6 +3,7 @@ import logging
|
||||
import re
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
from sqlite3 import IntegrityError
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import pytz
|
||||
from django.apps import apps
|
||||
@ -17,9 +18,10 @@ from scrobbles.tasks import (
|
||||
process_lastfm_import,
|
||||
process_retroarch_import,
|
||||
)
|
||||
from vrobbler.apps.scrobbles.notifications import NtfyNotification
|
||||
from webdav.client import get_webdav_client
|
||||
|
||||
from vrobbler.apps.scrobbles.notifications import NtfyNotification
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
User = get_user_model()
|
||||
|
||||
@ -67,7 +69,8 @@ def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet:
|
||||
return Scrobble.objects.filter(media_query, user=user)
|
||||
|
||||
|
||||
def get_recently_played_board_games(user: User) -> dict: ...
|
||||
def get_recently_played_board_games(user: User) -> dict:
|
||||
...
|
||||
|
||||
|
||||
def get_long_plays_in_progress(user: User) -> dict:
|
||||
@ -187,8 +190,8 @@ def delete_zombie_scrobbles(dry_run=True):
|
||||
|
||||
def import_from_webdav_for_all_users(restart=False):
|
||||
"""Grab a list of all users with WebDAV enabled and kickoff imports for them"""
|
||||
from scrobbles.models import KoReaderImport
|
||||
from books.koreader import fetch_file_from_webdav
|
||||
from scrobbles.models import KoReaderImport
|
||||
|
||||
# LastFmImport = apps.get_model("scrobbles", "LastFMImport")
|
||||
webdav_enabled_user_ids = UserProfile.objects.filter(
|
||||
@ -271,6 +274,7 @@ def get_file_md5_hash(file_path: str) -> str:
|
||||
file_hash.update(chunk)
|
||||
return file_hash.hexdigest()
|
||||
|
||||
|
||||
def deduplicate_tracks(commit=False) -> int:
|
||||
from music.models import Track
|
||||
|
||||
@ -293,7 +297,11 @@ def deduplicate_tracks(commit=False) -> int:
|
||||
try:
|
||||
other.delete()
|
||||
except IntegrityError as e:
|
||||
print("could not delete ", other.id, f": IntegrityError {e}")
|
||||
print(
|
||||
"could not delete ",
|
||||
other.id,
|
||||
f": IntegrityError {e}",
|
||||
)
|
||||
return len(dups)
|
||||
|
||||
|
||||
@ -318,3 +326,13 @@ def send_stop_notifications_for_in_progress_scrobbles() -> int:
|
||||
notifications_sent += 1
|
||||
|
||||
return notifications_sent
|
||||
|
||||
|
||||
def extract_domain(url):
|
||||
parsed_url = urlparse(url)
|
||||
domain = (
|
||||
parsed_url.netloc.split(".")[-2]
|
||||
+ "."
|
||||
+ parsed_url.netloc.split(".")[-1]
|
||||
)
|
||||
return domain
|
||||
|
||||
@ -57,6 +57,8 @@ class VideoMetadata:
|
||||
|
||||
def as_dict_with_cover_and_genres(self) -> tuple:
|
||||
video_dict = vars(self)
|
||||
cover = video_dict.pop("cover_url")
|
||||
cover = None
|
||||
if "cover_url" in video_dict.keys():
|
||||
cover = video_dict.pop("cover_url")
|
||||
genres = video_dict.pop("genres")
|
||||
return video_dict, cover, genres
|
||||
|
||||
@ -72,6 +72,13 @@
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if BrickSet %}
|
||||
<h4>Brick sets</h4>
|
||||
{% with scrobbles=BrickSet count=BrickSet_count time=BrickSet_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if Book %}
|
||||
<h4>Books</h4>
|
||||
{% with scrobbles=Book count=Book_count time=Book_time %}
|
||||
|
||||
Reference in New Issue
Block a user