Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a0c4f69f0 | |||
| 356e579558 | |||
| e980e3c5c9 | |||
| 8773542099 | |||
| 70378c9968 | |||
| c871087496 | |||
| 059d7780a0 |
@ -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(
|
||||
@ -48,13 +50,23 @@ class BrickSet(LongPlayScrobblableMixin):
|
||||
def get_absolute_url(self):
|
||||
return reverse("bricksets:brickset_detail", kwargs={"slug": self.uuid})
|
||||
|
||||
def __str__(self) -> str:
|
||||
name = str(self.title)
|
||||
if not self.title:
|
||||
name = str(self.number)
|
||||
return name
|
||||
|
||||
@property
|
||||
def logdata_cls(self):
|
||||
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(
|
||||
@ -635,6 +635,14 @@ class Scrobble(TimeStampedModel):
|
||||
|
||||
for scrobble in scrobble_qs:
|
||||
scrobbles_by_type[scrobble.media_type].append(scrobble)
|
||||
if not scrobbles_by_type.get(scrobble.media_type + "_count"):
|
||||
scrobbles_by_type[scrobble.media_type + "_count"] = 0
|
||||
scrobbles_by_type[scrobble.media_type + "_count"] += 1
|
||||
if not scrobbles_by_type.get(scrobble.media_type + "_time"):
|
||||
scrobbles_by_type[scrobble.media_type + "_time"] = 0
|
||||
scrobbles_by_type[scrobble.media_type + "_time"] += int(
|
||||
(scrobble.elapsed_time)
|
||||
)
|
||||
|
||||
return scrobbles_by_type
|
||||
|
||||
@ -1011,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)
|
||||
@ -500,6 +501,151 @@ def todoist_scrobble_task(
|
||||
return scrobble
|
||||
|
||||
|
||||
def emacs_scrobble_update_task(
|
||||
emacs_id: str, emacs_notes: dict, user_id: int
|
||||
) -> Optional[Scrobble]:
|
||||
scrobble = Scrobble.objects.filter(
|
||||
in_progress=True,
|
||||
user_id=user_id,
|
||||
log__source_id=emacs_id,
|
||||
log__source="emacs",
|
||||
).first()
|
||||
|
||||
if not scrobble:
|
||||
logger.info(
|
||||
"[emacs_scrobble_update_task] no task found",
|
||||
extra={
|
||||
"emacs_notes": emacs_notes,
|
||||
"user_id": user_id,
|
||||
"media_type": Scrobble.MediaType.TASK,
|
||||
},
|
||||
)
|
||||
return
|
||||
|
||||
notes_updated = False
|
||||
for note in emacs_notes:
|
||||
existing_note_ts = [
|
||||
n.get("timestamp") for n in scrobble.log.get("notes", [])
|
||||
]
|
||||
if not scrobble.log.get('notes"'):
|
||||
scrobble.log["notes"] = []
|
||||
if note.get("timestamp") not in existing_note_ts:
|
||||
scrobble.log["notes"].append(
|
||||
{note.get("timestamp"): note.get("content")}
|
||||
)
|
||||
notes_updated = True
|
||||
|
||||
if notes_updated:
|
||||
scrobble.save(update_fields=["log"])
|
||||
|
||||
logger.info(
|
||||
"[emacs_scrobble_update_task] emacs note added",
|
||||
extra={
|
||||
"emacs_note": emacs_notes,
|
||||
"user_id": user_id,
|
||||
"media_type": Scrobble.MediaType.TASK,
|
||||
},
|
||||
)
|
||||
|
||||
return scrobble
|
||||
|
||||
|
||||
def emacs_scrobble_task(
|
||||
task_data: dict,
|
||||
user_id: int,
|
||||
started: bool = False,
|
||||
stopped: bool = False,
|
||||
) -> Scrobble | None:
|
||||
|
||||
prefix = ""
|
||||
suffix = ""
|
||||
|
||||
source_id = task_data.get("source_id")
|
||||
for label in task_data.get("labels"):
|
||||
if label in TODOIST_TITLE_PREFIX_LABELS:
|
||||
prefix = label
|
||||
if label in TODOIST_TITLE_SUFFIX_LABELS:
|
||||
suffix = label
|
||||
|
||||
if not prefix and not suffix:
|
||||
logger.warning(
|
||||
"Missing a prefix and suffix tag for task",
|
||||
extra={"emacs_scrobble_task": task_data},
|
||||
)
|
||||
|
||||
title = " ".join([prefix.capitalize(), suffix.capitalize()])
|
||||
task = Task.find_or_create(title)
|
||||
|
||||
timestamp = pendulum.parse(task_data.get("updated_at", timezone.now()))
|
||||
in_progress_scrobble = Scrobble.objects.filter(
|
||||
user_id=user_id,
|
||||
in_progress=True,
|
||||
log__source_id=source_id,
|
||||
log__source="orgmode",
|
||||
task=task,
|
||||
).last()
|
||||
|
||||
if not in_progress_scrobble and stopped:
|
||||
logger.info(
|
||||
"[emacs_scrobble_task] cannot stop already stopped task",
|
||||
extra={
|
||||
"emacs_id": source_id,
|
||||
},
|
||||
)
|
||||
return
|
||||
|
||||
if in_progress_scrobble and started:
|
||||
logger.info(
|
||||
"[emacs_scrobble_task] cannot start already started task",
|
||||
extra={
|
||||
"emacs_id": source_id,
|
||||
},
|
||||
)
|
||||
return in_progress_scrobble
|
||||
|
||||
# Finish an in-progress scrobble
|
||||
if in_progress_scrobble and stopped:
|
||||
logger.info(
|
||||
"[emacs_scrobble_task] finishing",
|
||||
extra={
|
||||
"emacs_id": source_id,
|
||||
},
|
||||
)
|
||||
in_progress_scrobble.stop(timestamp=timestamp, force_finish=True)
|
||||
return in_progress_scrobble
|
||||
|
||||
if in_progress_scrobble:
|
||||
return in_progress_scrobble
|
||||
|
||||
notes = task_data.pop("notes")
|
||||
if notes:
|
||||
task_data["notes"] = []
|
||||
for note in notes:
|
||||
task_data["notes"].append(
|
||||
{note.get("timestamp"): note.get("content")}
|
||||
)
|
||||
|
||||
scrobble_dict = {
|
||||
"user_id": user_id,
|
||||
"timestamp": timestamp,
|
||||
"playback_position_seconds": 0,
|
||||
"source": "Org-mode",
|
||||
"log": task_data,
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"[emacs_scrobble_task] creating",
|
||||
extra={
|
||||
"task_id": task.id,
|
||||
"user_id": user_id,
|
||||
"scrobble_dict": scrobble_dict,
|
||||
"media_type": Scrobble.MediaType.TASK,
|
||||
},
|
||||
)
|
||||
scrobble = Scrobble.create_or_update(task, user_id, scrobble_dict)
|
||||
return scrobble
|
||||
|
||||
|
||||
def manual_scrobble_task(url: str, user_id: int, action: Optional[str] = None):
|
||||
source_id = re.findall(r"\d+", url)[0]
|
||||
|
||||
@ -708,3 +854,34 @@ 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",
|
||||
"log": {"serial_scrobble_id": ""},
|
||||
}
|
||||
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
|
||||
# TODO Need to check for past scrobbles and auto populate serial scrobble id if possible
|
||||
return Scrobble.create_or_update(brickset, user_id, scrobble_dict)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from django.urls import path
|
||||
from scrobbles import views
|
||||
from tasks.webhooks import todoist_webhook
|
||||
from tasks.webhooks import todoist_webhook, emacs_webhook
|
||||
|
||||
app_name = "scrobbles"
|
||||
|
||||
@ -36,11 +36,6 @@ urlpatterns = [
|
||||
views.web_scrobbler_webhook,
|
||||
name="web-scrobbler-webhook",
|
||||
),
|
||||
path(
|
||||
"webhook/emacs/",
|
||||
views.emacs_webhook,
|
||||
name="emacs-webhook",
|
||||
),
|
||||
path(
|
||||
"webhook/gps/",
|
||||
views.gps_webhook,
|
||||
@ -57,6 +52,13 @@ urlpatterns = [
|
||||
name="mopidy-webhook",
|
||||
),
|
||||
path("webhook/todoist/", todoist_webhook, name="todoist-webhook"),
|
||||
path("webhook/emacs/", emacs_webhook, name="emacs_webhook"),
|
||||
path("export/", views.export, name="export"),
|
||||
path(
|
||||
"imports/",
|
||||
views.ScrobbleImportListView.as_view(),
|
||||
name="import-detail",
|
||||
),
|
||||
path("export/", views.export, name="export"),
|
||||
path(
|
||||
"imports/",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -524,35 +524,6 @@ def gps_webhook(request):
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@api_view(["POST"])
|
||||
def emacs_webhook(request):
|
||||
try:
|
||||
data_dict = json.loads(request.data)
|
||||
except TypeError:
|
||||
data_dict = request.data
|
||||
|
||||
logger.info(
|
||||
"[emacs_webhook] called",
|
||||
extra={
|
||||
"post_data": data_dict,
|
||||
"user_id": 1,
|
||||
},
|
||||
)
|
||||
|
||||
# TODO Fix this so we have to authenticate!
|
||||
user_id = 1
|
||||
if request.user.id:
|
||||
user_id = request.user.id
|
||||
|
||||
# scrobble = gpslogger_scrobble_location(data_dict, user_id)
|
||||
|
||||
# if not scrobble:
|
||||
# return Response({}, status=status.HTTP_200_OK)
|
||||
|
||||
return Response({"post_data": post_data}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["POST"])
|
||||
|
||||
@ -50,7 +50,8 @@ class Task(LongPlayScrobblableMixin):
|
||||
url = ""
|
||||
scrobble = self.scrobbles(user_id).first()
|
||||
if scrobble:
|
||||
url = TODOIST_TASK_URL.format(id=scrobble.logdata.todoist_id)
|
||||
if scrobble.log.get("source") == "todoist":
|
||||
url = TODOIST_TASK_URL.format(id=scrobble.logdata.todist_id)
|
||||
return url
|
||||
|
||||
def subtitle_for_user(self, user_id):
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
import pendulum
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from profiles.models import UserProfile
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from scrobbles.scrobblers import (
|
||||
emacs_scrobble_task,
|
||||
emacs_scrobble_update_task,
|
||||
todoist_scrobble_task,
|
||||
todoist_scrobble_update_task,
|
||||
)
|
||||
from profiles.models import UserProfile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -29,7 +31,7 @@ def todoist_webhook(request):
|
||||
todoist_type, todoist_event = post_data.get("event_name").split(":")
|
||||
event_data = post_data.get("event_data", {})
|
||||
is_item_type = todoist_type == "item"
|
||||
is_note_type = todoist_type == "note"
|
||||
is_note_type = todoist_tyllll = "note"
|
||||
new_labels = event_data.get("labels", [])
|
||||
old_labels = (
|
||||
post_data.get("event_data_extra", {})
|
||||
@ -118,3 +120,54 @@ def todoist_webhook(request):
|
||||
extra={"scrobble_id": scrobble.id},
|
||||
)
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["POST"])
|
||||
def emacs_webhook(request):
|
||||
try:
|
||||
post_data = json.loads(request.data)
|
||||
except TypeError:
|
||||
post_data = request.data
|
||||
logger.info(
|
||||
"[emacs_webhook] called",
|
||||
extra={"post_data": post_data},
|
||||
)
|
||||
task_in_progress = post_data.get("state") == "STRT"
|
||||
task_stopped = post_data.get("state") == "DONE"
|
||||
post_data["source_id"] = post_data.pop("emacs_id")
|
||||
|
||||
# TODO: Figure out why token auth is not working
|
||||
user_id = request.user.id
|
||||
if not user_id:
|
||||
user_id = 1
|
||||
|
||||
scrobble = None
|
||||
if post_data.get("source_id"):
|
||||
scrobble = emacs_scrobble_task(
|
||||
post_data, user_id, started=task_in_progress, stopped=task_stopped
|
||||
)
|
||||
|
||||
if scrobble and task_in_progress and post_data.get("notes"):
|
||||
emacs_scrobble_update_task(
|
||||
post_data.get("source_id"),
|
||||
post_data.get("notes"),
|
||||
user_id,
|
||||
)
|
||||
|
||||
if not scrobble:
|
||||
logger.info(
|
||||
"[emacs_webhook] finished with no note or task found",
|
||||
extra={"scrobble_id": None},
|
||||
)
|
||||
return Response(
|
||||
{"error": "No scrobble found to be updated"},
|
||||
status=status.HTTP_304_NOT_MODIFIED,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[emacs_webhook] finished",
|
||||
extra={"scrobble_id": scrobble.id},
|
||||
)
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<div class="col-md">
|
||||
{% if Track %}
|
||||
<h2>Music</h2>
|
||||
{% with scrobbles=Track %}
|
||||
{% with scrobbles=Track count=Track_count time=Track_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
@ -18,63 +18,70 @@
|
||||
|
||||
{% if Task %}
|
||||
<h2>Latest tasks</h2>
|
||||
{% with scrobbles=Task %}
|
||||
{% with scrobbles=Task count=Task_count time=Task_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if Video %}
|
||||
<h2>Videos</h2>
|
||||
{% with scrobbles=Video %}
|
||||
{% with scrobbles=Video count=Video_count time=Video_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if WebPage %}
|
||||
<h4>Web pages</h4>
|
||||
{% with scrobbles=WebPage %}
|
||||
{% with scrobbles=WebPage count=WebPage_count time=WebPage_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if SportEvent %}
|
||||
<h2>Sports</h2>
|
||||
{% with scrobbles=SportEvent %}
|
||||
{% with scrobbles=SportEvent count=SportEvent_count time=SportEvent_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if PodcastEpisode %}
|
||||
<h2>Latest podcasts</h2>
|
||||
{% with scrobbles=PodcastEpisode %}
|
||||
{% with scrobbles=PodcastEpisode count=PodcastEpisode_count time=PodcastEpisode_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if VideoGame %}
|
||||
<h4>Video games</h4>
|
||||
{% with scrobbles=VideoGame %}
|
||||
{% with scrobbles=VideoGame count=VideoGame_count time=VideoGame_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if BoardGame %}
|
||||
<h4>Board games</h4>
|
||||
{% with scrobbles=BoardGame %}
|
||||
{% with scrobbles=BoardGame count=BoardGame_count time=BoardGame_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% if Beer %}
|
||||
<h4>Beers</h4>
|
||||
{% with scrobbles=Beer %}
|
||||
{% with scrobbles=Beer count=Beer_count time=Beer_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% 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 %}
|
||||
{% with scrobbles=Book count=Book_count time=Book_time %}
|
||||
{% include "scrobbles/_scrobble_table.html" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% load humanize %}
|
||||
{% load naturalduration %}
|
||||
<tr>
|
||||
<tr {% if scrobble.in_progress %}class="in-progress"{% endif %}>
|
||||
<td>{% if scrobble.in_progress %}{{scrobble.media_obj.strings.verb}} now | <a class="right" href="{% url "scrobbles:finish" scrobble.uuid %}">Finish</a>{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
|
||||
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj|truncatechars_html:50}}</a></td>
|
||||
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj|truncatechars_html:45}}</a></td>
|
||||
<td>{{scrobble.elapsed_time|natural_duration}}</td>
|
||||
</tr>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
<div class="tab-pane fade show" id="latest-beers" role="tabpanel"
|
||||
aria-labelledby="latest-beers-tab">
|
||||
<div class="table-responsive">
|
||||
{{count}} scrobble {% if time %}| {{time|natural_duration}}{% endif %}
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
background:rgba(0,0,0,0.4);
|
||||
|
||||
}
|
||||
.in-progress {
|
||||
background: #CCFFFF;
|
||||
}
|
||||
.in-progress a { color: black }
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
<td>{{scrobble.timestamp}}</td>
|
||||
<td><a href="{{scrobble.get_media_source_url}}">{{scrobble.logdata.description}}</a></td>
|
||||
<td>{{scrobble.source}}</td>
|
||||
<td>{{scrobble.log.notes}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user