From c871087496dd796f36a017fb0f8185b7244c6b76 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sun, 8 Jun 2025 01:09:03 -0400 Subject: [PATCH] [tasks] First try at adding an emacs webhook --- vrobbler/apps/scrobbles/scrobblers.py | 89 +++++++++++++++++++++++++++ vrobbler/apps/scrobbles/urls.py | 9 ++- vrobbler/apps/scrobbles/views.py | 18 +++--- vrobbler/apps/tasks/webhooks.py | 56 ++++++++++++++++- 4 files changed, 160 insertions(+), 12 deletions(-) diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index bc079c7..47d0065 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -500,6 +500,95 @@ def todoist_scrobble_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 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=task_data.get("source"), + task=task, + ).last() + print(in_progress_scrobble) + + 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( + "[todoist_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": emacs_task["emacs_id"], + }, + ) + in_progress_scrobble.stop(timestamp=timestamp, force_finish=True) + return in_progress_scrobble + + if in_progress_scrobble: + return in_progress_scrobble + + 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] diff --git a/vrobbler/apps/scrobbles/urls.py b/vrobbler/apps/scrobbles/urls.py index d8c162e..50ae296 100644 --- a/vrobbler/apps/scrobbles/urls.py +++ b/vrobbler/apps/scrobbles/urls.py @@ -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" @@ -63,6 +63,13 @@ urlpatterns = [ views.ScrobbleImportListView.as_view(), name="import-detail", ), + path("webhook/emacs/", emacs_webhook, name="emacs_webhook"), + path("export/", views.export, name="export"), + path( + "imports/", + views.ScrobbleImportListView.as_view(), + name="import-detail", + ), path( "imports/tsv//", views.ScrobbleTSVImportDetailView.as_view(), diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 48d16c5..9d249df 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -525,6 +525,7 @@ def gps_webhook(request): @csrf_exempt +@permission_classes([IsAuthenticated]) @api_view(["POST"]) def emacs_webhook(request): try: @@ -539,18 +540,19 @@ def emacs_webhook(request): "user_id": 1, }, ) + data_dict["source"] = "orgmode" + data_dict["source_id"] = data_dict.pop("emacs_id") - # TODO Fix this so we have to authenticate! - user_id = 1 - if request.user.id: - user_id = request.user.id + user_id = request.user.id + if not user_id: + user_id = 1 - # scrobble = gpslogger_scrobble_location(data_dict, user_id) + scrobble = emacs_scrobble_task(data_dict, user_id) - # if not scrobble: - # return Response({}, status=status.HTTP_200_OK) + if not scrobble: + return Response({"data": data_dict}, status=status.HTTP_200_OK) - return Response({"post_data": post_data}, status=status.HTTP_200_OK) + return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) @csrf_exempt diff --git a/vrobbler/apps/tasks/webhooks.py b/vrobbler/apps/tasks/webhooks.py index 0d9c793..0cf3f66 100644 --- a/vrobbler/apps/tasks/webhooks.py +++ b/vrobbler/apps/tasks/webhooks.py @@ -1,16 +1,16 @@ 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, todoist_scrobble_task, todoist_scrobble_update_task, ) -from profiles.models import UserProfile logger = logging.getLogger(__name__) @@ -29,7 +29,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 +118,53 @@ 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): + post_data = request.data + logger.info( + "[emacs_webhook] called", + extra={"post_data": post_data}, + ) + print(post_data) + emacs_task = post_data + + task_started = emacs_task.get("state") == "STRT" + task_stopped = emacs_task.get("state") == "DONE" + + user_id = ( + UserProfile.objects.filter(todoist_user_id=post_data.get("user_id")) + .first() + .user_id + ) + + scrobble = None + if emacs_task: + scrobble = emacs_scrobble_task( + emacs_task, user_id, stopped=task_stopped + ) + + if scrobble and task_started: + scrobble = emacs_scrobble_update_task( + emacs_task.get("notes"), + user_id, + ) + + if not scrobble: + logger.info( + "[todoist_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( + "[todoist_webhook] finished", + extra={"scrobble_id": scrobble.id}, + ) + return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)