From 0c8a486b6a68980af042cb69c645e09622548963 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Tue, 15 Oct 2024 14:12:55 -0400 Subject: [PATCH] [tasks] Implement todoist webhooks #8479861260 --- vrobbler/apps/scrobbles/scrobblers.py | 52 +++++++++++++++++++++++++++ vrobbler/apps/tasks/constants.py | 8 +++++ vrobbler/apps/tasks/models.py | 15 ++++++-- vrobbler/apps/tasks/webhooks.py | 42 ++++++++++++++++++---- 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 vrobbler/apps/tasks/constants.py diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index 28669e2..31f03b4 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/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] diff --git a/vrobbler/apps/tasks/constants.py b/vrobbler/apps/tasks/constants.py new file mode 100644 index 0000000..e06f16a --- /dev/null +++ b/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", +] diff --git a/vrobbler/apps/tasks/models.py b/vrobbler/apps/tasks/models.py index 5702781..b421c79 100644 --- a/vrobbler/apps/tasks/models.py +++ b/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") diff --git a/vrobbler/apps/tasks/webhooks.py b/vrobbler/apps/tasks/webhooks.py index a474696..18fa9bb 100644 --- a/vrobbler/apps/tasks/webhooks.py +++ b/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)