diff --git a/README.md b/README.md index 45bd46a..82ea6db 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.4) +## Features (v0.4.5) ### Read diff --git a/SPEC.md b/SPEC.md index a87cf7b..c7ea6b8 100644 --- a/SPEC.md +++ b/SPEC.md @@ -266,7 +266,7 @@ mit `{"a00": {"un": {"un": {"message": "missing value in: id"}}}}` auf Top-Level → wird vom fw_client fälschlich als Erfolg interpretiert. Daher ist der korrekte Parameter-Name kritisch. -### `wallmood` – Wall-Post liken / unliken +### `wallmood` – Wall-Post liken POST https://api.familywall.com/api/wallmood Content-Type: application/x-www-form-urlencoded @@ -278,9 +278,26 @@ Content-Type: application/x-www-form-urlencoded | `moodType` | ja | `"STAR"` (einziger bekannter Wert — entspricht dem Like-Button in der App) | **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"` +- `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):** ``` @@ -325,4 +342,5 @@ jeweilige Account geliked hat. Leere Map oder fehlender Key = kein Like. - ~~`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~~ → vollständig verifiziert via FW_DEBUG=1 (siehe oben) \ No newline at end of file +- ~~`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) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7300796..d3a3e24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.4.4" +version = "0.4.5" 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 f51a70f..333dbfc 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -478,22 +478,36 @@ def delete_task(task_id: str) -> str: @mcp.tool() def like_post(post_id: str, like: bool = True) -> str: - """Like or unlike a wall post/activity. + """Like a wall post/activity with a STAR mood. + + Note: Unlike (like=False) is not yet supported. The Family Wall API offers + no discoverable endpoint or parameter to remove a like. Passing like=False + returns an error without making any API call. Args: post_id: Wall post ID from get_activities (e.g. ``wall/23431854_31119189``). - like: ``True`` to like the post, ``False`` to unlike. + like: Must be ``True``. ``False`` is reserved for future unlike support. Returns: JSON success indicator or an error message. """ - # wallmood toggles the STAR mood on a wall post (same endpoint for like/unlike). + # Unlike is not yet supported: extensive FW_DEBUG=1 testing showed that + # wallmood with moodType="STAR" is an idempotent SET operation (not a toggle). + # Tested and ruled out: moodType variations ("NONE", "REMOVE", "DELETE", ""), + # moodStarShortcut parameter, and alternative endpoints (all return 502). + # See SPEC.md for full investigation notes. + if not like: + return json.dumps( + {"error": "Unlike is not yet supported. The unlike mechanism is unknown."}, + ensure_ascii=False, + indent=2, + ) + # 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). + # - Parameter 'moodType': "STAR" (Family Wall's internal like type; "LIKE" is silently + # mapped to "STAR" server-side — use "STAR" directly) + # - Response a00.r.r: full wall message object with moodMap showing resulting state params: dict[str, Any] = { "wall_message_id": post_id, "moodType": "STAR", @@ -504,7 +518,7 @@ def like_post(post_id: str, like: bool = True) -> str: except RuntimeError as exc: return f"Error: {exc}" - # Extract moodMap from response to determine actual resulting state. + # Extract moodMap from the response to confirm the like was recorded. try: wall_obj = data["a00"]["r"]["r"] if not isinstance(wall_obj, dict): @@ -518,8 +532,7 @@ 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). + # Confirm the STAR is present in moodMap (server-side representation of a like). now_liked = any("STAR" in moods for moods in mood_map.values()) return json.dumps(