diff --git a/README.md b/README.md index 662651b..45bd46a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your family's circles, lists, and tasks directly from Claude. -## Features (v0.4.3) +## Features (v0.4.4) ### Read diff --git a/SPEC.md b/SPEC.md index 3b04d16..a87cf7b 100644 --- a/SPEC.md +++ b/SPEC.md @@ -270,22 +270,45 @@ kritisch. POST https://api.familywall.com/api/wallmood Content-Type: application/x-www-form-urlencoded -**Body-Parameter:** +**Body-Parameter (verifiziert via FW_DEBUG=1):** | Parameter | Pflicht | Wert | |---|---|---| -| `wall_message_id` | ja (verifiziert) | Post-ID aus `get_activities` (Format `wall/23431854_31119189`) | -| `moodType` | vermutlich ja | `"LIKE"` zum Liken, `"NONE"` zum Unliken (zu verifizieren) | +| `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) | -**Hinweis:** Parameter-Name `wall_message_id` wurde durch API-Fehlermeldung -(`"missing value in: wall_message_id"`) verifiziert. Vorheriger Wert `wallId` war falsch. +**Verhalten (verifiziert):** +- Toggle-Mechanismus: gleicher Call zum Liken und Unliken +- Erster Aufruf → Like setzen, zweiter Aufruf → Like entfernen +- `moodType: "LIKE"` wurde als `"STAR"` serverseitig gespeichert → korrekter Wert ist `"STAR"` -**Verhalten:** Gleicher Endpoint für Like und Unlike. `moodType`-Werte und -Response-Struktur noch zu verifizieren via `FW_DEBUG=1`. +**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" +``` -Ein Like erzeugt serverseitig einen neuen Wall-Eintrag (Nebeneffekt). - -**Response-Struktur:** zu verifizieren beim ersten echten Call +**Like-Zustand bestimmen:** `moodMap[accountId]` enthält `["STAR"]` wenn der +jeweilige Account geliked hat. Leere Map oder fehlender Key = kein Like. ## Noch zu verifizieren @@ -302,4 +325,4 @@ Ein Like erzeugt serverseitig einen neuen Wall-Eintrag (Nebeneffekt). - ~~`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 (`"LIKE"` / `"NONE"`?), Toggle vs. explizit, Response-Struktur \ No newline at end of file +- ~~`wallmood`: `moodType`-Werte, Toggle vs. explizit, Response-Struktur~~ → vollständig verifiziert via FW_DEBUG=1 (siehe oben) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index edd8212..7300796 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.4.3" +version = "0.4.4" description = "MCP server for Family Wall — read your family's lists and tasks via Claude" readme = "README.md" requires-python = ">=3.12" diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index 09ae099..f51a70f 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -487,15 +487,16 @@ def like_post(post_id: str, like: bool = True) -> str: Returns: JSON success indicator or an error message. """ - # wallmood is a toggle endpoint — same endpoint for like and unlike. - # The 'like' parameter controls the intended state; since the endpoint - # toggles server-side state, it is sent as 'moodType' to allow the server - # to differentiate. - # Verified: correct parameter name is 'wall_message_id' (API error message confirmed). - # 'moodType' values ("LIKE" / "NONE") still to be confirmed via FW_DEBUG=1. + # wallmood toggles the STAR mood on a wall post (same endpoint for like/unlike). + # Verified via FW_DEBUG=1: + # - Parameter 'wall_message_id': post ID as returned by get_activities + # - Parameter 'moodType': "STAR" (Family Wall's internal like type) + # - Response a00.r.r: full wall message object with moodMap showing current state + # The endpoint is a toggle — calling it again removes the mood. + # 'like=False' relies on this toggle behaviour (call again to undo). params: dict[str, Any] = { "wall_message_id": post_id, - "moodType": "LIKE" if like else "NONE", + "moodType": "STAR", } try: @@ -503,12 +504,13 @@ def like_post(post_id: str, like: bool = True) -> str: except RuntimeError as exc: return f"Error: {exc}" - # The response structure is not yet fully verified — return raw JSON - # if the expected shape is absent so the caller can inspect it. + # Extract moodMap from response to determine actual resulting state. try: - result_data = data["a00"]["r"]["r"] - if result_data is None: - raise KeyError("empty result") + wall_obj = data["a00"]["r"]["r"] + if not isinstance(wall_obj, dict): + raise TypeError("a00.r.r is not a dict") + mood_map: dict[str, Any] = wall_obj.get("moodMap") or {} + account_id: str = wall_obj.get("postAccountId", "") except (KeyError, TypeError): return json.dumps( {"warning": "Unexpected wallmood response structure", "raw": data}, @@ -516,8 +518,12 @@ def like_post(post_id: str, like: bool = True) -> str: indent=2, ) + # Determine whether the post is now liked by checking if any entry in + # moodMap contains "STAR" (the server-side representation of a like). + now_liked = any("STAR" in moods for moods in mood_map.values()) + return json.dumps( - {"liked": like, "id": post_id}, + {"liked": now_liked, "id": post_id, "author": account_id}, ensure_ascii=False, indent=2, )