Files
mcp-familywall/SPEC.md
T
marcus da5be55070 fix: unlike not supported — wallmood is idempotent SET, not toggle (v0.4.5)
Extensive FW_DEBUG=1 testing revealed:
- wallmood with moodType=STAR is an idempotent SET operation, not a toggle
- Tested: all moodType variations, moodStarShortcut param, 9 alternative
  endpoints — none could remove a like
- Service Worker in the web app prevents intercepting the real unlike payload
- like=False now returns a clear error; like=True continues to work correctly
- SPEC.md updated with full investigation notes and ruled-out approaches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:02:08 +02:00

346 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Family Wall API Spezifikation
Erarbeitet durch Browser-Traffic-Analyse (April 2026).
Es gibt keine offizielle API-Dokumentation.
## Base URL
https://api.familywall.com/api
## Authentifizierung
### Login
POST https://api.familywall.com/api/log2in
Content-Type: application/x-www-form-urlencoded
**Request-Parameter:**
| Parameter | Wert |
|---|---|
| `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 |
**Response (Erfolg):**
```json
{ "a00": { "r": { "r": <SessionObject> }, "cn": "log2in" } }
```
`SessionObject` enthält u.a. `tokenCsrf` und `webApiUrl`.
`tokenCsrf` ist die Session-ID identisch zur `JSESSIONID` im Cookie.
**Response (Fehler):**
```json
{ "ex": { "ex": <ErrorObject> } }
{ "un": { "un": <ErrorObject> } }
```
Der Server setzt nach erfolgreichem Login ein Session-Cookie:
Set-Cookie: JSESSIONID=<session-id> (= tokenCsrf)
### Folgecalls (nach Login)
Alle API-Calls nach dem Login benötigen:
| | |
|---|---|
| **Cookie** | `JSESSIONID=<session-id>` |
| **Header** | `Tokencsrf: <session-id>` (identisch zur JSESSIONID) |
| **Content-Type** | `application/x-www-form-urlencoded` |
### Logout
POST https://api.familywall.com/api/log2out
Content-Type: application/x-www-form-urlencoded
Keine Parameter. Session wird serverseitig invalidiert.
### Session-Strategie
Kein Session-Caching. Jeder MCP-Tool-Call führt folgende Sequenz aus:
POST /api/log2in → Session-ID
POST /api/<endpoint> → Nutzdaten
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`.
## Bekannte Endpoints
### `famlistfamily` Kreise 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
.name → Kreisname
```
### `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
### `accgetallfamily` Listen + Tasks abrufen
POST https://api.familywall.com/api/accgetallfamily
Content-Type: application/x-www-form-urlencoded
**Body-Parameter:**
| Parameter | Wert |
|---|---|
| `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):**
```
a00 → famlistfamily-Daten (Kreise) Nebeneffekt, nicht verwendet
a01.r.r.updatedCreated[] → taskcategorysync (Einkaufskategorien/Abteilungen)
.sortingIndexByTaskList → dict, Keys = Listen-IDs (z.B. "taskList/23431854_29740942")
→ Quelle der Listen-IDs (Namen/Zähler noch unbekannt)
a02.r.r.updatedCreated[] → tasksync (Tasks)
.metaId → eindeutige Task-ID
.text → Aufgabentext
.description → optionale Beschreibung
.taskListId → Zugehörigkeit zur Liste (= Listen-ID aus sortingIndexByTaskList)
.complete → "true" / "false" (String, nicht Boolean!)
```
## 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
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):**
```
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
```
**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)
## 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):**
| Parameter | Pflicht | Wert |
|---|---|---|
| `taskListId` | ja | Listen-ID aus `get_lists` (z.B. `taskList/123_456`) |
| `text` | ja | Aufgabentitel |
| `description` | nein | Optionale Beschreibung |
**Response-Struktur (verifiziert):**
```
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)
```
### `taskupdate2` Task aktualisieren
POST https://api.familywall.com/api/taskupdate2
Content-Type: application/x-www-form-urlencoded
**Body-Parameter (verifiziert):**
| Parameter | Pflicht | Wert |
|---|---|---|
| `metaId` | ja | Task-ID aus `get_tasks` |
| `text` | nein | Neuer Titel (mindestens `text` oder `description` erforderlich) |
| `description` | nein | Neue Beschreibung |
Hinweis: `taskListId` ist **nicht** erforderlich (verifiziert Update ohne `taskListId` funktioniert).
**Response-Struktur:** kein spezifischer Rückgabewert Erfolg = kein `ex`/`un`-Key auf Top-Level.
### `taskmark` Task als erledigt/offen markieren
POST https://api.familywall.com/api/taskmark
Content-Type: application/x-www-form-urlencoded
**Body-Parameter (verifiziert):**
| Parameter | Pflicht | Wert |
|---|---|---|
| `taskId` | ja | Task-ID aus `get_tasks` (**WICHTIG: `taskId`, nicht `metaId`!**) |
| `complete` | ja | `"true"` oder `"false"` (String, nicht Boolean!) |
**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):**
```
a00.r.r → vollständiges Task-Objekt mit aktuellem Stand (inkl. lastAction: "MARK_COMPLETED")
```
### `metadelete` Objekt löschen
POST https://api.familywall.com/api/metadelete
Content-Type: application/x-www-form-urlencoded
**Body-Parameter (verifiziert):**
| Parameter | Pflicht | Wert |
|---|---|---|
| `id` | ja | Task-ID aus `get_tasks` (**WICHTIG: `id`, nicht `metaId`!**) |
Hinweis: `metadelete` ist ein generischer Lösch-Endpoint für beliebige Objekte (Tasks, etc.).
Entsprechend vorsichtig verwenden.
**Response-Struktur (verifiziert):**
```
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` 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 |
|---|---|
| `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 |
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.
**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"
```
**Like-Zustand bestimmen:** `moodMap[accountId]` enthält `["STAR"]` wenn der
jeweilige Account geliked hat. Leere Map oder fehlender Key = kein Like.
## Noch zu verifizieren
- ~~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)