[tasks] Implement todoist webhooks #8479861260

This commit is contained in:
2024-10-15 14:12:55 -04:00
parent 7954765b73
commit 0c8a486b6a
4 changed files with 108 additions and 9 deletions

View File

@ -27,6 +27,10 @@ from scrobbles.constants import (
SCROBBLE_CONTENT_URLS, SCROBBLE_CONTENT_URLS,
) )
from tasks.models import Task from tasks.models import Task
from vrobbler.apps.tasks.constants import (
TODOIST_TITLE_PREFIX_LABELS,
TODOIST_TITLE_SUFFIX_LABELS,
)
from webpages.models import WebPage from webpages.models import WebPage
logger = logging.getLogger(__name__) 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) 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): def manual_scrobble_task(url: str, user_id: int):
source_id = re.findall("\d+", url)[0] source_id = re.findall("\d+", url)[0]

View File

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

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from datetime import datetime
from typing import Optional from typing import Optional
from django.apps import apps from django.apps import apps
@ -16,10 +16,14 @@ TODOIST_TASK_URL = "https://app.todoist.com/app/task/{id}"
@dataclass @dataclass
class TaskLogData(LongPlayLogData): class TaskLogData(LongPlayLogData):
description: Optional[str] = None description: Optional[str] = None
title: Optional[str] = None
project: 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 serial_scrobble_id: Optional[int] = None
long_play_complete: Optional[bool] = None long_play_complete: Optional[bool] = None
timestamp_utc: Optional[datetime] = None
class Task(LongPlayScrobblableMixin): class Task(LongPlayScrobblableMixin):
@ -53,7 +57,12 @@ class Task(LongPlayScrobblableMixin):
@classmethod @classmethod
def find_or_create(cls, title: str) -> "Task": 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): def scrobbles(self, user_id):
Scrobble = apps.get_model("scrobbles", "Scrobble") Scrobble = apps.get_model("scrobbles", "Scrobble")

View File

@ -1,10 +1,15 @@
import logging import logging
import pendulum
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from scrobbles.scrobblers import (
todoist_scrobble_task,
todoist_scrobble_task_finish,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -18,22 +23,47 @@ def todoist_webhook(request):
"[todoist_webhook] called", "[todoist_webhook] called",
extra={"post_data": post_data}, 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 todoist_task["todoist_type"] != "item" or todoist_task[
if in_progress and is_music: "todoist_event"
] not in [
"updated",
"completed",
]:
logger.info( logger.info(
"[jellyfin_webhook] ignoring update of music in progress", "[todoist_webhook] ignoring wrong todoist type or event",
extra={"post_data": post_data}, extra={
"todoist_type": todoist_task["todoist_type"],
"todoist_event": todoist_task["todoist_event"],
},
) )
return Response({}, status=status.HTTP_304_NOT_MODIFIED) 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: if not scrobble:
return Response({}, status=status.HTTP_400_BAD_REQUEST) return Response({}, status=status.HTTP_400_BAD_REQUEST)
logger.info( logger.info(
"[jellyfin_webhook] finished", "[todoist_webhook] finished",
extra={"scrobble_id": scrobble.id}, extra={"scrobble_id": scrobble.id},
) )
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)