Преглед на файлове

[tasks] Implement todoist webhooks #8479861260

Colin Powell преди 8 месеца
родител
ревизия
0c8a486b6a
променени са 4 файла, в които са добавени 108 реда и са изтрити 9 реда
  1. 52 0
      vrobbler/apps/scrobbles/scrobblers.py
  2. 8 0
      vrobbler/apps/tasks/constants.py
  3. 12 3
      vrobbler/apps/tasks/models.py
  4. 36 6
      vrobbler/apps/tasks/webhooks.py

+ 52 - 0
vrobbler/apps/scrobbles/scrobblers.py

@@ -27,6 +27,10 @@ from scrobbles.constants import (
     SCROBBLE_CONTENT_URLS,
 )
 from tasks.models import Task
+from vrobbler.apps.tasks.constants import (
+    TODOIST_TITLE_PREFIX_LABELS,
+    TODOIST_TITLE_SUFFIX_LABELS,
+)
 from webpages.models import WebPage
 
 logger = logging.getLogger(__name__)
@@ -306,6 +310,54 @@ def manual_scrobble_from_url(url: str, user_id: int) -> Scrobble:
     return eval(scrobble_fn)(item_id, user_id)
 
 
+def todoist_scrobble_task_finish(todoist_task: dict, user_id: int) -> Scrobble:
+    scrobble = Scrobble.obejcts.filter(
+        user_id=user_id, logdata__todoist_id=todoist_task.get("todoist_id")
+    )
+
+    if not scrobble.in_progress or scrobble.played_to_completion:
+        logger.warning(
+            "[todoist_scrobble_task_finish] todoist webhook finish called on finished task"
+        )
+
+    scrobble.stop(force_finish=True)
+
+    return scrobble
+
+
+def todoist_scrobble_task(todoist_task: dict, user_id: int) -> Scrobble:
+
+    for label in todoist_task["todoist_label_list"]:
+        if label in TODOIST_TITLE_PREFIX_LABELS:
+            prefix = label
+        if label in TODOIST_TITLE_SUFFIX_LABELS:
+            suffix = label
+
+    title = " ".join([prefix.capitalize(), suffix.capitalize()])
+
+    task = Task.find_or_create(title)
+
+    scrobble_dict = {
+        "user_id": user_id,
+        "timestamp": todoist_task.get("timestamp_utc", timezone.now()),
+        "playback_position_seconds": 0,
+        "source": "Todoist Webhook",
+        "log": todoist_task,
+    }
+
+    logger.info(
+        "[todoist_scrobble_task] task scrobble request received",
+        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):
     source_id = re.findall("\d+", url)[0]
 

+ 8 - 0
vrobbler/apps/tasks/constants.py

@@ -0,0 +1,8 @@
+TODOIST_TITLE_PREFIX_LABELS = ["work", "home", "personal"]
+TODOIST_TITLE_SUFFIX_LABELS = [
+    "project",
+    "errand",
+    "chores",
+    "admin",
+    "meeting",
+]

+ 12 - 3
vrobbler/apps/tasks/models.py

@@ -1,5 +1,5 @@
 from dataclasses import dataclass
-from enum import Enum
+from datetime import datetime
 from typing import Optional
 
 from django.apps import apps
@@ -16,10 +16,14 @@ TODOIST_TASK_URL = "https://app.todoist.com/app/task/{id}"
 @dataclass
 class TaskLogData(LongPlayLogData):
     description: Optional[str] = None
+    title: Optional[str] = None
     project: Optional[str] = None
-    source_id: Optional[str] = None
+    todoist_id: Optional[str] = None
+    todoist_event: Optional[str] = None
+    todoist_type: Optional[str] = None
     serial_scrobble_id: Optional[int] = None
     long_play_complete: Optional[bool] = None
+    timestamp_utc: Optional[datetime] = None
 
 
 class Task(LongPlayScrobblableMixin):
@@ -53,7 +57,12 @@ class Task(LongPlayScrobblableMixin):
 
     @classmethod
     def find_or_create(cls, title: str) -> "Task":
-        return cls.objects.get_or_create(title=title)[0]
+        task, created = cls.objects.get_or_create(title=title)
+        if created:
+            task.run_time_seconds = 1800
+            task.save(update_fields=["run_time_seconds"])
+
+        return task
 
     def scrobbles(self, user_id):
         Scrobble = apps.get_model("scrobbles", "Scrobble")

+ 36 - 6
vrobbler/apps/tasks/webhooks.py

@@ -1,10 +1,15 @@
 import logging
 
+import pendulum
 from django.views.decorators.csrf import csrf_exempt
 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 (
+    todoist_scrobble_task,
+    todoist_scrobble_task_finish,
+)
 
 logger = logging.getLogger(__name__)
 
@@ -18,22 +23,47 @@ def todoist_webhook(request):
         "[todoist_webhook] called",
         extra={"post_data": post_data},
     )
+    # ["inprogress","project","work"],
+    # :item:updated,
+    todoist_type, todoist_event = post_data.get("event_name").split(":")
+    todoist_task = {
+        "todoist_id": post_data.get("id"),
+        "todoist_label_list": post_data.get("event_data", {}).get("labels"),
+        "todoist_type": todoist_type,
+        "todoist_event": todoist_event,
+        "title": post_data.get("content"),
+        "description": post_data.get("description"),
+        "timestamp_utc": pendulum.parse(
+            post_data.get("event_data", {}).get("updated_at")
+        ),
+    }
 
-    # Disregard progress updates
-    if in_progress and is_music:
+    if todoist_task["todoist_type"] != "item" or todoist_task[
+        "todoist_event"
+    ] not in [
+        "updated",
+        "completed",
+    ]:
         logger.info(
-            "[jellyfin_webhook] ignoring update of music in progress",
-            extra={"post_data": post_data},
+            "[todoist_webhook] ignoring wrong todoist type or event",
+            extra={
+                "todoist_type": todoist_task["todoist_type"],
+                "todoist_event": todoist_task["todoist_event"],
+            },
         )
         return Response({}, status=status.HTTP_304_NOT_MODIFIED)
 
-    scrobble = todoist_scrobble_task(post_data, request.user.id)
+    scrobble = None
+    if "inprogress" in todoist_task["todoist_label_list"]:
+        scrobble = todoist_scrobble_task(todoist_task, request.user.id)
+    if "completed" in todoist_task["todoist_event"]:
+        scrobble = todoist_scrobble_task_finish(todoist_task, request.user.id)
 
     if not scrobble:
         return Response({}, status=status.HTTP_400_BAD_REQUEST)
 
     logger.info(
-        "[jellyfin_webhook] finished",
+        "[todoist_webhook] finished",
         extra={"scrobble_id": scrobble.id},
     )
     return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)