Add webpage scrobbling

This commit is contained in:
2023-11-30 17:49:53 +01:00
parent f303115ca0
commit 4e1db4aa6f
11 changed files with 231 additions and 11 deletions

View File

@ -0,0 +1,44 @@
# Generated by Django 4.1.7 on 2023-11-30 16:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("webpages", "0001_initial"),
("scrobbles", "0044_scrobble_geo_location_alter_scrobble_media_type"),
]
operations = [
migrations.AddField(
model_name="scrobble",
name="webpage",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to="webpages.webpage",
),
),
migrations.AlterField(
model_name="scrobble",
name="media_type",
field=models.CharField(
choices=[
("Video", "Video"),
("Track", "Track"),
("Episode", "Podcast episode"),
("SportEvent", "Sport event"),
("Book", "Book"),
("VideoGame", "Video game"),
("BoardGame", "Board game"),
("GeoLocation", "GeoLocation"),
("WebPage", "Web Page"),
],
default="Video",
max_length=14,
),
),
]

View File

@ -14,6 +14,7 @@ from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from locations.models import GeoLocation from locations.models import GeoLocation
from webpages.models import WebPage
from music.lastfm import LastFM from music.lastfm import LastFM
from music.models import Artist, Track from music.models import Artist, Track
from podcasts.models import Episode from podcasts.models import Episode
@ -471,6 +472,7 @@ class Scrobble(TimeStampedModel):
VIDEO_GAME = "VideoGame", "Video game" VIDEO_GAME = "VideoGame", "Video game"
BOARD_GAME = "BoardGame", "Board game" BOARD_GAME = "BoardGame", "Board game"
GEO_LOCATION = "GeoLocation", "GeoLocation" GEO_LOCATION = "GeoLocation", "GeoLocation"
WEBPAGE = "WebPage", "Web Page"
uuid = models.UUIDField(editable=False, **BNULL) uuid = models.UUIDField(editable=False, **BNULL)
video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL) video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL)
@ -491,6 +493,7 @@ class Scrobble(TimeStampedModel):
geo_location = models.ForeignKey( geo_location = models.ForeignKey(
GeoLocation, on_delete=models.DO_NOTHING, **BNULL GeoLocation, on_delete=models.DO_NOTHING, **BNULL
) )
webpage = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL)
media_type = models.CharField( media_type = models.CharField(
max_length=14, choices=MediaType.choices, default=MediaType.VIDEO max_length=14, choices=MediaType.choices, default=MediaType.VIDEO
) )
@ -697,19 +700,24 @@ class Scrobble(TimeStampedModel):
if media_class == "BoardGame": if media_class == "BoardGame":
media_query = models.Q(board_game=media) media_query = models.Q(board_game=media)
scrobble_data["board_game_id"] = media.id scrobble_data["board_game_id"] = media.id
if media_class == "WebPage":
media_query = models.Q(webpage=media)
scrobble_data["webpage_id"] = media.id
if media_class == "GeoLocation": if media_class == "GeoLocation":
media_query = models.Q(media_type=Scrobble.MediaType.GEO_LOCATION) media_query = models.Q(media_type=Scrobble.MediaType.GEO_LOCATION)
scrobble_data["geo_location_id"] = media.id scrobble_data["geo_location_id"] = media.id
dup = cls.objects.filter( dup = cls.objects.filter(
media_type=cls.MediaType.GEO_LOCATION, media_type=cls.MediaType.GEO_LOCATION,
timestamp = scrobble_data.get("timestamp"), timestamp=scrobble_data.get("timestamp"),
).first() ).first()
if dup: if dup:
logger.info("[scrobbling] scrobble with identical timestamp found") logger.info(
"[scrobbling] scrobble for geo location with identical timestamp found"
)
# TODO Fix return type, can we ever return a Scrobble?
return return
scrobble = ( scrobble = (
cls.objects.filter( cls.objects.filter(
media_query, media_query,
@ -719,7 +727,6 @@ class Scrobble(TimeStampedModel):
.first() .first()
) )
if scrobble and scrobble.can_be_updated: if scrobble and scrobble.can_be_updated:
source = scrobble_data["source"] source = scrobble_data["source"]
mtype = media.__class__.__name__ mtype = media.__class__.__name__

View File

@ -1,13 +1,15 @@
import logging import logging
import pendulum
from typing import Optional from typing import Optional
import pendulum
from boardgames.bgg import lookup_boardgame_from_bgg from boardgames.bgg import lookup_boardgame_from_bgg
from boardgames.models import BoardGame from boardgames.models import BoardGame
from books.models import Book from books.models import Book
from books.openlibrary import lookup_book_from_openlibrary from books.openlibrary import lookup_book_from_openlibrary
from dateutil.parser import parse from dateutil.parser import parse
from django.utils import timezone from django.utils import timezone
from locations.constants import LOCATION_PROVIDERS
from locations.models import GeoLocation
from music.constants import JELLYFIN_POST_KEYS from music.constants import JELLYFIN_POST_KEYS
from music.models import Track from music.models import Track
from music.utils import ( from music.utils import (
@ -23,8 +25,7 @@ from sports.thesportsdb import lookup_event_from_thesportsdb
from videogames.howlongtobeat import lookup_game_from_hltb from videogames.howlongtobeat import lookup_game_from_hltb
from videogames.models import VideoGame from videogames.models import VideoGame
from videos.models import Video from videos.models import Video
from locations.models import GeoLocation, RawGeoLocation from webpages.models import WebPage
from locations.constants import LOCATION_PROVIDERS
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -247,6 +248,20 @@ def manual_scrobble_board_game(bggeek_id: str, user_id: int):
return Scrobble.create_or_update(boardgame, user_id, scrobble_dict) return Scrobble.create_or_update(boardgame, user_id, scrobble_dict)
def manual_scrobble_webpage(url: str, user_id: int):
webpage = WebPage.find_or_create({"url": url})
scrobble_dict = {
"user_id": user_id,
"timestamp": timezone.now(),
"playback_position_seconds": 0,
"source": "Vrobbler",
"source_id": "Manually scrobbled from Vrobbler",
}
return Scrobble.create_or_update(webpage, user_id, scrobble_dict)
def gpslogger_scrobble_location( def gpslogger_scrobble_location(
data_dict: dict, user_id: Optional[int] data_dict: dict, user_id: Optional[int]
) -> Optional[Scrobble]: ) -> Optional[Scrobble]:

View File

@ -65,6 +65,7 @@ from scrobbles.utils import (
get_long_plays_in_progress, get_long_plays_in_progress,
get_recently_played_board_games, get_recently_played_board_games,
) )
from vrobbler.apps.scrobbles.scrobblers import manual_scrobble_webpage
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -441,6 +442,8 @@ def scrobble_start(request, uuid):
scrobble = manual_scrobble_video_game(media_obj.hltb_id, user_id) scrobble = manual_scrobble_video_game(media_obj.hltb_id, user_id)
if media_obj.__class__.__name__ == Scrobble.MediaType.BOARD_GAME: if media_obj.__class__.__name__ == Scrobble.MediaType.BOARD_GAME:
scrobble = manual_scrobble_board_game(media_obj.bggeek_id, user_id) scrobble = manual_scrobble_board_game(media_obj.bggeek_id, user_id)
if media_obj.__class__.__name__ == Scrobble.MediaType.WEBPAGE:
scrobble = manual_scrobble_webpage(media_obj.url, user_id)
if scrobble: if scrobble:
messages.add_message( messages.add_message(

View File

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class WebpagesConfig(AppConfig):
name = "webpages"

View File

@ -0,0 +1,79 @@
# Generated by Django 4.1.7 on 2023-11-30 16:45
from django.db import migrations, models
import django_extensions.db.fields
import taggit.managers
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
("scrobbles", "0044_scrobble_geo_location_alter_scrobble_media_type"),
]
operations = [
migrations.CreateModel(
name="WebPage",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
),
),
(
"title",
models.CharField(blank=True, max_length=255, null=True),
),
(
"run_time_seconds",
models.IntegerField(blank=True, null=True),
),
(
"run_time_ticks",
models.PositiveBigIntegerField(blank=True, null=True),
),
(
"uuid",
models.UUIDField(
blank=True,
default=uuid.uuid4,
editable=False,
null=True,
),
),
("url", models.URLField(max_length=500)),
(
"genre",
taggit.managers.TaggableManager(
blank=True,
help_text="A comma-separated list of tags.",
through="scrobbles.ObjectWithGenres",
to="scrobbles.Genre",
verbose_name="Tags",
),
),
],
options={
"abstract": False,
},
),
]

View File

@ -0,0 +1,65 @@
import requests
import logging
from typing import Dict
from uuid import uuid4
from django.contrib.auth import get_user_model
from django.conf import settings
from django.db import models
from django.urls import reverse
from scrobbles.mixins import ScrobblableMixin
logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True}
User = get_user_model()
class WebPage(ScrobblableMixin):
COMPLETION_PERCENT = getattr(settings, "WEBSITE_COMPLETION_PERCENT", 100)
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
url = models.URLField(max_length=500)
def __str__(self):
if self.title:
return self.title
return self.domain
@property
def domain(self):
self.url.split("//")[-1].split("/")[0]
def get_absolute_url(self):
return reverse(
"locations:geo_location_detail", kwargs={"slug": self.uuid}
)
def _update_title_from_web(self, force=False):
headers = {
"headers": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0"
}
raw_text = requests.get(self.url, headers=headers).text
if not self.title or force:
self.title = raw_text[
raw_text.find("<title>") + 7 : raw_text.find("</title>")
]
self.save(update_fields=["title"])
@classmethod
def find_or_create(cls, data_dict: Dict) -> "GeoLocation":
"""Given a data dict from an manual URL scrobble, does the heavy lifting of looking up
the url, creating if if doesn't exist yet.
"""
# TODO Add constants for all these data keys
if "url" not in data_dict.keys():
logger.error("No url in data dict")
return
webpage = cls.objects.filter(url=data_dict.get("url")).first()
if not webpage:
webpage = cls.objects.create(url=data_dict.get("url"))
webpage._update_title_from_web()
return webpage

View File

@ -107,6 +107,7 @@ INSTALLED_APPS = [
"boardgames", "boardgames",
"videogames", "videogames",
"locations", "locations",
"webpages",
"mathfilters", "mathfilters",
"rest_framework", "rest_framework",
"allauth", "allauth",

View File

@ -115,6 +115,7 @@ INSTALLED_APPS = [
"boardgames", "boardgames",
"videogames", "videogames",
"locations", "locations",
"webpages",
"mathfilters", "mathfilters",
"rest_framework", "rest_framework",
"allauth", "allauth",