Guten abend,
ich hatte heute ein ähnliches problem und habe es mit so einem Code mir eine Liste im Symcon erstellt. Damit kann ich meinen Mitarbeitern direkt Aufgaben Anzeigen lassen.
Du möchtest diese aber runterladen?
Könnte dieser code gehen, vielleicht hilft es dir. Einfach Token Eingeben wie beschrieben.
<?php
/*******************************************************
* Todoist → CSV + HTML (IP-Symcon)
* - Liest alle AKTIVEN Tasks per Todoist API v1 (nur GET)
* - Schreibt todoist_tasks.csv + todoist_tasks.html in denselben Ordner wie dieses Skript
* - Optional: auch erledigte Tasks (zeitlich begrenzt) anhängen
*
* Doku: https://developer.todoist.com/api/v1/ (Tasks, Pagination, Completed)
*******************************************************/
/*** === Einstellungen (nur hier anpassen) =============================== ***/
$TODOIST_TOKEN = 'HIER_DEIN_TODOIST_TOKEN_EINTRAGEN'; // ⚠️ Pflicht
$INCLUDE_COMPLETED = false; // true = erledigte Tasks zusätzlich exportieren
$COMPLETED_SINCE_ISO = date('Y-m-d', strtotime('-90 days')); // Zeitfenster für erledigte (max. ~3 Monate sinnvoll)
$CSV_DELIMITER = ';'; // Excel-DE: meist Semikolon
$CSV_FILENAME = __DIR__ . '/todoist_tasks.csv';
$HTML_FILENAME = __DIR__ . '/todoist_tasks.html';
$REQUEST_LIMIT = 200; // Page-Größe für paginierte Endpunkte
$TIMEOUT_SEC = 20; // HTTP Timeout
/*** === Hilfsfunktionen ================================================== ***/
function http_get_json_paginated(string $baseUrl, string $token, array $query = [], int $timeout = 15): array {
$all = [];
$cursor = null;
do {
$params = $query;
if ($cursor !== null) $params['cursor'] = $cursor;
list($code, $body, $err) = http_get($baseUrl, $token, $params, $timeout);
if ($code < 200 || $code >= 300) {
throw new Exception("HTTP $code beim GET $baseUrl: $body");
}
$data = json_decode($body, true);
if ($data === null) throw new Exception("Ungueltige JSON-Antwort von $baseUrl");
// v1 liefert bei paginierten Endpunkten { results: [...], next_cursor: "..." }
if (isset($data['results']) && is_array($data['results'])) {
$all = array_merge($all, $data['results']);
$cursor = $data['next_cursor'] ?? null;
} elseif (is_array($data)) {
// Falls der Endpunkt (noch) nicht paginiert antwortet
$all = array_merge($all, $data);
$cursor = null;
} else {
$cursor = null;
}
} while ($cursor);
return $all;
}
function http_get(string $url, string $token, array $query = [], int $timeout = 15): array {
if (!empty($query)) {
$qs = http_build_query($query);
$url .= (strpos($url, '?') === false ? '?' : '&') . $qs;
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Accept: application/json'
],
CURLOPT_TIMEOUT => $timeout,
]);
$body = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
return [$code, $body, $err];
}
function as_string($v): string {
if ($v === null) return '';
if (is_bool($v)) return $v ? 'true' : 'false';
if (is_array($v)) return implode(',', array_map('as_string', $v));
return (string)$v;
}
function map_project_names(string $token, int $limit, int $timeout): array {
$projects = http_get_json_paginated('https://api.todoist.com/api/v1/projects', $token, ['limit' => $limit], $timeout);
$map = [];
foreach ($projects as $p) {
if (!isset($p['id'])) continue;
$map[ (string)$p['id'] ] = $p['name'] ?? '';
}
return $map;
}
function enrich_and_flatten_task(array $t, array $projectNameById): array {
// Todoist v1: priority 1..4 (4 = höchste Dringlichkeit). Im Client bedeutet P1 = höchste → clientPriority = 5 - apiPriority.
$apiPr = $t['priority'] ?? 1;
$clientP = 5 - (int)$apiPr;
$due = $t['due'] ?? null; // Objekt oder null
$deadline = $t['deadline'] ?? null; // Objekt oder null
$due_date = is_array($due) ? ($due['date'] ?? '') : '';
$due_string = is_array($due) ? ($due['string'] ?? '') : '';
$due_is_rec = is_array($due) ? ($due['is_recurring'] ?? false) : false;
$deadline_date = is_array($deadline) ? ($deadline['date'] ?? '') : '';
$labels = $t['labels'] ?? []; // Liste von Label-Namen in v1
return [
'id' => $t['id'] ?? '',
'content' => $t['content'] ?? '',
'description' => $t['description'] ?? '',
'project_id' => $t['project_id'] ?? '',
'project_name' => $projectNameById[ (string)($t['project_id'] ?? '') ] ?? '',
'section_id' => $t['section_id'] ?? '',
'parent_id' => $t['parent_id'] ?? '',
'priority_api' => $apiPr,
'priority_client' => $clientP, // 1 = höchste (Clientsicht)
'labels' => implode(',', $labels),
'due_date' => $due_date,
'due_text' => $due_string,
'due_recurring' => $due_is_rec ? 'true' : 'false',
'deadline_date' => $deadline_date,
'added_at' => $t['added_at'] ?? '',
'updated_at' => $t['updated_at'] ?? '',
'completed_at' => $t['completed_at'] ?? '',
'checked' => isset($t['checked']) ? ($t['checked'] ? 'true' : 'false') : '',
'url' => $t['url'] ?? ''
];
}
function write_csv(string $filename, array $rows, string $delimiter = ';'): void {
$fh = fopen($filename, 'wb');
if (!$fh) throw new Exception("Kann CSV nicht schreiben: $filename");
// UTF-8 BOM für Excel
fwrite($fh, "\xEF\xBB\xBF");
if (empty($rows)) {
fclose($fh);
return;
}
$headers = array_keys($rows[0]);
fputcsv($fh, $headers, $delimiter);
foreach ($rows as $r) {
$line = [];
foreach ($headers as $h) $line[] = as_string($r[$h] ?? '');
fputcsv($fh, $line, $delimiter);
}
fclose($fh);
}
function write_html_table(string $filename, array $rows): void {
$headers = !empty($rows) ? array_keys($rows[0]) : [];
$html = "<!doctype html>\n<html lang=\"de\">\n<head>\n<meta charset=\"utf-8\">";
$html .= "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
$html .= "<title>Todoist Tasks Export</title>";
$html .= "<style>
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;margin:24px;}
h1{font-size:20px;margin:0 0 12px;}
.meta{color:#666;margin:0 0 16px;}
table{border-collapse:collapse;width:100%;font-size:14px;}
th,td{border:1px solid #ddd;padding:8px;vertical-align:top;}
th{background:#f3f4f6;text-align:left;position:sticky;top:0;}
tr:nth-child(even){background:#fafafa;}
code{background:#f6f8fa;padding:2px 4px;border-radius:4px;}
</style>";
$html .= "</head><body>";
$html .= "<h1>Todoist Tasks Export</h1>";
$html .= "<p class=\"meta\">Generiert am ".date('Y-m-d H:i:s')." – Zeilen: ".count($rows)."</p>";
$html .= "<table><thead><tr>";
foreach ($headers as $h) $html .= "<th>".htmlspecialchars($h, ENT_QUOTES, 'UTF-8')."</th>";
$html .= "</tr></thead><tbody>";
foreach ($rows as $r) {
$html .= "<tr>";
foreach ($headers as $h) {
$val = isset($r[$h]) ? (string)$r[$h] : '';
$html .= "<td>".htmlspecialchars($val, ENT_QUOTES, 'UTF-8')."</td>";
}
$html .= "</tr>";
}
$html .= "</tbody></table></body></html>";
file_put_contents($filename, $html);
}
/*** === Ablauf =========================================================== ***/
try {
if (empty($TODOIST_TOKEN) || $TODOIST_TOKEN === 'HIER_DEIN_TODOIST_TOKEN_EINTRAGEN') {
throw new Exception('Bitte $TODOIST_TOKEN eintragen.');
}
// 1) Projektnamen holen (für schönere CSV/HTML)
$projectNameById = map_project_names($TODOIST_TOKEN, $REQUEST_LIMIT, $TIMEOUT_SEC);
// 2) Aktive Tasks holen (paginiert)
$active = http_get_json_paginated(
'https://api.todoist.com/api/v1/tasks',
$TODOIST_TOKEN,
['limit' => $REQUEST_LIMIT],
$TIMEOUT_SEC
);
$rows = [];
foreach ($active as $t) {
$rows[] = enrich_and_flatten_task($t, $projectNameById);
}
// 3) Optional: erledigte Tasks (nur lesend; by_completion_date ist auf ~3 Monate begrenzt)
if ($INCLUDE_COMPLETED) {
$completed = http_get_json_paginated(
'https://api.todoist.com/api/v1/tasks/completed/by_completion_date',
$TODOIST_TOKEN,
[
'since' => $COMPLETED_SINCE_ISO . 'T00:00:00Z',
'limit' => $REQUEST_LIMIT
],
$TIMEOUT_SEC
);
// completed liefert eine andere Struktur; normalisieren
foreach ($completed as $c) {
// Struktur laut v1: enthält Task-Felder ähnlich items + completed_at; absichern:
$rows[] = enrich_and_flatten_task($c, $projectNameById);
}
}
// 4) CSV + HTML schreiben
write_csv($CSV_FILENAME, $rows, $CSV_DELIMITER);
write_html_table($HTML_FILENAME, $rows);
echo "Fertig.\nCSV: $CSV_FILENAME\nHTML: $HTML_FILENAME\n";
} catch (Exception $e) {
echo "Fehler: ".$e->getMessage();
}