Todoist-API mit PowerShell
Als treuer Leser meines Blogs weißt du natürlich schon Bescheid: Todoist ist mit Abstand eines meiner wichtigsten Tools und meine persönliche Schaltzentrale, ohne die ich ganz schön aufgeschmissen wäre.
Wie ich schon in meinem Blog-Artikel „Dynamische Templates für Todoist mit PowerShell“ beschrieben habe, gibt es immer wieder Aufgaben, die einem ähnlichen Muster folgen, aber keine klassischen Wiederkehrenden Aufgaben sind. Bisher habe ich für diese Aufgaben – wie im verlinkten Artikel beschrieben – per PowerShell-Skript eine CSV-Datei erstellt, die ich dann in Todoist importiert habe. In meinem diesjährigen Sommerurlaub stand das Projekt „Automatisierung Level 2“ auf dem Plan und ich habe diese Skripts so weiterentwickelt, dass der CSV-Schritt wegfällt. Ab sofort werden direkt Aufgaben in meinem Todoist erstellt, nachdem ich die erforderlichen Parameter im PowerShell-Skript angegeben habe.
In meinem heutigen Blog-Beitrag möchte ich Dir die Todoist API vorstellen und anhand eines Beispiels Schritt für Schritt meine Implementierung erläutern. Ich hoffe, dem ein oder anderen hilft dieser Artikel weiter.
Inhalt
Todoist REST API
Die Dokumentation der Todoist REST API findest Du bei Todoist selbst: https://developer.todoist.com/rest/v1/#overview
Für die Authorisierung mit der API benötigst Du Deinen persönlichen API Token. Diesen findest Du in Deinen Todoist-Einstellungen unter dem Menüpunkt „Integrationen“.
Die Dokumentation ist durchaus brauchbar und zeigt in einem Getting Started Guide die ersten Schritte mit der API. Es lohnt sich auf jeden Fall zumindest diesen Teil durchzulesen. In meinem Anwendungsfall beschränke ich die Integration auf das schreiben von Aufgaben (Tasks), Unteraufgaben (Subtasks) und Kommentaren (Comments).
Mein Beispiel
Als Beispiel habe ich mir ein – hoffentlich – sehr anschauliches und erfreuliches herausgepickt. Nämlich Urlaub. 😉
Vor und nach dem Urlaub gibt es immer wieder die gleichen Aufgaben zu erledigen. In meinem Beispiel sind diese zwar trivial, aber dennoch ist es mir wichtig daran zu denken, bevor ich abhaue, bzw. wenn ich wieder zurückkomme.
Schauen wir uns also mal an, wie mein Skript aufgebaut ist.
PowerShell-Skript
Für den eigentlichen API-Aufruf nutze ich das Tool cURL, welches kostenlos zum Download zur Verfügung steht. Damit ist es mir am einfachsten gefallen, die API-Calls auszuführen. So weit ich weiß (und ich bin beileibe kein PowerShell-Experte) gibt es auch mit PowerShell die Möglichkeit Webcalls zu machen, jedoch habe ich es damit nicht geschafft auch entsprechende Rückgabewerte zu verarbeiten. Daher habe ich mich bei der Umsetzung auf cURL fokussiert.
Variablen abfragen und definieren
Mein Skript beginnt damit den Pfad zu cUrl zu definieren, ein paar Fragen zu stellen und Variablen zu berechnen.
# Define Path to cURL.exe
$curl = "C:\Apps\cURL\bin\curl.exe"
# Ask some questions to qualify the Task
$datumUrlaubStart = Read-Host 'Letzter Arbeitstag vor dem Urlaub (dd.mm.yyyy)'
$datumUrlaubEnde = Read-Host 'Erster Arbeitstag nach dem Urlaub (dd.mm.yyyy)'
# Calculate Variables
$dateStart = Get-Date $datumUrlaubStart -Format "dd.MM.yyyy"
$dateEnd = Get-Date $datumUrlaubEnde -Format "dd.MM.yyyy"
Danach habe ich insgesamt drei Funktionen erstellt, welche jeweils eine neue Aufgabe, eine Unteraufgabe oder einen Kommentar erstellen.
Erstellen einer Aufgabe
Der Aufruf benötigt einige definierte Parameter, die ich als Variablen der Funktion definiere und damit beim Aufruf übergeben kann. Es handelt sich dabei um den eigentlichen Inhalt, also die Aufgabe, das Datum, die Uhrzeit, die Prioritäten, das Projekt und die Sektion, wohin die Aufgabe einsortiert werden soll.
function CreateTask {
param(
[String]$Task,
[String]$Date,
[String]$Time,
[int]$Priority,
[bigint]$ProjectId,
[bigint]$SectionId)
Anhand dieser Informationen baue ich das JSON-Dokument zusammen, welches an die Todoist API übergeben wird:
# Create JSON Body
$json=@"
{\"content\": \"$Task\", \"due_string\": \"$Date um $Time Uhr\", \"due_lang\": \"de\", \"priority\": $Priority, \"project_id\": $ProjectId, \"section_id\": $SectionId}
"@
Nun folgt der eigentliche API-Aufruf:
# Post API-Request to create Task
$result = (& "$curl" -s --location --request POST "https://api.todoist.com/rest/v1/tasks" --header "Content-Type: application/json" --header "Authorization: Bearer <meinBearerToken>" --data $json)
Wie Du siehst rufe ich mit Hilfe von cURL die API auf, übergebe mein API-Token und das zuvor erstellte JSON-Dokument. Das Ergebnis, also das was mir die API zurückgibt, speichere ich in der Variable „result“.
Und zwar aus einem relativ simplen Zweck: Ich brauche nämlich die ID der neu erstellten Aufgabe, wenn ich für diese einen Subtask erstellen möchte. Dazu extrahiere ich die Task ID aus dem Rückgabewert des API-Calls:
# Get Task ID to use when creating SubTask
$taskId = ($result | ConvertFrom-Json).psobject.properties.Where({$_.name -eq "id"}).value
Als letztes in dieser Funktion, gebe ich genau diese Task-ID als Ausgabe zurück.
return $taskId
Erstellen einer Unteraufgabe
Um eine Unteraufgabe bzw. einen Subtask zu erstellen gehen wir genauso vor, wie beim Erstellen einer Aufgabe.
Zuerst die Funktion mit ihren Parametern. In diesem Fall sind die Aufgabe, die Priorität, die Projekt-ID und die ID des Elternobjektes, also der Hauptaufgabe erforderlich. Dabei handelt es sich aller meistens um die Aufgabe, die wir zuvor erstellt hatten. Und dafür benötigen wir jetzt die Task-ID, die wir uns vorhin gespeichert haben.
function CreateSubTask {
param(
[String]$Task,
[bigint]$Priority,
[bigint]$ProjectId,
[bigint]$ParentId)
Auch hierfür erstellen wir uns wieder ein JSON-Dokument.
# Create JSON Body for Subtask
$jsonSubTask=@"
{\"content\": \"$Task\", \"priority\": $Priority, \"project_id\": $ProjectId, \"parent_id\": $ParentId}
"@
Dieses übergeben wir abermals mit cURL und dem API-Token an die passende Schnittstelle
# POST Request for Subtask
$resultSubTask = (& "$curl" -s --location --request POST "https://api.todoist.com/rest/v1/tasks" --header "Content-Type: application/json" --header "Authorization: Bearer <meinBearerToken>" --data $jsonSubTask)
Falls wir eine weitere Unteraufgabe an diese Unteraufgabe erstellen möchten, oder einen Kommentar, benötigen wir auch hier wieder den eindeutigen Bezeichner, also die ID, der Aufgabe. Diese erhalten wir nach dem gleichen Muster wie zuvor und übergeben danach auch diese als Übergabeparameter aus der Funktion zurück.
# Get Task ID to use when creating SubTask
$taskIdSubTask = ($resultSubTask | ConvertFrom-Json).psobject.properties.Where({$_.name -eq "id"}).value
return $taskIdSubTask
Erstellen eines Kommentars
Zum Erstellen eines Kommentars benötigen wir lediglich den Kommentar und die dazugehörige Task-ID, zu der der Kommentar hinzugefügt werden soll.
function CreateComment {
param(
[String]$Comment,
[bigint]$TaskId)
# Create JSON Body
$json=@"
{\"task_id\": $TaskId, \"content\": \"$Comment\"}
"@
# Post API-Request to create Task
& "$curl" -s --location --request POST "https://api.todoist.com/rest/v1/comments" --header "Content-Type: application/json" --header "Authorization: Bearer <meinBearerToken>" --data $json
Funktionen aufrufen
Zu Guter Letzt nutzen wir die erstellten Funktionen und verbinden diese mit den zuvor erstellten Variablen, die wir als Parameter übergeben können. Ein einfaches Beispiel:
[bigint]$parentId = CreateTask -Task "Urlaub vorbereiten" -Date $dateStart -Time "18:00" -Priority 4 -ProjectId <ProjektId>
In der Variable „parentId“ speichere ich den Rückgabewert der Funktion, also die ID des neu erstellten Tasks. Dieser wird mit den jeweiligen Parametern erstellt. Ich habe also eine neue Aufgabe, die „Urlaub vorbereiten“ heißt und am letzten Arbeitstag um 18:00 Uhr fällig ist und die 4 hat.
!!ACHTUNG!!: Wer Todoist schon ein wenig kennt, wird sich hier wundern, weshalb diese Aufgabe bei mir die niedrigste Priorität erhält. Blöderweise ist es in der Oberfläche von Todoist genau umgekehrt wie in der API. Bei der API ist die Priorität 4 nämlich die Wichtigste und wird damit als Prio 1 im Tool später angezeigt. Sehr verwirrend, aber wenn man einmal raus hat, dass die Prioritäten genau andersherum sind, kommst Du damit sicherlich schnell klar.
Danach erstelle ich eine Reihe von Subtasks mit Dingen, die für meinen Urlaub noch vorzubereiten sind:
CreateSubTask -Task "Termin für Urlaubsübergabe vereinbaren" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Outlook // Abwesenheitsnotiz in Outlook einstellen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Outlook // Kalender durchgehen und Termine absagen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Outlook // Info an Petra (SFDC Approval)" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Todoist // Wiederholende Aufgaben erledigen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Todoist // Urlaubsmodus aktivieren" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Slack // Slack-Status einstellen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "E-Mail // Info an Kolleginnen und Kollegen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "E-Mail // Info ans Team" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
Das komplette Skript
Hier für Dich zur besseren Übersicht nochmal mein komplettes Skript:
# Define Path to cURL.exe
$curl = "C:\Apps\cURL\bin\curl.exe"
# Ask some questions to qualify the Task
$datumUrlaubStart = Read-Host 'Letzter Arbeitstag vor dem Urlaub (dd.mm.yyyy)'
$datumUrlaubEnde = Read-Host 'Erster Arbeitstag nach dem Urlaub (dd.mm.yyyy)'
# Calculate Variables
$dateStart = Get-Date $datumUrlaubStart -Format "dd.MM.yyyy"
$dateEnd = Get-Date $datumUrlaubEnde -Format "dd.MM.yyyy"
################################### function "CreateTask" ##################################Publer
function CreateTask {
param(
[String]$Task,
[String]$Date,
[String]$Time,
[int]$Priority,
[bigint]$ProjectId)
# Create JSON Body
$json=@"
{\"content\": \"$Task\", \"due_string\": \"$Date um $Time Uhr\", \"due_lang\": \"de\", \"priority\": $Priority, \"project_id\": $ProjectId}
"@
# Post API-Request to create Task
$result = (& "$curl" -s --location --request POST "https://api.todoist.com/rest/v1/tasks" --header "Content-Type: application/json" --header "Authorization: Bearer <BearerToken>" --data $json)
# Get Task ID to use when creating SubTask
$taskId = ($result | ConvertFrom-Json).psobject.properties.Where({$_.name -eq "id"}).value
return $taskId
}
################################### function "CreateSubTask" ###################################
# Function to Create Subtask
function CreateSubTask {
param(
[String]$Task,
[bigint]$Priority,
[bigint]$ProjectId,
[bigint]$ParentId)
# Create JSON Body for Subtask
$jsonSubTask=@"
{\"content\": \"$Task\", \"priority\": $Priority, \"project_id\": $ProjectId, \"parent_id\": $ParentId}
"@
# POST Request for Subtask
$resultSubTask = (& "$curl" -s --location --request POST "https://api.todoist.com/rest/v1/tasks" --header "Content-Type: application/json" --header "Authorization: Bearer <BearerToken>" --data $jsonSubTask)
# Get Task ID to use when creating SubTask
$taskIdSubTask = ($resultSubTask | ConvertFrom-Json).psobject.properties.Where({$_.name -eq "id"}).value
return $taskIdSubTask
}
################################### Call Functions ###################################
[bigint]$parentId = CreateTask -Task "Urlaub vorbereiten" -Date $dateStart -Time "18:00" -Priority 4 -ProjectId <ProjektId>
CreateSubTask -Task "Termin für Urlaubsübergabe vereinbaren" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Outlook // Abwesenheitsnotiz in Outlook einstellen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Outlook // Kalender durchgehen und Termine absagen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Outlook // Info an Petra (SFDC Approval)" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Todoist // Wiederholende Aufgaben erledigen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Todoist // Urlaubsmodus aktivieren" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Slack // Slack-Status einstellen" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "E-Mail // Info an AEs und Sales Directors" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "E-Mail // Info ans Team" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
[bigint]$parentId = CreateTask -Task "Urlaub nachbereiten" -Date $dateEnd -Time "10:00" -Priority 4 -ProjectId <ProjektId>
CreateSubTask -Task "Termin für Urlaubsübergabe vereinbaren" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Telefonat mit allen Directs" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Outlook // Info an Petra (SFDC Approval)" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
CreateSubTask -Task "Todoist // Urlaubsmodus deaktivieren" -Priority 0 -ProjectId <ProjektId> -ParentId $parentId
Fazit
Die Automatisierung macht natürlich nur Sinn, wenn die wiederkehrenden Aufgaben auch tatsächlich immer mal wieder vorkommen. Und die Implementierung via Todoist-API macht vermutlich auch nur Sinn, wenn Du ein wenig Spaß am basteln hast. 😉
Was hältst Du von der Idee? Gibt es Aufgaben, die Du vielleicht ebenfalls automatisieren könntest? Ich freue mich über Deine Kommentare und Nachrichten!