Add basic location tracking
This commit is contained in:
0
vrobbler/apps/locations/__init__.py
Normal file
0
vrobbler/apps/locations/__init__.py
Normal file
35
vrobbler/apps/locations/admin.py
Normal file
35
vrobbler/apps/locations/admin.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from locations.models import GeoLocation, RawGeoLocation
|
||||||
|
|
||||||
|
from scrobbles.admin import ScrobbleInline
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(GeoLocation)
|
||||||
|
class GeoLocationAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "created"
|
||||||
|
list_display = (
|
||||||
|
"lat",
|
||||||
|
"lon",
|
||||||
|
"title",
|
||||||
|
"altitude",
|
||||||
|
)
|
||||||
|
ordering = (
|
||||||
|
"lat",
|
||||||
|
"lon",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(RawGeoLocation)
|
||||||
|
class RawGeoLocationAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "created"
|
||||||
|
list_display = (
|
||||||
|
"lat",
|
||||||
|
"lon",
|
||||||
|
"altitude",
|
||||||
|
"speed",
|
||||||
|
)
|
||||||
|
ordering = (
|
||||||
|
"lat",
|
||||||
|
"lon",
|
||||||
|
)
|
||||||
77
vrobbler/apps/locations/migrations/0001_initial.py
Normal file
77
vrobbler/apps/locations/migrations/0001_initial.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-11-21 23:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django_extensions.db.fields
|
||||||
|
import taggit.managers
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="GeoLocation",
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("lat", models.FloatField()),
|
||||||
|
("lon", models.FloatField()),
|
||||||
|
("altitude", models.FloatField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"genre",
|
||||||
|
taggit.managers.TaggableManager(
|
||||||
|
blank=True,
|
||||||
|
help_text="A comma-separated list of tags.",
|
||||||
|
through="scrobbles.ObjectWithGenres",
|
||||||
|
to="scrobbles.Genre",
|
||||||
|
verbose_name="Tags",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"unique_together": {("lat", "lon", "altitude")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
58
vrobbler/apps/locations/migrations/0002_rawgeolocation.py
Normal file
58
vrobbler/apps/locations/migrations/0002_rawgeolocation.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-11-22 00:13
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django_extensions.db.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("locations", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="RawGeoLocation",
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("lat", models.FloatField()),
|
||||||
|
("lon", models.FloatField()),
|
||||||
|
("altitude", models.FloatField(blank=True, null=True)),
|
||||||
|
("speed", models.FloatField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"get_latest_by": "modified",
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-11-22 23:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("locations", "0002_rawgeolocation"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="rawgeolocation",
|
||||||
|
name="timestamp",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
vrobbler/apps/locations/migrations/__init__.py
Normal file
0
vrobbler/apps/locations/migrations/__init__.py
Normal file
88
vrobbler/apps/locations/models.py
Normal file
88
vrobbler/apps/locations/models.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
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 django_extensions.db.models import TimeStampedModel
|
||||||
|
from scrobbles.mixins import ScrobblableMixin
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
BNULL = {"blank": True, "null": True}
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class GeoLocation(ScrobblableMixin):
|
||||||
|
COMPLETION_PERCENT = getattr(settings, "LOCATION_COMPLETION_PERCENT", 100)
|
||||||
|
|
||||||
|
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||||
|
lat = models.FloatField()
|
||||||
|
lon = models.FloatField()
|
||||||
|
altitude = models.FloatField(**BNULL)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = [["lat", "lon", "altitude"]]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.lat} x {self.lon}"
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse(
|
||||||
|
"locations:geo_location_detail", kwargs={"slug": self.uuid}
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def truncated_lat(self):
|
||||||
|
return float(str(self.lat)[:-3])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def truncated_lan(self):
|
||||||
|
return float(str(self.lon)[:-3])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_or_create(cls, data_dict: Dict) -> "GeoLocation":
|
||||||
|
"""Given a data dict from GPSLogger, does the heavy lifting of looking up
|
||||||
|
the location, creating if if doesn't exist yet.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO Add constants for all these data keys
|
||||||
|
if "lat" not in data_dict.keys() or "lon" not in data_dict.keys():
|
||||||
|
logger.error("No lat or lon keys in data dict")
|
||||||
|
return
|
||||||
|
|
||||||
|
lat_int, lat_places = data_dict.get("lat", "").split(".")
|
||||||
|
lon_int, lon_places = data_dict.get("lon", "").split(".")
|
||||||
|
alt_int, alt_places = data_dict.get("alt", "").split(".")
|
||||||
|
|
||||||
|
truncated_lat = lat_places[0:4]
|
||||||
|
truncated_lon = lon_places[0:4]
|
||||||
|
truncated_alt = alt_places[0:3]
|
||||||
|
|
||||||
|
data_dict["lat"] = float(".".join([lat_int, truncated_lat]))
|
||||||
|
data_dict["lon"] = float(".".join([lon_int, truncated_lon]))
|
||||||
|
data_dict["altitude"] = float(".".join([alt_int, truncated_alt]))
|
||||||
|
|
||||||
|
location = cls.objects.filter(
|
||||||
|
lat=data_dict.get("lat"),
|
||||||
|
lon=data_dict.get("lon"),
|
||||||
|
altitude=data_dict.get("alt"),
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not location:
|
||||||
|
location = cls.objects.create(
|
||||||
|
lat=data_dict.get("lat"),
|
||||||
|
lon=data_dict.get("lon"),
|
||||||
|
altitude=data_dict.get("alt"),
|
||||||
|
)
|
||||||
|
return location
|
||||||
|
|
||||||
|
|
||||||
|
class RawGeoLocation(TimeStampedModel):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
lat = models.FloatField()
|
||||||
|
lon = models.FloatField()
|
||||||
|
altitude = models.FloatField(**BNULL)
|
||||||
|
speed = models.FloatField(**BNULL)
|
||||||
|
timestamp = models.DateTimeField(**BNULL)
|
||||||
1
vrobbler/apps/locations/views.py
Normal file
1
vrobbler/apps/locations/views.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-11-21 23:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("locations", "0001_initial"),
|
||||||
|
("scrobbles", "0043_scrobbledpage"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="geo_location",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
to="locations.geolocation",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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"),
|
||||||
|
],
|
||||||
|
default="Video",
|
||||||
|
max_length=14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -34,6 +34,7 @@ from sports.models import SportEvent
|
|||||||
from videogames import retroarch
|
from videogames import retroarch
|
||||||
from videogames.models import VideoGame
|
from videogames.models import VideoGame
|
||||||
from videos.models import Series, Video
|
from videos.models import Series, Video
|
||||||
|
from locations.models import GeoLocation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
@ -469,6 +470,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
BOOK = "Book", "Book"
|
BOOK = "Book", "Book"
|
||||||
VIDEO_GAME = "VideoGame", "Video game"
|
VIDEO_GAME = "VideoGame", "Video game"
|
||||||
BOARD_GAME = "BoardGame", "Board game"
|
BOARD_GAME = "BoardGame", "Board game"
|
||||||
|
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
||||||
|
|
||||||
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)
|
||||||
@ -486,6 +488,9 @@ class Scrobble(TimeStampedModel):
|
|||||||
board_game = models.ForeignKey(
|
board_game = models.ForeignKey(
|
||||||
BoardGame, on_delete=models.DO_NOTHING, **BNULL
|
BoardGame, on_delete=models.DO_NOTHING, **BNULL
|
||||||
)
|
)
|
||||||
|
geo_location = models.ForeignKey(
|
||||||
|
GeoLocation, 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
|
||||||
)
|
)
|
||||||
@ -612,6 +617,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
@property
|
@property
|
||||||
def can_be_updated(self) -> bool:
|
def can_be_updated(self) -> bool:
|
||||||
updatable = True
|
updatable = True
|
||||||
|
|
||||||
if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values():
|
if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values():
|
||||||
logger.info(f"No - Long play media")
|
logger.info(f"No - Long play media")
|
||||||
updatable = False
|
updatable = False
|
||||||
@ -621,6 +627,17 @@ class Scrobble(TimeStampedModel):
|
|||||||
if self.is_stale:
|
if self.is_stale:
|
||||||
logger.info(f"No - stale - {self.id} - {self.source}")
|
logger.info(f"No - stale - {self.id} - {self.source}")
|
||||||
updatable = False
|
updatable = False
|
||||||
|
if self.media_obj.__class__.__name__ in ["GeoLocation"]:
|
||||||
|
logger.info(f"Calculate proximity to last scrobble")
|
||||||
|
if self.previous:
|
||||||
|
same_lat = self.previous.media_obj.lat == self.media_obj.lat
|
||||||
|
same_lon = self.previous.media_obj.lon == self.media_obj.lon
|
||||||
|
if same_lat and same_lon: # We have moved
|
||||||
|
logger.info("Yes - We're in the same place!")
|
||||||
|
updatable = True
|
||||||
|
else:
|
||||||
|
logger.info("No - We've moved, start a new scrobble")
|
||||||
|
updatable = False
|
||||||
return updatable
|
return updatable
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -640,6 +657,8 @@ class Scrobble(TimeStampedModel):
|
|||||||
media_obj = self.video_game
|
media_obj = self.video_game
|
||||||
if self.board_game:
|
if self.board_game:
|
||||||
media_obj = self.board_game
|
media_obj = self.board_game
|
||||||
|
if self.geo_location:
|
||||||
|
media_obj = self.geo_location
|
||||||
return media_obj
|
return media_obj
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -648,7 +667,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_or_update(
|
def create_or_update(
|
||||||
cls, media, user_id: int, scrobble_data: dict
|
cls, media, user_id: int, scrobble_data: dict, **kwargs
|
||||||
) -> "Scrobble":
|
) -> "Scrobble":
|
||||||
|
|
||||||
media_class = media.__class__.__name__
|
media_class = media.__class__.__name__
|
||||||
@ -674,6 +693,9 @@ 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 == "GeoLocation":
|
||||||
|
media_query = models.Q(geo_location=media)
|
||||||
|
scrobble_data["geo_location_id"] = media.id
|
||||||
|
|
||||||
scrobble = (
|
scrobble = (
|
||||||
cls.objects.filter(
|
cls.objects.filter(
|
||||||
@ -683,6 +705,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
.order_by("-modified")
|
.order_by("-modified")
|
||||||
.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__
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import pendulum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from boardgames.bgg import lookup_boardgame_from_bgg
|
from boardgames.bgg import lookup_boardgame_from_bgg
|
||||||
@ -22,6 +23,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
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -242,3 +244,40 @@ 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 gpslogger_scrobble_location(
|
||||||
|
data_dict: dict, user_id: Optional[int]
|
||||||
|
) -> Optional[Scrobble]:
|
||||||
|
# Save the data coming in
|
||||||
|
if not user_id:
|
||||||
|
user_id = 1 # TODO fix authing the end point to get user
|
||||||
|
raw_location = RawGeoLocation.objects.create(
|
||||||
|
user_id=user_id,
|
||||||
|
lat=data_dict.get("lat"),
|
||||||
|
lon=data_dict.get("lon"),
|
||||||
|
altitude=data_dict.get("alt"),
|
||||||
|
speed=data_dict.get("spd"),
|
||||||
|
timestamp=pendulum.parse(data_dict.get("time", timezone.now())),
|
||||||
|
)
|
||||||
|
|
||||||
|
location = GeoLocation.find_or_create(data_dict)
|
||||||
|
|
||||||
|
# Now we run off a scrobble
|
||||||
|
playback_seconds = 1
|
||||||
|
extra_data = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"timestamp": pendulum.parse(data_dict.get("time", timezone.now())),
|
||||||
|
"playback_position_seconds": playback_seconds,
|
||||||
|
"source": "GPSLogger",
|
||||||
|
}
|
||||||
|
|
||||||
|
scrobble = Scrobble.create_or_update(location, user_id, extra_data)
|
||||||
|
provider = f"gps source - {data_dict.get('prov')}"
|
||||||
|
if scrobble.notes:
|
||||||
|
scrobble.notes = scrobble.notes + f"\n{provider}"
|
||||||
|
else:
|
||||||
|
scrobble.notes = provider
|
||||||
|
scrobble.save(update_fields=["notes"])
|
||||||
|
|
||||||
|
return scrobble
|
||||||
|
|||||||
@ -34,6 +34,11 @@ urlpatterns = [
|
|||||||
views.lastfm_import,
|
views.lastfm_import,
|
||||||
name="lastfm-import",
|
name="lastfm-import",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"webhook/gps/",
|
||||||
|
views.gps_webhook,
|
||||||
|
name="gps-webhook",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"webhook/jellyfin/",
|
"webhook/jellyfin/",
|
||||||
views.jellyfin_webhook,
|
views.jellyfin_webhook,
|
||||||
|
|||||||
@ -45,6 +45,7 @@ from scrobbles.models import (
|
|||||||
Scrobble,
|
Scrobble,
|
||||||
)
|
)
|
||||||
from scrobbles.scrobblers import (
|
from scrobbles.scrobblers import (
|
||||||
|
gpslogger_scrobble_location,
|
||||||
jellyfin_scrobble_track,
|
jellyfin_scrobble_track,
|
||||||
jellyfin_scrobble_video,
|
jellyfin_scrobble_video,
|
||||||
manual_scrobble_board_game,
|
manual_scrobble_board_game,
|
||||||
@ -366,6 +367,28 @@ def mopidy_webhook(request):
|
|||||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
@api_view(["POST"])
|
||||||
|
def gps_webhook(request):
|
||||||
|
try:
|
||||||
|
data_dict = json.loads(request.data)
|
||||||
|
except TypeError:
|
||||||
|
data_dict = request.data
|
||||||
|
|
||||||
|
# For making things easier to build new input processors
|
||||||
|
if getattr(settings, "DUMP_REQUEST_DATA", False):
|
||||||
|
json_data = json.dumps(data_dict, indent=4)
|
||||||
|
logger.debug(f"{json_data}")
|
||||||
|
|
||||||
|
scrobble = gpslogger_scrobble_location(data_dict, request.user.id)
|
||||||
|
|
||||||
|
if not scrobble:
|
||||||
|
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
@api_view(["POST"])
|
@api_view(["POST"])
|
||||||
|
|||||||
@ -114,6 +114,7 @@ INSTALLED_APPS = [
|
|||||||
"books",
|
"books",
|
||||||
"boardgames",
|
"boardgames",
|
||||||
"videogames",
|
"videogames",
|
||||||
|
"locations",
|
||||||
"mathfilters",
|
"mathfilters",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"allauth",
|
"allauth",
|
||||||
|
|||||||
Reference in New Issue
Block a user