From e76d80ece370c48537935910039c3922790caaf9 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Thu, 16 Apr 2026 11:11:48 +0200 Subject: [PATCH] Dokumente aktualisiert --- CLAUDE.md | 147 +++++++------- SPEC.md | 593 +++++++++++++++++------------------------------------- 2 files changed, 255 insertions(+), 485 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bded01b..eacc962 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,9 +2,9 @@ ## Kontext -Dieses Projekt entwickelt `mcp-familywall` – einen MCP-Server für den Lesezugriff -auf Family Wall (familywall.com). Der MCP-Server läuft lokal und wird in Claude Desktop -eingebunden. +Dieses Projekt entwickelt `mcp-familywall` – einen MCP-Server für den +Zugriff auf Family Wall (familywall.com). Der MCP-Server läuft lokal +und wird in Claude Desktop eingebunden. ## Infrastruktur @@ -20,48 +20,33 @@ eingebunden. ## Deploy-Workflow (nach jeder Code-Änderung) 1. Claude Code committet und pusht (bei Berechtigungsfehler: bis zu 2 Retries, je 1s warten) +2. Marcus installiert lokal und startet Claude Desktop neu ## Aktueller Stand -### Implementierte Tools (v0.4.16) +### Implementierte Tools (v0.4.x) | Kategorie | Tools | |---|---| -| Kreise | `get_circles`, `get_members` | -| Listen | `get_lists` | -| Tasks (Lesen) | `get_tasks` (inkl. `category_id`, `due_date`, `assignee_ids`), `get_categories` (inkl. `custom`-Flag) | -| Wall | `get_activities`, `like_post` | -| Tasks (Schreiben) | `create_task` (inkl. `category_id`, `due_date`, `assignee_ids`), `update_task` (inkl. `category_id`, `due_date`, `clear_due_date`, `assignee_ids`, `list_id`), `toggle_task`, `delete_task` | -| Kategorien (Schreiben) | `create_category` (inkl. `icon`), `delete_category` (System-Kategorien geschützt) | - +| Lesen | `get_circles`, `get_members`, `get_lists`, `get_tasks`, `get_categories`, `get_activities` | +| Tasks | `create_task`, `update_task`, `toggle_task`, `delete_task` | +| Kategorien | `create_category`, `delete_category` | +| Aktivitäten | `like_post` | ## Roadmap -- v0.x: Erweiterter Lese- + Schreibzugriff ← aktuell -- Offen: Unlike (`like_post(like=False)`) +- v0.4.x: Kategorie-Management, Task-Felder (due_date, assignee, list_id) ← aktuell +- v0.5.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich) +- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren) -## Referenzprojekt - -Im Ordner `reference/` liegen Dateien aus einem anderen MCP-Server-Projekt -als Orientierung für Struktur und Patterns: - -| Datei | Zweck | -|---|---| -| `reference/pyproject.toml` | Projektstruktur, Dependencies, Entry-Points, Build-System | -| `reference/config.py` | Config-Pattern: YAML laden, validieren, speichern | -| `reference/auth.py` | Keyring-Integration, Credential-Resolution-Reihenfolge | -| `reference/cli.py` | Setup-Wizard, check, serve – CLI-Struktur | - -**Wichtig:** Diese Dateien sind Referenz, kein Copy-Paste. Alle -Synology/DSM-spezifischen Teile werden nicht übernommen. -Family Wall nutzt ein anderes Auth-Schema – siehe SPEC.md. - ## Architektur-Entscheidungen ### Session-Strategie Kein Session-Caching. Jeder Tool-Call führt Login → API-Call → Logout durch. -Credentials liegen im OS Keyring (nur `email` + `password`), kein `session_id`. +Optimierung: Mehrere API-Calls in einer Session bündeln (login → call1 → call2 → logout) +um unnötige HTTP-Roundtrips zu vermeiden. Credentials liegen im OS Keyring +(nur `email` + `password`), kein `session_id`. ### Kreise (Scopes) Family Wall kennt mehrere Kreise (z.B. Familie, erweiterter Familienkreis). @@ -72,68 +57,77 @@ Ohne `scope` werden alle Kreise zurückgegeben. Systembezeichnungen (z.B. `SYS-CAT-SHOPPINGLIST`) werden in deutsche Klarnamen übersetzt. Mapping-Tabelle in `modules/lists.py`. +### Kategorien +- Kategorien sind family-wide, nicht list-spezifisch (`taskcategoryput` gilt für alle Listen) +- System-Kategorien: `rights.canDelete=null` → nicht löschbar +- Custom-Kategorien: `rights.canDelete="true"` → löschbar +- Locale-Filter: default `"de"` – Custom-Kategorien haben kein locale-Feld + und werden immer angezeigt unabhängig vom Locale-Parameter + +### Fehlerbehandlung +Die API gibt Fehler manchmal als `a00.un.un` zurück (nicht Top-Level). +`fw_client.py` prüft beides und wirft `FamilyWallError`. Nie silent-fail +als Erfolg werten – immer `a00.un` und Top-Level `ex`/`un` prüfen. + +### Service Worker +Die Family Wall Web-App nutzt einen Service Worker der bestimmte +HTTP-Requests abfängt und modifiziert. Browser-DevTools und JS-Interceptoren +können den echten Request-Body in diesen Fällen nicht sehen. +→ Immer `FW_DEBUG=1` für Traffic-Analyse nutzen, nicht Browser-DevTools. + ## Claude Code – Implementierungsregeln -- Keine destruktiven Operationen in v1.0 → kein Confirmation-Pattern erforderlich +- **Feature complete before next feature** – jedes Feature vollständig + implementieren, testen und verifizieren bevor das nächste beginnt +- **Kein destruktives Probing** – keine Probe-Calls auf System-Kategorien, + echte Listen oder echte Tasks; immer Test-Objekte anlegen und danach + sofort löschen - Fehlerbehandlung: API-Fehler als verständliche Meldung zurückgeben, keine Stacktraces - Keine Secrets in stderr-Ausgaben (Passwort bei Debug-Logging maskieren) -- Type Hints und Docstrings konsequent verwenden +- Type Hints und Docstrings konsequent verwenden (Englisch) - Formatter: `ruff format`, Linter: `ruff check`, Tests: `pytest` - Alle Texte (Docstrings, Kommentare, README): Englisch - Debug-Logging via `FW_DEBUG=1` Umgebungsvariable - Nach jeder Aufgabe: git commit + push. Bei Berechtigungsfehler: 1s warten, bis zu 2 Retries - .gitignore eigenständig pflegen (Credentials, __pycache__, .venv, .env, *.pyc etc.) -- README.md im Projekt-Root pflegen und bei jedem Aufruf aktualisieren +- README.md + CLAUDE.md im Projekt-Root bei jedem Aufruf aktualisieren +- Confirmation-Pattern in Docstrings: `IMPORTANT: Ask the user for confirmation + before calling this tool.` für destruktive oder schreibende Operationen -## Implementierungsreihenfolge +## Bekannte API-Eigenheiten und Fallstricke -### Gruppe 1 – Projektgerüst + Auth + CLI ✦ Prio: hoch +### Sentinel-Wert `$empty` +Das FiZ-Framework nutzt `$empty` als Sentinel um optionale Felder zu löschen. +Normale Werte wie `""`, `null`, `"null"`, `"0"` werden vom Server abgelehnt. +Aktuell genutzt für: `dueDate=$empty` (Fälligkeitsdatum entfernen) -- `pyproject.toml`: Package `mcp-familywall`, Entry-Point `mcp-familywall` -- `src/mcp_familywall/config.py`: `~/.config/mcp-familywall/config.yaml`, - schema_version 1 -- `src/mcp_familywall/auth.py`: Keyring-Service `mcp-familywall`, - Keys: `email`, `password`. Credential-Resolution: - 1. Umgebungsvariablen `FW_EMAIL`, `FW_PASSWORD` - 2. OS Keyring -- `src/mcp_familywall/fw_client.py`: HTTP-Client mit `httpx`. - Methoden: `login()`, `logout()`, `call(endpoint, params)`. - Debug-Logging wenn `FW_DEBUG=1` (Passwort maskieren). -- `src/mcp_familywall/cli.py`: - - `setup`: fragt E-Mail + Passwort, führt Login/Logout durch, - speichert Credentials im Keyring, gibt Claude-Desktop-Snippet aus - - `check`: testet Auth und API-Erreichbarkeit - - `serve`: startet MCP-Server - -Config-Datei enthält nur: -```yaml -schema_version: 1 +### Silent-Fail via `a00.un.un` +Fehler bei falschen Parametern kommen nicht immer auf Top-Level: +```json +{"a00": {"un": {"un": {"message": "missing value in: taskId"}}}} ``` +→ `fw_client` muss beide Ebenen prüfen -### Gruppe 2 – MCP Tools ✦ Prio: hoch +### Bekannte Parameter-Namen (verifiziert) +| Endpoint | Parameter | Wert/Format | +|---|---|---| +| `taskcreate2` | `taskListId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee` | – | +| `taskupdate2` | `metaId`, `text`, `description`, `taskCategoryId`, `dueDate`, `assignee`, `taskListId` | – | +| `taskupdate2` | `dueDate` löschen | `$empty` | +| `taskmark` | `taskId`, `complete` | `"true"`/`"false"` | +| `metadelete` | `id` | metaId des Tasks | +| `wallmood` | `wall_message_id`, `moodType` | `"STAR"` für Like | +| `taskcategoryput` | `name`, `emoji` | – | +| `taskcategorydelete` | `id` | metaId der Kategorie | -`src/mcp_familywall/server.py` + `src/mcp_familywall/modules/lists.py` +### Self-Like-Restriction +Eigene Posts können nicht geliked werden. API antwortet 200, macht aber nichts. -**`get_circles`** -- Ruft `famlistfamily` auf -- Gibt alle Kreise zurück: `id`, `name` -- Confirmation: nein - -**`get_lists`** -- Signatur: `get_lists(scope: str = None) -> str` -- Ruft `accgetallfamily` auf (Parameter: `a01call=taskcategorysync`, `a02call=tasksync`) -- Gibt zurück: `id` (metaId), `name` (übersetzt), `type`, offene Einträge, - Gesamteinträge, Kreis-Name -- Ohne `scope`: alle Kreise; mit `scope`: nur dieser Kreis -- Confirmation: nein - -**`get_tasks`** -- Signatur: `get_tasks(list_id: str, only_open: bool = True) -> str` -- `list_id`: metaId aus `get_lists` -- Gibt zurück: `id`, `text`, `description`, `completed` -- Confirmation: nein +### Unlike nicht möglich +Service Worker verschlüsselt den Unlike-Request-Body. +Endpoint unbekannt. `like_post(like=False)` gibt Fehlermeldung zurück. ## Test-Credentials (nur für Entwicklung) @@ -143,9 +137,12 @@ schema_version: 1 | E-Mail | `marcus@gecheckt.de` | | Passwort | `Lasdas1234` | +Hinweis: Das ist ein kostenloser Test-Account ohne Premium-Features. +Der echte Account (Premium) hat andere Credentials die im Keyring gespeichert sind. + ## Hintergrund Marcus ist Senior Software Engineer (Java, Jakarta EE). Präferenz: State-of-the-Art, Best Practices, saubere Architektur. -Automatisierung spart Zeit für die Familie. 🌱 \ No newline at end of file +Automatisierung spart Zeit für die Familie. 🌱 diff --git a/SPEC.md b/SPEC.md index 87bfffd..ed2b360 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,6 +1,6 @@ # Family Wall API – Spezifikation -Erarbeitet durch Browser-Traffic-Analyse (April 2026). +Erarbeitet durch Browser-Traffic-Analyse und React-Fiber-Analyse (April 2026). Es gibt keine offizielle API-Dokumentation. ## Base URL @@ -18,21 +18,14 @@ Content-Type: application/x-www-form-urlencoded |---|---| | `identifier` | E-Mail-Adresse | | `password` | Passwort | -| `type` | nicht senden — wird als `undefined` ignoriert (verifiziert per JS-Analyse) | -| `clientId` | weglassen | -| `clientSecret` | weglassen | -| `generateAutologinToken` | weglassen | -| `countryCode` | weglassen | +| `type` | `"email"` (verifiziert) | **Response (Erfolg):** ```json -{ "a00": { "r": { "r": }, "cn": "log2in" } } +{ "r": { "r": } } ``` -`SessionObject` enthält u.a. `tokenCsrf` und `webApiUrl`. -`tokenCsrf` ist die Session-ID – identisch zur `JSESSIONID` im Cookie. - **Response (Fehler):** ```json @@ -41,12 +34,10 @@ Content-Type: application/x-www-form-urlencoded ``` Der Server setzt nach erfolgreichem Login ein Session-Cookie: -Set-Cookie: JSESSIONID= (= tokenCsrf) +`Set-Cookie: JSESSIONID=` ### Folgecalls (nach Login) -Alle API-Calls nach dem Login benötigen: - | | | |---|---| | **Cookie** | `JSESSIONID=` | @@ -55,7 +46,6 @@ Alle API-Calls nach dem Login benötigen: ### Logout POST https://api.familywall.com/api/log2out -Content-Type: application/x-www-form-urlencoded Keine Parameter. Session wird serverseitig invalidiert. @@ -63,62 +53,66 @@ Keine Parameter. Session wird serverseitig invalidiert. Kein Session-Caching. Jeder MCP-Tool-Call führt folgende Sequenz aus: +``` POST /api/log2in → Session-ID -POST /api/ → Nutzdaten +POST /api/ → Nutzdaten (ggf. mehrere Calls in einer Session) POST /api/log2out → Session invalidieren - +``` Credentials (E-Mail + Passwort) werden einmalig via `mcp-familywall setup` -im OS Keyring gespeichert (Keys: `email`, `password`). Kein Keyring-Eintrag -für `session_id`. +im OS Keyring gespeichert (Keys: `email`, `password`). + +## Fehlerbehandlung + +### Silent-Fail Warnung +Die API gibt Fehler manchmal NICHT auf Top-Level zurück, sondern eingebettet: + +```json +{"a00": {"un": {"un": {"message": "missing value in: taskId"}}}} +``` + +`fw_client.py` prüft beide Ebenen (`a00.un.un` und Top-Level `ex`/`un`) +und wirft `FamilyWallError`. Nie eine Response als Erfolg werten ohne +beide Fehlerebenen zu prüfen. + +### Sentinel-Wert `$empty` +Das FiZ-Server-Framework nutzt `$empty` als Sentinel-String um optionale +Felder zu löschen. Alle anderen Werte (`""`, `null`, `"null"`, `"0"`, +`"-1"`, `"remove"`, `"clear"`, Epoch-Timestamps) werden vom Server mit +`"is not a valid Date"` abgelehnt. + +**Aktuell genutzt für:** +- `dueDate=$empty` → löscht Fälligkeitsdatum in `taskupdate2` + +**Gefunden durch:** React-Fiber-Analyse des nativen `datetime-local`-Inputs – +nach Klick auf "Löschen" im Custom-Datepicker wird der Input-Wert auf +`"$empty"` gesetzt, was beim Speichern an die API gesendet wird. ## Bekannte Endpoints -### `famlistfamily` – Kreise abrufen +### `famlistfamily` – Kreise + Mitglieder abrufen POST https://api.familywall.com/api/famlistfamily -Content-Type: application/x-www-form-urlencoded - -**Body-Parameter:** keine (verifiziert) - -**Response-Struktur (verifiziert):** -``` -a00.r.r[] → Kreise - .metaId → eindeutige Kreis-ID (Format family/) - .name → Kreisname - .family_id → numerische Kreis-ID - .members[] → Mitglieder des Kreises - .accountId → numerische Account-ID - .metaId → Mitglieds-ID (Format familymember/_) - .firstName → Vorname (Display-Name; bevorzugen gegenüber .name) - .name → E-Mail-Adresse (Family Wall Default wenn kein Anzeigename) - .role → Familienrolle (z.B. "Unknown", "Parent", "Child") - .right → Berechtigung (z.B. "SuperAdmin", "Admin", "Member") - .color → Profilfarbe als Hex-String (z.B. "#FF8086") - .medias[0].pictureUrl → Avatar-URL (generierter Default wenn pictureDefault=true) - .identifiers[] → Kontaktdaten - .type → Typ (z.B. "Email") - .value → Wert (z.B. E-Mail-Adresse) - .familyId → Zugehöriger Kreis (= metaId des Kreises) - .isloggedaccount → "true" wenn das der angemeldete Account ist - .joinDate → Beitrittsdatum (ISO 8601) - .lastLoginDate → Letzter Login (ISO 8601) - .locale → Spracheinstellung (z.B. "de_DE") - .timeZone → Zeitzone (z.B. "Europe/Berlin") - .invitations[] → Offene Einladungen (leer wenn keine) - .coverUri → Cover-Bild URL -``` - -### `taskgettasklists` – Listen abrufen -POST https://api.familywall.com/api/taskgettasklists -Content-Type: application/x-www-form-urlencoded **Body-Parameter:** keine -**Response-Struktur:** zu verifizieren beim ersten echten Call +**Response-Struktur:** +``` +a00.r.r[] → Kreise + .metaId → Kreis-ID (z.B. "family/23431854") + .name → Kreis-Name + .members[] → Mitglieder des Kreises + .accountId → numerische User-ID + .firstName → Anzeigename (bevorzugen) + .name → E-Mail als Fallback + .role → Familienrolle (Parent/Child/Unknown) + .right → Berechtigung (SuperAdmin etc.) + .color → Profilfarbe (#RRGGBB) + .medias[0].pictureUrl → Avatar-URL + .identifiers[type=Email].value → E-Mail-Adresse +``` -### `accgetallfamily` – Listen + Tasks abrufen +### `accgetallfamily` – Listen, Tasks, Kategorien abrufen POST https://api.familywall.com/api/accgetallfamily -Content-Type: application/x-www-form-urlencoded **Body-Parameter:** @@ -127,425 +121,204 @@ Content-Type: application/x-www-form-urlencoded | `a01call` | `"taskcategorysync"` | | `a02call` | `"tasksync"` | -Hinweis: `a03call=tasklistsync` ist **kein gültiger Endpoint** — API antwortet mit -"The call tasklistsync is not registered". Nicht verwenden. -`partnerScope`, `a03id`, `withStateBean` werden weggelassen. - -**Response-Struktur (verifiziert):** +**Response-Struktur:** ``` -a00 → famlistfamily-Daten (Kreise inkl. members[]) – Nebeneffekt -a01.r.r.updatedCreated[] → taskcategorysync (Kategorien/Abteilungen pro Liste) - .metaId → Kategorie-ID (Format taskCategory/_) - .name → Kategoriename (sprachabhängig, z.B. "Beverages") - .emoji → Emoji-Symbol der Kategorie - .systemCategoryId → numerische System-ID (sprach-unabhängig) - .taskListType → Listentyp der Kategorie (z.B. "SHOPPING_LIST") - **Wichtig:** Alle 171 Kategorien sind SHOPPING_LIST — - es gibt keine TODO-Kategorien in der API - .sortingIndexByTaskList → dict: Listen-ID → Sortierposition - **Achtung:** enthält ALLE Listen-IDs unabhängig vom Typ - → NICHT für Typ-Filterung verwenden! - Stattdessen: taskListType der Kategorie mit - taskListType der Liste (aus taskgettasklists) vergleichen - .locale → Sprache des Namens (z.B. "de", "en", "ru", "fr", "es", - "it", "nl", "pt", "sv", "ko", "ja") - Jede Sprache = eigener Eintrag mit eigenem metaId/systemCategoryId - **Wichtig:** Custom-Kategorien (rights.canDelete='true') haben - kein locale-Feld gesetzt — sie werden sprachunabhängig - zurückgegeben und dürfen nie über locale gefiltert werden. - .hiddenByTaskList → Liste von Listen-IDs, in denen die Kat. versteckt ist -a02.r.r.updatedCreated[] → tasksync (Tasks) - .metaId → eindeutige Task-ID - .taskId → identisch zu metaId (zweiter Alias) - .text → Aufgabentext - .description → optionale Beschreibung - .taskListId → Zugehörigkeit zur Liste - .complete → "true" / "false" (String, nicht Boolean!) - .categories[] → Listen-Level-Systemkategorie (z.B. SYS-CAT-SHOPPINGLIST); - NICHT die spezifische Task-Kategorie — immer identisch - für alle Tasks einer Liste - .system → "true" (immer System-Kategorie) - .name → Listen-Systemkategorien (z.B. "SYS-CAT-SHOPPINGLIST", "SYS-CAT-TODOS") - .taskCategoryId → spezifische Task-Kategorie (verifiziert): metaId-Format - (z.B. "taskCategory/23431854_200"), null wenn nicht gesetzt - .assignee[] → zugewiesene Mitglieder: [{accountId: "..."}] - .assigneeIds[] → zugewiesene Member-IDs als String-Array (z.B. ["23431898"]) - .dueDate → Fälligkeitsdatum (ISO 8601, z.B. "2026-04-30T18:00:00.000Z"; null wenn nicht gesetzt) - .reminder → Erinnerungsregel (Objekt mit reminderUnit, reminderType, reminderValue — nicht das Fälligkeitsdatum!) - .recurrency → Wiederholungsregel (optional) - .sortingIndex → Anzeigereihenfolge +a00.r.r[] → Kategorien (taskcategorysync) + .metaId → Kategorie-ID + .name → Kategoriename (Systembezeichnung) + .taskListType → SHOPPING_LIST oder TODOS + .sortingIndexByTaskList → Sortierreihenfolge pro Liste + .rights.canDelete → "true" = custom, null = System + .locale → Sprachcode (de/en/fr/...), fehlt bei custom + +a02.r.r.updatedCreated[] → Tasks (tasksync) + .metaId → Task-ID + .text → Aufgabentext + .description → optionale Beschreibung + .taskListId → Listen-ID + .complete → "true" / "false" (String!) + .taskCategoryId → Kategorie-ID (optional) + .dueDate → Fälligkeitsdatum (ISO 8601, optional) + .assignee[] → Liste von Member-IDs (optional) + .assigneeIds[] → alternativ zu assignee[] ``` -**Kategorie-Zuweisung bei taskcreate2 / taskupdate2 (verifiziert):** - -| Parameter | Pflicht | Wert | -|---|---|---| -| `taskCategoryId` | nein | Kategorie-MetaId aus `get_categories` (z.B. `taskCategory/23431854_200`) | - -Hinweise: -- Wert muss das vollständige metaId-Format `taskCategory/_` sein. - Nur der numerische `systemCategoryId`-Teil (z.B. `200`) führt zu API-Fehler - `"cannot find task category id=200"`. -- Das `categories[]`-Feld in der Response zeigt immer `SYS-CAT-SHOPPINGLIST` - (Listen-Level-Systemkategorie, unabhängig vom gesetzten `taskCategoryId`). - Die tatsächliche Task-Kategorie ist im Feld `taskCategoryId` der Task gespeichert. -- Nur für Einkaufslisten (`taskListType=SHOPPING_LIST`) relevant; - TODO-Listen haben keine Kategorien. - -## Systembezeichnungen für Listen-Namen - -Bekannte Systembezeichnungen werden deutsch übersetzt: - -| Systembezeichnung | Deutsch | -|---|---| -| `SYS-CAT-SHOPPINGLIST` | `Einkaufsliste` | - -Unbekannte Bezeichnungen werden unverändert zurückgegeben. -Mapping-Tabelle bei Bedarf erweitern. - -### `wallget` – Aktivitäten (Wall) abrufen +### `wallget` – Wall-Aktivitäten abrufen POST https://api.familywall.com/api/wallget -Content-Type: application/x-www-form-urlencoded -**Body-Parameter:** - -| Parameter | Wert | -|---|---| -| `nb` | Anzahl Einträge (z.B. `"20"`) | -| `date` | optional, für Paginierung (Datum des letzten Eintrags) | -| `accountId` | optional | -| `type` | optional | -| `nested` | optional | -| `masterNested` | optional | -| `sortBy` | optional | - -**Response-Struktur (verifiziert):** +**Response-Struktur:** ``` a00.r.r[] - .metaId → ID der Aktivität (= wallMessageId) - .refType → Aktivitätstyp (z.B. STATUS, FAMILY_CREATED) - .text → Text (optional, fehlt bei System-Events) - .creationDate → Datum (ISO 8601) - .accountId → Autor-ID + .metaId → Post-ID (z.B. "wall/23431854_31119189") + .type → STATUS, FAMILY_CREATED, etc. + .text → Post-Text + .modifDate → Timestamp (ISO 8601) + .creator.accountId → Author-ID + .moodMap → {"": ["STAR"]} für Likes + .moodStarShortcut → true wenn geliked + .comments[] → Kommentare ``` -**Response-Struktur:** zu verifizieren beim ersten echten Call - -### `wallactivityget` – Einzelne Aktivität abrufen -POST https://api.familywall.com/api/wallactivityget -Content-Type: application/x-www-form-urlencoded - -**Body-Parameter:** - -| Parameter | Wert | -|---|---| -| `accountId` | optional | -| `masterNested` | optional | - -**Response-Struktur:** zu verifizieren beim ersten echten Call -(Endpoint noch nicht implementiert — für spätere Versionen) - -## Fehlerstruktur-Varianten - -Die Family Wall API gibt Fehler in zwei verschiedenen Strukturen zurück: - -| Struktur | Beispiel | Erkennungsregel | -|---|---|---| -| Top-Level `ex` | `{"ex": {"ex": {...}}}` | `"ex" in body` | -| Top-Level `un` | `{"un": {"un": {...}}}` | `"un" in body` | -| Verschachtelt `a00.un` | `{"a00": {"un": {"un": {...}}}}` | `"un" in body["a00"]` | - -Ab v0.4.15 erkennt `fw_client.py` alle drei Varianten und wirft `FamilyWallError`. -Zuvor wurden `a00.un`-Fehler still ignoriert (silent fail), was zu irreführenden -`{"updated": True}`-Antworten bei fehlgeschlagenen Updates führte. - -**Bekannte Endpoints mit `a00.un`-Fehlern:** -- `taskupdate2` mit ungültigem `dueDate`-Wert -- `taskcategorydelete` mit falschem Parameter-Namen (obsolet nach v0.4.x-Fix) - -## Debug-Logging - -Wenn die Umgebungsvariable `FW_DEBUG=1` gesetzt ist, loggt `fw_client.py` -vollständige Request-Bodies und Responses nach stderr. Dient zur Verifikation -offener Punkte (z.B. `type`-Parameter beim Login, Kreis-Felder in Response). - -**Wichtig:** Keine Secrets in Debug-Ausgaben (Passwort maskieren). - ### `taskcreate2` – Task erstellen POST https://api.familywall.com/api/taskcreate2 -Content-Type: application/x-www-form-urlencoded -**Body-Parameter (verifiziert):** +**Body-Parameter:** | Parameter | Pflicht | Wert | |---|---|---| -| `taskListId` | ja | Listen-ID aus `get_lists` (z.B. `taskList/123_456`) | -| `text` | ja | Aufgabentitel | -| `description` | nein | Optionale Beschreibung | -| `taskCategoryId` | nein | Kategorie-MetaId aus `get_categories` (z.B. `taskCategory/23431854_200`) | -| `dueDate` | nein | Fälligkeitsdatum ISO 8601 (z.B. `"2026-04-30T18:00:00"`); gespeichert als `dueDate` im Task-Objekt | -| `assignee` | nein | Member-ID aus `get_members` (z.B. `"23431898"`); für mehrere Zuweisungen: mehrfach senden (`assignee=id1&assignee=id2`); gespeichert als `assigneeIds[]` im Task-Objekt | +| `taskListId` | ja | Listen-metaId | +| `text` | ja | Aufgabentext | +| `description` | nein | Beschreibung | +| `taskCategoryId` | nein | Kategorie-metaId (vollständig, z.B. `taskCategory/23431854_200`) | +| `dueDate` | nein | ISO 8601 (z.B. `2026-04-30T18:00:00`) | +| `assignee` | nein | Member-accountId, mehrfach sendbar für mehrere Zuweisungen | -**Response-Struktur (verifiziert):** +**Response:** ``` -a00.r.r → vollständiges Task-Objekt der neu erstellten Task - .metaId → eindeutige Task-ID (z.B. "task/23431854_726362809") - .taskListId → Listen-ID - .text → Titel - .complete → "false" (immer, direkt nach Erstellung) +a00.r.r → vollständiges Task-Objekt + .metaId → neue Task-ID ``` ### `taskupdate2` – Task aktualisieren POST https://api.familywall.com/api/taskupdate2 -Content-Type: application/x-www-form-urlencoded -**Body-Parameter (verifiziert):** +**Body-Parameter:** | Parameter | Pflicht | Wert | |---|---|---| -| `metaId` | ja | Task-ID aus `get_tasks` | -| `text` | nein | Neuer Titel (mindestens eines der optionalen Felder erforderlich) | -| `description` | nein | Neue Beschreibung | -| `taskCategoryId` | nein | Kategorie-MetaId aus `get_categories` (z.B. `taskCategory/23431854_200`) | -| `dueDate` | nein | Fälligkeitsdatum ISO 8601 (z.B. `"2026-04-30T18:00:00"`); gespeichert als `dueDate` im Task-Objekt | -| `assignee` | nein | Member-ID aus `get_members` (z.B. `"23431898"`); für mehrere Zuweisungen: mehrfach senden (`assignee=id1&assignee=id2`); leerer String (`""`) entfernt alle Zuweisungen (nicht verifiziert); gespeichert als `assigneeIds[]` im Task-Objekt | -| `taskListId` | nein | Ziel-Listen-ID zum Verschieben des Tasks (verifiziert – ändert `taskListId` im Task-Objekt) | +| `metaId` | ja | Task-metaId | +| `text` | nein | neuer Titel | +| `description` | nein | neue Beschreibung | +| `taskCategoryId` | nein | neue Kategorie-metaId | +| `dueDate` | nein | ISO 8601 oder `$empty` zum Löschen | +| `assignee` | nein | Member-accountId (mehrfach sendbar), `""` zum Entfernen aller | +| `taskListId` | nein | neue Listen-metaId (verschiebt Task) | -Hinweis: `taskListId` ist optional – ohne diesen Parameter bleibt der Task in seiner aktuellen Liste. - -**Response-Struktur:** kein spezifischer Rückgabewert – Erfolg = kein `ex`/`un`-Key auf Top-Level. - -**dueDate Clearing – `"$empty"` Sentinel (verifiziert April 2026, v0.4.16):** - -Der FiZ-Framework verwendet den Sentinel-Wert `"$empty"` um optionale Felder zu leeren. +**Hinweis:** `taskListId` ist NICHT Pflicht beim Update. +**Response:** ``` -POST taskupdate2 -metaId=task/23431854_726333919&dueDate=$empty -→ dueDate wird auf null gesetzt ✓ +a00.r.r → vollständiges Task-Objekt ``` -`update_task(clear_due_date=True)` sendet intern `dueDate=$empty`. - -**Getestete Werte die fehlschlugen (vor der Lösung):** - -| Wert | API-Response | -|---|---| -| `""` (leerer String) | `a00.un: " is not a valid Date"` | -| `"null"` | `a00.un: "Cannot parse date val=null"` | -| `"undefined"` | `a00.un: "Cannot parse date val=undefined"` | -| `"0"`, `"-1"` | `a00.un: "... is not a valid Date"` | -| `"remove"` / `"clear"` | `a00.un: "Cannot parse date val=..."` | -| `"1970-01-01T00:00:00"` | Erfolgreich – setzt dueDate auf Unix-Epoch (kein Clearing!) | -| Feld weglassen | dueDate bleibt unverändert | -| `removeDueDate=1`, `clearDueDate=1` | werden ignoriert | - -**Wichtig:** Die Fehler werden als `a00.un` zurückgegeben (nicht Top-Level `un`). -Ab v0.4.15 erkennt `fw_client` diese und wirft `FamilyWallError`. - -### `taskmark` – Task als erledigt/offen markieren +### `taskmark` – Task abhaken/wiedereröffnen POST https://api.familywall.com/api/taskmark -Content-Type: application/x-www-form-urlencoded -**Body-Parameter (verifiziert):** +**Body-Parameter:** -| Parameter | Pflicht | Wert | -|---|---|---| -| `taskId` | ja | Task-ID aus `get_tasks` (**WICHTIG: `taskId`, nicht `metaId`!**) | -| `complete` | ja | `"true"` oder `"false"` (String, nicht Boolean!) | +| Parameter | Wert | +|---|---| +| `taskId` | Task-metaId ⚠️ nicht `metaId`! | +| `complete` | `"true"` oder `"false"` (String!) | -**Achtung:** Der Endpoint heißt intern `taskId`, nicht `metaId`. -Falsche Parameter (`metaId`, `id`, `taskMetaId`) werden serverseitig ignoriert – -die API antwortet dann mit einem Fehler in `a00.un.un` (nicht Top-Level!), -der vom Standard-Error-Check im fw_client übersehen wird. - -**Response-Struktur (verifiziert):** +**Response:** ``` -a00.r.r → vollständiges Task-Objekt mit aktuellem Stand (inkl. lastAction: "MARK_COMPLETED") +a00.r.r → Task-Objekt mit lastAction: "MARK_COMPLETED" ``` -### `metadelete` – Objekt löschen +### `metadelete` – Task löschen POST https://api.familywall.com/api/metadelete -Content-Type: application/x-www-form-urlencoded -**Body-Parameter (verifiziert):** +**Body-Parameter:** -| Parameter | Pflicht | Wert | -|---|---|---| -| `id` | ja | Task-ID aus `get_tasks` (**WICHTIG: `id`, nicht `metaId`!**) | +| Parameter | Wert | +|---|---| +| `id` | Task-metaId ⚠️ nicht `metaId`! | -Hinweis: `metadelete` ist ein generischer Lösch-Endpoint für beliebige Objekte (Tasks, etc.). -Entsprechend vorsichtig verwenden. - -**Response-Struktur (verifiziert):** +**Response:** ``` -a00.r.r → "true" (String) +a00.r.r → "true" (String) ``` -**Fehlerverhalten:** Bei falschem Parameter-Namen (`metaId`, `taskId` etc.) antwortet die API -mit `{"a00": {"un": {"un": {"message": "missing value in: id"}}}}` auf Top-Level ohne `un`-Key -→ wird vom fw_client fälschlich als Erfolg interpretiert. Daher ist der korrekte Parameter-Name -kritisch. +### `wallmood` – Post liken +POST https://api.familywall.com/api/wallmood -### `taskcategoryput` – Kategorie erstellen +**Body-Parameter:** + +| Parameter | Wert | +|---|---| +| `wall_message_id` | Post-metaId ⚠️ nicht `wallId` oder `id`! | +| `moodType` | `"STAR"` für Like | + +**Bekannte Einschränkungen:** +- Unlike: Endpoint/Parameter unbekannt (Service Worker verschlüsselt Request-Body) +- Self-Like: API antwortet 200, macht aber serverseitig nichts +- `moodType="NONE"` und andere Werte haben keine Wirkung + +**Response:** +``` +a00.r.r → Wall-Objekt mit moodMap, refAction: "MOOD_STAR" +``` + +### `taskcategoryput` – Kategorie erstellen/aktualisieren POST https://api.familywall.com/api/taskcategoryput -Content-Type: application/x-www-form-urlencoded -**Body-Parameter (verifiziert via FW_DEBUG=1):** +**Body-Parameter:** | Parameter | Pflicht | Wert | |---|---|---| -| `name` | ja | Kategorie-Name (beliebiger String) | -| `emoji` | nein | Icon: Unicode-Emoji-Zeichen (z.B. `🌿`) oder beliebiger String-Code (z.B. `"FOOD"`) — wird as-is gespeichert | +| `name` | ja | Kategoriename | +| `emoji` | nein | Unicode-Emoji oder beliebiger String | -Hinweise: -- Die neue Kategorie wird **allen** Listen der Familie zugeordnet — es gibt keine per-Liste-Einschränkung. -- Benutzerdefinierte Kategorien haben `systemCategoryId=null` und `rights.canDelete='true'`. -- System-Kategorien haben `rights.canDelete=null` — API erlaubt Löschen, aber `delete_category` Tool verweigert es. +**Hinweis:** Kategorien sind family-wide – `listId` hat keine Wirkung. -**Response-Struktur (verifiziert):** +**Response:** ``` -a00.r.r → vollständiges Kategorie-Objekt - .metaId → neue Kategorie-ID (z.B. "taskCategory/23431854_4956637") - .name → Kategorie-Name - .taskListType → "SHOPPING_LIST" (automatisch gesetzt) - .familyId → Familien-ID - .accountId → Account-ID des Erstellers - .rights.canDelete → "true" (custom Kategorien) - .rights.canUpdate → "true" (custom Kategorien) - .emoji → gespeicherter Icon-Wert (falls übergeben) -``` - -**Fehlerverhalten:** Ohne `name`-Parameter: -```json -{"a00": {"un": {"un": {"FiZClassId": "502", "message": "cat without a name ..."}}}} +a00.r.r → Kategorie-Objekt mit metaId ``` ### `taskcategorydelete` – Kategorie löschen POST https://api.familywall.com/api/taskcategorydelete -Content-Type: application/x-www-form-urlencoded -**Body-Parameter (verifiziert via FW_DEBUG=1):** +**Body-Parameter:** -| Parameter | Pflicht | Wert | -|---|---|---| -| `id` | ja | Kategorie-MetaId aus `get_categories` (**WICHTIG: `id`, nicht `metaId`!**) | - -**Achtung:** Falscher Parameter-Name `metaId` führt zu: -```json -{"a00": {"un": {"un": {"FiZClassId": "502", "message": "In request, missing value in : id"}}}} -``` - -**Response-Struktur (verifiziert):** -``` -a00.r.r → "true" (String) -``` - -**Wichtig – System-Kategorien:** Die API erlaubt technisch das Löschen von System-Kategorien -(`taskCategory/_200` etc.), entfernt sie aber nur aus der Familie — nicht global. -Das `delete_category`-MCP-Tool verweigert dies (Schutz via `rights.canDelete`-Check). -Erkennung: custom Kategorien haben `rights.canDelete='true'`; System-Kategorien haben `rights.canDelete=null`. - -### `wallmood` – Wall-Post liken -POST https://api.familywall.com/api/wallmood -Content-Type: application/x-www-form-urlencoded - -**Body-Parameter (verifiziert via FW_DEBUG=1):** - -| Parameter | Pflicht | Wert | -|---|---|---| -| `wall_message_id` | ja | Post-ID aus `get_activities` (z.B. `wall/23431854_31119189`) | -| `moodType` | ja | `"STAR"` (einziger bekannter Wert — entspricht dem Like-Button in der App) | - -**Verhalten (verifiziert):** -- `wallmood` mit `moodType: "STAR"` ist eine **idempotente SET-Operation** — kein Toggle! -- Mehrfaches Aufrufen mit denselben Parametern hinterlässt denselben Zustand -- `moodType: "LIKE"` wurde serverseitig als `"STAR"` gespeichert → korrekter Wert ist `"STAR"` -- Nur für Post-Typ `STATUS` wirksam; `FAMILY_CREATED` und vermutlich System-Posts ignorieren den Call - -**Unlike – nicht implementierbar (Stand: April 2026):** - -Ausgiebig getestete Ansätze, die alle fehlschlugen: - -| Ansatz | Ergebnis | +| Parameter | Wert | |---|---| -| `moodType: "NONE"` / `"REMOVE"` / `"DELETE"` / `""` | moodMap unverändert | -| `moodType` ganz weglassen | moodMap unverändert | -| `moodStarShortcut: "false"` als Parameter | moodMap unverändert | -| Alternative Endpoints: `wallmooddelete`, `wallmoodremove`, `wallmoodelete`, `wallunmood`, `wallcommentdelete`, `wallmoodstar`, `wallstar`, `wallreact`, `wallreactdelete` | alle: `"The call X is not registered"` (502) | -| `metadelete` auf Mood-Comment-ID | löscht den Comment, aber `moodStarShortcut` bleibt gesetzt | +| `id` | Kategorie-metaId ⚠️ nicht `metaId`! | -Die Web-App nutzt einen Service Worker, der Requests abfängt — der echte Unlike-Payload -ist dadurch nicht per Browser-DevTools inspizierbar. Unlike bleibt bis zur weiteren -Analyse nicht unterstützt. +**Hinweis:** System-Kategorien (`rights.canDelete=null`) können technisch +gelöscht werden, sind dann aber dauerhaft weg und nicht wiederherstellbar. +MCP-Server schützt dagegen durch Check auf `rights.canDelete`. -**Response-Struktur (verifiziert):** -``` -a00.r.r → vollständiges Wall-Message-Objekt - .metaId → Post-ID (= wallMessageId) - .wallMessageId → Post-ID (identisch zu metaId) - .refType → Aktivitätstyp (z.B. "STATUS") - .refAction → Letzte Aktion (z.B. "MOOD_STAR") - .text → Post-Text - .postAccountId → Account-ID des Post-Autors - .accountId → Account-ID (identisch) - .moodMap → dict: accountId → ["STAR"] wenn geliked - .moodStarShortcut → "true" wenn Like-Shortcut aktiv - .comments[] → Liste von Kommentaren/Moods - .commentId → Kommentar-ID (Format wallComment/...) - .accountId → Account-ID des Kommentators - .mood → Mood-Typ (z.B. "STAR") - .text → Kommentartext (falls vorhanden) - .creationDate → Erstelldatum (ISO 8601) - .creationDate → Erstelldatum des Posts (ISO 8601) - .modifDate → Letztes Änderungsdatum (ISO 8601) - .familyId → Kreis-ID (Format family/...) - .rights.canUpdate → "true"/"false" - .rights.canDelete → "true"/"false" -``` +### `taskgettasklists` – Listen abrufen (alternativ) +POST https://api.familywall.com/api/taskgettasklists -**Like-Zustand bestimmen (zwei Indikatoren, beide auswerten):** +Wird intern zur Verifikation von `taskListType` genutzt. -| Feld | Typ | Bedeutung | -|---|---|---| -| `moodStarShortcut` | `"true"` / `"false"` | Primär: direktes User-Like-Flag für den anfragenden Account | -| `moodMap[accountId]` | `["STAR"]` | Sekundär: accountId → Mood-Liste; enthält `"STAR"` wenn geliked | +## Systembezeichnungen für Listen-Namen -Beide Indikatoren können den Like-Zustand korrekt abbilden — je nach API-internem -Speicherpfad ist nur einer gesetzt. Immer beide prüfen. +| Systembezeichnung | Deutsch | +|---|---| +| `SYS-CAT-SHOPPINGLIST` | `Einkaufsliste` | +| `SYS-CAT-TODOLIST` | `Aufgaben` | -**Silent-Fail-Szenarien (API antwortet 200, aber Like wird nicht gesetzt):** -- **Self-Like-Restriction**: Eigener Post kann nicht geliked werden - (verifiziert: Account 23431898 kann Post `wall/23431854_31119189` nicht liken, - obwohl API regulär antwortet — `modifDate` bleibt eingefroren) -- **Unsupported Post-Typ**: `FAMILY_CREATED`-Posts ignorieren wallmood-Calls -- **Rate-Limit**: Nach vielen Calls kann die API Still-Fails zurückgeben +## Debug-Logging -Erkennungsmerkmal für Silent-Fail: `modifDate` im Response identisch zum Vorherigen -AND `moodStarShortcut: false` AND `moodMap: {}`. +Wenn die Umgebungsvariable `FW_DEBUG=1` gesetzt ist, loggt `fw_client.py` +vollständige Request-Bodies und Responses nach stderr. -## Noch zu verifizieren +**Wichtig:** Keine Secrets in Debug-Ausgaben (Passwort maskieren). -- ~~Exakter Wert für `type`-Parameter beim Login~~ → nicht senden (verifiziert per JS-Analyse) -- ~~Response-Struktur von `famlistfamily` (Kreise)~~ → a00.r.r[], metaId + name (verifiziert) -- ~~Ob `a03call=tasklistsync` benötigt wird~~ → **nein**, kein gültiger Endpoint (verifiziert) -- Listen-IDs aus `a01.r.r.updatedCreated[].sortingIndexByTaskList`-Keys (verifiziert) -- Listen-Namen und Zähler (remainingTaskNumber, totalTaskNumber) → noch unbekannt -- Kreis-Zuordnung in `accgetallfamily`-Response → noch offen -- ~~Ob `partnerScope` / `withStateBean` benötigt werden~~ → nein (verifiziert) -- Session-Lebensdauer (irrelevant da kein Caching) -- ~~`taskcreate2`: Response-Struktur~~ → `a00.r.r` = vollständiges Task-Objekt (verifiziert) -- ~~`taskupdate2`: ob `taskListId` Pflichtfeld ist~~ → **nein**, nicht erforderlich (verifiziert) -- ~~`taskmark`: korrekter Parameter-Name~~ → **`taskId`** (nicht `metaId`!) (verifiziert) -- ~~`metadelete`: korrekter Parameter-Name + Response-Struktur~~ → **`id`**, Response `"true"` (verifiziert) -- ~~`wallmood`: Parameter-Name `wallId`~~ → **`wall_message_id`** (verifiziert via API-Fehlermeldung) -- ~~`wallmood`: `moodType`-Werte, Toggle vs. explizit, Response-Struktur~~ → verifiziert: idempotentes SET mit `"STAR"`, kein Toggle (siehe oben) -- `wallmood` Unlike: Mechanismus unbekannt — Service Worker verhindert Browser-Inspektion; alle getesteten Ansätze fehlgeschlagen (siehe oben) -- ~~`taskcreate2` / `taskupdate2`: Kategorie-Paramter-Name~~ → **`taskCategoryId`**, Wert = vollständige metaId (verifiziert) -- ~~`taskcategoryput`: Body-Parameter, Response-Struktur~~ → `name` (Pflicht), `emoji` (optional), Response = neues Kategorie-Objekt (verifiziert) -- ~~`taskcategorydelete`: Body-Parameter~~ → **`id`** (nicht `metaId`!), Response = `"true"` (verifiziert) -- ~~`taskcreate2` / `taskupdate2`: Fälligkeitsdatum-Parameter~~ → **`dueDate`**, ISO 8601 String, gespeichert als `dueDate` im Task-Objekt (verifiziert) -- ~~`taskcreate2` / `taskupdate2`: Zuweisung-Parameter~~ → **`assignee`** (Member-ID String; mehrere Werte = mehrfach senden); gespeichert als `assigneeIds[]` (verifiziert) -- ~~`taskupdate2`: Task verschieben~~ → **`taskListId`** als optionaler Parameter setzt neue Liste (verifiziert) -- `taskupdate2`: Alle Zuweisungen entfernen (leere `assignee`-Liste) → noch nicht verifiziert -- ~~`taskupdate2`: `dueDate` entfernen (Clearing)~~ → **`dueDate=$empty`** (FiZ-Sentinel, verifiziert April 2026, v0.4.16) \ No newline at end of file +**Wann FW_DEBUG=1 nutzen:** +- Neue Endpoints verifizieren +- Parameter-Namen unbekannt +- Silent-Fail debuggen +- Service Worker blockiert Browser-DevTools + +## Service Worker + +Die Family Wall Web-App registriert einen Service Worker der bestimmte +HTTP-Requests abfängt und modifiziert bevor sie das Netzwerk erreichen. +Betroffen sind u.a. Unlike-Calls und möglicherweise andere schreibende +Operationen. + +**Folge:** Browser-DevTools Network-Tab und JS-Interceptoren (XHR/Fetch) +zeigen nicht den echten Request-Body für diese Calls. +**Lösung:** `FW_DEBUG=1` auf MCP-Server-Seite zeigt was tatsächlich gesendet wird. + +## Offene Punkte + +- Unlike-Endpoint (Service Worker blockiert Analyse) +- Erinnerungen (reminder) – nur Premium-Account +- Wiederholungen (repeat) – nur Premium-Account +- Sortierung von Kategorien via API